From 2065c927cfe52fe72534fcc0ee74260c760d9966 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Mon, 1 Aug 2022 00:33:28 +0200 Subject: [PATCH] style: update all sources, use tabs instead of spaces - update prettier config - update all *.ts, *.json files --- .prettierrc | 5 +- examples/adaptive-threshold/package.json | 76 +- examples/adaptive-threshold/src/api.ts | 20 +- examples/adaptive-threshold/src/events.ts | 112 +- examples/adaptive-threshold/src/index.ts | 134 +- examples/adaptive-threshold/src/state.ts | 2 +- examples/adaptive-threshold/src/threshold.ts | 42 +- examples/adaptive-threshold/tsconfig.json | 7 +- examples/async-effect/package.json | 62 +- examples/async-effect/src/index.ts | 128 +- examples/async-effect/tsconfig.json | 7 +- examples/big-font/package.json | 64 +- examples/big-font/src/index.ts | 280 +- examples/big-font/tsconfig.json | 7 +- examples/bitmap-font/package.json | 80 +- examples/bitmap-font/src/font.ts | 190 +- examples/bitmap-font/src/index.ts | 126 +- examples/bitmap-font/tsconfig.json | 7 +- examples/canvas-dial/package.json | 88 +- examples/canvas-dial/src/dial.ts | 394 +-- examples/canvas-dial/src/index.ts | 132 +- examples/canvas-dial/tsconfig.json | 7 +- examples/cellular-automata/package.json | 72 +- examples/cellular-automata/src/index.ts | 166 +- examples/cellular-automata/tsconfig.json | 7 +- examples/color-themes/package.json | 96 +- examples/color-themes/src/api.ts | 18 +- examples/color-themes/src/index.ts | 280 +- examples/color-themes/src/palette.ts | 32 +- examples/color-themes/src/serialize.ts | 66 +- examples/color-themes/src/state.ts | 94 +- examples/color-themes/tsconfig.json | 7 +- examples/commit-heatmap/package.json | 88 +- examples/commit-heatmap/src/index.ts | 274 +- examples/commit-heatmap/tsconfig.json | 10 +- examples/commit-table-ssr/package.json | 90 +- examples/commit-table-ssr/src/client/index.ts | 186 +- examples/commit-table-ssr/src/common/api.ts | 62 +- .../src/common/components/commit-link.ts | 12 +- .../src/common/components/header.ts | 6 +- .../src/common/components/link.ts | 12 +- .../src/common/components/repo-table.ts | 48 +- .../src/common/components/table.ts | 70 +- .../commit-table-ssr/src/common/config.ts | 112 +- .../src/server/build-table.ts | 14 +- examples/commit-table-ssr/src/server/git.ts | 100 +- examples/commit-table-ssr/src/server/html.ts | 40 +- examples/commit-table-ssr/src/server/index.ts | 48 +- examples/commit-table-ssr/tsconfig.json | 16 +- examples/crypto-chart/ohlc.json | 3068 ++++++++--------- examples/crypto-chart/package.json | 94 +- examples/crypto-chart/src/index.ts | 760 ++-- examples/crypto-chart/tsconfig.json | 7 +- examples/devcards/package.json | 64 +- examples/devcards/src/index.ts | 298 +- examples/devcards/tsconfig.json | 7 +- examples/dominant-colors/package.json | 106 +- examples/dominant-colors/src/api.ts | 22 +- .../dominant-colors/src/components/css.ts | 52 +- .../src/components/pixelcanvas.ts | 30 +- .../dominant-colors/src/components/slider.ts | 32 +- .../src/components/swatches.ts | 20 +- examples/dominant-colors/src/index.ts | 168 +- examples/dominant-colors/src/palette.ts | 32 +- examples/dominant-colors/src/process.ts | 56 +- examples/dominant-colors/tsconfig.json | 7 +- examples/ellipse-proximity/package.json | 82 +- examples/ellipse-proximity/src/index.ts | 86 +- examples/ellipse-proximity/tsconfig.json | 7 +- examples/fft-synth/package.json | 102 +- examples/fft-synth/src/audio.ts | 64 +- examples/fft-synth/src/automode.ts | 56 +- examples/fft-synth/src/config.ts | 14 +- examples/fft-synth/src/gui.ts | 328 +- examples/fft-synth/src/index.ts | 116 +- examples/fft-synth/src/state.ts | 34 +- examples/fft-synth/tsconfig.json | 7 +- examples/geom-convex-hull/package.json | 70 +- examples/geom-convex-hull/src/index.ts | 42 +- examples/geom-convex-hull/tsconfig.json | 7 +- examples/geom-fuzz-basics/package.json | 72 +- examples/geom-fuzz-basics/src/index.ts | 128 +- examples/geom-fuzz-basics/tsconfig.json | 7 +- examples/geom-knn/package.json | 84 +- examples/geom-knn/src/index.ts | 160 +- examples/geom-knn/tsconfig.json | 7 +- examples/geom-tessel/package.json | 80 +- examples/geom-tessel/src/index.ts | 102 +- examples/geom-tessel/tsconfig.json | 7 +- examples/geom-voronoi-mst/package.json | 92 +- examples/geom-voronoi-mst/src/index.ts | 98 +- examples/geom-voronoi-mst/tsconfig.json | 7 +- examples/gesture-analysis/package.json | 84 +- examples/gesture-analysis/src/index.ts | 192 +- examples/gesture-analysis/tsconfig.json | 7 +- examples/grid-iterators/package.json | 70 +- examples/grid-iterators/src/index.ts | 56 +- examples/grid-iterators/tsconfig.json | 7 +- examples/hdom-basics/package.json | 48 +- examples/hdom-basics/src/index.ts | 10 +- examples/hdom-basics/tsconfig.json | 7 +- examples/hdom-benchmark/package.json | 62 +- examples/hdom-benchmark/src/index.ts | 180 +- examples/hdom-benchmark/tsconfig.json | 7 +- examples/hdom-benchmark2/package.json | 78 +- examples/hdom-benchmark2/src/index.ts | 352 +- examples/hdom-benchmark2/tsconfig.json | 7 +- examples/hdom-canvas-clock/package.json | 76 +- examples/hdom-canvas-clock/src/index.ts | 216 +- examples/hdom-canvas-clock/tsconfig.json | 7 +- examples/hdom-canvas-draw/package.json | 84 +- examples/hdom-canvas-draw/src/index.ts | 210 +- examples/hdom-canvas-draw/tsconfig.json | 7 +- examples/hdom-canvas-particles/package.json | 86 +- examples/hdom-canvas-particles/src/index.ts | 178 +- examples/hdom-canvas-particles/tsconfig.json | 7 +- examples/hdom-canvas-shapes/package.json | 108 +- examples/hdom-canvas-shapes/src/index.ts | 662 ++-- examples/hdom-canvas-shapes/tsconfig.json | 7 +- examples/hdom-dropdown-fuzzy/package.json | 80 +- examples/hdom-dropdown-fuzzy/src/config.ts | 482 ++- examples/hdom-dropdown-fuzzy/src/dropdown.ts | 176 +- examples/hdom-dropdown-fuzzy/src/fuzzy.ts | 164 +- examples/hdom-dropdown-fuzzy/src/index.ts | 64 +- examples/hdom-dropdown-fuzzy/src/input.ts | 118 +- examples/hdom-dropdown-fuzzy/tsconfig.json | 7 +- examples/hdom-dropdown/package.json | 74 +- examples/hdom-dropdown/src/config.ts | 130 +- examples/hdom-dropdown/src/dropdown.ts | 164 +- examples/hdom-dropdown/src/index.ts | 76 +- examples/hdom-dropdown/tsconfig.json | 7 +- examples/hdom-dyn-context/package.json | 62 +- examples/hdom-dyn-context/src/index.ts | 46 +- examples/hdom-dyn-context/tsconfig.json | 7 +- examples/hdom-elm/package.json | 68 +- examples/hdom-elm/src/api.ts | 8 +- examples/hdom-elm/src/elm.ts | 60 +- examples/hdom-elm/src/index.ts | 120 +- examples/hdom-elm/tsconfig.json | 7 +- examples/hdom-inner-html/package.json | 58 +- examples/hdom-inner-html/src/index.ts | 54 +- examples/hdom-inner-html/tsconfig.json | 7 +- examples/hdom-local-render/package.json | 72 +- examples/hdom-local-render/src/index.ts | 144 +- examples/hdom-local-render/tsconfig.json | 7 +- examples/hdom-localstate/package.json | 62 +- examples/hdom-localstate/src/index.ts | 64 +- examples/hdom-localstate/tsconfig.json | 7 +- examples/hdom-skip-nested/package.json | 54 +- examples/hdom-skip-nested/src/index.ts | 122 +- examples/hdom-skip-nested/tsconfig.json | 7 +- examples/hdom-skip/package.json | 54 +- examples/hdom-skip/src/index.ts | 88 +- examples/hdom-skip/tsconfig.json | 7 +- examples/hdom-theme-adr-0003/package.json | 64 +- examples/hdom-theme-adr-0003/src/index.ts | 308 +- examples/hdom-theme-adr-0003/tsconfig.json | 7 +- examples/hdom-toggle/package.json | 58 +- examples/hdom-toggle/src/index.ts | 98 +- examples/hdom-toggle/tsconfig.json | 7 +- examples/hdom-vscroller/package.json | 52 +- examples/hdom-vscroller/src/index.ts | 108 +- examples/hdom-vscroller/src/vscroller.ts | 126 +- examples/hdom-vscroller/tsconfig.json | 7 +- examples/hiccup-canvas-arcs/package.json | 76 +- examples/hiccup-canvas-arcs/src/index.ts | 104 +- examples/hiccup-canvas-arcs/tsconfig.json | 7 +- examples/hydrate-basics/package.json | 70 +- examples/hydrate-basics/src/index.ts | 170 +- examples/hydrate-basics/tsconfig.json | 7 +- examples/imgui-basics/package.json | 76 +- examples/imgui-basics/src/index.ts | 194 +- examples/imgui-basics/tsconfig.json | 7 +- examples/imgui/package.json | 112 +- examples/imgui/src/index.ts | 614 ++-- examples/imgui/tsconfig.json | 7 +- examples/interceptor-basics/package.json | 64 +- examples/interceptor-basics/src/index.ts | 64 +- examples/interceptor-basics/tsconfig.json | 7 +- examples/interceptor-basics2/package.json | 68 +- examples/interceptor-basics2/src/index.ts | 223 +- examples/interceptor-basics2/tsconfig.json | 7 +- examples/iso-plasma/package.json | 86 +- examples/iso-plasma/src/index.ts | 58 +- examples/iso-plasma/tsconfig.json | 7 +- examples/json-components/package.json | 58 +- examples/json-components/src/index.ts | 124 +- examples/json-components/tsconfig.json | 7 +- examples/login-form/package.json | 70 +- examples/login-form/src/index.ts | 96 +- examples/login-form/tsconfig.json | 7 +- examples/mandelbrot/package.json | 90 +- examples/mandelbrot/src/gradient.ts | 114 +- examples/mandelbrot/src/index.ts | 456 +-- examples/mandelbrot/src/worker.ts | 50 +- examples/mandelbrot/tsconfig.json | 7 +- examples/markdown/package.json | 76 +- examples/markdown/src/index.ts | 104 +- examples/markdown/tsconfig.json | 7 +- examples/multitouch/package.json | 76 +- examples/multitouch/src/index.ts | 96 +- examples/multitouch/tsconfig.json | 7 +- examples/package-stats/package.json | 82 +- examples/package-stats/src/dep-chart.ts | 142 +- examples/package-stats/src/size-chart.ts | 126 +- examples/package-stats/src/viz.ts | 146 +- examples/package-stats/tsconfig.json | 14 +- examples/parse-playground/package.json | 102 +- examples/parse-playground/src/api.ts | 18 +- examples/parse-playground/src/config.ts | 50 +- examples/parse-playground/src/index.ts | 388 +-- examples/parse-playground/tsconfig.json | 7 +- examples/pixel-basics/package.json | 58 +- examples/pixel-basics/src/index.ts | 84 +- examples/pixel-basics/tsconfig.json | 7 +- examples/pixel-dither/package.json | 66 +- examples/pixel-dither/src/index.ts | 82 +- examples/pixel-dither/tsconfig.json | 7 +- examples/pixel-indexed/package.json | 74 +- examples/pixel-indexed/src/index.ts | 40 +- examples/pixel-indexed/tsconfig.json | 7 +- examples/pixel-sorting/package.json | 90 +- examples/pixel-sorting/src/index.ts | 314 +- examples/pixel-sorting/tsconfig.json | 7 +- examples/pointfree-svg/package.json | 62 +- examples/pointfree-svg/src/index.ts | 40 +- examples/pointfree-svg/tsconfig.json | 10 +- examples/poisson-circles/package.json | 72 +- examples/poisson-circles/src/index.ts | 16 +- examples/poisson-circles/tsconfig.json | 7 +- examples/poly-spline/package.json | 78 +- examples/poly-spline/src/index.ts | 260 +- examples/poly-spline/tsconfig.json | 7 +- examples/porter-duff/package.json | 58 +- examples/porter-duff/src/index.ts | 108 +- examples/porter-duff/tsconfig.json | 7 +- examples/ramp-synth/package.json | 82 +- examples/ramp-synth/src/api.ts | 26 +- examples/ramp-synth/src/audio.ts | 56 +- examples/ramp-synth/src/components.ts | 60 +- examples/ramp-synth/src/index.ts | 316 +- examples/ramp-synth/tsconfig.json | 7 +- examples/rdom-basics/package.json | 70 +- examples/rdom-basics/src/index.ts | 118 +- examples/rdom-basics/tsconfig.json | 7 +- examples/rdom-delayed-update/package.json | 70 +- examples/rdom-delayed-update/src/index.ts | 154 +- examples/rdom-delayed-update/tsconfig.json | 7 +- examples/rdom-dnd/package.json | 72 +- examples/rdom-dnd/src/draggable.ts | 148 +- examples/rdom-dnd/src/index.ts | 66 +- examples/rdom-dnd/src/notification.ts | 68 +- examples/rdom-dnd/tsconfig.json | 7 +- examples/rdom-lissajous/package.json | 80 +- examples/rdom-lissajous/src/index.ts | 164 +- examples/rdom-lissajous/tsconfig.json | 7 +- examples/rdom-search-docs/package.json | 86 +- examples/rdom-search-docs/src/index.ts | 240 +- examples/rdom-search-docs/src/pagination.ts | 142 +- examples/rdom-search-docs/src/search.ts | 70 +- examples/rdom-search-docs/tsconfig.json | 7 +- examples/rdom-svg-nodes/package.json | 80 +- examples/rdom-svg-nodes/src/index.ts | 100 +- examples/rdom-svg-nodes/tsconfig.json | 7 +- examples/rotating-voronoi/package.json | 114 +- examples/rotating-voronoi/src/controllers.ts | 90 +- examples/rotating-voronoi/src/index.ts | 256 +- examples/rotating-voronoi/src/stream-state.ts | 26 +- examples/rotating-voronoi/tsconfig.json | 7 +- examples/router-basics/package.json | 76 +- examples/router-basics/src/api.ts | 96 +- examples/router-basics/src/app.ts | 196 +- .../router-basics/src/components/all-users.ts | 68 +- .../router-basics/src/components/contact.ts | 26 +- .../src/components/debug-container.ts | 26 +- .../src/components/event-link.ts | 30 +- .../src/components/external-link.ts | 10 +- examples/router-basics/src/components/home.ts | 90 +- examples/router-basics/src/components/nav.ts | 24 +- .../src/components/route-link.ts | 32 +- .../router-basics/src/components/status.ts | 4 +- .../src/components/user-profile.ts | 40 +- examples/router-basics/src/config.ts | 476 +-- examples/router-basics/src/routes.ts | 28 +- examples/router-basics/tsconfig.json | 7 +- examples/rstream-dataflow/package.json | 84 +- examples/rstream-dataflow/src/circle.ts | 24 +- examples/rstream-dataflow/src/index.ts | 208 +- examples/rstream-dataflow/tsconfig.json | 7 +- examples/rstream-event-loop/package.json | 76 +- examples/rstream-event-loop/src/api.ts | 14 +- examples/rstream-event-loop/src/events.ts | 109 +- examples/rstream-event-loop/src/index.ts | 100 +- examples/rstream-event-loop/src/state.ts | 6 +- examples/rstream-event-loop/tsconfig.json | 7 +- examples/rstream-grid/package.json | 102 +- examples/rstream-grid/src/api.ts | 44 +- examples/rstream-grid/src/app.ts | 174 +- .../src/components/button-group.ts | 6 +- .../rstream-grid/src/components/button.ts | 8 +- .../rstream-grid/src/components/event-link.ts | 28 +- examples/rstream-grid/src/components/link.ts | 6 +- examples/rstream-grid/src/components/main.ts | 4 +- .../rstream-grid/src/components/sidebar.ts | 60 +- .../rstream-grid/src/components/slider.ts | 98 +- examples/rstream-grid/src/config.ts | 186 +- examples/rstream-grid/src/dataflow.ts | 136 +- examples/rstream-grid/src/sliders.ts | 66 +- examples/rstream-grid/tsconfig.json | 7 +- examples/rstream-hdom/package.json | 58 +- examples/rstream-hdom/src/index.ts | 134 +- examples/rstream-hdom/tsconfig.json | 7 +- examples/rstream-spreadsheet/package.json | 108 +- examples/rstream-spreadsheet/src/api.ts | 14 +- examples/rstream-spreadsheet/src/dsl.ts | 254 +- examples/rstream-spreadsheet/src/index.ts | 182 +- examples/rstream-spreadsheet/src/state.ts | 86 +- examples/rstream-spreadsheet/tsconfig.json | 7 +- examples/scenegraph-image/package.json | 90 +- examples/scenegraph-image/src/index.ts | 212 +- examples/scenegraph-image/tsconfig.json | 7 +- examples/scenegraph/package.json | 88 +- examples/scenegraph/src/index.ts | 200 +- examples/scenegraph/tsconfig.json | 7 +- examples/shader-ast-canvas2d/package.json | 62 +- examples/shader-ast-canvas2d/src/index.ts | 88 +- examples/shader-ast-canvas2d/tsconfig.json | 7 +- examples/shader-ast-evo/package.json | 86 +- examples/shader-ast-evo/src/index.ts | 198 +- examples/shader-ast-evo/tsconfig.json | 7 +- examples/shader-ast-noise/package.json | 64 +- examples/shader-ast-noise/src/index.ts | 132 +- examples/shader-ast-noise/tsconfig.json | 7 +- examples/shader-ast-raymarch/package.json | 64 +- examples/shader-ast-raymarch/src/index.ts | 316 +- examples/shader-ast-raymarch/tsconfig.json | 7 +- examples/shader-ast-sdf2d/package.json | 64 +- examples/shader-ast-sdf2d/src/index.ts | 166 +- examples/shader-ast-sdf2d/tsconfig.json | 7 +- examples/shader-ast-tunnel/package.json | 80 +- examples/shader-ast-tunnel/src/index.ts | 232 +- examples/shader-ast-tunnel/tsconfig.json | 7 +- examples/shader-ast-workers/package.json | 100 +- examples/shader-ast-workers/src/api.ts | 16 +- examples/shader-ast-workers/src/index.ts | 78 +- examples/shader-ast-workers/src/worker.ts | 280 +- examples/shader-ast-workers/tsconfig.json | 7 +- examples/shader-graph/package.json | 92 +- examples/shader-graph/src/api.ts | 110 +- examples/shader-graph/src/index.ts | 434 +-- examples/shader-graph/src/opnode.ts | 148 +- examples/shader-graph/tsconfig.json | 7 +- examples/soa-ecs/package.json | 92 +- examples/soa-ecs/src/index.ts | 266 +- examples/soa-ecs/tsconfig.json | 7 +- examples/spline-tangent/package.json | 74 +- examples/spline-tangent/src/index.ts | 88 +- examples/spline-tangent/tsconfig.json | 7 +- examples/stratified-grid/package.json | 70 +- examples/stratified-grid/src/index.ts | 34 +- examples/stratified-grid/tsconfig.json | 7 +- examples/svg-barchart/package.json | 60 +- examples/svg-barchart/src/index.ts | 142 +- examples/svg-barchart/tsconfig.json | 7 +- examples/svg-particles/package.json | 56 +- examples/svg-particles/src/index.ts | 54 +- examples/svg-particles/tsconfig.json | 7 +- examples/svg-waveform/package.json | 82 +- examples/svg-waveform/src/api.ts | 46 +- examples/svg-waveform/src/app.ts | 154 +- .../src/components/button-group.ts | 10 +- .../svg-waveform/src/components/button.ts | 2 +- .../svg-waveform/src/components/event-link.ts | 32 +- examples/svg-waveform/src/components/link.ts | 2 +- examples/svg-waveform/src/components/main.ts | 34 +- .../svg-waveform/src/components/sidebar.ts | 64 +- .../svg-waveform/src/components/slider.ts | 94 +- .../svg-waveform/src/components/waveform.ts | 126 +- examples/svg-waveform/src/config.ts | 146 +- examples/svg-waveform/src/sliders.ts | 64 +- examples/svg-waveform/tsconfig.json | 7 +- examples/talk-slides/package.json | 76 +- examples/talk-slides/src/components.ts | 185 +- examples/talk-slides/src/config.ts | 118 +- examples/talk-slides/src/index.ts | 76 +- examples/talk-slides/src/slides.ts | 1330 +++---- examples/talk-slides/tsconfig.json | 7 +- examples/text-canvas-image/package.json | 60 +- examples/text-canvas-image/src/index.ts | 64 +- examples/text-canvas-image/tsconfig.json | 7 +- examples/text-canvas/package.json | 78 +- examples/text-canvas/src/index.ts | 144 +- examples/text-canvas/tsconfig.json | 7 +- examples/todo-list/package.json | 70 +- examples/todo-list/src/index.ts | 110 +- examples/todo-list/tsconfig.json | 7 +- examples/transducers-hdom/package.json | 64 +- examples/transducers-hdom/src/index.ts | 68 +- examples/transducers-hdom/tsconfig.json | 7 +- examples/triple-query/package.json | 94 +- examples/triple-query/src/api.ts | 78 +- examples/triple-query/src/app.ts | 156 +- .../src/components/button-group.ts | 10 +- .../triple-query/src/components/button.ts | 14 +- .../triple-query/src/components/event-link.ts | 32 +- examples/triple-query/src/components/link.ts | 2 +- examples/triple-query/src/components/main.ts | 26 +- .../src/components/query-results.ts | 42 +- .../triple-query/src/components/section.ts | 8 +- examples/triple-query/src/components/table.ts | 30 +- .../src/components/triple-table.ts | 72 +- examples/triple-query/src/config.ts | 258 +- examples/triple-query/src/handlers.ts | 148 +- examples/triple-query/tsconfig.json | 7 +- examples/webgl-cube/package.json | 80 +- examples/webgl-cube/src/index.ts | 112 +- examples/webgl-cube/tsconfig.json | 7 +- examples/webgl-cubemap/package.json | 86 +- examples/webgl-cubemap/src/index.ts | 237 +- examples/webgl-cubemap/tsconfig.json | 7 +- examples/webgl-grid/package.json | 84 +- examples/webgl-grid/src/index.ts | 183 +- examples/webgl-grid/tsconfig.json | 7 +- examples/webgl-msdf/package.json | 98 +- examples/webgl-msdf/src/index.ts | 389 ++- examples/webgl-msdf/tsconfig.json | 7 +- examples/webgl-multipass/package.json | 60 +- examples/webgl-multipass/src/index.ts | 162 +- examples/webgl-multipass/tsconfig.json | 7 +- examples/webgl-shadertoy/package.json | 62 +- examples/webgl-shadertoy/src/index.ts | 102 +- examples/webgl-shadertoy/tsconfig.json | 7 +- examples/webgl-ssao/package.json | 88 +- examples/webgl-ssao/src/index.ts | 312 +- examples/webgl-ssao/src/params.ts | 48 +- examples/webgl-ssao/src/shaders.ts | 150 +- examples/webgl-ssao/tsconfig.json | 7 +- examples/wolfram/package.json | 74 +- examples/wolfram/src/index.ts | 214 +- examples/wolfram/tsconfig.json | 7 +- examples/xml-converter/package.json | 86 +- examples/xml-converter/src/cli.ts | 70 +- examples/xml-converter/src/convert.ts | 172 +- examples/xml-converter/src/format.ts | 208 +- examples/xml-converter/src/index.ts | 72 +- examples/xml-converter/src/ui.ts | 324 +- examples/xml-converter/src/utils.ts | 26 +- examples/xml-converter/tsconfig-cli.json | 20 +- examples/xml-converter/tsconfig.json | 7 +- packages/adapt-dpi/api-extractor.json | 2 +- packages/adapt-dpi/package.json | 132 +- packages/adapt-dpi/src/index.ts | 22 +- packages/adapt-dpi/tsconfig.json | 10 +- packages/adjacency/api-extractor.json | 2 +- packages/adjacency/package.json | 240 +- packages/adjacency/src/api.ts | 116 +- packages/adjacency/src/bfs.ts | 92 +- packages/adjacency/src/binary.ts | 188 +- packages/adjacency/src/dfs.ts | 72 +- packages/adjacency/src/disjoint-set.ts | 168 +- packages/adjacency/src/list.ts | 272 +- packages/adjacency/src/mst.ts | 28 +- packages/adjacency/src/sparse.ts | 358 +- packages/adjacency/src/utils.ts | 42 +- packages/adjacency/test/binary.ts | 74 +- packages/adjacency/test/list.ts | 44 +- packages/adjacency/test/mst.ts | 94 +- packages/adjacency/test/sparse.ts | 60 +- packages/adjacency/tsconfig.json | 10 +- packages/api/api-extractor.json | 2 +- packages/api/package.json | 446 +-- packages/api/src/assoc.ts | 8 +- packages/api/src/bind.ts | 16 +- packages/api/src/buffered.ts | 18 +- packages/api/src/clear.ts | 2 +- packages/api/src/compare.ts | 26 +- packages/api/src/contains.ts | 12 +- packages/api/src/copy.ts | 10 +- packages/api/src/decorators/configurable.ts | 6 +- packages/api/src/decorators/deprecated.ts | 32 +- packages/api/src/decorators/nomixin.ts | 2 +- packages/api/src/decorators/sealed.ts | 4 +- packages/api/src/deref.ts | 18 +- packages/api/src/dissoc.ts | 4 +- packages/api/src/empty.ts | 10 +- packages/api/src/enable.ts | 24 +- packages/api/src/equiv.ts | 30 +- packages/api/src/event.ts | 12 +- packages/api/src/fn.ts | 182 +- packages/api/src/get.ts | 4 +- packages/api/src/grid.ts | 724 ++-- packages/api/src/hash.ts | 12 +- packages/api/src/hiccup.ts | 22 +- packages/api/src/id.ts | 2 +- packages/api/src/indexed.ts | 2 +- packages/api/src/into.ts | 2 +- packages/api/src/keyval.ts | 405 +-- packages/api/src/length.ts | 2 +- packages/api/src/meta.ts | 16 +- packages/api/src/mixin.ts | 62 +- packages/api/src/mixins/ienable.ts | 40 +- packages/api/src/mixins/igrid.ts | 360 +- packages/api/src/mixins/inotify.ts | 100 +- packages/api/src/mixins/iterable.ts | 10 +- packages/api/src/mixins/iwatch.ts | 48 +- packages/api/src/object.ts | 2 +- packages/api/src/path.ts | 180 +- packages/api/src/range.ts | 128 +- packages/api/src/release.ts | 2 +- packages/api/src/reset.ts | 2 +- packages/api/src/select.ts | 20 +- packages/api/src/seq.ts | 52 +- packages/api/src/set.ts | 24 +- packages/api/src/stack.ts | 18 +- packages/api/src/tuple.ts | 40 +- packages/api/src/typedarray.ts | 224 +- packages/api/src/watch.ts | 6 +- packages/api/test/mixins.ts | 72 +- packages/api/tsconfig.json | 10 +- packages/args/api-extractor.json | 2 +- packages/args/package.json | 194 +- packages/args/src/api.ts | 308 +- packages/args/src/args.ts | 270 +- packages/args/src/coerce.ts | 84 +- packages/args/src/parse.ts | 224 +- packages/args/src/usage.ts | 200 +- packages/args/test/index.ts | 522 +-- packages/args/tsconfig.json | 12 +- packages/arrays/api-extractor.json | 2 +- packages/arrays/package.json | 294 +- packages/arrays/src/arg-sort.ts | 2 +- packages/arrays/src/binary-search.ts | 114 +- packages/arrays/src/bisect.ts | 8 +- packages/arrays/src/blit.ts | 118 +- packages/arrays/src/ends-with.ts | 16 +- packages/arrays/src/ensure-array.ts | 4 +- packages/arrays/src/ensure-iterable.ts | 6 +- packages/arrays/src/fill-range.ts | 22 +- packages/arrays/src/find.ts | 24 +- packages/arrays/src/fuzzy-match.ts | 42 +- packages/arrays/src/insert.ts | 10 +- packages/arrays/src/into.ts | 10 +- packages/arrays/src/is-sorted.ts | 22 +- packages/arrays/src/iterator.ts | 20 +- packages/arrays/src/levenshtein.ts | 196 +- packages/arrays/src/quicksort.ts | 63 +- packages/arrays/src/shuffle.ts | 40 +- packages/arrays/src/sort-cached.ts | 14 +- packages/arrays/src/starts-with.ts | 16 +- packages/arrays/src/swap.ts | 64 +- packages/arrays/src/swizzle.ts | 58 +- packages/arrays/test/binary-search.ts | 44 +- packages/arrays/test/iterator.ts | 24 +- packages/arrays/test/shuffle.ts | 42 +- packages/arrays/test/sort-cached.ts | 62 +- packages/arrays/tsconfig.json | 10 +- packages/associative/api-extractor.json | 2 +- packages/associative/package.json | 378 +- packages/associative/src/api.ts | 176 +- packages/associative/src/array-set.ts | 256 +- packages/associative/src/bidir-index.ts | 454 +-- packages/associative/src/checks.ts | 4 +- packages/associative/src/common-keys.ts | 28 +- packages/associative/src/copy.ts | 16 +- packages/associative/src/difference.ts | 18 +- packages/associative/src/dissoc.ts | 20 +- packages/associative/src/empty.ts | 6 +- packages/associative/src/equiv-map.ts | 316 +- packages/associative/src/hash-map.ts | 398 +-- packages/associative/src/indexed.ts | 22 +- packages/associative/src/internal/combine.ts | 20 +- packages/associative/src/internal/equiv.ts | 48 +- packages/associative/src/internal/inspect.ts | 74 +- packages/associative/src/intersection.ts | 28 +- packages/associative/src/into.ts | 26 +- packages/associative/src/invert.ts | 22 +- packages/associative/src/join.ts | 108 +- packages/associative/src/ll-set.ts | 276 +- packages/associative/src/merge-apply.ts | 34 +- packages/associative/src/merge-deep.ts | 32 +- packages/associative/src/merge-with.ts | 62 +- packages/associative/src/merge.ts | 24 +- packages/associative/src/multi-trie.ts | 372 +- packages/associative/src/rename-keys.ts | 56 +- packages/associative/src/select-keys.ts | 68 +- packages/associative/src/sorted-map.ts | 528 +-- packages/associative/src/sorted-obj.ts | 4 +- packages/associative/src/sorted-set.ts | 266 +- packages/associative/src/sparse-set.ts | 428 +-- packages/associative/src/trie-map.ts | 294 +- packages/associative/src/union.ts | 16 +- packages/associative/src/without-keys.ts | 30 +- packages/associative/test/bidir.ts | 84 +- packages/associative/test/difference.ts | 78 +- packages/associative/test/intersection.ts | 62 +- packages/associative/test/join.ts | 108 +- packages/associative/test/merge.ts | 182 +- packages/associative/test/multi-trie.ts | 152 +- packages/associative/test/object.ts | 30 +- packages/associative/test/sorted-map.ts | 378 +- packages/associative/test/sparse-set.ts | 137 +- packages/associative/test/trie-map.ts | 134 +- packages/associative/test/union.ts | 76 +- packages/associative/tsconfig.json | 10 +- packages/atom/api-extractor.json | 2 +- packages/atom/package.json | 216 +- packages/atom/src/api.ts | 232 +- packages/atom/src/atom.ts | 344 +- packages/atom/src/cursor.ts | 418 +-- packages/atom/src/history.ts | 656 ++-- packages/atom/src/transacted.ts | 424 +-- packages/atom/src/view.ts | 350 +- packages/atom/test/atom.ts | 130 +- packages/atom/test/cursor.ts | 262 +- packages/atom/test/history.ts | 340 +- packages/atom/test/transacted.ts | 266 +- packages/atom/test/view.ts | 186 +- packages/atom/tsconfig.json | 10 +- packages/base-n/api-extractor.json | 2 +- packages/base-n/package.json | 220 +- packages/base-n/src/api.ts | 86 +- packages/base-n/src/base.ts | 138 +- packages/base-n/test/index.ts | 52 +- packages/base-n/tsconfig.json | 12 +- packages/bench/api-extractor.json | 2 +- packages/bench/package.json | 230 +- packages/bench/src/api.ts | 238 +- packages/bench/src/bench.ts | 28 +- packages/bench/src/benchmark.ts | 92 +- packages/bench/src/format/csv.ts | 36 +- packages/bench/src/format/default.ts | 22 +- packages/bench/src/format/markdown.ts | 80 +- packages/bench/src/now.ts | 20 +- packages/bench/src/suite.ts | 28 +- packages/bench/src/timed.ts | 14 +- packages/bench/tsconfig.json | 10 +- packages/bencode/api-extractor.json | 2 +- packages/bencode/package.json | 160 +- packages/bencode/src/decode.ts | 274 +- packages/bencode/src/encode.ts | 124 +- packages/bencode/tsconfig.json | 14 +- packages/binary/api-extractor.json | 2 +- packages/binary/package.json | 260 +- packages/binary/src/align.ts | 2 +- packages/binary/src/api.ts | 160 +- packages/binary/src/bytes.ts | 36 +- packages/binary/src/constants.ts | 2 +- packages/binary/src/count.ts | 44 +- packages/binary/src/edit.ts | 14 +- packages/binary/src/float.ts | 52 +- packages/binary/src/gray.ts | 12 +- packages/binary/src/logic.ts | 12 +- packages/binary/src/one-hot.ts | 4 +- packages/binary/src/pow.ts | 28 +- packages/binary/src/rotate.ts | 8 +- packages/binary/src/splat.ts | 16 +- packages/binary/src/swizzle.ts | 86 +- packages/binary/tsconfig.json | 10 +- packages/bitfield/api-extractor.json | 2 +- packages/bitfield/package.json | 174 +- packages/bitfield/src/bitfield.ts | 340 +- packages/bitfield/src/bitmatrix.ts | 384 +-- packages/bitfield/src/util.ts | 8 +- packages/bitfield/tsconfig.json | 10 +- packages/bitstream/api-extractor.json | 2 +- packages/bitstream/package.json | 164 +- packages/bitstream/src/input.ts | 218 +- packages/bitstream/src/output.ts | 216 +- packages/bitstream/src/simple.ts | 104 +- packages/bitstream/test/index.ts | 330 +- packages/bitstream/tsconfig.json | 10 +- packages/cache/api-extractor.json | 2 +- packages/cache/package.json | 182 +- packages/cache/src/api.ts | 60 +- packages/cache/src/lru.ts | 338 +- packages/cache/src/mru.ts | 56 +- packages/cache/src/tlru.ts | 162 +- packages/cache/test/lru.ts | 78 +- packages/cache/test/mru.ts | 78 +- packages/cache/test/tlru.ts | 150 +- packages/cache/tsconfig.json | 10 +- packages/cellular/api-extractor.json | 2 +- packages/cellular/package.json | 186 +- packages/cellular/src/1d.ts | 504 +-- packages/cellular/src/api.ts | 206 +- packages/cellular/tsconfig.json | 12 +- packages/checks/api-extractor.json | 2 +- packages/checks/package.json | 540 +-- packages/checks/src/exists-not-null.ts | 2 +- packages/checks/src/has-crypto.ts | 2 +- packages/checks/src/has-max-length.ts | 2 +- packages/checks/src/has-min-length.ts | 2 +- packages/checks/src/has-performance.ts | 2 +- packages/checks/src/has-wasm.ts | 8 +- packages/checks/src/has-webgl.ts | 12 +- packages/checks/src/implements-function.ts | 4 +- packages/checks/src/is-arraylike.ts | 2 +- packages/checks/src/is-ascii.ts | 4 +- packages/checks/src/is-async-iterable.ts | 2 +- packages/checks/src/is-chrome.ts | 2 +- packages/checks/src/is-firefox.ts | 2 +- packages/checks/src/is-ie.ts | 6 +- packages/checks/src/is-in-range.ts | 2 +- packages/checks/src/is-int32.ts | 2 +- packages/checks/src/is-iterable.ts | 2 +- packages/checks/src/is-mobile.ts | 8 +- packages/checks/src/is-negative.ts | 2 +- packages/checks/src/is-node.ts | 6 +- packages/checks/src/is-not-string-iterable.ts | 6 +- packages/checks/src/is-numeric.ts | 6 +- packages/checks/src/is-object.ts | 2 +- packages/checks/src/is-plain-object.ts | 12 +- packages/checks/src/is-positive.ts | 2 +- packages/checks/src/is-primitive.ts | 6 +- packages/checks/src/is-promiselike.ts | 4 +- packages/checks/src/is-proto-path.ts | 20 +- packages/checks/src/is-safari.ts | 6 +- packages/checks/src/is-transferable.ts | 8 +- packages/checks/src/is-typedarray.ts | 38 +- packages/checks/src/is-uint32.ts | 2 +- packages/checks/src/is-uuid4.ts | 3 +- packages/checks/test/index.ts | 410 +-- packages/checks/tsconfig.json | 10 +- packages/color-palettes/api-extractor.json | 2 +- packages/color-palettes/package.json | 144 +- packages/color-palettes/src/index.ts | 2818 +++++++-------- packages/color-palettes/tools/index.ts | 144 +- packages/color-palettes/tsconfig.json | 12 +- packages/color/api-extractor.json | 2 +- packages/color/package.json | 844 ++--- packages/color/src/alpha.ts | 8 +- packages/color/src/analog.ts | 154 +- packages/color/src/api.ts | 376 +- packages/color/src/api/constants.ts | 38 +- packages/color/src/api/gradients.ts | 84 +- packages/color/src/api/names.ts | 600 ++-- packages/color/src/api/ranges.ts | 170 +- packages/color/src/api/system.ts | 158 +- packages/color/src/clamp.ts | 28 +- packages/color/src/color-range.ts | 364 +- packages/color/src/color.ts | 80 +- packages/color/src/convert.ts | 66 +- packages/color/src/cosine-gradients.ts | 384 +-- packages/color/src/css/css.ts | 60 +- packages/color/src/css/parse-css.ts | 164 +- packages/color/src/defcolor.ts | 318 +- packages/color/src/distance.ts | 218 +- packages/color/src/gradients.ts | 68 +- packages/color/src/hcy/hcy-rgb.ts | 32 +- packages/color/src/hcy/hcy.ts | 52 +- packages/color/src/hsi/hsi-rgb.ts | 56 +- packages/color/src/hsi/hsi.ts | 52 +- packages/color/src/hsl/hsl-css.ts | 18 +- packages/color/src/hsl/hsl-hsv.ts | 16 +- packages/color/src/hsl/hsl-rgb.ts | 20 +- packages/color/src/hsl/hsl.ts | 62 +- packages/color/src/hsv/hsv-hsl.ts | 14 +- packages/color/src/hsv/hsv-rgb.ts | 20 +- packages/color/src/hsv/hsv.ts | 62 +- packages/color/src/int/int-css.ts | 12 +- packages/color/src/int/int-int.ts | 2 +- packages/color/src/int/int-rgb.ts | 16 +- packages/color/src/int/int-srgb.ts | 32 +- packages/color/src/int/int.ts | 328 +- packages/color/src/internal/ensure.ts | 28 +- packages/color/src/internal/matrix-ops.ts | 86 +- packages/color/src/internal/scale.ts | 2 +- packages/color/src/invert.ts | 96 +- packages/color/src/is-black.ts | 34 +- packages/color/src/is-gamut.ts | 20 +- packages/color/src/is-gray.ts | 36 +- packages/color/src/is-white.ts | 40 +- packages/color/src/lab/lab-css.ts | 12 +- packages/color/src/lab/lab-lab.ts | 4 +- packages/color/src/lab/lab-lch.ts | 28 +- packages/color/src/lab/lab-rgb.ts | 10 +- packages/color/src/lab/lab-xyz.ts | 30 +- packages/color/src/lab/lab50.ts | 70 +- packages/color/src/lab/lab65.ts | 70 +- packages/color/src/lch/lch-css.ts | 12 +- packages/color/src/lch/lch.ts | 70 +- packages/color/src/lighten.ts | 28 +- packages/color/src/linear.ts | 4 +- packages/color/src/luminance-rgb.ts | 30 +- packages/color/src/luminance.ts | 32 +- packages/color/src/max-chroma.ts | 456 +-- packages/color/src/mix.ts | 84 +- packages/color/src/oklab/oklab-rgb.ts | 20 +- packages/color/src/oklab/oklab-xyz.ts | 14 +- packages/color/src/oklab/oklab.ts | 72 +- packages/color/src/rgb/hue-rgb.ts | 18 +- packages/color/src/rgb/kelvin-rgba.ts | 48 +- packages/color/src/rgb/rgb-hcv.ts | 28 +- packages/color/src/rgb/rgb-hcy.ts | 12 +- packages/color/src/rgb/rgb-hsi.ts | 22 +- packages/color/src/rgb/rgb-hsl.ts | 8 +- packages/color/src/rgb/rgb-hsv.ts | 6 +- packages/color/src/rgb/rgb-lab.ts | 10 +- packages/color/src/rgb/rgb-oklab.ts | 10 +- packages/color/src/rgb/rgb-srgb.ts | 28 +- packages/color/src/rgb/rgb-xyz.ts | 10 +- packages/color/src/rgb/rgb-ycc.ts | 28 +- packages/color/src/rgb/rgb.ts | 84 +- packages/color/src/rotate.ts | 42 +- packages/color/src/sort.ts | 58 +- packages/color/src/srgb/srgb-css.ts | 16 +- packages/color/src/srgb/srgb-int.ts | 20 +- packages/color/src/srgb/srgb-rgb.ts | 28 +- packages/color/src/srgb/srgb.ts | 66 +- packages/color/src/strategies.ts | 144 +- packages/color/src/swatches.ts | 82 +- packages/color/src/tint.ts | 54 +- packages/color/src/transform.ts | 2 +- packages/color/src/variations.ts | 66 +- packages/color/src/xyy/xyy-xyz.ts | 20 +- packages/color/src/xyy/xyy.ts | 66 +- packages/color/src/xyz/wavelength-xyz.ts | 44 +- packages/color/src/xyz/xyz-lab.ts | 32 +- packages/color/src/xyz/xyz-oklab.ts | 2 +- packages/color/src/xyz/xyz-rgb.ts | 10 +- packages/color/src/xyz/xyz-xyy.ts | 22 +- packages/color/src/xyz/xyz-xyz.ts | 4 +- packages/color/src/xyz/xyz50.ts | 74 +- packages/color/src/xyz/xyz65.ts | 74 +- packages/color/src/ycc/ycc-rgb.ts | 32 +- packages/color/src/ycc/ycc.ts | 58 +- packages/color/test/index.ts | 204 +- packages/color/tools/blackbody.ts | 48 +- packages/color/tools/gradients.ts | 70 +- packages/color/tools/index.ts | 250 +- packages/color/tools/lch-slices.ts | 46 +- packages/color/tools/limits.ts | 52 +- packages/color/tools/max-chroma.ts | 46 +- packages/color/tsconfig.json | 12 +- packages/colored-noise/api-extractor.json | 2 +- packages/colored-noise/package.json | 218 +- packages/colored-noise/src/blue.ts | 26 +- packages/colored-noise/src/green.ts | 8 +- packages/colored-noise/src/pink.ts | 24 +- packages/colored-noise/src/red.ts | 22 +- packages/colored-noise/src/utils.ts | 22 +- packages/colored-noise/src/violet.ts | 8 +- packages/colored-noise/src/white.ts | 10 +- packages/colored-noise/tools/hihat.ts | 18 +- packages/colored-noise/tools/spectrum.ts | 40 +- packages/colored-noise/tools/write-samples.ts | 30 +- packages/colored-noise/tsconfig.json | 10 +- packages/compare/api-extractor.json | 2 +- packages/compare/package.json | 154 +- packages/compare/src/compare.ts | 32 +- packages/compare/src/keys.ts | 206 +- packages/compare/src/reverse.ts | 8 +- packages/compare/test/index.ts | 204 +- packages/compare/tsconfig.json | 10 +- packages/complex/api-extractor.json | 2 +- packages/complex/package.json | 158 +- packages/complex/src/index.ts | 82 +- packages/complex/test/index.ts | 432 +-- packages/complex/tools/mandelbrot.ts | 42 +- packages/complex/tsconfig.json | 12 +- packages/compose/api-extractor.json | 2 +- packages/compose/package.json | 212 +- packages/compose/src/comp.ts | 296 +- packages/compose/src/complement.ts | 34 +- packages/compose/src/constantly.ts | 5 +- packages/compose/src/delay.ts | 34 +- packages/compose/src/delayed.ts | 2 +- packages/compose/src/ifdef.ts | 2 +- packages/compose/src/juxt.ts | 138 +- packages/compose/src/partial.ts | 138 +- packages/compose/src/promisify.ts | 6 +- packages/compose/src/thread-first.ts | 18 +- packages/compose/src/thread-last.ts | 18 +- packages/compose/src/trampoline.ts | 8 +- packages/compose/test/delay.ts | 18 +- packages/compose/test/juxt.ts | 208 +- packages/compose/test/partial.ts | 68 +- packages/compose/tsconfig.json | 10 +- packages/csp/api-extractor.json | 2 +- packages/csp/package.json | 206 +- packages/csp/src/api.ts | 24 +- packages/csp/src/buffer.ts | 110 +- packages/csp/src/channel.ts | 1212 +++---- packages/csp/src/mult.ts | 212 +- packages/csp/src/pubsub.ts | 178 +- packages/csp/test/async.ts | 162 +- packages/csp/test/file.ts | 34 +- packages/csp/test/graph.ts | 170 +- packages/csp/test/node.ts | 430 +-- packages/csp/tsconfig.json | 14 +- packages/csv/api-extractor.json | 2 +- packages/csv/package.json | 172 +- packages/csv/src/api.ts | 202 +- packages/csv/src/format.ts | 139 +- packages/csv/src/parse.ts | 432 +-- packages/csv/src/transforms.ts | 66 +- packages/csv/test/format.ts | 64 +- packages/csv/test/parse.ts | 180 +- packages/csv/tsconfig.json | 10 +- packages/date/api-extractor.json | 2 +- packages/date/package.json | 260 +- packages/date/src/api.ts | 186 +- packages/date/src/checks.ts | 18 +- packages/date/src/datetime.ts | 720 ++-- packages/date/src/format.ts | 306 +- packages/date/src/i18n.ts | 94 +- packages/date/src/i18n/de.ts | 142 +- packages/date/src/i18n/en.ts | 142 +- packages/date/src/i18n/es.ts | 76 +- packages/date/src/i18n/fr.ts | 80 +- packages/date/src/i18n/it.ts | 80 +- packages/date/src/internal/precision.ts | 4 +- packages/date/src/iterators.ts | 82 +- packages/date/src/relative.ts | 358 +- packages/date/src/round.ts | 138 +- packages/date/src/timecode.ts | 62 +- packages/date/src/units.ts | 22 +- packages/date/test/datetime.ts | 130 +- packages/date/test/format.ts | 124 +- packages/date/test/i18n.ts | 226 +- packages/date/test/iterators.ts | 274 +- packages/date/test/relative.ts | 164 +- packages/date/tsconfig.json | 10 +- packages/dcons/api-extractor.json | 2 +- packages/dcons/package.json | 192 +- packages/dcons/src/alist.ts | 584 ++-- packages/dcons/src/api.ts | 14 +- packages/dcons/src/dcons.ts | 744 ++-- packages/dcons/src/ring.ts | 266 +- packages/dcons/src/sol.ts | 84 +- packages/dcons/test/index.ts | 172 +- packages/dcons/test/sol.ts | 70 +- packages/dcons/tsconfig.json | 10 +- packages/defmulti/api-extractor.json | 2 +- packages/defmulti/package.json | 182 +- packages/defmulti/src/api.ts | 464 +-- packages/defmulti/src/defmulti-n.ts | 16 +- packages/defmulti/src/defmulti.ts | 410 +-- packages/defmulti/src/impls.ts | 30 +- packages/defmulti/test/index.ts | 434 +-- packages/defmulti/tsconfig.json | 10 +- packages/dgraph-dot/api-extractor.json | 2 +- packages/dgraph-dot/package.json | 152 +- packages/dgraph-dot/src/index.ts | 78 +- packages/dgraph-dot/test/index.ts | 54 +- packages/dgraph-dot/tsconfig.json | 10 +- packages/dgraph/api-extractor.json | 2 +- packages/dgraph/package.json | 166 +- packages/dgraph/src/index.ts | 306 +- packages/dgraph/test/index.ts | 160 +- packages/dgraph/tsconfig.json | 10 +- packages/diff/api-extractor.json | 2 +- packages/diff/package.json | 154 +- packages/diff/src/api.ts | 26 +- packages/diff/src/array.ts | 400 +-- packages/diff/src/object.ts | 98 +- packages/diff/test/array.ts | 140 +- packages/diff/tsconfig.json | 10 +- .../distance-transform/api-extractor.json | 2 +- packages/distance-transform/package.json | 162 +- packages/distance-transform/src/api.ts | 24 +- packages/distance-transform/src/metric.ts | 34 +- packages/distance-transform/src/transform.ts | 132 +- packages/distance-transform/test/index.ts | 36 +- packages/distance-transform/tools/index.ts | 14 +- packages/distance-transform/tsconfig.json | 12 +- packages/distance/api-extractor.json | 2 +- packages/distance/package.json | 220 +- packages/distance/src/api.ts | 94 +- packages/distance/src/argmin.ts | 122 +- packages/distance/src/eucledian.ts | 14 +- packages/distance/src/haversine.ts | 4 +- packages/distance/src/knearest.ts | 194 +- packages/distance/src/manhattan.ts | 34 +- packages/distance/src/nearest.ts | 106 +- packages/distance/src/squared.ts | 14 +- packages/distance/test/index.ts | 162 +- packages/distance/tsconfig.json | 12 +- packages/dl-asset/api-extractor.json | 2 +- packages/dl-asset/package.json | 180 +- packages/dl-asset/src/api.ts | 62 +- packages/dl-asset/src/canvas.ts | 86 +- packages/dl-asset/src/download.ts | 22 +- packages/dl-asset/src/raw.ts | 54 +- packages/dl-asset/tsconfig.json | 10 +- packages/dlogic/api-extractor.json | 2 +- packages/dlogic/package.json | 142 +- packages/dlogic/src/index.ts | 52 +- packages/dlogic/tsconfig.json | 10 +- packages/dot/api-extractor.json | 2 +- packages/dot/package.json | 170 +- packages/dot/src/api.ts | 172 +- packages/dot/src/serialize.ts | 190 +- packages/dot/test/example.ts | 114 +- packages/dot/tsconfig.json | 10 +- packages/dsp-io-wav/api-extractor.json | 2 +- packages/dsp-io-wav/package.json | 180 +- packages/dsp-io-wav/src/api.ts | 32 +- packages/dsp-io-wav/src/write.ts | 104 +- packages/dsp-io-wav/test/index.ts | 26 +- packages/dsp-io-wav/tsconfig.json | 10 +- packages/dsp/api-extractor.json | 2 +- packages/dsp/package.json | 546 +-- packages/dsp/src/add.ts | 50 +- packages/dsp/src/addg.ts | 6 +- packages/dsp/src/adsr.ts | 360 +- packages/dsp/src/agen.ts | 22 +- packages/dsp/src/allpass.ts | 76 +- packages/dsp/src/alt.ts | 22 +- packages/dsp/src/anti-alias.ts | 10 +- packages/dsp/src/api.ts | 68 +- packages/dsp/src/aproc.ts | 26 +- packages/dsp/src/biquad.ts | 396 +-- packages/dsp/src/bounce.ts | 16 +- packages/dsp/src/complex.ts | 2 +- packages/dsp/src/const.ts | 8 +- packages/dsp/src/convert.ts | 26 +- packages/dsp/src/cosine.ts | 86 +- packages/dsp/src/curve.ts | 28 +- packages/dsp/src/dcblock.ts | 8 +- packages/dsp/src/delay.ts | 178 +- packages/dsp/src/feedback-delay.ts | 28 +- packages/dsp/src/fft.ts | 392 +-- packages/dsp/src/filter-response.ts | 44 +- packages/dsp/src/foldback.ts | 38 +- packages/dsp/src/impulse-train.ts | 54 +- packages/dsp/src/impulse.ts | 28 +- packages/dsp/src/integrator.ts | 34 +- packages/dsp/src/internal/take.ts | 16 +- packages/dsp/src/iterable.ts | 58 +- packages/dsp/src/line.ts | 22 +- packages/dsp/src/madd.ts | 52 +- packages/dsp/src/mapg.ts | 168 +- packages/dsp/src/mix.ts | 24 +- packages/dsp/src/mul.ts | 44 +- packages/dsp/src/multiplex.ts | 40 +- packages/dsp/src/onepole.ts | 80 +- packages/dsp/src/osc-additive.ts | 66 +- packages/dsp/src/osc-cos.ts | 2 +- packages/dsp/src/osc-dsf.ts | 68 +- packages/dsp/src/osc-mix.ts | 36 +- packages/dsp/src/osc-parabolic.ts | 2 +- packages/dsp/src/osc-rect.ts | 18 +- packages/dsp/src/osc-saw.ts | 2 +- packages/dsp/src/osc-sin.ts | 2 +- packages/dsp/src/osc-tri.ts | 2 +- packages/dsp/src/osc-wavetable.ts | 20 +- packages/dsp/src/osc.ts | 76 +- packages/dsp/src/pink-noise.ts | 78 +- packages/dsp/src/pipe.ts | 78 +- packages/dsp/src/power.ts | 84 +- packages/dsp/src/product.ts | 12 +- packages/dsp/src/reciprocal.ts | 28 +- packages/dsp/src/serial.ts | 120 +- packages/dsp/src/sincos.ts | 72 +- packages/dsp/src/sum.ts | 12 +- packages/dsp/src/svf.ts | 122 +- packages/dsp/src/sweep.ts | 18 +- packages/dsp/src/waveshaper.ts | 70 +- packages/dsp/src/white-noise.ts | 20 +- packages/dsp/src/window.ts | 90 +- packages/dsp/test/fft.ts | 124 +- packages/dsp/test/osc.ts | 20 +- packages/dsp/tools/generate-diagrams.ts | 428 +-- packages/dsp/tsconfig.json | 10 +- packages/dual-algebra/api-extractor.json | 2 +- packages/dual-algebra/package.json | 184 +- packages/dual-algebra/src/api.ts | 16 +- packages/dual-algebra/src/ops.ts | 300 +- packages/dual-algebra/src/poly.ts | 96 +- packages/dual-algebra/src/vector.ts | 28 +- packages/dual-algebra/tsconfig.json | 10 +- packages/dynvar/api-extractor.json | 2 +- packages/dynvar/package.json | 156 +- packages/dynvar/src/index.ts | 148 +- packages/dynvar/test/index.ts | 82 +- packages/dynvar/tsconfig.json | 10 +- packages/ecs/api-extractor.json | 2 +- packages/ecs/package.json | 246 +- packages/ecs/src/api.ts | 152 +- packages/ecs/src/caches/lru.ts | 108 +- packages/ecs/src/caches/null.ts | 34 +- packages/ecs/src/caches/unbounded.ts | 72 +- packages/ecs/src/components/acomponent.ts | 258 +- packages/ecs/src/components/mem-component.ts | 202 +- .../ecs/src/components/object-component.ts | 120 +- packages/ecs/src/ecs.ts | 240 +- packages/ecs/src/groups/group.ts | 428 +-- packages/ecs/test/component.ts | 214 +- packages/ecs/test/group.ts | 88 +- packages/ecs/tsconfig.json | 10 +- packages/egf/api-extractor.json | 2 +- packages/egf/package.json | 206 +- packages/egf/src/api.ts | 132 +- packages/egf/src/checks.ts | 2 +- packages/egf/src/convert.ts | 114 +- packages/egf/src/dot.ts | 70 +- packages/egf/src/parser.ts | 376 +- packages/egf/src/prefix.ts | 48 +- packages/egf/src/tags.ts | 54 +- packages/egf/test/escape.ts | 152 +- packages/egf/test/prefix.ts | 62 +- packages/egf/test/ref.ts | 112 +- packages/egf/test/serialize.ts | 56 +- packages/egf/tsconfig.json | 10 +- packages/equiv/api-extractor.json | 2 +- packages/equiv/package.json | 138 +- packages/equiv/src/index.ts | 136 +- packages/equiv/test/index.ts | 256 +- packages/equiv/tsconfig.json | 10 +- packages/errors/api-extractor.json | 2 +- packages/errors/package.json | 182 +- packages/errors/src/assert.ts | 30 +- packages/errors/src/deferror.ts | 14 +- packages/errors/src/illegal-arguments.ts | 2 +- packages/errors/src/illegal-arity.ts | 2 +- packages/errors/src/illegal-state.ts | 2 +- packages/errors/src/out-of-bounds.ts | 12 +- packages/errors/src/unsupported.ts | 4 +- packages/errors/tsconfig.json | 10 +- packages/expose/api-extractor.json | 2 +- packages/expose/package.json | 138 +- packages/expose/src/index.ts | 44 +- packages/expose/test/index.ts | 4 +- packages/expose/tsconfig.json | 12 +- packages/file-io/api-extractor.json | 2 +- packages/file-io/package.json | 218 +- packages/file-io/src/delete.ts | 6 +- packages/file-io/src/dir.ts | 8 +- packages/file-io/src/ext.ts | 4 +- packages/file-io/src/files.ts | 96 +- packages/file-io/src/hash.ts | 58 +- packages/file-io/src/json.ts | 26 +- packages/file-io/src/mask.ts | 6 +- packages/file-io/src/temp.ts | 18 +- packages/file-io/src/text.ts | 26 +- packages/file-io/src/write.ts | 18 +- packages/file-io/test/index.ts | 4 +- packages/file-io/tsconfig.json | 12 +- packages/fsm/api-extractor.json | 2 +- packages/fsm/package.json | 252 +- packages/fsm/src/alts-lit.ts | 40 +- packages/fsm/src/alts.ts | 86 +- packages/fsm/src/always.ts | 8 +- packages/fsm/src/api.ts | 50 +- packages/fsm/src/fsm.ts | 141 +- packages/fsm/src/lit.ts | 34 +- packages/fsm/src/never.ts | 8 +- packages/fsm/src/not.ts | 38 +- packages/fsm/src/range.ts | 38 +- packages/fsm/src/repeat.ts | 64 +- packages/fsm/src/result.ts | 4 +- packages/fsm/src/seq.ts | 62 +- packages/fsm/src/str.ts | 52 +- packages/fsm/src/until.ts | 64 +- packages/fsm/tsconfig.json | 10 +- packages/fuzzy-viz/api-extractor.json | 2 +- packages/fuzzy-viz/package.json | 184 +- packages/fuzzy-viz/src/api.ts | 104 +- packages/fuzzy-viz/src/strategy.ts | 124 +- packages/fuzzy-viz/src/var.ts | 200 +- packages/fuzzy-viz/test/index.ts | 22 +- packages/fuzzy-viz/tsconfig.json | 12 +- packages/fuzzy/api-extractor.json | 2 +- packages/fuzzy/package.json | 212 +- packages/fuzzy/src/api.ts | 70 +- packages/fuzzy/src/defuzz.ts | 92 +- packages/fuzzy/src/rules.ts | 40 +- packages/fuzzy/src/shapes.ts | 160 +- packages/fuzzy/src/strategies/bisector.ts | 50 +- packages/fuzzy/src/strategies/centroid.ts | 30 +- packages/fuzzy/src/strategies/maxima.ts | 86 +- packages/fuzzy/src/strategies/opts.ts | 8 +- packages/fuzzy/src/tnorms.ts | 86 +- packages/fuzzy/src/var.ts | 58 +- packages/fuzzy/test/defuzz.ts | 200 +- packages/fuzzy/test/lvar.ts | 80 +- packages/fuzzy/tsconfig.json | 12 +- packages/geom-accel/api-extractor.json | 2 +- packages/geom-accel/package.json | 228 +- packages/geom-accel/src/aspatial-grid.ts | 300 +- packages/geom-accel/src/kd-tree-map.ts | 706 ++-- packages/geom-accel/src/kd-tree-set.ts | 166 +- packages/geom-accel/src/nd-quadtree-map.ts | 680 ++-- packages/geom-accel/src/nd-quadtree-set.ts | 226 +- packages/geom-accel/src/spatial-grid2.ts | 100 +- packages/geom-accel/src/spatial-grid3.ts | 118 +- packages/geom-accel/src/utils.ts | 38 +- packages/geom-accel/test/kdtree.ts | 138 +- packages/geom-accel/test/quadtree.ts | 110 +- packages/geom-accel/tsconfig.json | 10 +- packages/geom-api/api-extractor.json | 2 +- packages/geom-api/package.json | 200 +- packages/geom-api/src/accel.ts | 54 +- packages/geom-api/src/convex.ts | 8 +- packages/geom-api/src/cubic.ts | 32 +- packages/geom-api/src/isec.ts | 24 +- packages/geom-api/src/path.ts | 22 +- packages/geom-api/src/sample.ts | 80 +- packages/geom-api/src/shape.ts | 26 +- packages/geom-api/src/subdiv.ts | 44 +- packages/geom-api/tsconfig.json | 10 +- packages/geom-arc/api-extractor.json | 2 +- packages/geom-arc/package.json | 196 +- packages/geom-arc/src/bounds.ts | 58 +- packages/geom-arc/src/closest-point.ts | 24 +- packages/geom-arc/src/from-endpoints.ts | 110 +- packages/geom-arc/src/point-at.ts | 24 +- packages/geom-arc/src/sample.ts | 116 +- packages/geom-arc/tsconfig.json | 10 +- packages/geom-clip-line/api-extractor.json | 2 +- packages/geom-clip-line/package.json | 170 +- packages/geom-clip-line/src/clip-poly.ts | 60 +- packages/geom-clip-line/src/liang-barsky.ts | 154 +- packages/geom-clip-line/tsconfig.json | 10 +- packages/geom-clip-poly/api-extractor.json | 2 +- packages/geom-clip-poly/package.json | 168 +- packages/geom-clip-poly/src/index.ts | 60 +- packages/geom-clip-poly/tsconfig.json | 10 +- .../geom-closest-point/api-extractor.json | 2 +- packages/geom-closest-point/package.json | 208 +- packages/geom-closest-point/src/box.ts | 102 +- packages/geom-closest-point/src/circle.ts | 16 +- packages/geom-closest-point/src/ellipse.ts | 46 +- packages/geom-closest-point/src/line.ts | 122 +- packages/geom-closest-point/src/plane.ts | 24 +- packages/geom-closest-point/src/points.ts | 36 +- packages/geom-closest-point/tsconfig.json | 10 +- packages/geom-fuzz/api-extractor.json | 2 +- packages/geom-fuzz/package.json | 242 +- packages/geom-fuzz/src/api.ts | 48 +- packages/geom-fuzz/src/comp.ts | 6 +- packages/geom-fuzz/src/dots.ts | 58 +- packages/geom-fuzz/src/hatch.ts | 62 +- packages/geom-fuzz/src/line.ts | 34 +- packages/geom-fuzz/src/points.ts | 2 +- packages/geom-fuzz/src/polygon.ts | 52 +- packages/geom-fuzz/src/presets.ts | 34 +- packages/geom-fuzz/tsconfig.json | 10 +- packages/geom-hull/api-extractor.json | 2 +- packages/geom-hull/package.json | 154 +- packages/geom-hull/src/graham-scan.ts | 118 +- packages/geom-hull/tsconfig.json | 10 +- packages/geom-io-obj/api-extractor.json | 2 +- packages/geom-io-obj/package.json | 170 +- packages/geom-io-obj/src/api.ts | 132 +- packages/geom-io-obj/src/parser.ts | 332 +- packages/geom-io-obj/test/index.ts | 172 +- packages/geom-io-obj/tsconfig.json | 10 +- packages/geom-isec/api-extractor.json | 2 +- packages/geom-isec/package.json | 240 +- packages/geom-isec/src/api.ts | 2 +- packages/geom-isec/src/circle-circle.ts | 50 +- packages/geom-isec/src/line-line.ts | 84 +- packages/geom-isec/src/line-poly.ts | 28 +- packages/geom-isec/src/plane-plane.ts | 36 +- packages/geom-isec/src/point.ts | 300 +- packages/geom-isec/src/ray-circle.ts | 44 +- packages/geom-isec/src/ray-line.ts | 46 +- packages/geom-isec/src/ray-plane.ts | 44 +- packages/geom-isec/src/ray-poly.ts | 94 +- packages/geom-isec/src/ray-rect.ts | 130 +- packages/geom-isec/src/rect-circle.ts | 110 +- packages/geom-isec/src/rect-rect.ts | 32 +- packages/geom-isec/test/point-segment.ts | 62 +- packages/geom-isec/test/polyline.ts | 261 +- packages/geom-isec/test/ray.ts | 130 +- packages/geom-isec/tsconfig.json | 10 +- packages/geom-isoline/api-extractor.json | 2 +- packages/geom-isoline/package.json | 156 +- packages/geom-isoline/src/index.ts | 234 +- packages/geom-isoline/tsconfig.json | 10 +- packages/geom-poly-utils/api-extractor.json | 2 +- packages/geom-poly-utils/package.json | 222 +- packages/geom-poly-utils/src/area.ts | 18 +- packages/geom-poly-utils/src/barycentric.ts | 44 +- packages/geom-poly-utils/src/bounds.ts | 158 +- .../geom-poly-utils/src/center-of-weight.ts | 32 +- packages/geom-poly-utils/src/centroid.ts | 14 +- packages/geom-poly-utils/src/circumcenter.ts | 132 +- packages/geom-poly-utils/src/convexity.ts | 34 +- packages/geom-poly-utils/src/equilateral.ts | 20 +- packages/geom-poly-utils/src/perimeter.ts | 22 +- packages/geom-poly-utils/src/tangent.ts | 130 +- packages/geom-poly-utils/tsconfig.json | 10 +- packages/geom-resample/api-extractor.json | 2 +- packages/geom-resample/package.json | 190 +- packages/geom-resample/src/resample.ts | 46 +- packages/geom-resample/src/sampler.ts | 320 +- packages/geom-resample/src/simplify.ts | 64 +- packages/geom-resample/tsconfig.json | 10 +- packages/geom-sdf/api-extractor.json | 2 +- packages/geom-sdf/package.json | 256 +- packages/geom-sdf/src/api.ts | 160 +- packages/geom-sdf/src/as-polygons.ts | 38 +- packages/geom-sdf/src/as-sdf.ts | 280 +- packages/geom-sdf/src/bounds.ts | 56 +- packages/geom-sdf/src/dist.ts | 254 +- packages/geom-sdf/src/domain.ts | 72 +- packages/geom-sdf/src/ops.ts | 118 +- packages/geom-sdf/src/sample.ts | 42 +- packages/geom-sdf/src/shapes.ts | 156 +- packages/geom-sdf/test/index.ts | 4 +- packages/geom-sdf/tools/combinators.ts | 78 +- packages/geom-sdf/tools/index.ts | 52 +- packages/geom-sdf/tsconfig.json | 12 +- packages/geom-splines/api-extractor.json | 2 +- packages/geom-splines/package.json | 290 +- packages/geom-splines/src/cubic-arc.ts | 76 +- packages/geom-splines/src/cubic-bounds.ts | 62 +- .../geom-splines/src/cubic-closest-point.ts | 22 +- .../src/cubic-from-breakpoints.ts | 90 +- .../src/cubic-from-controlpoints.ts | 84 +- packages/geom-splines/src/cubic-line.ts | 8 +- packages/geom-splines/src/cubic-quadratic.ts | 8 +- packages/geom-splines/src/cubic-sample.ts | 8 +- packages/geom-splines/src/cubic-split.ts | 82 +- packages/geom-splines/src/cubic-tangent.ts | 32 +- packages/geom-splines/src/internal/sample.ts | 82 +- packages/geom-splines/src/quadratic-bounds.ts | 36 +- .../src/quadratic-closest-point.ts | 20 +- packages/geom-splines/src/quadratic-line.ts | 6 +- packages/geom-splines/src/quadratic-sample.ts | 8 +- packages/geom-splines/src/quadratic-split.ts | 70 +- .../geom-splines/src/quadratic-tangent.ts | 22 +- packages/geom-splines/tsconfig.json | 10 +- packages/geom-subdiv-curve/api-extractor.json | 2 +- packages/geom-subdiv-curve/package.json | 184 +- packages/geom-subdiv-curve/src/api.ts | 58 +- packages/geom-subdiv-curve/src/kernels.ts | 24 +- packages/geom-subdiv-curve/src/subdivide.ts | 30 +- packages/geom-subdiv-curve/tsconfig.json | 10 +- packages/geom-tessellate/api-extractor.json | 2 +- packages/geom-tessellate/package.json | 202 +- packages/geom-tessellate/src/earcut.ts | 88 +- packages/geom-tessellate/src/edge-split.ts | 30 +- packages/geom-tessellate/src/inset.ts | 28 +- packages/geom-tessellate/src/quad-fan.ts | 18 +- packages/geom-tessellate/src/rim-tris.ts | 34 +- packages/geom-tessellate/src/tessellate.ts | 32 +- packages/geom-tessellate/src/tri-fan.ts | 18 +- packages/geom-tessellate/tsconfig.json | 10 +- packages/geom-voronoi/api-extractor.json | 2 +- packages/geom-voronoi/package.json | 184 +- packages/geom-voronoi/src/index.ts | 537 +-- packages/geom-voronoi/tsconfig.json | 10 +- packages/geom/api-extractor.json | 2 +- packages/geom/package.json | 762 ++-- packages/geom/src/aabb.ts | 54 +- packages/geom/src/api/aabb.ts | 58 +- packages/geom/src/api/apc.ts | 14 +- packages/geom/src/api/arc.ts | 178 +- packages/geom/src/api/bpatch.ts | 154 +- packages/geom/src/api/circle.ts | 34 +- packages/geom/src/api/cubic.ts | 40 +- packages/geom/src/api/ellipse.ts | 48 +- packages/geom/src/api/group.ts | 64 +- packages/geom/src/api/line.ts | 50 +- packages/geom/src/api/path.ts | 110 +- packages/geom/src/api/plane.ts | 34 +- packages/geom/src/api/points.ts | 48 +- packages/geom/src/api/polygon.ts | 24 +- packages/geom/src/api/polyline.ts | 44 +- packages/geom/src/api/quad.ts | 24 +- packages/geom/src/api/quad3.ts | 24 +- packages/geom/src/api/quadratic.ts | 46 +- packages/geom/src/api/ray.ts | 44 +- packages/geom/src/api/rect.ts | 78 +- packages/geom/src/api/sphere.ts | 34 +- packages/geom/src/api/text.ts | 26 +- packages/geom/src/api/triangle.ts | 24 +- packages/geom/src/apply-transforms.ts | 46 +- packages/geom/src/arc-length.ts | 44 +- packages/geom/src/arc.ts | 54 +- packages/geom/src/area.ts | 50 +- packages/geom/src/as-cubic.ts | 86 +- packages/geom/src/as-path.ts | 2 +- packages/geom/src/as-polygon.ts | 36 +- packages/geom/src/as-polyline.ts | 58 +- packages/geom/src/as-svg.ts | 44 +- packages/geom/src/bounds.ts | 138 +- packages/geom/src/bpatch.ts | 32 +- packages/geom/src/center.ts | 54 +- packages/geom/src/centroid.ts | 64 +- packages/geom/src/circle.ts | 20 +- packages/geom/src/classify-point.ts | 38 +- packages/geom/src/clip-convex.ts | 104 +- packages/geom/src/closest-point.ts | 86 +- packages/geom/src/convex-hull.ts | 40 +- packages/geom/src/cubic.ts | 12 +- packages/geom/src/edges.ts | 82 +- packages/geom/src/ellipse.ts | 2 +- packages/geom/src/fit-into-bounds.ts | 144 +- packages/geom/src/flip.ts | 74 +- packages/geom/src/group.ts | 2 +- packages/geom/src/internal/args.ts | 46 +- packages/geom/src/internal/bounds.ts | 42 +- packages/geom/src/internal/collate.ts | 92 +- packages/geom/src/internal/copy.ts | 12 +- packages/geom/src/internal/edges.ts | 6 +- packages/geom/src/internal/pclike.ts | 4 +- packages/geom/src/internal/points-as-shape.ts | 12 +- packages/geom/src/internal/rotate.ts | 6 +- packages/geom/src/internal/scale.ts | 14 +- packages/geom/src/internal/split.ts | 10 +- packages/geom/src/internal/transform.ts | 52 +- packages/geom/src/internal/translate.ts | 6 +- packages/geom/src/intersects.ts | 88 +- packages/geom/src/line.ts | 16 +- packages/geom/src/map-point.ts | 20 +- packages/geom/src/offset.ts | 70 +- packages/geom/src/path-builder.ts | 368 +- packages/geom/src/path-from-svg.ts | 262 +- packages/geom/src/path.ts | 78 +- packages/geom/src/plane.ts | 20 +- packages/geom/src/point-at.ts | 46 +- packages/geom/src/point-inside.ts | 50 +- packages/geom/src/points.ts | 4 +- packages/geom/src/polygon.ts | 26 +- packages/geom/src/polyline.ts | 2 +- packages/geom/src/quad.ts | 38 +- packages/geom/src/quadratic.ts | 4 +- packages/geom/src/ray.ts | 2 +- packages/geom/src/rect.ts | 78 +- packages/geom/src/resample.ts | 48 +- packages/geom/src/rotate.ts | 102 +- packages/geom/src/scale.ts | 228 +- packages/geom/src/scatter.ts | 36 +- packages/geom/src/simplify.ts | 88 +- packages/geom/src/sphere.ts | 8 +- packages/geom/src/split-at.ts | 100 +- packages/geom/src/split-near.ts | 62 +- packages/geom/src/subdiv-curve.ts | 32 +- packages/geom/src/tangent-at.ts | 38 +- packages/geom/src/tessellate.ts | 12 +- packages/geom/src/text.ts | 2 +- packages/geom/src/transform-vertices.ts | 88 +- packages/geom/src/transform.ts | 94 +- packages/geom/src/translate.ts | 164 +- packages/geom/src/triangle.ts | 4 +- packages/geom/src/union.ts | 26 +- packages/geom/src/unmap-point.ts | 46 +- packages/geom/src/vertices.ts | 218 +- packages/geom/src/volume.ts | 14 +- packages/geom/src/warp-points.ts | 34 +- packages/geom/src/with-attribs.ts | 6 +- packages/geom/test/ctors.ts | 172 +- packages/geom/tsconfig.json | 12 +- packages/gp/api-extractor.json | 2 +- packages/gp/package.json | 218 +- packages/gp/src/api.ts | 86 +- packages/gp/src/ast.ts | 278 +- packages/gp/src/mep.ts | 220 +- packages/gp/src/utils.ts | 32 +- packages/gp/test/ast.ts | 114 +- packages/gp/test/mep.ts | 838 ++--- packages/gp/tsconfig.json | 10 +- packages/grid-iterators/api-extractor.json | 2 +- packages/grid-iterators/package.json | 306 +- packages/grid-iterators/src/circle.ts | 120 +- packages/grid-iterators/src/clipping.ts | 138 +- packages/grid-iterators/src/column-ends.ts | 12 +- packages/grid-iterators/src/columns.ts | 6 +- packages/grid-iterators/src/diagonal-ends.ts | 24 +- packages/grid-iterators/src/diagonal.ts | 36 +- packages/grid-iterators/src/diamond-square.ts | 96 +- packages/grid-iterators/src/flood-fill.ts | 110 +- packages/grid-iterators/src/hilbert.ts | 106 +- packages/grid-iterators/src/hvline.ts | 72 +- packages/grid-iterators/src/interleave.ts | 12 +- packages/grid-iterators/src/line.ts | 74 +- packages/grid-iterators/src/random.ts | 8 +- packages/grid-iterators/src/row-ends.ts | 12 +- packages/grid-iterators/src/rows.ts | 6 +- packages/grid-iterators/src/spiral.ts | 66 +- packages/grid-iterators/src/utils.ts | 10 +- packages/grid-iterators/src/zcurve.ts | 16 +- packages/grid-iterators/src/zigzag-columns.ts | 16 +- .../grid-iterators/src/zigzag-diagonal.ts | 58 +- packages/grid-iterators/src/zigzag-rows.ts | 16 +- packages/grid-iterators/tools/build-assets.js | 112 +- packages/grid-iterators/tsconfig.json | 10 +- packages/hdiff/api-extractor.json | 2 +- packages/hdiff/package.json | 188 +- packages/hdiff/src/api.ts | 96 +- packages/hdiff/src/cli.ts | 20 +- packages/hdiff/src/diff.ts | 122 +- packages/hdiff/src/html.ts | 54 +- packages/hdiff/src/theme.ts | 230 +- packages/hdiff/tsconfig.json | 10 +- packages/hdom-canvas/api-extractor.json | 2 +- packages/hdom-canvas/package.json | 180 +- packages/hdom-canvas/src/index.ts | 230 +- packages/hdom-canvas/tsconfig.json | 10 +- packages/hdom-components/api-extractor.json | 2 +- packages/hdom-components/package.json | 222 +- packages/hdom-components/src/button-group.ts | 132 +- packages/hdom-components/src/button.ts | 120 +- packages/hdom-components/src/canvas.ts | 134 +- packages/hdom-components/src/dropdown.ts | 48 +- packages/hdom-components/src/fps-counter.ts | 120 +- packages/hdom-components/src/link.ts | 34 +- packages/hdom-components/src/notification.ts | 76 +- packages/hdom-components/src/pager.ts | 240 +- packages/hdom-components/src/sparkline.ts | 160 +- packages/hdom-components/src/title.ts | 58 +- packages/hdom-components/src/toggle.ts | 216 +- .../src/utils/merge-attribs.ts | 10 +- packages/hdom-components/tsconfig.json | 10 +- packages/hdom-mock/api-extractor.json | 2 +- packages/hdom-mock/package.json | 142 +- packages/hdom-mock/src/index.ts | 438 +-- packages/hdom-mock/test/index.ts | 142 +- packages/hdom-mock/tsconfig.json | 10 +- packages/hdom/api-extractor.json | 2 +- packages/hdom/package.json | 250 +- packages/hdom/src/api.ts | 854 ++--- packages/hdom/src/default.ts | 66 +- packages/hdom/src/diff.ts | 520 +-- packages/hdom/src/dom.ts | 446 +-- packages/hdom/src/normalize.ts | 298 +- packages/hdom/src/render-once.ts | 22 +- packages/hdom/src/resolve.ts | 2 +- packages/hdom/src/start.ts | 52 +- packages/hdom/test/index.ts | 236 +- packages/hdom/tsconfig.json | 10 +- packages/heaps/api-extractor.json | 2 +- packages/heaps/package.json | 182 +- packages/heaps/src/api.ts | 22 +- packages/heaps/src/dheap.ts | 190 +- packages/heaps/src/heap.ts | 506 +-- packages/heaps/src/pairing.ts | 362 +- packages/heaps/src/priority-queue.ts | 216 +- packages/heaps/test/dheap.ts | 174 +- packages/heaps/test/heap.ts | 174 +- packages/heaps/tsconfig.json | 10 +- packages/hex/api-extractor.json | 2 +- packages/hex/package.json | 140 +- packages/hex/src/index.ts | 38 +- packages/hex/test/index.ts | 102 +- packages/hex/tsconfig.json | 10 +- packages/hiccup-canvas/api-extractor.json | 2 +- packages/hiccup-canvas/package.json | 262 +- packages/hiccup-canvas/src/api.ts | 8 +- packages/hiccup-canvas/src/arc.ts | 42 +- packages/hiccup-canvas/src/color.ts | 42 +- packages/hiccup-canvas/src/draw.ts | 222 +- packages/hiccup-canvas/src/image.ts | 70 +- .../hiccup-canvas/src/internal/end-shape.ts | 24 +- packages/hiccup-canvas/src/internal/state.ts | 256 +- packages/hiccup-canvas/src/line.ts | 38 +- packages/hiccup-canvas/src/packed-points.ts | 78 +- packages/hiccup-canvas/src/path.ts | 212 +- packages/hiccup-canvas/src/points.ts | 58 +- packages/hiccup-canvas/src/polygon.ts | 32 +- packages/hiccup-canvas/src/polyline.ts | 12 +- packages/hiccup-canvas/src/rect.ts | 58 +- packages/hiccup-canvas/src/text.ts | 24 +- packages/hiccup-canvas/tsconfig.json | 10 +- .../hiccup-carbon-icons/api-extractor.json | 2 +- packages/hiccup-carbon-icons/package.json | 156 +- packages/hiccup-carbon-icons/src/_svg.ts | 6 +- packages/hiccup-carbon-icons/src/with-size.ts | 30 +- .../tools/convert-icons.ts | 50 +- packages/hiccup-carbon-icons/tsconfig.json | 10 +- packages/hiccup-css/api-extractor.json | 2 +- packages/hiccup-css/package.json | 256 +- packages/hiccup-css/src/animation.ts | 68 +- packages/hiccup-css/src/api.ts | 112 +- packages/hiccup-css/src/attribs.ts | 9 +- packages/hiccup-css/src/comment.ts | 32 +- packages/hiccup-css/src/conditional.ts | 60 +- packages/hiccup-css/src/css.ts | 44 +- packages/hiccup-css/src/impl.ts | 182 +- packages/hiccup-css/src/import.ts | 21 +- packages/hiccup-css/src/inject.ts | 28 +- packages/hiccup-css/src/keyframes.ts | 48 +- packages/hiccup-css/src/media.ts | 2 +- packages/hiccup-css/src/namespace.ts | 16 +- packages/hiccup-css/src/quoted-functions.ts | 12 +- packages/hiccup-css/src/supports.ts | 2 +- packages/hiccup-css/test/index.ts | 298 +- packages/hiccup-css/tsconfig.json | 10 +- packages/hiccup-html/api-extractor.json | 2 +- packages/hiccup-html/package.json | 222 +- packages/hiccup-html/src/api.ts | 1158 +++---- packages/hiccup-html/src/blocks.ts | 84 +- packages/hiccup-html/src/def.ts | 48 +- packages/hiccup-html/src/forms.ts | 160 +- packages/hiccup-html/src/head.ts | 250 +- packages/hiccup-html/src/inline.ts | 60 +- packages/hiccup-html/src/lists.ts | 26 +- packages/hiccup-html/src/media.ts | 94 +- packages/hiccup-html/src/sections.ts | 76 +- packages/hiccup-html/src/table.ts | 34 +- packages/hiccup-html/test/index.ts | 122 +- packages/hiccup-html/tsconfig.json | 10 +- packages/hiccup-markdown/api-extractor.json | 2 +- packages/hiccup-markdown/package.json | 192 +- packages/hiccup-markdown/src/api.ts | 32 +- packages/hiccup-markdown/src/parse.ts | 522 +-- packages/hiccup-markdown/src/serialize.ts | 362 +- packages/hiccup-markdown/test/parse.ts | 332 +- packages/hiccup-markdown/test/serialize.ts | 192 +- packages/hiccup-markdown/tsconfig.json | 10 +- packages/hiccup-svg/api-extractor.json | 2 +- packages/hiccup-svg/package.json | 272 +- packages/hiccup-svg/src/api.ts | 34 +- packages/hiccup-svg/src/circle.ts | 24 +- packages/hiccup-svg/src/convert.ts | 308 +- packages/hiccup-svg/src/ellipse.ts | 28 +- packages/hiccup-svg/src/format.ts | 132 +- packages/hiccup-svg/src/gradients.ts | 104 +- packages/hiccup-svg/src/group.ts | 6 +- packages/hiccup-svg/src/image.ts | 26 +- packages/hiccup-svg/src/line.ts | 30 +- packages/hiccup-svg/src/path.ts | 82 +- packages/hiccup-svg/src/points.ts | 132 +- packages/hiccup-svg/src/polygon.ts | 18 +- packages/hiccup-svg/src/polyline.ts | 20 +- packages/hiccup-svg/src/rect.ts | 48 +- packages/hiccup-svg/src/svg.ts | 32 +- packages/hiccup-svg/src/text.ts | 24 +- packages/hiccup-svg/tsconfig.json | 10 +- packages/hiccup/api-extractor.json | 2 +- packages/hiccup/package.json | 242 +- packages/hiccup/src/api.ts | 42 +- packages/hiccup/src/attribs.ts | 50 +- packages/hiccup/src/css.ts | 16 +- packages/hiccup/src/deref.ts | 14 +- packages/hiccup/src/normalize.ts | 32 +- packages/hiccup/src/prefix.ts | 12 +- packages/hiccup/src/serialize.ts | 316 +- packages/hiccup/test/index.ts | 672 ++-- packages/hiccup/tsconfig.json | 10 +- packages/idgen/api-extractor.json | 2 +- packages/idgen/package.json | 156 +- packages/idgen/src/index.ts | 368 +- packages/idgen/test/index.ts | 296 +- packages/idgen/tsconfig.json | 10 +- packages/iges/api-extractor.json | 2 +- packages/iges/package.json | 174 +- packages/iges/src/api.ts | 292 +- packages/iges/src/index.ts | 638 ++-- packages/iges/test/index.ts | 54 +- packages/iges/tsconfig.json | 10 +- packages/imgui/api-extractor.json | 2 +- packages/imgui/package.json | 318 +- packages/imgui/src/api.ts | 182 +- packages/imgui/src/behaviors/button.ts | 46 +- packages/imgui/src/behaviors/dial.ts | 30 +- packages/imgui/src/behaviors/slider.ts | 118 +- packages/imgui/src/behaviors/text.ts | 154 +- packages/imgui/src/components/button.ts | 180 +- packages/imgui/src/components/dial.ts | 270 +- packages/imgui/src/components/dropdown.ts | 146 +- packages/imgui/src/components/icon-button.ts | 97 +- packages/imgui/src/components/radial-menu.ts | 96 +- packages/imgui/src/components/radio.ts | 58 +- packages/imgui/src/components/ring.ts | 352 +- packages/imgui/src/components/sliderh.ts | 242 +- packages/imgui/src/components/sliderv.ts | 256 +- packages/imgui/src/components/textfield.ts | 190 +- packages/imgui/src/components/textlabel.ts | 82 +- packages/imgui/src/components/toggle.ts | 114 +- packages/imgui/src/components/tooltip.ts | 24 +- packages/imgui/src/components/xypad.ts | 228 +- packages/imgui/src/events.ts | 68 +- packages/imgui/src/gui.ts | 926 ++--- packages/imgui/src/hash.ts | 14 +- packages/imgui/src/layout.ts | 4 +- packages/imgui/tsconfig.json | 10 +- packages/interceptors/api-extractor.json | 2 +- packages/interceptors/package.json | 184 +- packages/interceptors/src/api.ts | 56 +- packages/interceptors/src/event-bus.ts | 1418 ++++---- packages/interceptors/src/interceptors.ts | 114 +- packages/interceptors/tsconfig.json | 10 +- packages/intervals/api-extractor.json | 2 +- packages/intervals/package.json | 156 +- packages/intervals/src/index.ts | 444 +-- packages/intervals/test/index.ts | 314 +- packages/intervals/tsconfig.json | 10 +- packages/iterators/api-extractor.json | 2 +- packages/iterators/package.json | 460 +-- packages/iterators/src/butlast.ts | 22 +- packages/iterators/src/cached.ts | 50 +- packages/iterators/src/concat.ts | 14 +- packages/iterators/src/constantly.ts | 5 +- packages/iterators/src/consume.ts | 4 +- packages/iterators/src/cycle.ts | 24 +- packages/iterators/src/dedupe-with.ts | 18 +- packages/iterators/src/dedupe.ts | 18 +- packages/iterators/src/drop-nth.ts | 8 +- packages/iterators/src/drop-while.ts | 14 +- packages/iterators/src/drop.ts | 6 +- packages/iterators/src/ensure.ts | 6 +- packages/iterators/src/every.ts | 20 +- packages/iterators/src/filter.ts | 14 +- packages/iterators/src/flatten-with.ts | 24 +- packages/iterators/src/flatten.ts | 16 +- packages/iterators/src/fnil.ts | 70 +- packages/iterators/src/fork.ts | 88 +- packages/iterators/src/frequencies.ts | 36 +- packages/iterators/src/group-by.ts | 30 +- packages/iterators/src/indexed.ts | 2 +- packages/iterators/src/interleave.ts | 32 +- packages/iterators/src/interpose.ts | 18 +- packages/iterators/src/iterate.ts | 8 +- packages/iterators/src/iterator.ts | 2 +- packages/iterators/src/juxt.ts | 16 +- packages/iterators/src/last.ts | 14 +- packages/iterators/src/map-indexed.ts | 10 +- packages/iterators/src/map.ts | 50 +- packages/iterators/src/mapcat.ts | 20 +- packages/iterators/src/object-iterator.ts | 10 +- packages/iterators/src/partition-by.ts | 32 +- packages/iterators/src/partition.ts | 60 +- packages/iterators/src/random-sample.ts | 16 +- packages/iterators/src/range.ts | 44 +- packages/iterators/src/reduce.ts | 34 +- packages/iterators/src/reductions.ts | 38 +- packages/iterators/src/repeat.ts | 6 +- packages/iterators/src/repeatedly.ts | 6 +- packages/iterators/src/reverse.ts | 14 +- packages/iterators/src/run.ts | 10 +- packages/iterators/src/some.ts | 14 +- packages/iterators/src/take-last.ts | 20 +- packages/iterators/src/take-nth.ts | 12 +- packages/iterators/src/take-while.ts | 10 +- packages/iterators/src/take.ts | 16 +- packages/iterators/src/walk.ts | 146 +- packages/iterators/src/zip.ts | 26 +- packages/iterators/test/index.ts | 2002 +++++------ packages/iterators/tsconfig.json | 10 +- packages/k-means/api-extractor.json | 2 +- packages/k-means/package.json | 166 +- packages/k-means/src/api.ts | 76 +- packages/k-means/src/kmeans.ts | 254 +- packages/k-means/tsconfig.json | 12 +- packages/ksuid/api-extractor.json | 2 +- packages/ksuid/package.json | 220 +- packages/ksuid/src/aksuid.ts | 110 +- packages/ksuid/src/api.ts | 162 +- packages/ksuid/src/ksuid32.ts | 46 +- packages/ksuid/src/ksuid64.ts | 70 +- packages/ksuid/src/ulid.ts | 70 +- packages/ksuid/test/index.ts | 76 +- packages/ksuid/tsconfig.json | 12 +- packages/layout/api-extractor.json | 2 +- packages/layout/package.json | 174 +- packages/layout/src/api.ts | 206 +- packages/layout/src/box.ts | 14 +- packages/layout/src/checks.ts | 2 +- packages/layout/src/grid-layout.ts | 206 +- packages/layout/tsconfig.json | 10 +- packages/leb128/api-extractor.json | 2 +- packages/leb128/package.json | 156 +- packages/leb128/src/binary.ts | 3 +- packages/leb128/src/index.ts | 44 +- packages/leb128/test/index.ts | 92 +- packages/leb128/tsconfig.json | 10 +- packages/logger/api-extractor.json | 2 +- packages/logger/package.json | 158 +- packages/logger/src/api.ts | 42 +- packages/logger/src/console.ts | 48 +- packages/logger/src/null.ts | 12 +- packages/logger/test/index.ts | 4 +- packages/logger/tsconfig.json | 12 +- packages/lowdisc/api-extractor.json | 2 +- packages/lowdisc/package.json | 188 +- packages/lowdisc/src/halton.ts | 42 +- packages/lowdisc/src/kronecker.ts | 12 +- packages/lowdisc/src/lowdisc.ts | 58 +- packages/lowdisc/src/plastic.ts | 30 +- packages/lowdisc/tools/index.ts | 34 +- packages/lowdisc/tsconfig.json | 12 +- packages/lsys/api-extractor.json | 2 +- packages/lsys/package.json | 178 +- packages/lsys/src/index.ts | 292 +- packages/lsys/test/examples.ts | 88 +- packages/lsys/test/tree.ts | 74 +- packages/lsys/tsconfig.json | 10 +- packages/malloc/api-extractor.json | 2 +- packages/malloc/package.json | 176 +- packages/malloc/src/api.ts | 382 +- packages/malloc/src/native.ts | 60 +- packages/malloc/src/pool.ts | 924 ++--- packages/malloc/test/index.ts | 900 ++--- packages/malloc/test/native.ts | 42 +- packages/malloc/tsconfig.json | 10 +- packages/markdown-table/api-extractor.json | 2 +- packages/markdown-table/package.json | 176 +- packages/markdown-table/src/api.ts | 26 +- packages/markdown-table/src/table.ts | 130 +- packages/markdown-table/test/index.ts | 64 +- packages/markdown-table/tsconfig.json | 12 +- packages/math/api-extractor.json | 2 +- packages/math/package.json | 266 +- packages/math/src/angle.ts | 48 +- packages/math/src/api.ts | 34 +- packages/math/src/crossing.ts | 32 +- packages/math/src/eqdelta.ts | 2 +- packages/math/src/extrema.ts | 50 +- packages/math/src/fit.ts | 2 +- packages/math/src/interval.ts | 120 +- packages/math/src/libc.ts | 68 +- packages/math/src/min-error.ts | 60 +- packages/math/src/mix.ts | 168 +- packages/math/src/prec.ts | 4 +- packages/math/src/prime.ts | 52 +- packages/math/src/ratio.ts | 30 +- packages/math/src/safe-div.ts | 4 +- packages/math/src/solve.ts | 88 +- packages/math/src/step.ts | 8 +- packages/math/test/index.ts | 76 +- packages/math/tsconfig.json | 10 +- packages/matrices/api-extractor.json | 2 +- packages/matrices/package.json | 540 +-- packages/matrices/src/alignment-quat.ts | 18 +- packages/matrices/src/column.ts | 6 +- packages/matrices/src/compile/emit.ts | 28 +- packages/matrices/src/concat.ts | 8 +- packages/matrices/src/conjugate.ts | 2 +- packages/matrices/src/determinant.ts | 78 +- packages/matrices/src/frustum.ts | 86 +- packages/matrices/src/invert.ts | 164 +- packages/matrices/src/lookat.ts | 52 +- packages/matrices/src/m22-m23.ts | 2 +- packages/matrices/src/m23-m44.ts | 46 +- packages/matrices/src/m33-m44.ts | 46 +- packages/matrices/src/m44-m33.ts | 8 +- packages/matrices/src/matn.ts | 2 +- packages/matrices/src/mixq.ts | 40 +- packages/matrices/src/mulm.ts | 94 +- packages/matrices/src/mulq.ts | 18 +- packages/matrices/src/mulv.ts | 72 +- packages/matrices/src/mulvm.ts | 18 +- packages/matrices/src/normal-mat.ts | 8 +- packages/matrices/src/orthagonal.ts | 34 +- packages/matrices/src/ortho.ts | 58 +- packages/matrices/src/outer-product.ts | 84 +- packages/matrices/src/perspective.ts | 14 +- packages/matrices/src/project.ts | 52 +- packages/matrices/src/quat-axis-angle.ts | 18 +- packages/matrices/src/quat-euler.ts | 32 +- packages/matrices/src/quat-m33.ts | 50 +- packages/matrices/src/quat-m44.ts | 70 +- packages/matrices/src/rotation-around-axis.ts | 58 +- packages/matrices/src/rotation.ts | 32 +- packages/matrices/src/scale-center.ts | 36 +- packages/matrices/src/scale.ts | 56 +- packages/matrices/src/shear.ts | 7 +- packages/matrices/src/skew.ts | 38 +- packages/matrices/src/transform.ts | 46 +- packages/matrices/src/translation.ts | 4 +- packages/matrices/src/transpose.ts | 42 +- packages/matrices/src/viewport.ts | 20 +- packages/matrices/test/index.ts | 26 +- packages/matrices/tsconfig.json | 10 +- packages/memoize/api-extractor.json | 2 +- packages/memoize/package.json | 180 +- packages/memoize/src/api.ts | 6 +- packages/memoize/src/defonce.ts | 2 +- packages/memoize/src/do-once.ts | 18 +- packages/memoize/src/memoize.ts | 24 +- packages/memoize/src/memoize1.ts | 14 +- packages/memoize/src/memoizej.ts | 32 +- packages/memoize/test/index.ts | 122 +- packages/memoize/tsconfig.json | 10 +- packages/mime/api-extractor.json | 2 +- packages/mime/package.json | 144 +- packages/mime/src/generated.ts | 1148 +++--- packages/mime/src/index.ts | 46 +- packages/mime/tools/convert.ts | 154 +- packages/mime/tsconfig.json | 10 +- packages/morton/api-extractor.json | 2 +- packages/morton/package.json | 210 +- packages/morton/src/mux.ts | 134 +- packages/morton/src/raw.ts | 66 +- packages/morton/src/scaled.ts | 28 +- packages/morton/src/tree.ts | 92 +- packages/morton/src/zcurve.ts | 624 ++-- packages/morton/test/index.ts | 44 +- packages/morton/test/zcurve.ts | 206 +- packages/morton/tsconfig.json | 10 +- packages/oquery/api-extractor.json | 2 +- packages/oquery/package.json | 184 +- packages/oquery/src/api.ts | 236 +- packages/oquery/src/query.ts | 636 ++-- packages/oquery/test/index.ts | 828 ++--- packages/oquery/tsconfig.json | 10 +- packages/parse/api-extractor.json | 2 +- packages/parse/package.json | 484 +-- packages/parse/src/api.ts | 106 +- packages/parse/src/combinators/alt.ts | 20 +- packages/parse/src/combinators/boundary.ts | 4 +- packages/parse/src/combinators/check.ts | 14 +- packages/parse/src/combinators/dynamic.ts | 8 +- packages/parse/src/combinators/expect.ts | 6 +- packages/parse/src/combinators/lookahead.ts | 48 +- packages/parse/src/combinators/maybe.ts | 14 +- packages/parse/src/combinators/not.ts | 16 +- packages/parse/src/combinators/repeat.ts | 60 +- packages/parse/src/combinators/seq.ts | 22 +- packages/parse/src/combinators/wrap.ts | 2 +- packages/parse/src/combinators/xform.ts | 32 +- packages/parse/src/context.ts | 324 +- packages/parse/src/error.ts | 4 +- packages/parse/src/grammar.ts | 634 ++-- packages/parse/src/presets/alpha.ts | 4 +- packages/parse/src/presets/escape.ts | 18 +- packages/parse/src/presets/string.ts | 2 +- packages/parse/src/prims/anchor.ts | 36 +- packages/parse/src/prims/fail.ts | 6 +- packages/parse/src/prims/lit.ts | 14 +- packages/parse/src/prims/none-of.ts | 28 +- packages/parse/src/prims/one-of.ts | 24 +- packages/parse/src/prims/pass.ts | 6 +- packages/parse/src/prims/range.ts | 36 +- packages/parse/src/prims/satisfy.ts | 22 +- packages/parse/src/prims/skip.ts | 24 +- packages/parse/src/prims/string.ts | 100 +- packages/parse/src/readers/array-reader.ts | 32 +- packages/parse/src/readers/string-reader.ts | 42 +- packages/parse/src/utils.ts | 2 +- packages/parse/src/xform/collect.ts | 10 +- packages/parse/src/xform/comp.ts | 38 +- packages/parse/src/xform/count.ts | 10 +- packages/parse/src/xform/discard.ts | 2 +- packages/parse/src/xform/hoist.ts | 12 +- packages/parse/src/xform/join.ts | 22 +- packages/parse/src/xform/json.ts | 4 +- packages/parse/src/xform/nest.ts | 38 +- packages/parse/src/xform/number.ts | 14 +- packages/parse/src/xform/print.ts | 38 +- packages/parse/src/xform/replace.ts | 14 +- packages/parse/src/xform/trim.ts | 8 +- packages/parse/src/xform/with-id.ts | 10 +- packages/parse/test/binary.ts | 32 +- packages/parse/test/float.ts | 40 +- packages/parse/test/grammar.ts | 120 +- packages/parse/test/index.ts | 152 +- packages/parse/test/lookahead.ts | 192 +- packages/parse/test/rpn.ts | 58 +- packages/parse/test/sexpr.ts | 234 +- packages/parse/test/svg.ts | 88 +- packages/parse/tsconfig.json | 10 +- packages/paths/api-extractor.json | 2 +- packages/paths/package.json | 248 +- packages/paths/src/delete-in.ts | 148 +- packages/paths/src/get-in.ts | 60 +- packages/paths/src/getter.ts | 124 +- packages/paths/src/mut-in-many.ts | 74 +- packages/paths/src/mut-in.ts | 82 +- packages/paths/src/mutator.ts | 148 +- packages/paths/src/path.ts | 52 +- packages/paths/src/set-in-many.ts | 76 +- packages/paths/src/set-in.ts | 82 +- packages/paths/src/setter.ts | 142 +- packages/paths/src/update-in.ts | 146 +- packages/paths/src/updater.ts | 94 +- packages/paths/test/index.ts | 367 +- packages/paths/tsconfig.json | 10 +- packages/pixel-dither/api-extractor.json | 2 +- packages/pixel-dither/package.json | 212 +- packages/pixel-dither/src/api.ts | 52 +- packages/pixel-dither/src/atkinson.ts | 8 +- packages/pixel-dither/src/burkes.ts | 8 +- packages/pixel-dither/src/diffusion.ts | 26 +- packages/pixel-dither/src/dither.ts | 78 +- packages/pixel-dither/src/floyd-steinberg.ts | 8 +- packages/pixel-dither/src/jarvis.ts | 8 +- packages/pixel-dither/src/ordered.ts | 128 +- packages/pixel-dither/src/sierra2.ts | 8 +- packages/pixel-dither/src/stucki.ts | 8 +- packages/pixel-dither/src/threshold.ts | 8 +- packages/pixel-dither/test/index.ts | 4 +- packages/pixel-dither/tsconfig.json | 12 +- packages/pixel-io-netpbm/api-extractor.json | 2 +- packages/pixel-io-netpbm/package.json | 174 +- packages/pixel-io-netpbm/src/read.ts | 246 +- packages/pixel-io-netpbm/src/write.ts | 184 +- packages/pixel-io-netpbm/test/read.ts | 82 +- packages/pixel-io-netpbm/tsconfig.json | 12 +- packages/pixel/api-extractor.json | 2 +- packages/pixel/package.json | 426 +-- packages/pixel/src/api.ts | 636 ++-- packages/pixel/src/canvas.ts | 96 +- packages/pixel/src/checks.ts | 18 +- packages/pixel/src/convolve.ts | 656 ++-- packages/pixel/src/dominant-colors.ts | 52 +- packages/pixel/src/float.ts | 936 ++--- packages/pixel/src/format/abgr8888.ts | 22 +- packages/pixel/src/format/alpha8.ts | 8 +- packages/pixel/src/format/argb1555.ts | 18 +- packages/pixel/src/format/argb4444.ts | 18 +- packages/pixel/src/format/argb8888.ts | 22 +- packages/pixel/src/format/bgr888.ts | 18 +- packages/pixel/src/format/float-format.ts | 220 +- packages/pixel/src/format/float-gray-alpha.ts | 6 +- packages/pixel/src/format/float-gray.ts | 4 +- packages/pixel/src/format/float-hsva.ts | 102 +- packages/pixel/src/format/float-norm.ts | 28 +- packages/pixel/src/format/float-rgb.ts | 2 +- packages/pixel/src/format/float-rgba.ts | 4 +- packages/pixel/src/format/gray-alpha16.ts | 20 +- packages/pixel/src/format/gray-alpha8.ts | 18 +- packages/pixel/src/format/gray16.ts | 10 +- packages/pixel/src/format/gray8.ts | 10 +- packages/pixel/src/format/indexed.ts | 54 +- packages/pixel/src/format/int-format.ts | 90 +- packages/pixel/src/format/rgb565.ts | 14 +- packages/pixel/src/format/rgb888.ts | 14 +- packages/pixel/src/int.ts | 818 ++--- packages/pixel/src/internal/codegen.ts | 112 +- packages/pixel/src/internal/utils.ts | 154 +- packages/pixel/src/normal-map.ts | 62 +- packages/pixel/src/pyramid.ts | 30 +- packages/pixel/src/range.ts | 2 +- packages/pixel/src/sample.ts | 612 ++-- packages/pixel/test/float.ts | 79 +- packages/pixel/test/index.ts | 302 +- packages/pixel/tsconfig.json | 10 +- packages/pointfree-lang/api-extractor.json | 2 +- packages/pointfree-lang/package.json | 210 +- packages/pointfree-lang/src/api.ts | 138 +- packages/pointfree-lang/src/cli.ts | 136 +- packages/pointfree-lang/src/parser.ts | 14 +- packages/pointfree-lang/src/runtime.ts | 436 +-- packages/pointfree-lang/test/index.ts | 184 +- packages/pointfree-lang/test/readme.ts | 40 +- packages/pointfree-lang/tsconfig.json | 12 +- packages/pointfree/api-extractor.json | 2 +- packages/pointfree/package.json | 266 +- packages/pointfree/src/api.ts | 6 +- packages/pointfree/src/array.ts | 180 +- packages/pointfree/src/cond.ts | 62 +- packages/pointfree/src/context.ts | 6 +- packages/pointfree/src/dataflow.ts | 14 +- packages/pointfree/src/env.ts | 28 +- packages/pointfree/src/io.ts | 2 +- packages/pointfree/src/loop.ts | 48 +- packages/pointfree/src/ops.ts | 92 +- packages/pointfree/src/run.ts | 36 +- packages/pointfree/src/safe.ts | 12 +- packages/pointfree/src/stack.ts | 180 +- packages/pointfree/src/string.ts | 34 +- packages/pointfree/src/word.ts | 112 +- packages/pointfree/test/index.ts | 1956 +++++------ packages/pointfree/test/loop.ts | 74 +- packages/pointfree/tsconfig.json | 10 +- packages/poisson/api-extractor.json | 2 +- packages/poisson/package.json | 196 +- packages/poisson/src/api.ts | 160 +- packages/poisson/src/poisson.ts | 68 +- packages/poisson/src/stratified.ts | 14 +- packages/poisson/tsconfig.json | 10 +- packages/porter-duff/api-extractor.json | 2 +- packages/porter-duff/package.json | 178 +- packages/porter-duff/src/api.ts | 2 +- packages/porter-duff/src/porter-duff.ts | 128 +- packages/porter-duff/src/premultiply.ts | 62 +- packages/porter-duff/src/utils.ts | 12 +- packages/porter-duff/tsconfig.json | 10 +- packages/prefixes/api-extractor.json | 2 +- packages/prefixes/package.json | 214 +- packages/prefixes/tsconfig.json | 10 +- packages/quad-edge/api-extractor.json | 2 +- packages/quad-edge/package.json | 158 +- packages/quad-edge/src/index.ts | 338 +- packages/quad-edge/tsconfig.json | 10 +- packages/ramp/api-extractor.json | 2 +- packages/ramp/package.json | 186 +- packages/ramp/src/api.ts | 30 +- packages/ramp/src/aramp.ts | 152 +- packages/ramp/src/hermite.ts | 74 +- packages/ramp/src/linear.ts | 34 +- packages/ramp/tsconfig.json | 10 +- packages/random-fxhash/api-extractor.json | 2 +- packages/random-fxhash/package.json | 164 +- packages/random-fxhash/src/index.ts | 52 +- packages/random-fxhash/test/index.ts | 4 +- packages/random-fxhash/tsconfig.json | 12 +- packages/random/api-extractor.json | 2 +- packages/random/package.json | 314 +- packages/random/src/api.ts | 70 +- packages/random/src/arandom.ts | 30 +- packages/random/src/coin.ts | 16 +- packages/random/src/constants.ts | 5 +- packages/random/src/crypto.ts | 54 +- .../random/src/distributions/exponential.ts | 4 +- packages/random/src/distributions/gaussian.ts | 14 +- .../random/src/distributions/geometric.ts | 14 +- packages/random/src/distributions/normal.ts | 38 +- packages/random/src/distributions/uniform.ts | 12 +- packages/random/src/pick-random.ts | 10 +- packages/random/src/random-bytes.ts | 26 +- packages/random/src/random-id.ts | 18 +- packages/random/src/sfc32.ts | 60 +- packages/random/src/smush32.ts | 46 +- packages/random/src/system.ts | 18 +- packages/random/src/unique-indices.ts | 64 +- packages/random/src/uuid.ts | 12 +- packages/random/src/weighted-random.ts | 62 +- packages/random/src/xorshift128.ts | 64 +- packages/random/src/xorwow.ts | 70 +- packages/random/src/xoshiro128.ts | 70 +- packages/random/src/xsadd.ts | 76 +- packages/random/test/uuid.ts | 36 +- packages/random/tsconfig.json | 10 +- packages/range-coder/api-extractor.json | 2 +- packages/range-coder/package.json | 152 +- packages/range-coder/src/index.ts | 250 +- packages/range-coder/test/index.ts | 38 +- packages/range-coder/tsconfig.json | 10 +- packages/rasterize/api-extractor.json | 2 +- packages/rasterize/package.json | 238 +- packages/rasterize/src/checks.ts | 8 +- packages/rasterize/src/circle.ts | 22 +- packages/rasterize/src/draw.ts | 88 +- packages/rasterize/src/flood-fill.ts | 62 +- packages/rasterize/src/line.ts | 44 +- packages/rasterize/src/poly.ts | 108 +- packages/rasterize/src/polyline.ts | 24 +- packages/rasterize/src/rect.ts | 106 +- packages/rasterize/src/shader.ts | 58 +- packages/rasterize/tsconfig.json | 12 +- packages/rdom-canvas/api-extractor.json | 2 +- packages/rdom-canvas/package.json | 182 +- packages/rdom-canvas/src/index.ts | 182 +- packages/rdom-canvas/tsconfig.json | 10 +- packages/rdom-components/api-extractor.json | 2 +- packages/rdom-components/package.json | 212 +- packages/rdom-components/src/accordion.ts | 82 +- packages/rdom-components/src/dropdown.ts | 80 +- packages/rdom-components/src/editor.ts | 126 +- packages/rdom-components/src/icon-button.ts | 34 +- packages/rdom-components/src/input.ts | 56 +- packages/rdom-components/src/radio.ts | 54 +- packages/rdom-components/src/tabs.ts | 74 +- packages/rdom-components/tsconfig.json | 10 +- packages/rdom/api-extractor.json | 2 +- packages/rdom/package.json | 282 +- packages/rdom/src/api.ts | 148 +- packages/rdom/src/checks.ts | 2 +- packages/rdom/src/compile.ts | 130 +- packages/rdom/src/component.ts | 114 +- packages/rdom/src/dom.ts | 422 +-- packages/rdom/src/event.ts | 20 +- packages/rdom/src/klist.ts | 216 +- packages/rdom/src/list.ts | 122 +- packages/rdom/src/object.ts | 74 +- packages/rdom/src/promise.ts | 44 +- packages/rdom/src/replace.ts | 54 +- packages/rdom/src/scheduler.ts | 60 +- packages/rdom/src/sub.ts | 94 +- packages/rdom/src/switch.ts | 128 +- packages/rdom/src/wrap.ts | 32 +- packages/rdom/tsconfig.json | 10 +- packages/resolve-map/api-extractor.json | 2 +- packages/resolve-map/package.json | 154 +- packages/resolve-map/src/index.ts | 446 +-- packages/resolve-map/test/index.ts | 578 ++-- packages/resolve-map/tsconfig.json | 10 +- packages/rle-pack/api-extractor.json | 2 +- packages/rle-pack/package.json | 158 +- packages/rle-pack/src/index.ts | 184 +- packages/rle-pack/test/index.ts | 2 +- packages/rle-pack/tsconfig.json | 10 +- packages/router/api-extractor.json | 2 +- packages/router/package.json | 184 +- packages/router/src/api.ts | 220 +- packages/router/src/basic.ts | 374 +- packages/router/src/history.ts | 188 +- packages/router/tsconfig.json | 10 +- packages/rstream-csp/api-extractor.json | 2 +- packages/rstream-csp/package.json | 154 +- packages/rstream-csp/src/index.ts | 60 +- packages/rstream-csp/test/index.ts | 42 +- packages/rstream-csp/tsconfig.json | 10 +- packages/rstream-dot/api-extractor.json | 2 +- packages/rstream-dot/package.json | 172 +- packages/rstream-dot/src/api.ts | 40 +- packages/rstream-dot/src/index.ts | 146 +- packages/rstream-dot/tsconfig.json | 10 +- packages/rstream-gestures/api-extractor.json | 2 +- packages/rstream-gestures/package.json | 184 +- packages/rstream-gestures/src/api.ts | 254 +- .../rstream-gestures/src/gesture-stream.ts | 364 +- packages/rstream-gestures/tsconfig.json | 10 +- packages/rstream-graph/api-extractor.json | 2 +- packages/rstream-graph/package.json | 204 +- packages/rstream-graph/src/api.ts | 30 +- packages/rstream-graph/src/graph.ts | 296 +- packages/rstream-graph/src/nodes/extract.ts | 8 +- packages/rstream-graph/src/nodes/math.ts | 40 +- packages/rstream-graph/test/index.ts | 146 +- packages/rstream-graph/tsconfig.json | 10 +- packages/rstream-log-file/api-extractor.json | 2 +- packages/rstream-log-file/package.json | 146 +- packages/rstream-log-file/src/index.ts | 14 +- packages/rstream-log-file/tsconfig.json | 10 +- packages/rstream-log/api-extractor.json | 2 +- packages/rstream-log/package.json | 198 +- packages/rstream-log/src/api.ts | 14 +- packages/rstream-log/src/filter.ts | 8 +- packages/rstream-log/src/format.ts | 44 +- packages/rstream-log/src/logger.ts | 110 +- packages/rstream-log/test/index.ts | 60 +- packages/rstream-log/tsconfig.json | 10 +- packages/rstream-query/api-extractor.json | 2 +- packages/rstream-query/package.json | 218 +- packages/rstream-query/src/api.ts | 22 +- packages/rstream-query/src/convert.ts | 46 +- packages/rstream-query/src/pattern.ts | 40 +- packages/rstream-query/src/qvar.ts | 60 +- packages/rstream-query/src/store.ts | 822 ++--- packages/rstream-query/src/xforms.ts | 116 +- packages/rstream-query/test/example.ts | 76 +- packages/rstream-query/test/index.ts | 316 +- packages/rstream-query/tsconfig.json | 10 +- packages/rstream/api-extractor.json | 2 +- packages/rstream/package.json | 404 +-- packages/rstream/src/api.ts | 364 +- packages/rstream/src/asidechain.ts | 30 +- packages/rstream/src/atom.ts | 60 +- packages/rstream/src/bisect.ts | 14 +- packages/rstream/src/checks.ts | 4 +- packages/rstream/src/debounce.ts | 16 +- packages/rstream/src/defworker.ts | 16 +- packages/rstream/src/event.ts | 28 +- packages/rstream/src/forkjoin.ts | 270 +- packages/rstream/src/idgen.ts | 14 +- packages/rstream/src/internal/remove.ts | 14 +- packages/rstream/src/interval.ts | 54 +- packages/rstream/src/iterable.ts | 60 +- packages/rstream/src/merge.ts | 154 +- packages/rstream/src/metastream.ts | 148 +- packages/rstream/src/nodejs.ts | 44 +- packages/rstream/src/object.ts | 164 +- packages/rstream/src/post-worker.ts | 56 +- packages/rstream/src/promise.ts | 56 +- packages/rstream/src/promises.ts | 12 +- packages/rstream/src/pubsub.ts | 264 +- packages/rstream/src/raf.ts | 30 +- packages/rstream/src/resolve.ts | 84 +- packages/rstream/src/sidechain-partition.ts | 74 +- packages/rstream/src/sidechain-toggle.ts | 60 +- packages/rstream/src/stream.ts | 192 +- packages/rstream/src/subscription.ts | 592 ++-- packages/rstream/src/sync.ts | 386 +-- packages/rstream/src/timeout.ts | 96 +- packages/rstream/src/trace.ts | 20 +- packages/rstream/src/transduce.ts | 86 +- packages/rstream/src/trigger.ts | 2 +- packages/rstream/src/tunnel.ts | 178 +- packages/rstream/src/tween.ts | 74 +- packages/rstream/src/view.ts | 150 +- packages/rstream/src/worker.ts | 58 +- packages/rstream/test/bisect.ts | 150 +- packages/rstream/test/config.ts | 2 +- packages/rstream/test/debounce.ts | 66 +- packages/rstream/test/from-atom.ts | 2 +- packages/rstream/test/from-iterable.ts | 180 +- packages/rstream/test/from-promise.ts | 195 +- packages/rstream/test/metastream.ts | 186 +- packages/rstream/test/object.ts | 174 +- packages/rstream/test/pubsub.ts | 265 +- packages/rstream/test/sidechain-partition.ts | 132 +- packages/rstream/test/sidechain-toggle.ts | 100 +- packages/rstream/test/stream-merge.ts | 188 +- packages/rstream/test/stream-sync.ts | 464 +-- packages/rstream/test/subscription.ts | 416 +-- packages/rstream/test/timeout.ts | 144 +- packages/rstream/test/transducers.ts | 118 +- packages/rstream/test/utils.ts | 14 +- packages/rstream/tsconfig.json | 12 +- packages/sax/api-extractor.json | 2 +- packages/sax/package.json | 162 +- packages/sax/src/index.ts | 976 +++--- packages/sax/test/index.ts | 336 +- packages/sax/tsconfig.json | 10 +- packages/scenegraph/api-extractor.json | 2 +- packages/scenegraph/package.json | 198 +- packages/scenegraph/src/anode.ts | 214 +- packages/scenegraph/src/api.ts | 36 +- packages/scenegraph/src/hiccup.ts | 36 +- packages/scenegraph/src/node2.ts | 120 +- packages/scenegraph/src/node3.ts | 118 +- packages/scenegraph/tsconfig.json | 10 +- packages/seq/api-extractor.json | 2 +- packages/seq/package.json | 176 +- packages/seq/src/array.ts | 60 +- packages/seq/src/concat.ts | 88 +- packages/seq/src/cons.ts | 16 +- packages/seq/src/ensure.ts | 20 +- packages/seq/src/iterator.ts | 12 +- packages/seq/src/lazyseq.ts | 36 +- packages/seq/test/aseq.ts | 46 +- packages/seq/test/concat.ts | 82 +- packages/seq/test/cons.ts | 14 +- packages/seq/test/custom.ts | 62 +- packages/seq/test/lazyseq.ts | 32 +- packages/seq/test/rseq.ts | 46 +- packages/seq/tsconfig.json | 10 +- packages/sexpr/api-extractor.json | 2 +- packages/sexpr/package.json | 188 +- packages/sexpr/src/api.ts | 114 +- packages/sexpr/src/parse.ts | 116 +- packages/sexpr/src/runtime.ts | 4 +- packages/sexpr/src/tokenize.ts | 128 +- packages/sexpr/test/index.ts | 240 +- packages/sexpr/tsconfig.json | 10 +- packages/shader-ast-glsl/api-extractor.json | 2 +- packages/shader-ast-glsl/package.json | 178 +- packages/shader-ast-glsl/src/api.ts | 58 +- packages/shader-ast-glsl/src/target.ts | 360 +- packages/shader-ast-glsl/test/index.ts | 360 +- packages/shader-ast-glsl/tsconfig.json | 10 +- packages/shader-ast-js/api-extractor.json | 2 +- packages/shader-ast-js/package.json | 296 +- packages/shader-ast-js/src/api.ts | 410 +-- packages/shader-ast-js/src/env.ts | 198 +- packages/shader-ast-js/src/env/bvec.ts | 18 +- packages/shader-ast-js/src/env/float.ts | 66 +- packages/shader-ast-js/src/env/int.ts | 38 +- packages/shader-ast-js/src/env/ivec2.ts | 44 +- packages/shader-ast-js/src/env/ivec3.ts | 44 +- packages/shader-ast-js/src/env/ivec4.ts | 44 +- packages/shader-ast-js/src/env/mat2.ts | 38 +- packages/shader-ast-js/src/env/mat3.ts | 38 +- packages/shader-ast-js/src/env/mat4.ts | 38 +- packages/shader-ast-js/src/env/uint.ts | 38 +- packages/shader-ast-js/src/env/uvec2.ts | 44 +- packages/shader-ast-js/src/env/uvec3.ts | 44 +- packages/shader-ast-js/src/env/uvec4.ts | 44 +- packages/shader-ast-js/src/env/vec2.ts | 122 +- packages/shader-ast-js/src/env/vec3.ts | 124 +- packages/shader-ast-js/src/env/vec4.ts | 122 +- packages/shader-ast-js/src/runtime.ts | 130 +- packages/shader-ast-js/src/target.ts | 482 +-- packages/shader-ast-js/test/index.ts | 154 +- packages/shader-ast-js/tsconfig.json | 10 +- .../shader-ast-optimize/api-extractor.json | 2 +- packages/shader-ast-optimize/package.json | 156 +- .../src/contant-folding.ts | 272 +- packages/shader-ast-optimize/test/index.ts | 4 +- packages/shader-ast-optimize/tsconfig.json | 12 +- packages/shader-ast-stdlib/api-extractor.json | 2 +- packages/shader-ast-stdlib/package.json | 662 ++-- packages/shader-ast-stdlib/src/api.ts | 50 +- .../shader-ast-stdlib/src/color/aces-film.ts | 18 +- .../shader-ast-stdlib/src/color/levels.ts | 170 +- .../src/color/linear-srgb.ts | 10 +- .../shader-ast-stdlib/src/color/luminance.ts | 2 +- .../src/color/porter-duff.ts | 38 +- packages/shader-ast-stdlib/src/color/rgbe.ts | 18 +- packages/shader-ast-stdlib/src/fog/exp.ts | 8 +- packages/shader-ast-stdlib/src/fog/exp2.ts | 18 +- packages/shader-ast-stdlib/src/fog/linear.ts | 8 +- .../shader-ast-stdlib/src/light/lambert.ts | 12 +- .../shader-ast-stdlib/src/light/trilight.ts | 30 +- .../shader-ast-stdlib/src/math/additive.ts | 58 +- .../shader-ast-stdlib/src/math/cartesian.ts | 30 +- packages/shader-ast-stdlib/src/math/clamp.ts | 32 +- packages/shader-ast-stdlib/src/math/cross2.ts | 10 +- .../src/math/dist-chebyshev.ts | 10 +- .../src/math/dist-manhattan.ts | 10 +- packages/shader-ast-stdlib/src/math/fit.ts | 58 +- packages/shader-ast-stdlib/src/math/magsq.ts | 2 +- .../shader-ast-stdlib/src/math/mix-cubic.ts | 54 +- .../src/math/mix-quadratic.ts | 40 +- .../shader-ast-stdlib/src/math/orthogonal.ts | 14 +- packages/shader-ast-stdlib/src/math/osc.ts | 58 +- packages/shader-ast-stdlib/src/math/polar.ts | 12 +- .../shader-ast-stdlib/src/matrix/convert.ts | 26 +- .../shader-ast-stdlib/src/matrix/lookat.ts | 42 +- packages/shader-ast-stdlib/src/matrix/mvp.ts | 8 +- .../shader-ast-stdlib/src/matrix/normal.ts | 2 +- .../shader-ast-stdlib/src/matrix/rotation.ts | 244 +- packages/shader-ast-stdlib/src/noise/curl3.ts | 62 +- packages/shader-ast-stdlib/src/noise/hash.ts | 188 +- .../shader-ast-stdlib/src/noise/permute.ts | 6 +- .../shader-ast-stdlib/src/noise/simplex2.ts | 158 +- .../shader-ast-stdlib/src/noise/simplex3.ts | 290 +- .../shader-ast-stdlib/src/noise/voronoi2.ts | 136 +- .../shader-ast-stdlib/src/noise/worley2.ts | 130 +- packages/shader-ast-stdlib/src/raymarch/ao.ts | 46 +- .../src/raymarch/direction.ts | 28 +- .../shader-ast-stdlib/src/raymarch/normal.ts | 18 +- .../src/raymarch/point-at.ts | 8 +- .../shader-ast-stdlib/src/raymarch/scene.ts | 66 +- packages/shader-ast-stdlib/src/screen/uv.ts | 50 +- packages/shader-ast-stdlib/src/sdf/arc.ts | 42 +- packages/shader-ast-stdlib/src/sdf/bezier.ts | 180 +- .../shader-ast-stdlib/src/sdf/box-rounded.ts | 24 +- packages/shader-ast-stdlib/src/sdf/box.ts | 20 +- packages/shader-ast-stdlib/src/sdf/cross.ts | 64 +- .../shader-ast-stdlib/src/sdf/cylinder.ts | 20 +- packages/shader-ast-stdlib/src/sdf/hex.ts | 48 +- packages/shader-ast-stdlib/src/sdf/isec.ts | 2 +- packages/shader-ast-stdlib/src/sdf/line.ts | 20 +- packages/shader-ast-stdlib/src/sdf/mirror.ts | 36 +- packages/shader-ast-stdlib/src/sdf/plane.ts | 16 +- packages/shader-ast-stdlib/src/sdf/polygon.ts | 106 +- .../shader-ast-stdlib/src/sdf/polyhedra.ts | 132 +- .../shader-ast-stdlib/src/sdf/repeat-polar.ts | 28 +- packages/shader-ast-stdlib/src/sdf/repeat.ts | 6 +- .../shader-ast-stdlib/src/sdf/smooth-isec.ts | 26 +- .../shader-ast-stdlib/src/sdf/smooth-sub.ts | 26 +- .../shader-ast-stdlib/src/sdf/smooth-union.ts | 26 +- packages/shader-ast-stdlib/src/sdf/sphere.ts | 16 +- packages/shader-ast-stdlib/src/sdf/sub.ts | 2 +- packages/shader-ast-stdlib/src/sdf/torus.ts | 12 +- packages/shader-ast-stdlib/src/sdf/tri.ts | 68 +- packages/shader-ast-stdlib/src/sdf/union.ts | 4 +- packages/shader-ast-stdlib/src/tex/blur.ts | 142 +- .../shader-ast-stdlib/src/tex/index-coord.ts | 4 +- .../shader-ast-stdlib/src/tex/index-uv.ts | 44 +- .../shader-ast-stdlib/src/tex/read-index.ts | 8 +- packages/shader-ast-stdlib/tsconfig.json | 10 +- packages/shader-ast/api-extractor.json | 2 +- packages/shader-ast/package.json | 348 +- packages/shader-ast/src/api/function.ts | 276 +- packages/shader-ast/src/api/nodes.ts | 232 +- packages/shader-ast/src/api/ops.ts | 8 +- packages/shader-ast/src/api/syms.ts | 76 +- packages/shader-ast/src/api/tags.ts | 42 +- packages/shader-ast/src/api/target.ts | 78 +- packages/shader-ast/src/api/types.ts | 250 +- packages/shader-ast/src/ast/assign.ts | 24 +- packages/shader-ast/src/ast/checks.ts | 20 +- packages/shader-ast/src/ast/controlflow.ts | 88 +- packages/shader-ast/src/ast/function.ts | 106 +- packages/shader-ast/src/ast/idgen.ts | 2 +- packages/shader-ast/src/ast/indexed.ts | 60 +- packages/shader-ast/src/ast/item.ts | 54 +- packages/shader-ast/src/ast/lit.ts | 130 +- packages/shader-ast/src/ast/ops.ts | 224 +- packages/shader-ast/src/ast/scope.ts | 166 +- packages/shader-ast/src/ast/swizzle.ts | 118 +- packages/shader-ast/src/ast/sym.ts | 146 +- packages/shader-ast/src/builtin/bvec.ts | 20 +- packages/shader-ast/src/builtin/math.ts | 110 +- packages/shader-ast/src/builtin/texture.ts | 106 +- packages/shader-ast/src/target.ts | 18 +- packages/shader-ast/test/index.ts | 500 +-- packages/shader-ast/tsconfig.json | 10 +- packages/simd/api-extractor.json | 2 +- packages/simd/package.json | 176 +- packages/simd/src/api.ts | 440 +-- packages/simd/src/binary.ts | 3 +- packages/simd/src/index.ts | 48 +- packages/simd/test/index.ts | 82 +- packages/simd/tsconfig.json | 10 +- packages/soa/api-extractor.json | 2 +- packages/soa/package.json | 212 +- packages/soa/src/aos.ts | 58 +- packages/soa/src/api.ts | 70 +- packages/soa/src/serialize.ts | 80 +- packages/soa/src/soa.ts | 372 +- packages/soa/src/utils.ts | 6 +- packages/soa/test/aos.ts | 88 +- packages/soa/test/serialize.ts | 74 +- packages/soa/test/soa.ts | 98 +- packages/soa/tsconfig.json | 10 +- packages/sparse/api-extractor.json | 2 +- packages/sparse/package.json | 210 +- packages/sparse/src/amatrix.ts | 62 +- packages/sparse/src/compressed.ts | 176 +- packages/sparse/src/coo.ts | 288 +- packages/sparse/src/csc.ts | 462 +-- packages/sparse/src/csr.ts | 634 ++-- packages/sparse/src/diag.ts | 128 +- packages/sparse/src/vec.ts | 416 +-- packages/sparse/test/index.ts | 18 +- packages/sparse/tsconfig.json | 10 +- packages/strings/api-extractor.json | 2 +- packages/strings/package.json | 402 +-- packages/strings/src/ansi.ts | 10 +- packages/strings/src/api.ts | 104 +- packages/strings/src/case.ts | 20 +- packages/strings/src/center.ts | 32 +- packages/strings/src/currency.ts | 6 +- packages/strings/src/cursor.ts | 28 +- packages/strings/src/entities.ts | 46 +- packages/strings/src/escape.ts | 74 +- packages/strings/src/float.ts | 78 +- packages/strings/src/format.ts | 30 +- packages/strings/src/groups.ts | 46 +- packages/strings/src/hollerith.ts | 2 +- packages/strings/src/initials.ts | 16 +- packages/strings/src/int.ts | 2 +- packages/strings/src/interpolate.ts | 24 +- packages/strings/src/join.ts | 2 +- packages/strings/src/pad-left.ts | 24 +- packages/strings/src/pad-right.ts | 24 +- packages/strings/src/parse.ts | 8 +- packages/strings/src/percent.ts | 6 +- packages/strings/src/radix.ts | 30 +- packages/strings/src/range.ts | 22 +- packages/strings/src/repeat.ts | 2 +- packages/strings/src/ruler.ts | 24 +- packages/strings/src/slugify.ts | 46 +- packages/strings/src/splice.ts | 38 +- packages/strings/src/split.ts | 40 +- packages/strings/src/stringify.ts | 14 +- packages/strings/src/tabs.ts | 82 +- packages/strings/src/trim.ts | 12 +- packages/strings/src/truncate-left.ts | 14 +- packages/strings/src/truncate.ts | 10 +- packages/strings/src/units.ts | 168 +- packages/strings/src/vector.ts | 74 +- packages/strings/src/word-wrap.ts | 158 +- packages/strings/src/wrap.ts | 2 +- packages/strings/test/escape.ts | 14 +- packages/strings/test/index.ts | 42 +- packages/strings/test/interpolate.ts | 20 +- packages/strings/test/splice.ts | 46 +- packages/strings/tsconfig.json | 10 +- packages/system/api-extractor.json | 2 +- packages/system/package.json | 164 +- packages/system/src/api.ts | 56 +- packages/system/src/system.ts | 124 +- packages/system/test/index.ts | 186 +- packages/system/tsconfig.json | 10 +- packages/testament/api-extractor.json | 2 +- packages/testament/package.json | 170 +- packages/testament/src/api.ts | 196 +- packages/testament/src/cli.ts | 382 +- packages/testament/src/exec.ts | 22 +- packages/testament/src/group.ts | 116 +- packages/testament/src/test.ts | 198 +- packages/testament/src/utils.ts | 30 +- packages/testament/test/index.ts | 122 +- packages/testament/tsconfig.json | 12 +- packages/text-canvas/api-extractor.json | 2 +- packages/text-canvas/package.json | 280 +- packages/text-canvas/src/api.ts | 240 +- packages/text-canvas/src/bars.ts | 96 +- packages/text-canvas/src/canvas.ts | 326 +- packages/text-canvas/src/circle.ts | 138 +- packages/text-canvas/src/format.ts | 24 +- packages/text-canvas/src/hvline.ts | 200 +- packages/text-canvas/src/image.ts | 468 +-- packages/text-canvas/src/line.ts | 86 +- packages/text-canvas/src/rect.ts | 138 +- packages/text-canvas/src/style.ts | 48 +- packages/text-canvas/src/table.ts | 322 +- packages/text-canvas/src/text.ts | 220 +- packages/text-canvas/src/utils.ts | 28 +- packages/text-canvas/tsconfig.json | 10 +- packages/text-format/api-extractor.json | 2 +- packages/text-format/package.json | 202 +- packages/text-format/src/ansi.ts | 80 +- packages/text-format/src/api.ts | 162 +- packages/text-format/src/format.ts | 76 +- packages/text-format/src/html.ts | 170 +- packages/text-format/src/none.ts | 8 +- packages/text-format/tsconfig.json | 12 +- .../transducers-binary/api-extractor.json | 2 +- packages/transducers-binary/package.json | 226 +- packages/transducers-binary/src/api.ts | 104 +- packages/transducers-binary/src/base64.ts | 206 +- packages/transducers-binary/src/bits.ts | 58 +- packages/transducers-binary/src/bytes.ts | 422 +-- packages/transducers-binary/src/hex-dump.ts | 48 +- .../transducers-binary/src/partition-bits.ts | 142 +- .../transducers-binary/src/random-bits.ts | 8 +- packages/transducers-binary/src/utf8.ts | 350 +- .../transducers-binary/test/partition-bits.ts | 48 +- packages/transducers-binary/tsconfig.json | 10 +- packages/transducers-fsm/api-extractor.json | 2 +- packages/transducers-fsm/package.json | 150 +- packages/transducers-fsm/src/index.ts | 56 +- packages/transducers-fsm/test/index.ts | 106 +- packages/transducers-fsm/tsconfig.json | 10 +- packages/transducers-hdom/api-extractor.json | 2 +- packages/transducers-hdom/package.json | 160 +- packages/transducers-hdom/src/index.ts | 44 +- packages/transducers-hdom/tsconfig.json | 10 +- packages/transducers-patch/api-extractor.json | 2 +- packages/transducers-patch/package.json | 174 +- packages/transducers-patch/src/api.ts | 14 +- packages/transducers-patch/src/patch-array.ts | 114 +- packages/transducers-patch/src/patch-obj.ts | 54 +- packages/transducers-patch/test/index.ts | 82 +- packages/transducers-patch/tsconfig.json | 10 +- packages/transducers-stats/api-extractor.json | 2 +- packages/transducers-stats/package.json | 264 +- packages/transducers-stats/src/bollinger.ts | 58 +- packages/transducers-stats/src/bounds.ts | 16 +- packages/transducers-stats/src/donchian.ts | 10 +- packages/transducers-stats/src/dot.ts | 10 +- packages/transducers-stats/src/ema.ts | 58 +- packages/transducers-stats/src/hma.ts | 20 +- packages/transducers-stats/src/macd.ts | 102 +- packages/transducers-stats/src/momentum.ts | 38 +- packages/transducers-stats/src/mse.ts | 10 +- packages/transducers-stats/src/roc.ts | 36 +- packages/transducers-stats/src/rsi.ts | 38 +- packages/transducers-stats/src/sd.ts | 34 +- packages/transducers-stats/src/sma.ts | 38 +- packages/transducers-stats/src/stochastic.ts | 58 +- packages/transducers-stats/src/trix.ts | 10 +- packages/transducers-stats/src/wma.ts | 36 +- packages/transducers-stats/tsconfig.json | 10 +- packages/transducers/api-extractor.json | 2 +- packages/transducers/package.json | 1136 +++--- packages/transducers/src/add.ts | 2 +- packages/transducers/src/api.ts | 48 +- packages/transducers/src/as-iterable.ts | 2 +- packages/transducers/src/assoc-map.ts | 12 +- packages/transducers/src/assoc-obj.ts | 12 +- packages/transducers/src/auto-obj.ts | 14 +- packages/transducers/src/benchmark.ts | 24 +- packages/transducers/src/cat.ts | 30 +- packages/transducers/src/choices.ts | 16 +- packages/transducers/src/comp.ts | 134 +- packages/transducers/src/compr.ts | 4 +- packages/transducers/src/concat.ts | 8 +- packages/transducers/src/conj.ts | 12 +- packages/transducers/src/converge.ts | 38 +- packages/transducers/src/convolve.ts | 260 +- packages/transducers/src/count.ts | 20 +- packages/transducers/src/curve.ts | 24 +- packages/transducers/src/cycle.ts | 22 +- packages/transducers/src/dedupe.ts | 54 +- packages/transducers/src/deep-transform.ts | 28 +- packages/transducers/src/delayed.ts | 2 +- packages/transducers/src/distinct.ts | 62 +- packages/transducers/src/div.ts | 12 +- packages/transducers/src/drop-nth.ts | 16 +- packages/transducers/src/drop-while.ts | 26 +- packages/transducers/src/drop.ts | 18 +- packages/transducers/src/dup.ts | 10 +- packages/transducers/src/duplicate.ts | 22 +- packages/transducers/src/ensure.ts | 2 +- packages/transducers/src/every.ts | 22 +- packages/transducers/src/extend-sides.ts | 30 +- packages/transducers/src/fill.ts | 20 +- packages/transducers/src/filter-fuzzy.ts | 48 +- packages/transducers/src/filter.ts | 16 +- packages/transducers/src/flatten-with.ts | 48 +- packages/transducers/src/flatten.ts | 10 +- packages/transducers/src/frequencies.ts | 12 +- packages/transducers/src/group-binary.ts | 42 +- packages/transducers/src/group-by-map.ts | 54 +- packages/transducers/src/group-by-obj.ts | 50 +- packages/transducers/src/indexed.ts | 16 +- packages/transducers/src/interleave.ts | 25 +- packages/transducers/src/internal/drain.ts | 14 +- .../transducers/src/internal/group-opts.ts | 12 +- packages/transducers/src/internal/mathop.ts | 20 +- .../transducers/src/internal/sort-opts.ts | 10 +- .../transducers/src/interpolate-hermite.ts | 18 +- .../transducers/src/interpolate-linear.ts | 18 +- packages/transducers/src/interpolate.ts | 34 +- packages/transducers/src/interpose.ts | 35 +- packages/transducers/src/iterate.ts | 14 +- packages/transducers/src/iterator.ts | 86 +- packages/transducers/src/juxtr.ts | 166 +- packages/transducers/src/keep.ts | 28 +- packages/transducers/src/key-permutations.ts | 14 +- packages/transducers/src/key-selector.ts | 2 +- packages/transducers/src/keys.ts | 10 +- packages/transducers/src/labeled.ts | 10 +- packages/transducers/src/last.ts | 2 +- packages/transducers/src/line.ts | 4 +- packages/transducers/src/lookup.ts | 15 +- packages/transducers/src/map-deep.ts | 10 +- packages/transducers/src/map-indexed.ts | 32 +- packages/transducers/src/map-keys.ts | 40 +- packages/transducers/src/map-nth.ts | 82 +- packages/transducers/src/map-vals.ts | 40 +- packages/transducers/src/map.ts | 14 +- packages/transducers/src/mapcat-indexed.ts | 22 +- packages/transducers/src/mapcat.ts | 12 +- packages/transducers/src/match-first.ts | 10 +- packages/transducers/src/match-last.ts | 10 +- packages/transducers/src/max-compare.ts | 20 +- packages/transducers/src/max-mag.ts | 12 +- packages/transducers/src/max.ts | 12 +- packages/transducers/src/mean.ts | 16 +- packages/transducers/src/min-compare.ts | 20 +- packages/transducers/src/min-mag.ts | 12 +- packages/transducers/src/min.ts | 12 +- packages/transducers/src/moving-average.ts | 36 +- packages/transducers/src/moving-median.ts | 47 +- packages/transducers/src/mul.ts | 2 +- packages/transducers/src/multiplex-obj.ts | 34 +- packages/transducers/src/multiplex.ts | 72 +- packages/transducers/src/noop.ts | 6 +- packages/transducers/src/norm-count.ts | 12 +- .../transducers/src/norm-frequencies-auto.ts | 40 +- packages/transducers/src/norm-frequencies.ts | 28 +- packages/transducers/src/norm-range.ts | 66 +- packages/transducers/src/pad-last.ts | 42 +- packages/transducers/src/pad-sides.ts | 22 +- packages/transducers/src/page.ts | 14 +- packages/transducers/src/pairs.ts | 10 +- packages/transducers/src/palindrome.ts | 10 +- packages/transducers/src/partition-by.ts | 80 +- packages/transducers/src/partition-of.ts | 30 +- packages/transducers/src/partition-sort.ts | 41 +- packages/transducers/src/partition-sync.ts | 345 +- packages/transducers/src/partition-time.ts | 24 +- packages/transducers/src/partition-when.ts | 68 +- packages/transducers/src/partition.ts | 116 +- packages/transducers/src/peek.ts | 2 +- packages/transducers/src/permutations.ts | 84 +- packages/transducers/src/pluck.ts | 10 +- packages/transducers/src/push-copy.ts | 8 +- packages/transducers/src/push-sort.ts | 20 +- packages/transducers/src/push.ts | 12 +- packages/transducers/src/range-nd.ts | 22 +- packages/transducers/src/range.ts | 110 +- packages/transducers/src/range2d.ts | 70 +- packages/transducers/src/range3d.ts | 92 +- packages/transducers/src/rechunk.ts | 62 +- packages/transducers/src/reduce.ts | 144 +- packages/transducers/src/reduced.ts | 16 +- packages/transducers/src/reductions.ts | 36 +- packages/transducers/src/rename.ts | 56 +- packages/transducers/src/renamer.ts | 82 +- packages/transducers/src/repeat.ts | 6 +- packages/transducers/src/repeatedly.ts | 6 +- packages/transducers/src/reverse.ts | 10 +- packages/transducers/src/run.ts | 24 +- packages/transducers/src/sample.ts | 30 +- packages/transducers/src/scan.ts | 56 +- packages/transducers/src/select-keys.ts | 10 +- packages/transducers/src/side-effect.ts | 2 +- packages/transducers/src/sliding-window.ts | 48 +- packages/transducers/src/some.ts | 22 +- packages/transducers/src/sorted-keys.ts | 2 +- packages/transducers/src/step.ts | 24 +- packages/transducers/src/str.ts | 22 +- packages/transducers/src/stream-shuffle.ts | 134 +- packages/transducers/src/stream-sort.ts | 61 +- packages/transducers/src/struct.ts | 39 +- packages/transducers/src/sub.ts | 2 +- packages/transducers/src/swizzle.ts | 10 +- packages/transducers/src/symmetric.ts | 22 +- packages/transducers/src/take-last.ts | 32 +- packages/transducers/src/take-nth.ts | 16 +- packages/transducers/src/take-while.ts | 26 +- packages/transducers/src/take.ts | 26 +- packages/transducers/src/throttle-time.ts | 22 +- packages/transducers/src/throttle.ts | 22 +- packages/transducers/src/toggle.ts | 36 +- packages/transducers/src/trace.ts | 2 +- packages/transducers/src/transduce.ts | 84 +- packages/transducers/src/tween.ts | 118 +- packages/transducers/src/vals.ts | 10 +- packages/transducers/src/word-wrap.ts | 62 +- packages/transducers/src/wrap-sides.ts | 34 +- packages/transducers/src/zip.ts | 94 +- packages/transducers/test/drop.ts | 32 +- packages/transducers/test/filter.ts | 42 +- packages/transducers/test/flatten.ts | 56 +- packages/transducers/test/fuzzy.ts | 92 +- packages/transducers/test/juxtr.ts | 154 +- packages/transducers/test/keyperms.ts | 44 +- packages/transducers/test/map-deep.ts | 94 +- packages/transducers/test/map.ts | 30 +- packages/transducers/test/pad-last.ts | 28 +- packages/transducers/test/partition-sync.ts | 230 +- packages/transducers/test/permutations.ts | 214 +- packages/transducers/test/range.ts | 232 +- packages/transducers/test/take.ts | 32 +- packages/transducers/tsconfig.json | 10 +- packages/unionstruct/api-extractor.json | 2 +- packages/unionstruct/package.json | 162 +- packages/unionstruct/src/index.ts | 432 +-- packages/unionstruct/test/index.ts | 184 +- packages/unionstruct/tsconfig.json | 10 +- packages/vclock/api-extractor.json | 2 +- packages/vclock/package.json | 156 +- packages/vclock/src/index.ts | 128 +- packages/vclock/test/index.ts | 94 +- packages/vclock/tsconfig.json | 10 +- packages/vector-pools/api-extractor.json | 2 +- packages/vector-pools/package.json | 230 +- packages/vector-pools/src/alist.ts | 138 +- packages/vector-pools/src/api.ts | 72 +- packages/vector-pools/src/array-list.ts | 116 +- packages/vector-pools/src/attrib-pool.ts | 714 ++-- packages/vector-pools/src/linked-list.ts | 286 +- packages/vector-pools/src/vec-pool.ts | 156 +- packages/vector-pools/src/wrap.ts | 36 +- packages/vector-pools/test/attribs.ts | 160 +- packages/vector-pools/tsconfig.json | 10 +- packages/vectors/api-extractor.json | 2 +- packages/vectors/package.json | 1508 ++++---- packages/vectors/src/acos.ts | 2 +- packages/vectors/src/addm.ts | 4 +- packages/vectors/src/addmn.ts | 4 +- packages/vectors/src/addmns.ts | 4 +- packages/vectors/src/addms.ts | 8 +- packages/vectors/src/addns.ts | 8 +- packages/vectors/src/adds.ts | 2 +- packages/vectors/src/addw.ts | 94 +- packages/vectors/src/angle-between.ts | 24 +- packages/vectors/src/api.ts | 594 ++-- packages/vectors/src/asin.ts | 2 +- packages/vectors/src/atan.ts | 6 +- packages/vectors/src/avec.ts | 26 +- packages/vectors/src/bisect.ts | 64 +- packages/vectors/src/bit-not.ts | 8 +- packages/vectors/src/buffer.ts | 94 +- packages/vectors/src/cartesian.ts | 22 +- packages/vectors/src/ceil.ts | 2 +- packages/vectors/src/center.ts | 6 +- packages/vectors/src/clamp.ts | 18 +- packages/vectors/src/clampn.ts | 4 +- packages/vectors/src/clockwise.ts | 16 +- packages/vectors/src/compare.ts | 132 +- packages/vectors/src/compile/accessors.ts | 88 +- packages/vectors/src/compile/emit.ts | 478 +-- packages/vectors/src/convert.ts | 26 +- packages/vectors/src/copy.ts | 2 +- packages/vectors/src/correlation.ts | 12 +- packages/vectors/src/cosh.ts | 2 +- packages/vectors/src/covariance.ts | 6 +- packages/vectors/src/cross.ts | 14 +- packages/vectors/src/crosss.ts | 46 +- packages/vectors/src/degrees.ts | 4 +- packages/vectors/src/direction.ts | 8 +- packages/vectors/src/dist-braycurtis.ts | 18 +- packages/vectors/src/dist-canberra.ts | 20 +- packages/vectors/src/dist-chebyshev.ts | 36 +- packages/vectors/src/dist-hamming.ts | 14 +- packages/vectors/src/dist-haversine.ts | 32 +- packages/vectors/src/dist-jaccard.ts | 22 +- packages/vectors/src/dist-manhattan.ts | 40 +- packages/vectors/src/dist-minkowski.ts | 16 +- packages/vectors/src/dist-sorensendice.ts | 22 +- packages/vectors/src/dist-weighted.ts | 10 +- packages/vectors/src/distsq.ts | 2 +- packages/vectors/src/divns.ts | 8 +- packages/vectors/src/divs.ts | 2 +- packages/vectors/src/dot.ts | 2 +- packages/vectors/src/dotc.ts | 2 +- packages/vectors/src/dots.ts | 22 +- packages/vectors/src/empty.ts | 2 +- packages/vectors/src/eq.ts | 2 +- packages/vectors/src/eqdelta.ts | 106 +- packages/vectors/src/equals.ts | 8 +- packages/vectors/src/every.ts | 8 +- packages/vectors/src/exp_2.ts | 4 +- packages/vectors/src/face-forward.ts | 12 +- packages/vectors/src/fill.ts | 8 +- packages/vectors/src/fit.ts | 28 +- packages/vectors/src/floor.ts | 2 +- packages/vectors/src/fract.ts | 2 +- packages/vectors/src/gte.ts | 2 +- packages/vectors/src/gvec.ts | 150 +- packages/vectors/src/hash.ts | 14 +- packages/vectors/src/heading-segment.ts | 6 +- packages/vectors/src/homogeneous.ts | 4 +- packages/vectors/src/internal/ensure.ts | 2 +- packages/vectors/src/invert.ts | 2 +- packages/vectors/src/invsqrt.ts | 4 +- packages/vectors/src/is-inf.ts | 4 +- packages/vectors/src/is-nan.ts | 4 +- packages/vectors/src/iterator.ts | 36 +- packages/vectors/src/jitter.ts | 8 +- packages/vectors/src/limit.ts | 12 +- packages/vectors/src/log_2.ts | 2 +- packages/vectors/src/logic-and.ts | 16 +- packages/vectors/src/logic-not.ts | 4 +- packages/vectors/src/logic-or.ts | 16 +- packages/vectors/src/lshift.ts | 6 +- packages/vectors/src/lte.ts | 2 +- packages/vectors/src/madd.ts | 4 +- packages/vectors/src/maddn.ts | 6 +- packages/vectors/src/maddns.ts | 4 +- packages/vectors/src/madds.ts | 8 +- packages/vectors/src/mags.ts | 8 +- packages/vectors/src/magsq.ts | 10 +- packages/vectors/src/magsqs.ts | 6 +- packages/vectors/src/major.ts | 24 +- packages/vectors/src/map-vectors.ts | 132 +- packages/vectors/src/map.ts | 160 +- packages/vectors/src/max-bounds.ts | 12 +- packages/vectors/src/max.ts | 2 +- packages/vectors/src/mean.ts | 18 +- packages/vectors/src/median.ts | 26 +- packages/vectors/src/min-bounds.ts | 12 +- packages/vectors/src/min.ts | 2 +- packages/vectors/src/minor.ts | 24 +- packages/vectors/src/mix-bilinear.ts | 10 +- packages/vectors/src/mix-cubic.ts | 20 +- packages/vectors/src/mix-hermite.ts | 136 +- packages/vectors/src/mix-quadratic.ts | 14 +- packages/vectors/src/mix.ts | 4 +- packages/vectors/src/mixn.ts | 4 +- packages/vectors/src/mixns.ts | 6 +- packages/vectors/src/mixs.ts | 8 +- packages/vectors/src/mod.ts | 6 +- packages/vectors/src/modn.ts | 8 +- packages/vectors/src/msub.ts | 4 +- packages/vectors/src/msubn.ts | 6 +- packages/vectors/src/msubns.ts | 4 +- packages/vectors/src/msubs.ts | 8 +- packages/vectors/src/mulns.ts | 8 +- packages/vectors/src/muls.ts | 2 +- packages/vectors/src/neg.ts | 2 +- packages/vectors/src/neq.ts | 2 +- packages/vectors/src/normal.ts | 4 +- packages/vectors/src/normalize.ts | 6 +- packages/vectors/src/normalizes.ts | 114 +- packages/vectors/src/not.ts | 12 +- packages/vectors/src/ortho-normal.ts | 14 +- packages/vectors/src/perpendicular.ts | 2 +- packages/vectors/src/point-on-ray.ts | 24 +- packages/vectors/src/polar.ts | 12 +- packages/vectors/src/pow.ts | 2 +- packages/vectors/src/pown.ts | 4 +- packages/vectors/src/project.ts | 2 +- packages/vectors/src/radians.ts | 4 +- packages/vectors/src/random.ts | 72 +- packages/vectors/src/randoms.ts | 162 +- packages/vectors/src/reflect.ts | 2 +- packages/vectors/src/refract.ts | 12 +- packages/vectors/src/remainder.ts | 4 +- packages/vectors/src/remaindern.ts | 4 +- packages/vectors/src/rotate-around-axis.ts | 68 +- packages/vectors/src/rotate-around-point.ts | 10 +- packages/vectors/src/rotate.ts | 16 +- packages/vectors/src/rotates.ts | 22 +- packages/vectors/src/round.ts | 10 +- packages/vectors/src/rshift.ts | 6 +- packages/vectors/src/safe-div.ts | 4 +- packages/vectors/src/select.ts | 18 +- packages/vectors/src/set.ts | 12 +- packages/vectors/src/setc.ts | 66 +- packages/vectors/src/setcs.ts | 58 +- packages/vectors/src/setn.ts | 12 +- packages/vectors/src/setns.ts | 12 +- packages/vectors/src/sets.ts | 12 +- packages/vectors/src/setvv.ts | 54 +- packages/vectors/src/sign.ts | 2 +- packages/vectors/src/signed-area.ts | 8 +- packages/vectors/src/signed-volume.ts | 38 +- packages/vectors/src/sinh.ts | 2 +- packages/vectors/src/smoothstep.ts | 4 +- packages/vectors/src/softmax.ts | 8 +- packages/vectors/src/some.ts | 8 +- packages/vectors/src/sqrt.ts | 2 +- packages/vectors/src/standardize.ts | 18 +- packages/vectors/src/step.ts | 14 +- packages/vectors/src/string.ts | 36 +- packages/vectors/src/subm.ts | 4 +- packages/vectors/src/submn.ts | 4 +- packages/vectors/src/submns.ts | 4 +- packages/vectors/src/subms.ts | 8 +- packages/vectors/src/subns.ts | 8 +- packages/vectors/src/subs.ts | 2 +- packages/vectors/src/swizzle.ts | 58 +- packages/vectors/src/tanh.ts | 2 +- packages/vectors/src/tensor.ts | 26 +- packages/vectors/src/trunc.ts | 2 +- packages/vectors/src/variance.ts | 22 +- packages/vectors/src/vec-of.ts | 6 +- packages/vectors/src/vec2.ts | 232 +- packages/vectors/src/vec3.ts | 246 +- packages/vectors/src/vec4.ts | 248 +- packages/vectors/src/vop.ts | 26 +- packages/vectors/src/wrap.ts | 6 +- packages/vectors/test/index.ts | 68 +- packages/vectors/tsconfig.json | 12 +- packages/viz/api-extractor.json | 2 +- packages/viz/package.json | 290 +- packages/viz/src/api.ts | 52 +- packages/viz/src/axis/common.ts | 24 +- packages/viz/src/axis/lens.ts | 56 +- packages/viz/src/axis/linear.ts | 38 +- packages/viz/src/axis/log.ts | 102 +- packages/viz/src/domain.ts | 44 +- packages/viz/src/plot.ts | 276 +- packages/viz/src/plot/area.ts | 32 +- packages/viz/src/plot/bar.ts | 68 +- packages/viz/src/plot/candle.ts | 118 +- packages/viz/src/plot/line.ts | 2 +- packages/viz/src/plot/scatter.ts | 2 +- packages/viz/src/plot/stacked-intervals.ts | 108 +- packages/viz/src/plot/utils.ts | 50 +- packages/viz/test/index.ts | 262 +- packages/viz/tools/candles.ts | 96 +- packages/viz/tools/line.ts | 122 +- packages/viz/tsconfig.json | 10 +- packages/webgl-msdf/api-extractor.json | 2 +- packages/webgl-msdf/package.json | 190 +- packages/webgl-msdf/src/api.ts | 124 +- packages/webgl-msdf/src/convert.ts | 30 +- packages/webgl-msdf/src/shader.ts | 132 +- packages/webgl-msdf/src/text.ts | 186 +- packages/webgl-msdf/tsconfig.json | 10 +- packages/webgl-shadertoy/api-extractor.json | 2 +- packages/webgl-shadertoy/package.json | 182 +- packages/webgl-shadertoy/src/api.ts | 68 +- packages/webgl-shadertoy/src/shadertoy.ts | 184 +- packages/webgl-shadertoy/tsconfig.json | 10 +- packages/webgl/api-extractor.json | 2 +- packages/webgl/package.json | 432 +-- packages/webgl/src/api/blend.ts | 44 +- packages/webgl/src/api/buffers.ts | 70 +- packages/webgl/src/api/canvas.ts | 18 +- packages/webgl/src/api/ext.ts | 70 +- packages/webgl/src/api/glsl.ts | 68 +- packages/webgl/src/api/material.ts | 6 +- packages/webgl/src/api/model.ts | 174 +- packages/webgl/src/api/multipass.ts | 96 +- packages/webgl/src/api/shader.ts | 456 +-- packages/webgl/src/api/stencil.ts | 32 +- packages/webgl/src/api/texture.ts | 1422 ++++---- packages/webgl/src/buffer.ts | 270 +- packages/webgl/src/canvas.ts | 86 +- packages/webgl/src/checks.ts | 14 +- packages/webgl/src/draw.ts | 238 +- packages/webgl/src/error.ts | 2 +- packages/webgl/src/fbo.ts | 202 +- packages/webgl/src/geo/cube.ts | 6 +- packages/webgl/src/geo/quad.ts | 98 +- packages/webgl/src/material.ts | 28 +- packages/webgl/src/matrices.ts | 28 +- packages/webgl/src/multipass.ts | 342 +- packages/webgl/src/rbo.ts | 76 +- packages/webgl/src/readpixels.ts | 52 +- packages/webgl/src/shader.ts | 852 ++--- packages/webgl/src/shaders/lambert.ts | 158 +- packages/webgl/src/shaders/phong.ts | 182 +- packages/webgl/src/shaders/pipeline.ts | 42 +- packages/webgl/src/syntax.ts | 128 +- packages/webgl/src/texture.ts | 614 ++-- packages/webgl/src/textures/checkerboard.ts | 60 +- packages/webgl/src/textures/stripes.ts | 42 +- packages/webgl/src/uniforms.ts | 202 +- packages/webgl/src/utils.ts | 28 +- packages/webgl/tsconfig.json | 10 +- packages/zipper/api-extractor.json | 2 +- packages/zipper/package.json | 190 +- packages/zipper/src/api.ts | 16 +- packages/zipper/src/zipper.ts | 614 ++-- packages/zipper/test/array.ts | 536 +-- packages/zipper/test/custom.ts | 246 +- packages/zipper/tsconfig.json | 10 +- 3050 files changed, 139706 insertions(+), 139601 deletions(-) diff --git a/.prettierrc b/.prettierrc index 2bbeb7c420..11d1113d73 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,8 @@ { + "useTabs": true, "tabWidth": 4, + "semi": true, + "singleQuote": false, "arrowParens": "always", "endOfLine": "lf" -} \ No newline at end of file +} diff --git a/examples/adaptive-threshold/package.json b/examples/adaptive-threshold/package.json index 585b247ad6..4a977bb2ac 100644 --- a/examples/adaptive-threshold/package.json +++ b/examples/adaptive-threshold/package.json @@ -1,40 +1,40 @@ { - "name": "@example/adaptive-threshold", - "private": true, - "version": "0.0.1", - "description": "Interactive image processing (adaptive threshold)", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/paths": "workspace:^", - "@thi.ng/pixel": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "pixel", - "rstream", - "transducers", - "transducers-hdom" - ], - "screenshot": "examples/adaptive-threshold.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/adaptive-threshold", + "private": true, + "version": "0.0.1", + "description": "Interactive image processing (adaptive threshold)", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/paths": "workspace:^", + "@thi.ng/pixel": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "pixel", + "rstream", + "transducers", + "transducers-hdom" + ], + "screenshot": "examples/adaptive-threshold.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/adaptive-threshold/src/api.ts b/examples/adaptive-threshold/src/api.ts index ba463f5af5..60db0e1533 100644 --- a/examples/adaptive-threshold/src/api.ts +++ b/examples/adaptive-threshold/src/api.ts @@ -9,10 +9,10 @@ export const SET_KERNEL_OFFSET = "set-kernel-offset"; // define all possible event structures/signatures export interface EventTypeMap { - [SET_IMAGE]: [typeof SET_IMAGE, File]; - [UPDATE_IMAGE]: [typeof UPDATE_IMAGE]; - [SET_KERNEL_WIDTH]: [typeof SET_KERNEL_WIDTH, number]; - [SET_KERNEL_OFFSET]: [typeof SET_KERNEL_OFFSET, number]; + [SET_IMAGE]: [typeof SET_IMAGE, File]; + [UPDATE_IMAGE]: [typeof UPDATE_IMAGE]; + [SET_KERNEL_WIDTH]: [typeof SET_KERNEL_WIDTH, number]; + [SET_KERNEL_OFFSET]: [typeof SET_KERNEL_OFFSET, number]; } export type EventType = keyof EventTypeMap; @@ -20,10 +20,10 @@ export type EventType = keyof EventTypeMap; export type Event = Val1; export interface AppState { - srcImg?: FloatBuffer; - destImg?: FloatBuffer; - threshold: { - windowSize: number; - offset: number; - }; + srcImg?: FloatBuffer; + destImg?: FloatBuffer; + threshold: { + windowSize: number; + offset: number; + }; } diff --git a/examples/adaptive-threshold/src/events.ts b/examples/adaptive-threshold/src/events.ts index 1f6266963e..5da38cc685 100644 --- a/examples/adaptive-threshold/src/events.ts +++ b/examples/adaptive-threshold/src/events.ts @@ -8,13 +8,13 @@ import { stream } from "@thi.ng/rstream/stream"; import { trace } from "@thi.ng/rstream/trace"; import type { Transducer } from "@thi.ng/transducers"; import { - type Event, - type EventType, - type EventTypeMap, - SET_IMAGE, - SET_KERNEL_OFFSET, - SET_KERNEL_WIDTH, - UPDATE_IMAGE, + type Event, + type EventType, + type EventTypeMap, + SET_IMAGE, + SET_KERNEL_OFFSET, + SET_KERNEL_WIDTH, + UPDATE_IMAGE, } from "./api"; import { state } from "./state"; import { adaptiveThreshold } from "./threshold"; @@ -41,7 +41,7 @@ events.subscribe(eventProc); /** * Event dispatch function. Sends given event into the event stream. * - * @param e - + * @param e - */ export const dispatch = (e: Event) => events.next(e); @@ -51,69 +51,69 @@ export const dispatch = (e: Event) => events.next(e); * The handler's subscription also includes an error handler to display * errors in the console. * - * @param id - - * @param handler - - * @param xform - + * @param id - + * @param handler - + * @param xform - */ export const defHandler = ( - id: E, - handler: Fn, - xform?: Transducer + id: E, + handler: Fn, + xform?: Transducer ) => { - const sub: ISubscriber = { - next: >handler, - error: (e) => { - console.warn(e); - return false; - }, - }; - return xform - ? eventProc.subscribeTopic(id, sub, { xform }) - : eventProc.subscribeTopic(id, sub); + const sub: ISubscriber = { + next: >handler, + error: (e) => { + console.warn(e); + return false; + }, + }; + return xform + ? eventProc.subscribeTopic(id, sub, { xform }) + : eventProc.subscribeTopic(id, sub); }; // event handlers defHandler(SET_IMAGE, ([_, file]) => { - const reader = new FileReader(); - reader.onload = async (e: any) => { - const img = new Image(); - img.src = e.target.result; - await img.decode(); - state.next( - setIn( - state.deref()!, - ["srcImg"], - floatBufferFromImage(img, FLOAT_GRAY) - ) - ); - dispatch([UPDATE_IMAGE]); - }; - reader.readAsDataURL(file); + const reader = new FileReader(); + reader.onload = async (e: any) => { + const img = new Image(); + img.src = e.target.result; + await img.decode(); + state.next( + setIn( + state.deref()!, + ["srcImg"], + floatBufferFromImage(img, FLOAT_GRAY) + ) + ); + dispatch([UPDATE_IMAGE]); + }; + reader.readAsDataURL(file); }); defHandler(UPDATE_IMAGE, () => { - const curr = state.deref()!; - // create & store threshold image in state - state.next( - setIn( - curr, - ["destImg"], - adaptiveThreshold( - curr.srcImg!, - curr.threshold.windowSize, - curr.threshold.offset - ) - ) - ); + const curr = state.deref()!; + // create & store threshold image in state + state.next( + setIn( + curr, + ["destImg"], + adaptiveThreshold( + curr.srcImg!, + curr.threshold.windowSize, + curr.threshold.offset + ) + ) + ); }); defHandler(SET_KERNEL_WIDTH, ([_, width]) => { - state.next(setIn(state.deref()!, ["threshold", "windowSize"], width)); - dispatch([UPDATE_IMAGE]); + state.next(setIn(state.deref()!, ["threshold", "windowSize"], width)); + dispatch([UPDATE_IMAGE]); }); defHandler(SET_KERNEL_OFFSET, ([_, offset]) => { - state.next(setIn(state.deref()!, ["threshold", "offset"], offset)); - dispatch([UPDATE_IMAGE]); + state.next(setIn(state.deref()!, ["threshold", "offset"], offset)); + dispatch([UPDATE_IMAGE]); }); diff --git a/examples/adaptive-threshold/src/index.ts b/examples/adaptive-threshold/src/index.ts index 5cd46f4b4b..4e20f89f7f 100644 --- a/examples/adaptive-threshold/src/index.ts +++ b/examples/adaptive-threshold/src/index.ts @@ -3,11 +3,11 @@ import { sidechainPartitionRAF } from "@thi.ng/rstream/sidechain-partition"; import { map } from "@thi.ng/transducers/map"; import { updateDOM } from "@thi.ng/transducers-hdom"; import { - type AppState, - type Event, - SET_IMAGE, - SET_KERNEL_OFFSET, - SET_KERNEL_WIDTH, + type AppState, + type Event, + SET_IMAGE, + SET_KERNEL_OFFSET, + SET_KERNEL_WIDTH, } from "./api"; import { dispatch } from "./events"; import { state } from "./state"; @@ -16,96 +16,96 @@ import { state } from "./state"; * Canvas component w/ life cycle methods */ const canvas = { - init(el: HTMLCanvasElement, _: any, pix: IntBuffer) { - this.el = el; - this.render(null, pix); - }, - render(_: any, pix: IntBuffer) { - // delay blitting until just after DOM update. this is needed - // due to setting canvas size also clears content... - this.el && setTimeout(() => pix.blitCanvas(this.el), 0); - return [ - "canvas.mv3.pa1.ba", - { - width: pix.width, - height: pix.height, - }, - ]; - }, + init(el: HTMLCanvasElement, _: any, pix: IntBuffer) { + this.el = el; + this.render(null, pix); + }, + render(_: any, pix: IntBuffer) { + // delay blitting until just after DOM update. this is needed + // due to setting canvas size also clears content... + this.el && setTimeout(() => pix.blitCanvas(this.el), 0); + return [ + "canvas.mv3.pa1.ba", + { + width: pix.width, + height: pix.height, + }, + ]; + }, }; /** * File/image chooser component w/ event dispatch */ const fileChooser = [ - "div.mb3", - ["label.dib.w3", `Image:`], - [ - "input.f7", - { - type: "file", - accept: "image/png, image/jpeg, image/webp", - onchange: (e: any) => dispatch([SET_IMAGE, e.target.files[0]]), - }, - ], + "div.mb3", + ["label.dib.w3", `Image:`], + [ + "input.f7", + { + type: "file", + accept: "image/png, image/jpeg, image/webp", + onchange: (e: any) => dispatch([SET_IMAGE, e.target.files[0]]), + }, + ], ]; /** * Numeric input component. * - * @param eventID - - * @param label - - * @param value - - * @param opts - + * @param eventID - + * @param label - + * @param value - + * @param opts - */ const param = (eventID: string, label: string, value: number, opts: any) => [ - "div", - ["label.dib.w3", `${label}:`], - [ - "input.w3", - { - type: "number", - value, - onchange: (e: any) => - dispatch([eventID, parseInt(e.target.value)]), - ...opts, - }, - ], + "div", + ["label.dib.w3", `${label}:`], + [ + "input.w3", + { + type: "number", + value, + onchange: (e: any) => + dispatch([eventID, parseInt(e.target.value)]), + ...opts, + }, + ], ]; /** * Composite component of image canvas & controls for adaptive threshold * computation. Only used/shown once an image has been loaded. * - * @param state - + * @param state - */ const imageEditor = ({ destImg, threshold }: AppState) => [ - "div", - param(SET_KERNEL_WIDTH, "Width", threshold.windowSize, { - min: 3, - max: 29, - step: 2, - }), - param(SET_KERNEL_OFFSET, "Offset", threshold.offset, { - min: -20, - max: 20, - step: 1, - }), - [canvas, destImg], + "div", + param(SET_KERNEL_WIDTH, "Width", threshold.windowSize, { + min: 3, + max: 29, + step: 2, + }), + param(SET_KERNEL_OFFSET, "Offset", threshold.offset, { + min: -20, + max: 20, + step: 1, + }), + [canvas, destImg], ]; /** * Main/root UI component, receives app state and returns hdom component tree. * - * @param state - + * @param state - */ const app = (state: AppState) => { - return [ - "div", - ["h1", "Adaptive thresholding"], - fileChooser, - state.destImg ? imageEditor(state) : null, - ]; + return [ + "div", + ["h1", "Adaptive thresholding"], + fileChooser, + state.destImg ? imageEditor(state) : null, + ]; }; // subscription & transformation of app state stream. uses a RAF diff --git a/examples/adaptive-threshold/src/state.ts b/examples/adaptive-threshold/src/state.ts index 9ecce01e6f..768c8957a5 100644 --- a/examples/adaptive-threshold/src/state.ts +++ b/examples/adaptive-threshold/src/state.ts @@ -5,5 +5,5 @@ import type { AppState } from "./api"; * Stream of app state values. */ export const state = reactive({ - threshold: { windowSize: 7, offset: 1 }, + threshold: { windowSize: 7, offset: 1 }, }); diff --git a/examples/adaptive-threshold/src/threshold.ts b/examples/adaptive-threshold/src/threshold.ts index 2d9f4c871b..773c41ef92 100644 --- a/examples/adaptive-threshold/src/threshold.ts +++ b/examples/adaptive-threshold/src/threshold.ts @@ -6,27 +6,27 @@ import { convolveChannel, POOL_THRESHOLD } from "@thi.ng/pixel/convolve"; * kernel to compute mean brightness for each pixel's neighborhood, then applies * offset value and checks if pixel itself is above/below localized threshold. * - * @param src - - * @param windowSize - - * @param offset - + * @param src - + * @param windowSize - + * @param offset - */ export const adaptiveThreshold = ( - src: FloatBuffer, - windowSize: number, - offset = 0 + src: FloatBuffer, + windowSize: number, + offset = 0 ) => - convolveChannel(src, { - kernel: { - // pool kernel template for code generator: - // take a `body` array of pixel lookups (W x H items) - // w, h are kernel size - // pool: (body, w, h) => { - // const center = body[(h >> 1) * w + (w >> 1)]; - // const mean = `(${body.join("+")})/${w * h}`; - // return `(${center} - ${mean} + ${offset / 255}) < 0 ? 0 : 1`; - // }, - // the same logic also available as preset: - pool: POOL_THRESHOLD(offset / 255), - size: windowSize, - }, - }); + convolveChannel(src, { + kernel: { + // pool kernel template for code generator: + // take a `body` array of pixel lookups (W x H items) + // w, h are kernel size + // pool: (body, w, h) => { + // const center = body[(h >> 1) * w + (w >> 1)]; + // const mean = `(${body.join("+")})/${w * h}`; + // return `(${center} - ${mean} + ${offset / 255}) < 0 ? 0 : 1`; + // }, + // the same logic also available as preset: + pool: POOL_THRESHOLD(offset / 255), + size: windowSize, + }, + }); diff --git a/examples/adaptive-threshold/tsconfig.json b/examples/adaptive-threshold/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/adaptive-threshold/tsconfig.json +++ b/examples/adaptive-threshold/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/async-effect/package.json b/examples/async-effect/package.json index 0c62d2e56a..43cc8ce27f 100644 --- a/examples/async-effect/package.json +++ b/examples/async-effect/package.json @@ -1,33 +1,33 @@ { - "name": "@example/async-effect", - "private": true, - "version": "0.0.1", - "description": "Minimal demo using interceptors with an async side effect", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/interceptors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "interceptors" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/async-effect", + "private": true, + "version": "0.0.1", + "description": "Minimal demo using interceptors with an async side effect", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/interceptors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "interceptors" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/async-effect/src/index.ts b/examples/async-effect/src/index.ts index f6d77a32a5..a4d50c4bb1 100644 --- a/examples/async-effect/src/index.ts +++ b/examples/async-effect/src/index.ts @@ -1,12 +1,12 @@ import type { IObjectOf } from "@thi.ng/api"; import { start } from "@thi.ng/hdom/start"; import { - EventBus, - FX_DISPATCH_ASYNC, - FX_DISPATCH_NOW, - valueSetter, - type EffectDef, - type EventDef, + EventBus, + FX_DISPATCH_ASYNC, + FX_DISPATCH_NOW, + valueSetter, + type EffectDef, + type EventDef, } from "@thi.ng/interceptors"; import DATA_URL from "./data/foo.json?url"; @@ -26,77 +26,77 @@ const FX_DELAY = "delay"; // event handler definitions const events: IObjectOf = { - // valueSetter() produces an interceptor to set value at given path - [EV_SET_STATUS]: valueSetter("status"), + // valueSetter() produces an interceptor to set value at given path + [EV_SET_STATUS]: valueSetter("status"), - // this event is the initial trigger for starting an async IO operation - // via the FX_DISPATCH_ASYNC side effect, which takes this general definition: - // [fx-id, fx-arg, success-event-id, error-event-id] - // - // FX_DISPATCH_ASYNC acts as a wrapper for the actual side effect to be executed, - // in this case it's the "json" side effect defined below - // the last items in the array are the event IDs for success & error outcomes - [EV_LOAD_JSON]: (_, [__, url]) => ({ - [FX_DISPATCH_NOW]: [EV_SET_STATUS, ["idle", `loading: ${url}...`]], - [FX_DISPATCH_ASYNC]: [FX_JSON, url, EV_RECEIVE_JSON, EV_ERROR], - }), + // this event is the initial trigger for starting an async IO operation + // via the FX_DISPATCH_ASYNC side effect, which takes this general definition: + // [fx-id, fx-arg, success-event-id, error-event-id] + // + // FX_DISPATCH_ASYNC acts as a wrapper for the actual side effect to be executed, + // in this case it's the "json" side effect defined below + // the last items in the array are the event IDs for success & error outcomes + [EV_LOAD_JSON]: (_, [__, url]) => ({ + [FX_DISPATCH_NOW]: [EV_SET_STATUS, ["idle", `loading: ${url}...`]], + [FX_DISPATCH_ASYNC]: [FX_JSON, url, EV_RECEIVE_JSON, EV_ERROR], + }), - // this event will be triggered after JSON data has been successfully loaded - // sets `json` state value, status and triggers another, delayed invocation - // of EV_SET_STATUS event to reset message after 1sec + // this event will be triggered after JSON data has been successfully loaded + // sets `json` state value, status and triggers another, delayed invocation + // of EV_SET_STATUS event to reset message after 1sec - // as with the EV_SET_STATUS event, we're using the higher-order valueSetter() - // to produce an interceptor, here with additional value transformer to - // create a formatted JSON string - [EV_RECEIVE_JSON]: [ - valueSetter("json", (json) => JSON.stringify(json, null, 2)), - () => ({ - [FX_DISPATCH_NOW]: [ - EV_SET_STATUS, - ["success", "JSON successfully loaded"], - ], - [FX_DISPATCH_ASYNC]: [ - FX_DELAY, - [1000, ["idle", "done."]], - EV_SET_STATUS, - EV_ERROR, - ], - }), - ], + // as with the EV_SET_STATUS event, we're using the higher-order valueSetter() + // to produce an interceptor, here with additional value transformer to + // create a formatted JSON string + [EV_RECEIVE_JSON]: [ + valueSetter("json", (json) => JSON.stringify(json, null, 2)), + () => ({ + [FX_DISPATCH_NOW]: [ + EV_SET_STATUS, + ["success", "JSON successfully loaded"], + ], + [FX_DISPATCH_ASYNC]: [ + FX_DELAY, + [1000, ["idle", "done."]], + EV_SET_STATUS, + EV_ERROR, + ], + }), + ], - // error event handler - [EV_ERROR]: (_, [__, err]) => ({ - [FX_DISPATCH_NOW]: [EV_SET_STATUS, ["error", err.message]], - }), + // error event handler + [EV_ERROR]: (_, [__, err]) => ({ + [FX_DISPATCH_NOW]: [EV_SET_STATUS, ["error", err.message]], + }), }; const effects: IObjectOf = { - // these are async side effects. ALWAYS MUST RETURN A PROMISE - [FX_JSON]: (url) => fetch(url).then((res) => res.json()), - [FX_DELAY]: ([x, msg]) => - new Promise((res) => setTimeout(() => res(msg), x)), + // these are async side effects. ALWAYS MUST RETURN A PROMISE + [FX_JSON]: (url) => fetch(url).then((res) => res.json()), + [FX_DELAY]: ([x, msg]) => + new Promise((res) => setTimeout(() => res(msg), x)), }; // main app component const app = () => { - // create event bus with empty state (null arg) - const bus = new EventBus(null, events, effects); + // create event bus with empty state (null arg) + const bus = new EventBus(null, events, effects); - // kick off JSON request - bus.dispatch([EV_LOAD_JSON, DATA_URL]); + // kick off JSON request + bus.dispatch([EV_LOAD_JSON, DATA_URL]); - // root component function - return () => { - if (bus.processQueue()) { - // the event bus' state can be obtained via `deref()` - const { json, status } = bus.deref(); - return [ - "div", - ["p#status", { class: status[0] }, `status: ${status[1]}`], - ["pre", json], - ]; - } - }; + // root component function + return () => { + if (bus.processQueue()) { + // the event bus' state can be obtained via `deref()` + const { json, status } = bus.deref(); + return [ + "div", + ["p#status", { class: status[0] }, `status: ${status[1]}`], + ["pre", json], + ]; + } + }; }; start(app()); diff --git a/examples/async-effect/tsconfig.json b/examples/async-effect/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/async-effect/tsconfig.json +++ b/examples/async-effect/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/big-font/package.json b/examples/big-font/package.json index f4142ab0d7..be6b735b42 100644 --- a/examples/big-font/package.json +++ b/examples/big-font/package.json @@ -1,34 +1,34 @@ { - "name": "@example/big-font", - "version": "0.0.1", - "private": true, - "description": "Large ASCII font text generator using @thi.ng/rdom", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "dependencies": { - "@thi.ng/hiccup-html": "workspace:^", - "@thi.ng/paths": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/rdom-components": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false - }, - "thi.ng": { - "readme": true, - "screenshot": "examples/big-font.png" - } + "name": "@example/big-font", + "version": "0.0.1", + "private": true, + "description": "Large ASCII font text generator using @thi.ng/rdom", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "dependencies": { + "@thi.ng/hiccup-html": "workspace:^", + "@thi.ng/paths": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/rdom-components": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false + }, + "thi.ng": { + "readme": true, + "screenshot": "examples/big-font.png" + } } diff --git a/examples/big-font/src/index.ts b/examples/big-font/src/index.ts index 90a972d948..37dbf17d5a 100644 --- a/examples/big-font/src/index.ts +++ b/examples/big-font/src/index.ts @@ -1,11 +1,11 @@ import type { Keys } from "@thi.ng/api"; import { - checkbox, - div, - inputRange, - inputText, - label, - pre, + checkbox, + div, + inputRange, + inputText, + label, + pre, } from "@thi.ng/hiccup-html"; import { getIn, setIn } from "@thi.ng/paths"; import { $compile, $input, $inputCheckbox, $inputNum } from "@thi.ng/rdom"; @@ -28,102 +28,102 @@ import FONT_SMALL from "./fonts/smallblock.txt?raw"; * See example fonts in /src/fonts */ class Font { - chars: Record = {}; - rows: string[]; - kerning: Record> = {}; + chars: Record = {}; + rows: string[]; + kerning: Record> = {}; - constructor([$height, header, ...rows]: string[]) { - const height = parseInt($height); - // parse header line to determine characters - // and their offsets & widths... - let prev: string | undefined; - for (let x = 0; x < header.length; x++) { - // skipping non-markers - if (header[x] !== "|") continue; - const id = header[x + 1]; - this.chars[id] = [x, 0]; - if (prev !== undefined) { - const char = this.chars[prev]; - char[1] = x; - } - prev = id; - x++; - } - if (prev !== undefined) { - this.chars[prev][1] = header.length; - } - // process kern pairs/table - // build index of character pairs & their kern values - // e.g. { L: { Y: -2 } } for a Y following an L will be shifted left - this.rows = rows.slice(0, height); - for (let pair of rows.slice(height)) { - if (!pair.length) continue; - const [[a, b], k] = pair.split(" "); - this.kerning = setIn(this.kerning, [a, b], parseInt(k)); - } - } + constructor([$height, header, ...rows]: string[]) { + const height = parseInt($height); + // parse header line to determine characters + // and their offsets & widths... + let prev: string | undefined; + for (let x = 0; x < header.length; x++) { + // skipping non-markers + if (header[x] !== "|") continue; + const id = header[x + 1]; + this.chars[id] = [x, 0]; + if (prev !== undefined) { + const char = this.chars[prev]; + char[1] = x; + } + prev = id; + x++; + } + if (prev !== undefined) { + this.chars[prev][1] = header.length; + } + // process kern pairs/table + // build index of character pairs & their kern values + // e.g. { L: { Y: -2 } } for a Y following an L will be shifted left + this.rows = rows.slice(0, height); + for (let pair of rows.slice(height)) { + if (!pair.length) continue; + const [[a, b], k] = pair.split(" "); + this.kerning = setIn(this.kerning, [a, b], parseInt(k)); + } + } - /** - * Returns string array for a single char, optionally with applied letter - * spacing. If the character is not defined, returns an array of empty - * strings. - * - * @param id - * @param spacing - */ - getChar(id: string, spacing = 0) { - const char = this.chars[id]; - const pad = spacing > 0 ? repeat(" ", spacing) : ""; - return char !== undefined - ? this.rows.map((row) => row.substring(char[0], char[1]) + pad) - : [...repeatedly(() => pad, this.rows.length)]; - } + /** + * Returns string array for a single char, optionally with applied letter + * spacing. If the character is not defined, returns an array of empty + * strings. + * + * @param id + * @param spacing + */ + getChar(id: string, spacing = 0) { + const char = this.chars[id]; + const pad = spacing > 0 ? repeat(" ", spacing) : ""; + return char !== undefined + ? this.rows.map((row) => row.substring(char[0], char[1]) + pad) + : [...repeatedly(() => pad, this.rows.length)]; + } - kernPair(acc: string[], a: string, b: string, spacing = 0, kern = true) { - const brows = this.getChar(b, spacing); - const k = kern ? (getIn(this.kerning, [a, b]) || 0) + spacing : spacing; - const merge = (a: string, b: string) => - b.replace(/./g, (x, i) => (x != " " ? x : a[i])); - if (k < 0) { - return acc.map((row, i) => - [ - row.substring(0, row.length + k), - merge( - row.substring(row.length + k), - brows[i].substring(0, -k) - ), - brows[i].substring(-k), - ].join("") - ); - } else { - return acc.map((row, i) => row + brows[i]); - } - } + kernPair(acc: string[], a: string, b: string, spacing = 0, kern = true) { + const brows = this.getChar(b, spacing); + const k = kern ? (getIn(this.kerning, [a, b]) || 0) + spacing : spacing; + const merge = (a: string, b: string) => + b.replace(/./g, (x, i) => (x != " " ? x : a[i])); + if (k < 0) { + return acc.map((row, i) => + [ + row.substring(0, row.length + k), + merge( + row.substring(row.length + k), + brows[i].substring(0, -k) + ), + brows[i].substring(-k), + ].join("") + ); + } else { + return acc.map((row, i) => row + brows[i]); + } + } - /** - * Similar to {@link Font.getChar}, but for an arbitrarily long string - * (single line) and kerning (by default). - * - * @param txt - * @param spacing - * @param kern - */ - getText([first, ...rest]: string, spacing = 0, kern = true) { - let res = this.getChar(first, spacing); - let prev = first; - for (let c of rest) { - res = this.kernPair(res, prev, c, spacing, kern); - prev = c; - } - return res; - } + /** + * Similar to {@link Font.getChar}, but for an arbitrarily long string + * (single line) and kerning (by default). + * + * @param txt + * @param spacing + * @param kern + */ + getText([first, ...rest]: string, spacing = 0, kern = true) { + let res = this.getChar(first, spacing); + let prev = first; + for (let c of rest) { + res = this.kernPair(res, prev, c, spacing, kern); + prev = c; + } + return res; + } } // instantiate fonts (see *.txt files in /src/fonts dir) const FONTS = { - atari: new Font(FONT_ATARI.split("\n")), - newgothic: new Font(FONT_GOTHIC.split("\n")), - small: new Font(FONT_SMALL.split("\n")), + atari: new Font(FONT_ATARI.split("\n")), + newgothic: new Font(FONT_GOTHIC.split("\n")), + small: new Font(FONT_SMALL.split("\n")), }; type FontID = Keys; @@ -139,58 +139,58 @@ const font = reactive("atari"); // reactive stream combinator const main = sync({ - src: { - msg, - spacing, - kerning, - font, - }, - // compute ASCII output - xform: map(({ msg, spacing, kerning, font }) => - FONTS[font].getText(msg, spacing, kerning).join("\n") - ), + src: { + msg, + spacing, + kerning, + font, + }, + // compute ASCII output + xform: map(({ msg, spacing, kerning, font }) => + FONTS[font].getText(msg, spacing, kerning).join("\n") + ), }); // helper component to wrap form elements const formParam = (el: [string, ...any[]]) => { - const id = el[1].id; - return div(".mb3", {}, label(".dib.w4.ttu", { for: id }, id), el); + const id = el[1].id; + return div(".mb3", {}, label(".dib.w4.ttu", { for: id }, id), el); }; // compile UI $compile( - div( - {}, - formParam( - inputText(".w5", { - id: "text", - autofocus: true, - oninput: $input(msg), - value: msg, - }) - ), - formParam( - staticDropdown(FONT_NAMES, font, { - attribs: { id: "font", class: "w5" }, - }) - ), - formParam( - inputRange(".w4", { - id: "spacing", - oninput: $inputNum(spacing), - min: 0, - max: 4, - step: 1, - value: spacing, - }) - ), - formParam( - checkbox({ - id: "kerning", - oninput: $inputCheckbox(kerning), - checked: kerning, - }) - ), - pre({}, main) - ) + div( + {}, + formParam( + inputText(".w5", { + id: "text", + autofocus: true, + oninput: $input(msg), + value: msg, + }) + ), + formParam( + staticDropdown(FONT_NAMES, font, { + attribs: { id: "font", class: "w5" }, + }) + ), + formParam( + inputRange(".w4", { + id: "spacing", + oninput: $inputNum(spacing), + min: 0, + max: 4, + step: 1, + value: spacing, + }) + ), + formParam( + checkbox({ + id: "kerning", + oninput: $inputCheckbox(kerning), + checked: kerning, + }) + ), + pre({}, main) + ) ).mount(document.getElementById("app")!); diff --git a/examples/big-font/tsconfig.json b/examples/big-font/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/big-font/tsconfig.json +++ b/examples/big-font/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/bitmap-font/package.json b/examples/bitmap-font/package.json index a52787a52b..29c9553ee6 100644 --- a/examples/bitmap-font/package.json +++ b/examples/bitmap-font/package.json @@ -1,42 +1,42 @@ { - "name": "@example/bitmap-font", - "private": true, - "version": "0.0.1", - "description": "Figlet-style bitmap font creation with transducers", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-binary": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom-components", - "rstream", - "transducers", - "transducers-binary", - "transducers-hdom" - ], - "screenshot": "examples/bitmap-font.gif" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/bitmap-font", + "private": true, + "version": "0.0.1", + "description": "Figlet-style bitmap font creation with transducers", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-binary": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom-components", + "rstream", + "transducers", + "transducers-binary", + "transducers-hdom" + ], + "screenshot": "examples/bitmap-font.gif" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/bitmap-font/src/font.ts b/examples/bitmap-font/src/font.ts index da3d15d9b3..ec5f0c824e 100644 --- a/examples/bitmap-font/src/font.ts +++ b/examples/bitmap-font/src/font.ts @@ -5,99 +5,99 @@ * added custom chars for ASCII completeness: `~{}| */ export const FONT = [ - [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], - [0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00], - [0x00, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00], - [0x00, 0x66, 0xff, 0x66, 0x66, 0xff, 0x66, 0x00], - [0x18, 0x3e, 0x60, 0x3c, 0x06, 0x7c, 0x18, 0x00], - [0x00, 0x66, 0x6c, 0x18, 0x30, 0x66, 0x46, 0x00], - [0x1c, 0x36, 0x1c, 0x38, 0x6f, 0x66, 0x3b, 0x00], - [0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00], - [0x00, 0x0e, 0x1c, 0x18, 0x18, 0x1c, 0x0e, 0x00], - [0x00, 0x70, 0x38, 0x18, 0x18, 0x38, 0x70, 0x00], - [0x00, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x00, 0x00], - [0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00], - [0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30], - [0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00], - [0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00], - [0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x40, 0x00], - [0x00, 0x3c, 0x66, 0x6e, 0x76, 0x66, 0x3c, 0x00], - [0x00, 0x18, 0x38, 0x18, 0x18, 0x18, 0x7e, 0x00], - [0x00, 0x3c, 0x66, 0x0c, 0x18, 0x30, 0x7e, 0x00], - [0x00, 0x7e, 0x0c, 0x18, 0x0c, 0x66, 0x3c, 0x00], - [0x00, 0x0c, 0x1c, 0x3c, 0x6c, 0x7e, 0x0c, 0x00], - [0x00, 0x7e, 0x60, 0x7c, 0x06, 0x66, 0x3c, 0x00], - [0x00, 0x3c, 0x60, 0x7c, 0x66, 0x66, 0x3c, 0x00], - [0x00, 0x7e, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x00], - [0x00, 0x3c, 0x66, 0x3c, 0x66, 0x66, 0x3c, 0x00], - [0x00, 0x3c, 0x66, 0x3e, 0x06, 0x0c, 0x38, 0x00], - [0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00], - [0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x30], - [0x06, 0x0c, 0x18, 0x30, 0x18, 0x0c, 0x06, 0x00], - [0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00], - [0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0x00], - [0x00, 0x3c, 0x66, 0x0c, 0x18, 0x00, 0x18, 0x00], - [0x00, 0x3c, 0x66, 0x6e, 0x6e, 0x60, 0x3e, 0x00], - [0x00, 0x18, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x00], - [0x00, 0x7c, 0x66, 0x7c, 0x66, 0x66, 0x7c, 0x00], - [0x00, 0x3c, 0x66, 0x60, 0x60, 0x66, 0x3c, 0x00], - [0x00, 0x78, 0x6c, 0x66, 0x66, 0x6c, 0x78, 0x00], - [0x00, 0x7e, 0x60, 0x7c, 0x60, 0x60, 0x7e, 0x00], - [0x00, 0x7e, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x00], - [0x00, 0x3e, 0x60, 0x60, 0x6e, 0x66, 0x3e, 0x00], - [0x00, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x00], - [0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00], - [0x00, 0x06, 0x06, 0x06, 0x06, 0x66, 0x3c, 0x00], - [0x00, 0x66, 0x6c, 0x78, 0x78, 0x6c, 0x66, 0x00], - [0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00], - [0x00, 0x63, 0x77, 0x7f, 0x6b, 0x63, 0x63, 0x00], - [0x00, 0x66, 0x76, 0x7e, 0x7e, 0x6e, 0x66, 0x00], - [0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00], - [0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x00], - [0x00, 0x3c, 0x66, 0x66, 0x66, 0x6c, 0x36, 0x00], - [0x00, 0x7c, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0x00], - [0x00, 0x3c, 0x60, 0x3c, 0x06, 0x06, 0x3c, 0x00], - [0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00], - [0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7e, 0x00], - [0x00, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00], - [0x00, 0x63, 0x63, 0x6b, 0x7f, 0x77, 0x63, 0x00], - [0x00, 0x66, 0x66, 0x3c, 0x3c, 0x66, 0x66, 0x00], - [0x00, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00], - [0x00, 0x7e, 0x0c, 0x18, 0x30, 0x60, 0x7e, 0x00], - [0x00, 0x1e, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00], - [0x00, 0x40, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00], - [0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00], - [0x00, 0x08, 0x1c, 0x36, 0x63, 0x00, 0x00, 0x00], - [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00], - [0x00, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00], - [0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x3e, 0x00], - [0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x7c, 0x00], - [0x00, 0x00, 0x3c, 0x60, 0x60, 0x60, 0x3c, 0x00], - [0x00, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x3e, 0x00], - [0x00, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00], - [0x00, 0x0e, 0x18, 0x3e, 0x18, 0x18, 0x18, 0x00], - [0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x7c], - [0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x00], - [0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x3c, 0x00], - [0x00, 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0x3c], - [0x00, 0x60, 0x60, 0x6c, 0x78, 0x6c, 0x66, 0x00], - [0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00], - [0x00, 0x00, 0x66, 0x7f, 0x7f, 0x6b, 0x63, 0x00], - [0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x00], - [0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00], - [0x00, 0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60], - [0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x06], - [0x00, 0x00, 0x7c, 0x66, 0x60, 0x60, 0x60, 0x00], - [0x00, 0x00, 0x3e, 0x60, 0x3c, 0x06, 0x7c, 0x00], - [0x00, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x0e, 0x00], - [0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00], - [0x00, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00], - [0x00, 0x00, 0x63, 0x6b, 0x7f, 0x3e, 0x36, 0x00], - [0x00, 0x00, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0x00], - [0x00, 0x00, 0x66, 0x66, 0x66, 0x3e, 0x0c, 0x78], - [0x00, 0x00, 0x7e, 0x0c, 0x18, 0x30, 0x7e, 0x00], - [0x06, 0x0c, 0x0c, 0x30, 0x0c, 0x0c, 0x06, 0x00], - [0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00], - [0x60, 0x30, 0x30, 0x0c, 0x30, 0x30, 0x60, 0x00], - [0x00, 0x00, 0x60, 0x99, 0x06, 0x00, 0x00, 0x00], + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + [0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00], + [0x00, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00], + [0x00, 0x66, 0xff, 0x66, 0x66, 0xff, 0x66, 0x00], + [0x18, 0x3e, 0x60, 0x3c, 0x06, 0x7c, 0x18, 0x00], + [0x00, 0x66, 0x6c, 0x18, 0x30, 0x66, 0x46, 0x00], + [0x1c, 0x36, 0x1c, 0x38, 0x6f, 0x66, 0x3b, 0x00], + [0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00], + [0x00, 0x0e, 0x1c, 0x18, 0x18, 0x1c, 0x0e, 0x00], + [0x00, 0x70, 0x38, 0x18, 0x18, 0x38, 0x70, 0x00], + [0x00, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x00, 0x00], + [0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00], + [0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30], + [0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00], + [0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00], + [0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x40, 0x00], + [0x00, 0x3c, 0x66, 0x6e, 0x76, 0x66, 0x3c, 0x00], + [0x00, 0x18, 0x38, 0x18, 0x18, 0x18, 0x7e, 0x00], + [0x00, 0x3c, 0x66, 0x0c, 0x18, 0x30, 0x7e, 0x00], + [0x00, 0x7e, 0x0c, 0x18, 0x0c, 0x66, 0x3c, 0x00], + [0x00, 0x0c, 0x1c, 0x3c, 0x6c, 0x7e, 0x0c, 0x00], + [0x00, 0x7e, 0x60, 0x7c, 0x06, 0x66, 0x3c, 0x00], + [0x00, 0x3c, 0x60, 0x7c, 0x66, 0x66, 0x3c, 0x00], + [0x00, 0x7e, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x00], + [0x00, 0x3c, 0x66, 0x3c, 0x66, 0x66, 0x3c, 0x00], + [0x00, 0x3c, 0x66, 0x3e, 0x06, 0x0c, 0x38, 0x00], + [0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00], + [0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x30], + [0x06, 0x0c, 0x18, 0x30, 0x18, 0x0c, 0x06, 0x00], + [0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00], + [0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0x00], + [0x00, 0x3c, 0x66, 0x0c, 0x18, 0x00, 0x18, 0x00], + [0x00, 0x3c, 0x66, 0x6e, 0x6e, 0x60, 0x3e, 0x00], + [0x00, 0x18, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x00], + [0x00, 0x7c, 0x66, 0x7c, 0x66, 0x66, 0x7c, 0x00], + [0x00, 0x3c, 0x66, 0x60, 0x60, 0x66, 0x3c, 0x00], + [0x00, 0x78, 0x6c, 0x66, 0x66, 0x6c, 0x78, 0x00], + [0x00, 0x7e, 0x60, 0x7c, 0x60, 0x60, 0x7e, 0x00], + [0x00, 0x7e, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x00], + [0x00, 0x3e, 0x60, 0x60, 0x6e, 0x66, 0x3e, 0x00], + [0x00, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x00], + [0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00], + [0x00, 0x06, 0x06, 0x06, 0x06, 0x66, 0x3c, 0x00], + [0x00, 0x66, 0x6c, 0x78, 0x78, 0x6c, 0x66, 0x00], + [0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00], + [0x00, 0x63, 0x77, 0x7f, 0x6b, 0x63, 0x63, 0x00], + [0x00, 0x66, 0x76, 0x7e, 0x7e, 0x6e, 0x66, 0x00], + [0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00], + [0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x00], + [0x00, 0x3c, 0x66, 0x66, 0x66, 0x6c, 0x36, 0x00], + [0x00, 0x7c, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0x00], + [0x00, 0x3c, 0x60, 0x3c, 0x06, 0x06, 0x3c, 0x00], + [0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00], + [0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7e, 0x00], + [0x00, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00], + [0x00, 0x63, 0x63, 0x6b, 0x7f, 0x77, 0x63, 0x00], + [0x00, 0x66, 0x66, 0x3c, 0x3c, 0x66, 0x66, 0x00], + [0x00, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00], + [0x00, 0x7e, 0x0c, 0x18, 0x30, 0x60, 0x7e, 0x00], + [0x00, 0x1e, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00], + [0x00, 0x40, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00], + [0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00], + [0x00, 0x08, 0x1c, 0x36, 0x63, 0x00, 0x00, 0x00], + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00], + [0x00, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00], + [0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x3e, 0x00], + [0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x7c, 0x00], + [0x00, 0x00, 0x3c, 0x60, 0x60, 0x60, 0x3c, 0x00], + [0x00, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x3e, 0x00], + [0x00, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00], + [0x00, 0x0e, 0x18, 0x3e, 0x18, 0x18, 0x18, 0x00], + [0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x7c], + [0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x00], + [0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x3c, 0x00], + [0x00, 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0x3c], + [0x00, 0x60, 0x60, 0x6c, 0x78, 0x6c, 0x66, 0x00], + [0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00], + [0x00, 0x00, 0x66, 0x7f, 0x7f, 0x6b, 0x63, 0x00], + [0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x00], + [0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00], + [0x00, 0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60], + [0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x06], + [0x00, 0x00, 0x7c, 0x66, 0x60, 0x60, 0x60, 0x00], + [0x00, 0x00, 0x3e, 0x60, 0x3c, 0x06, 0x7c, 0x00], + [0x00, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x0e, 0x00], + [0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00], + [0x00, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00], + [0x00, 0x00, 0x63, 0x6b, 0x7f, 0x3e, 0x36, 0x00], + [0x00, 0x00, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0x00], + [0x00, 0x00, 0x66, 0x66, 0x66, 0x3e, 0x0c, 0x78], + [0x00, 0x00, 0x7e, 0x0c, 0x18, 0x30, 0x7e, 0x00], + [0x06, 0x0c, 0x0c, 0x30, 0x0c, 0x0c, 0x06, 0x00], + [0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00], + [0x60, 0x30, 0x30, 0x0c, 0x30, 0x30, 0x60, 0x00], + [0x00, 0x00, 0x60, 0x99, 0x06, 0x00, 0x00, 0x00], ]; diff --git a/examples/bitmap-font/src/index.ts b/examples/bitmap-font/src/index.ts index 32e25c515c..91a1e7891f 100644 --- a/examples/bitmap-font/src/index.ts +++ b/examples/bitmap-font/src/index.ts @@ -18,88 +18,88 @@ import { zip } from "@thi.ng/transducers/zip"; import { FONT } from "./font"; const emitOnStream = (stream: ISubscriber) => (e: Event) => - stream.next((e.target).value); + stream.next((e.target).value); // retrieve font bytes for given char const lookupChar = (c: string) => - FONT[clamp(c.charCodeAt(0) - 32, 0, FONT.length - 1)]; + FONT[clamp(c.charCodeAt(0) - 32, 0, FONT.length - 1)]; // re-usable transducer const xfJoin = map((x: string[]) => x.join("")); // higher order transducer to transform single char from string const xfChar = (i: number, on: string, off: string) => - comp( - // use byte `i` lane from current row - pluck(i), - // split into bits - bits(8), - // transform each bit - map((x) => (x ? on : off)), - // re-group - partition(8), - // build string - xfJoin - ); + comp( + // use byte `i` lane from current row + pluck(i), + // split into bits + bits(8), + // transform each bit + map((x) => (x ? on : off)), + // re-group + partition(8), + // build string + xfJoin + ); // transform entire string const banner = ({ input, on, off }: IObjectOf) => - transduce( - comp( - // dynamically create `xfChar` transducers for each char - // and run them in parallel via `multiplex()` - // @ts-ignore - multiplex.apply(null, [ - ...map((i) => xfChar(i, on, off), range(input.length)), - ]), - // then join the results for each line - xfJoin - ), - // use `str()` reducer to build string result - str("\n"), - // convert input string into stream of row-major bitmap font tuples - // @ts-ignore - zip.apply(null, [...map(lookupChar, input || " ")]) - ); + transduce( + comp( + // dynamically create `xfChar` transducers for each char + // and run them in parallel via `multiplex()` + // @ts-ignore + multiplex.apply(null, [ + ...map((i) => xfChar(i, on, off), range(input.length)), + ]), + // then join the results for each line + xfJoin + ), + // use `str()` reducer to build string result + str("\n"), + // convert input string into stream of row-major bitmap font tuples + // @ts-ignore + zip.apply(null, [...map(lookupChar, input || " ")]) + ); // dropdown menu for on/off bits const charSelector = (stream: Stream) => [ - dropdown, - { - class: "ml3", - onchange: emitOnStream(stream), - }, - [ - ["#", "#"], - ["@", "@"], - ["*", "*"], - ["X", "X"], - ["/", "/"], - ["=", "="], - ["-", "-"], - ["^", "^"], - [".", "."], - [" ", "space"], - ], - stream.deref(), + dropdown, + { + class: "ml3", + onchange: emitOnStream(stream), + }, + [ + ["#", "#"], + ["@", "@"], + ["*", "*"], + ["X", "X"], + ["/", "/"], + ["=", "="], + ["-", "-"], + ["^", "^"], + [".", "."], + [" ", "space"], + ], + stream.deref(), ]; // main UI root component const app = ({ raw, result }: any) => [ - "div", - [ - "div", - [ - "input", - { - oninput: emitOnStream(input), - value: raw, - }, - ], - charSelector(on), - charSelector(off), - ], - ["pre.code.w-100.pa2.overflow-x-auto.bg-washed-yellow", result], + "div", + [ + "div", + [ + "input", + { + oninput: emitOnStream(input), + value: raw, + }, + ], + charSelector(on), + charSelector(off), + ], + ["pre.code.w-100.pa2.overflow-x-auto.bg-washed-yellow", result], ]; // reactive stream setup diff --git a/examples/bitmap-font/tsconfig.json b/examples/bitmap-font/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/bitmap-font/tsconfig.json +++ b/examples/bitmap-font/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/canvas-dial/package.json b/examples/canvas-dial/package.json index 61a5a2037b..d5aa1e7851 100644 --- a/examples/canvas-dial/package.json +++ b/examples/canvas-dial/package.json @@ -1,46 +1,46 @@ { - "name": "@example/canvas-dial", - "private": true, - "version": "0.0.1", - "description": "Canvas based dial widget", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/arrays": "workspace:^", - "@thi.ng/checks": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom-components", - "rstream", - "rstream-gestures", - "transducers-hdom", - "vectors" - ], - "screenshot": "examples/canvas-dial.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/canvas-dial", + "private": true, + "version": "0.0.1", + "description": "Canvas based dial widget", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/arrays": "workspace:^", + "@thi.ng/checks": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom-components", + "rstream", + "rstream-gestures", + "transducers-hdom", + "vectors" + ], + "screenshot": "examples/canvas-dial.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/canvas-dial/src/dial.ts b/examples/canvas-dial/src/dial.ts index 774b628b28..261ca2ef9f 100644 --- a/examples/canvas-dial/src/dial.ts +++ b/examples/canvas-dial/src/dial.ts @@ -12,96 +12,96 @@ import { sub2 } from "@thi.ng/vectors/sub"; * Dial component options. */ export interface DialOpts { - /** - * Dial center X (normalized) - * Default: 0.5 - */ - cx: number; - /** - * Dial center Y (normalized) - * Default: 0.5 - */ - cy: number; - /** - * Inner radius (normalized) - * Default: 0.5 - */ - r1: number; - /** - * Outer radius (normalized) - * Default: 0.99 - */ - r2: number; - /** - * Dial min value - * Default: 0 - */ - min: number; - /** - * Dial min value - * Default: 1 - */ - max: number; - /** - * Orientation / start angle (in radians) - * Default: PI/2 - */ - base: number; - /** - * Angular gap between min / max values - * Default: PI/10 - */ - gap: number; - /** - * Fill color (or gradient) for value area - * Default: black - */ - color: string | GradientDef; - /** - * Fill color (or gradient) for background ring - * Default: rgba(0,0,0,0.1) - */ - bgColor: string | GradientDef; - /** - * Label formatter. No label will be displayed, if missing. - */ - label: Fn; - /** - * Label Y offset from `cy` - * Default: 0 - */ - labelYOffset: number; - /** - * Default: black - */ - labelColor: string; - /** - * Label font CSS string - * Default (10px sans-serif) - */ - font: string; - /** - * Event callback (receives new dial value) - */ - onchange: Fn; + /** + * Dial center X (normalized) + * Default: 0.5 + */ + cx: number; + /** + * Dial center Y (normalized) + * Default: 0.5 + */ + cy: number; + /** + * Inner radius (normalized) + * Default: 0.5 + */ + r1: number; + /** + * Outer radius (normalized) + * Default: 0.99 + */ + r2: number; + /** + * Dial min value + * Default: 0 + */ + min: number; + /** + * Dial min value + * Default: 1 + */ + max: number; + /** + * Orientation / start angle (in radians) + * Default: PI/2 + */ + base: number; + /** + * Angular gap between min / max values + * Default: PI/10 + */ + gap: number; + /** + * Fill color (or gradient) for value area + * Default: black + */ + color: string | GradientDef; + /** + * Fill color (or gradient) for background ring + * Default: rgba(0,0,0,0.1) + */ + bgColor: string | GradientDef; + /** + * Label formatter. No label will be displayed, if missing. + */ + label: Fn; + /** + * Label Y offset from `cy` + * Default: 0 + */ + labelYOffset: number; + /** + * Default: black + */ + labelColor: string; + /** + * Label font CSS string + * Default (10px sans-serif) + */ + font: string; + /** + * Event callback (receives new dial value) + */ + onchange: Fn; } /** * Multi-stop linear gradient definition. */ export interface GradientDef { - /** - * Start point (normalized) - */ - from: number[]; - /** - * End point (normalized) - */ - to: number[]; - /** - * Color stops (position normalized) - */ - stops: [number, string][]; + /** + * Start point (normalized) + */ + from: number[]; + /** + * End point (normalized) + */ + to: number[]; + /** + * Color stops (position normalized) + */ + stops: [number, string][]; } const PI = Math.PI; @@ -110,124 +110,124 @@ const TAU = 2 * PI; /** * HOF component. Returns pre-configured dial component. * - * @param opts - + * @param opts - */ export const dial = (_opts: Partial) => { - const opts = { - cx: 0.5, - cy: 0.5, - r1: 0.5, - r2: 0.99, - min: 0, - max: 1, - base: PI * 0.5, - gap: PI * 0.1, - color: "black", - bgColor: "rgba(0,0,0,0.1)", - labelColor: "black", - labelYOffset: 0, - font: "10px sans-serif", - ..._opts, - }; - let events: ISubscription; - let cx: number, cy: number; - const startTheta = opts.base + opts.gap / 2; + const opts = { + cx: 0.5, + cy: 0.5, + r1: 0.5, + r2: 0.99, + min: 0, + max: 1, + base: PI * 0.5, + gap: PI * 0.1, + color: "black", + bgColor: "rgba(0,0,0,0.1)", + labelColor: "black", + labelYOffset: 0, + font: "10px sans-serif", + ..._opts, + }; + let events: ISubscription; + let cx: number, cy: number; + const startTheta = opts.base + opts.gap / 2; - const drawRing = ( - ctx: CanvasRenderingContext2D, - amount: number, - col: any - ) => { - const endTheta = startTheta + (TAU - opts.gap) * amount; - ctx.fillStyle = col; - ctx.beginPath(); - ctx.arc(cx, cy, opts.r2, startTheta, endTheta, false); - ctx.arc(cx, cy, opts.r1, endTheta, startTheta, true); - ctx.fill(); - }; + const drawRing = ( + ctx: CanvasRenderingContext2D, + amount: number, + col: any + ) => { + const endTheta = startTheta + (TAU - opts.gap) * amount; + ctx.fillStyle = col; + ctx.beginPath(); + ctx.arc(cx, cy, opts.r2, startTheta, endTheta, false); + ctx.arc(cx, cy, opts.r1, endTheta, startTheta, true); + ctx.fill(); + }; - const makeGradient = ( - el: HTMLCanvasElement, - ctx: CanvasRenderingContext2D, - def: GradientDef - ) => { - const g = ctx.createLinearGradient( - def.from[0] * el.width, - def.from[1] * el.height, - def.to[0] * el.width, - def.to[1] * el.height - ); - def.stops.forEach(([pos, col]) => g.addColorStop(pos, col)); - return g; - }; + const makeGradient = ( + el: HTMLCanvasElement, + ctx: CanvasRenderingContext2D, + def: GradientDef + ) => { + const g = ctx.createLinearGradient( + def.from[0] * el.width, + def.from[1] * el.height, + def.to[0] * el.width, + def.to[1] * el.height + ); + def.stops.forEach(([pos, col]) => g.addColorStop(pos, col)); + return g; + }; - return canvas2D({ - init: (el, ctx) => { - cx = el.width * opts.cx; - cy = el.height * opts.cy; - ctx.strokeStyle = "none"; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.font = opts.font; - const scale = Math.min(cx, cy); - opts.r1 *= scale; - opts.r2 *= scale; - if (!isString(opts.bgColor)) { - opts.bgColor = ( - makeGradient(el, ctx, opts.bgColor) - ); - } - if (!isString(opts.color)) { - opts.color = ( - makeGradient(el, ctx, opts.color) - ); - } - if (opts.onchange) { - // add interaction event stream (mouse & touch) - // configure stream to return scaled coords (devicePixelRatio) - events = gestureStream(el, { scale: true }).subscribe({ - next: (e) => { - if (e.type === "start" || e.type === "drag") { - let theta = - heading(sub2([], e.pos, [cx, cy])) - startTheta; - if (theta < 0) theta += TAU; - theta %= TAU; - opts.onchange.call( - null, - fitClamped( - Math.min(theta / (TAU - opts.gap)), - 0, - 1, - opts.min, - opts.max - ) - ); - } - }, - }); - } - }, + return canvas2D({ + init: (el, ctx) => { + cx = el.width * opts.cx; + cy = el.height * opts.cy; + ctx.strokeStyle = "none"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.font = opts.font; + const scale = Math.min(cx, cy); + opts.r1 *= scale; + opts.r2 *= scale; + if (!isString(opts.bgColor)) { + opts.bgColor = ( + makeGradient(el, ctx, opts.bgColor) + ); + } + if (!isString(opts.color)) { + opts.color = ( + makeGradient(el, ctx, opts.color) + ); + } + if (opts.onchange) { + // add interaction event stream (mouse & touch) + // configure stream to return scaled coords (devicePixelRatio) + events = gestureStream(el, { scale: true }).subscribe({ + next: (e) => { + if (e.type === "start" || e.type === "drag") { + let theta = + heading(sub2([], e.pos, [cx, cy])) - startTheta; + if (theta < 0) theta += TAU; + theta %= TAU; + opts.onchange.call( + null, + fitClamped( + Math.min(theta / (TAU - opts.gap)), + 0, + 1, + opts.min, + opts.max + ) + ); + } + }, + }); + } + }, - // clean up gesture event stream when component is released - release: () => { - events && events.unsubscribe(); - }, + // clean up gesture event stream when component is released + release: () => { + events && events.unsubscribe(); + }, - // there're a few args we're not interested in here, so we use var args instead. - // the dial value is the last arg - update: (el, ctx, ...args: any[]) => { - const val = peek(args); - ctx.clearRect(0, 0, el.width, el.height); - drawRing(ctx, 1, opts.bgColor); - drawRing( - ctx, - fitClamped(val, opts.min, opts.max, 0.005, 1), - opts.color - ); - if (opts.label) { - ctx.fillStyle = opts.labelColor; - ctx.fillText(opts.label(val), cx, cy + opts.labelYOffset); - } - }, - }); + // there're a few args we're not interested in here, so we use var args instead. + // the dial value is the last arg + update: (el, ctx, ...args: any[]) => { + const val = peek(args); + ctx.clearRect(0, 0, el.width, el.height); + drawRing(ctx, 1, opts.bgColor); + drawRing( + ctx, + fitClamped(val, opts.min, opts.max, 0.005, 1), + opts.color + ); + if (opts.label) { + ctx.fillStyle = opts.labelColor; + ctx.fillText(opts.label(val), cx, cy + opts.labelYOffset); + } + }, + }); }; diff --git a/examples/canvas-dial/src/index.ts b/examples/canvas-dial/src/index.ts index 61741ceed6..9e318dc6a7 100644 --- a/examples/canvas-dial/src/index.ts +++ b/examples/canvas-dial/src/index.ts @@ -8,17 +8,17 @@ import { dial } from "./dial"; // hdom context & app state object export const ctx = { - // streams to hold dial values - streams: { - a: reactive(0.66), - b: reactive(1), - c: reactive(0.75), - }, - // component styling - ui: { - root: { class: "vh-100 flex justify-center items-center" }, - dial: { width: 100, height: 100, class: "pointer ma1" }, - }, + // streams to hold dial values + streams: { + a: reactive(0.66), + b: reactive(1), + c: reactive(0.75), + }, + // component styling + ui: { + root: { class: "vh-100 flex justify-center items-center" }, + dial: { width: 100, height: 100, class: "pointer ma1" }, + }, }; /** @@ -31,63 +31,63 @@ export const ctx = { * be pre-initialized before use. */ const app = () => { - const dialA = dial({ - r1: 0.5, - color: { - from: [0, 0], - to: [1, 1], - stops: [ - [0, "#075"], - [1, "#6f9"], - ], - }, - font: "20px Menlo", - label: (x) => percent(0)(x), - onchange: (x) => ctx.streams.a.next(x), - }); - const dialB = dial({ - r1: 0.66, - base: -Math.PI / 2, - gap: Math.PI / 2, - color: { - from: [0, 0], - to: [1, 0.75], - stops: [ - [0, "#00f"], - [0.5, "#f60"], - [1, "#ff0"], - ], - }, - font: "20px Menlo", - label: (x) => percent(1)(x), - onchange: (x) => ctx.streams.b.next(x), - }); - const dialC = dial({ - r1: 0.75, - gap: Math.PI, - color: { - from: [0, 0], - to: [1, 0], - stops: [ - [0, "#407"], - [1, "#09f"], - ], - }, - font: "20px Menlo", - label: (x) => percent(2)(x), - onchange: (x) => ctx.streams.c.next(x), - }); - return ({ a, b, c }: any) => [ - "div", - ctx.ui.root, - [dialA, ctx.ui.dial, a], - [dialB, ctx.ui.dial, b], - [dialC, ctx.ui.dial, c], - ]; + const dialA = dial({ + r1: 0.5, + color: { + from: [0, 0], + to: [1, 1], + stops: [ + [0, "#075"], + [1, "#6f9"], + ], + }, + font: "20px Menlo", + label: (x) => percent(0)(x), + onchange: (x) => ctx.streams.a.next(x), + }); + const dialB = dial({ + r1: 0.66, + base: -Math.PI / 2, + gap: Math.PI / 2, + color: { + from: [0, 0], + to: [1, 0.75], + stops: [ + [0, "#00f"], + [0.5, "#f60"], + [1, "#ff0"], + ], + }, + font: "20px Menlo", + label: (x) => percent(1)(x), + onchange: (x) => ctx.streams.b.next(x), + }); + const dialC = dial({ + r1: 0.75, + gap: Math.PI, + color: { + from: [0, 0], + to: [1, 0], + stops: [ + [0, "#407"], + [1, "#09f"], + ], + }, + font: "20px Menlo", + label: (x) => percent(2)(x), + onchange: (x) => ctx.streams.c.next(x), + }); + return ({ a, b, c }: any) => [ + "div", + ctx.ui.root, + [dialA, ctx.ui.dial, a], + [dialB, ctx.ui.dial, b], + [dialC, ctx.ui.dial, c], + ]; }; // stream combinator & reactive DOM update sync({ - src: ctx.streams, - xform: comp(map(app()), updateDOM()), + src: ctx.streams, + xform: comp(map(app()), updateDOM()), }); diff --git a/examples/canvas-dial/tsconfig.json b/examples/canvas-dial/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/canvas-dial/tsconfig.json +++ b/examples/canvas-dial/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/cellular-automata/package.json b/examples/cellular-automata/package.json index 3dca9972d3..936083b3c8 100644 --- a/examples/cellular-automata/package.json +++ b/examples/cellular-automata/package.json @@ -1,38 +1,38 @@ { - "name": "@example/cellular-automata", - "private": true, - "version": "0.0.1", - "description": "2D transducer based cellular automata", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-binary": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom", - "hdom-components", - "transducers", - "transducers-binary" - ], - "screenshot": "examples/cellular-automata.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/cellular-automata", + "private": true, + "version": "0.0.1", + "description": "2D transducer based cellular automata", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-binary": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom", + "hdom-components", + "transducers", + "transducers-binary" + ], + "screenshot": "examples/cellular-automata.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/cellular-automata/src/index.ts b/examples/cellular-automata/src/index.ts index 8843440a1a..5c02286552 100644 --- a/examples/cellular-automata/src/index.ts +++ b/examples/cellular-automata/src/index.ts @@ -1,6 +1,6 @@ import { - dropdown, - type DropDownOption, + dropdown, + type DropDownOption, } from "@thi.ng/hdom-components/dropdown"; import { start } from "@thi.ng/hdom/start"; import { bits } from "@thi.ng/transducers-binary/bits"; @@ -20,14 +20,14 @@ const W = 128; const H = 48; const presets: DropDownOption[] = [ - ["", "custom"], - ["000100000001100000", "conway"], - ["000100000001110000", "maze #1"], - ["000111111000001111", "maze #2"], - ["000001111111111110", "dots"], - ["000101111000001111", "growth"], - ["000001111000011111", "organic"], - ["000010011000011111", "angular"], + ["", "custom"], + ["000100000001100000", "conway"], + ["000100000001110000", "maze #1"], + ["000111111000001111", "maze #2"], + ["000001111111111110", "dots"], + ["000101111000001111", "growth"], + ["000001111000011111", "organic"], + ["000010011000011111", "angular"], ]; // container for cell states @@ -43,31 +43,31 @@ const setHash = () => (location.hash = rules.join("")); // build transducer to parse rules from string (e.g. location hash or preset) // (an older version used a preset format w/ "-" to separate rule groups) const parseRules = step( - comp( - map((x: string) => parseInt(x.replace("-", ""), 2)), - bits(18) - ) + comp( + map((x: string) => parseInt(x.replace("-", ""), 2)), + bits(18) + ) ); const applyRules = (raw: string) => { - if (raw.length >= 18) { - rules = parseRules(raw); - randomizeGrid(); - setHash(); - } + if (raw.length >= 18) { + rules = parseRules(raw); + randomizeGrid(); + setHash(); + } }; // create random bit sequence w/ ones appearing in given probability const randomSeq = (num: number, prob = 0.5) => [ - ...repeatedly(() => (Math.random() < prob ? 1 : 0), num), + ...repeatedly(() => (Math.random() < prob ? 1 : 0), num), ]; const randomizeGrid = (prob = 0.5) => (grid = randomSeq(W * H, prob)); const randomizeRules = () => { - rules = randomSeq(18); - randomizeGrid(); - setHash(); + rules = randomSeq(18); + randomizeGrid(); + setHash(); }; // apply convolution & CA rules (in basically 2 lines of code, i.e. the transducer part!!) @@ -76,89 +76,89 @@ const randomizeRules = () => { // produce a tuple of `[neighbor-count, orig-cell-value]` // this tuple is then used to lookup the next cell state using the current rule set export const convolve = ( - src: number[], - rules: number[], - width: number, - height: number, - rstride = 9, - wrap = true + src: number[], + rules: number[], + width: number, + height: number, + rstride = 9, + wrap = true ) => - transduce( - comp( - convolve2d({ src, width, height, kernel, wrap }), - mapIndexed((i, x) => rules[x + src[i] * rstride]) - ), - push(), - range2d(width, height) - ); + transduce( + comp( + convolve2d({ src, width, height, kernel, wrap }), + mapIndexed((i, x) => rules[x + src[i] * rstride]) + ), + push(), + range2d(width, height) + ); // format grid values as string const format = (src: number[], width: number, fill = "\u2588", empty = " ") => - transduce( - comp( - map((x: number) => (x ? fill : empty)), - partition(width), - map((x) => x.join("")) - ), - str("\n"), - src - ); + transduce( + comp( + map((x: number) => (x ? fill : empty)), + partition(width), + map((x) => x.join("")) + ), + str("\n"), + src + ); // event handler for rule edits const setRule = (i: number, j: number, s: boolean, rstride = 9) => { - rules[i * rstride + j] = s ? 1 : 0; - setHash(); + rules[i * rstride + j] = s ? 1 : 0; + setHash(); }; // single checkbox component const checkbox = (x: number, onchange: EventListener) => [ - "input", - { type: "checkbox", checked: !!x, onchange }, + "input", + { type: "checkbox", checked: !!x, onchange }, ]; // component for single CA rule group (alive / dead FSM) const ruleBoxes = (prefix: string, i: number, rstride = 9) => [ - "div", - ["label", prefix], - ...rules - .slice(i * rstride, (i + 1) * rstride) - .map((rule, j) => - checkbox(rule, (e) => - setRule(i, j, (e.target).checked) - ) - ), + "div", + ["label", prefix], + ...rules + .slice(i * rstride, (i + 1) * rstride) + .map((rule, j) => + checkbox(rule, (e) => + setRule(i, j, (e.target).checked) + ) + ), ]; const isPreset = (id: string) => presets.findIndex((x) => x[0] === id) !== -1; // Use Conway CA default state rules [[dead], [alive]] if no preset present in hash applyRules( - location.hash.length > 18 - ? location.hash.substring(1) - : presets[1][0] + location.hash.length > 18 + ? location.hash.substring(1) + : presets[1][0] ); // define & start main app component start(() => { - const id = location.hash.substring(1); - return [ - "div", - ruleBoxes("birth", 0), - ruleBoxes("survive", 1), - [ - "div", - ["button", { onclick: () => randomizeRules() }, "randomize rules"], - ["button", { onclick: () => randomizeGrid() }, "reset grid"], - [ - dropdown, - { - onchange: (e: Event) => - applyRules((e.target).value), - }, - presets, - isPreset(id) ? id : "", - ], - ], - ["pre", format((grid = convolve(grid, rules, W, H)), W)], - ]; + const id = location.hash.substring(1); + return [ + "div", + ruleBoxes("birth", 0), + ruleBoxes("survive", 1), + [ + "div", + ["button", { onclick: () => randomizeRules() }, "randomize rules"], + ["button", { onclick: () => randomizeGrid() }, "reset grid"], + [ + dropdown, + { + onchange: (e: Event) => + applyRules((e.target).value), + }, + presets, + isPreset(id) ? id : "", + ], + ], + ["pre", format((grid = convolve(grid, rules, W, H)), W)], + ]; }); diff --git a/examples/cellular-automata/tsconfig.json b/examples/cellular-automata/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/cellular-automata/tsconfig.json +++ b/examples/cellular-automata/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/color-themes/package.json b/examples/color-themes/package.json index 41b94d6a71..847458ab65 100644 --- a/examples/color-themes/package.json +++ b/examples/color-themes/package.json @@ -1,50 +1,50 @@ { - "name": "@example/color-themes", - "private": true, - "version": "0.0.1", - "description": "Probabilistic color theme generator", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/checks": "workspace:^", - "@thi.ng/color": "workspace:^", - "@thi.ng/dl-asset": "workspace:^", - "@thi.ng/hiccup-html": "workspace:^", - "@thi.ng/hiccup-svg": "workspace:^", - "@thi.ng/random": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/rdom-components": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-dot": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "color", - "dl-asset", - "hiccup-html", - "hiccup-svg", - "random", - "rdom", - "rdom-components", - "rstream" - ], - "screenshot": "examples/color-themes.png" - } + "name": "@example/color-themes", + "private": true, + "version": "0.0.1", + "description": "Probabilistic color theme generator", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/checks": "workspace:^", + "@thi.ng/color": "workspace:^", + "@thi.ng/dl-asset": "workspace:^", + "@thi.ng/hiccup-html": "workspace:^", + "@thi.ng/hiccup-svg": "workspace:^", + "@thi.ng/random": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/rdom-components": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-dot": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "color", + "dl-asset", + "hiccup-html", + "hiccup-svg", + "random", + "rdom", + "rdom-components", + "rstream" + ], + "screenshot": "examples/color-themes.png" + } } diff --git a/examples/color-themes/src/api.ts b/examples/color-themes/src/api.ts index 4fa126f1fe..7d1e9874e0 100644 --- a/examples/color-themes/src/api.ts +++ b/examples/color-themes/src/api.ts @@ -1,21 +1,21 @@ import type { IObjectOf } from "@thi.ng/api"; import type { - ColorRangePreset, - ColorThemePart, - ReadonlyColor, + ColorRangePreset, + ColorThemePart, + ReadonlyColor, } from "@thi.ng/color"; import { COLOR_RANGES } from "@thi.ng/color/color-range"; export interface MainInputs { - parts: IObjectOf; - num: number; - variance: number; - seed: number; - sorted: boolean; + parts: IObjectOf; + num: number; + variance: number; + seed: number; + sorted: boolean; } export interface MainOutputs extends MainInputs { - colors: ReadonlyColor[]; + colors: ReadonlyColor[]; } // pre-sort range preset IDs for dropdown menus diff --git a/examples/color-themes/src/index.ts b/examples/color-themes/src/index.ts index d496cda507..a7c0681acd 100644 --- a/examples/color-themes/src/index.ts +++ b/examples/color-themes/src/index.ts @@ -4,11 +4,11 @@ import { lch, type LCH } from "@thi.ng/color/lch/lch"; import { swatchesH } from "@thi.ng/color/swatches"; import { div } from "@thi.ng/hiccup-html/blocks"; import { - button, - checkbox, - inputColor, - inputRange, - option, + button, + checkbox, + inputColor, + inputRange, + option, } from "@thi.ng/hiccup-html/forms"; import { span } from "@thi.ng/hiccup-html/inline"; import { datalist } from "@thi.ng/hiccup-html/lists"; @@ -22,15 +22,15 @@ import { $replace } from "@thi.ng/rdom/replace"; import { reactive } from "@thi.ng/rstream/stream"; import { type MainOutputs, RANGE_IDs } from "./api"; import { - debouncedParts, - downloadTrigger, - main, - num, - parts, - randomizeThemeParts, - seed, - sorted, - variance, + debouncedParts, + downloadTrigger, + main, + num, + parts, + randomizeThemeParts, + seed, + sorted, + variance, } from "./state"; /** @@ -38,147 +38,147 @@ import { * preset, base color, weight). */ const themePartControls = ([id, part]: [string, ColorThemePart]) => { - const stream = parts[id]; - return div( - ".grid.mb3", - {}, - staticDropdown(RANGE_IDs, reactive(part.range), { - attribs: { - title: "color range preset", - oninput: (e) => - stream.next({ - ...part, - range: ( - (e.target).value - ), - }), - }, - }), - inputColor({ - value: css(part.base), - title: "base color", - onchange: (e) => - stream.next({ - ...part, - base: lch((e.target).value), - }), - }), - inputRange({ - min: 0, - max: 1, - step: 0.01, - value: part.weight, - title: "weight", - onchange: (e) => - stream.next({ - ...part, - weight: parseFloat((e.target).value), - }), - }) - ); + const stream = parts[id]; + return div( + ".grid.mb3", + {}, + staticDropdown(RANGE_IDs, reactive(part.range), { + attribs: { + title: "color range preset", + oninput: (e) => + stream.next({ + ...part, + range: ( + (e.target).value + ), + }), + }, + }), + inputColor({ + value: css(part.base), + title: "base color", + onchange: (e) => + stream.next({ + ...part, + base: lch((e.target).value), + }), + }), + inputRange({ + min: 0, + max: 1, + step: 0.01, + value: part.weight, + title: "weight", + onchange: (e) => + stream.next({ + ...part, + weight: parseFloat((e.target).value), + }), + }) + ); }; /** * Simple 2-column component wrapper for given label & body component. * - * @param label - - * @param body - + * @param label - + * @param body - */ const control = (label: string, ...body: ComponentLike[]) => - div(".grid2.mb3", {}, span({}, label), ...body); + div(".grid2.mb3", {}, span({}, label), ...body); /** * SVG component wrapper for color swatches. * - * @param state - + * @param state - */ const svgSwatches = ({ colors, num }: MainOutputs) => svg( - { - width: "100vw", - height: "100vh", - viewBox: `0 0 ${num * 5} 100`, - preserveAspectRatio: "none", - convert: true, - }, - swatchesH(colors, 5, 100) - ); + { + width: "100vw", + height: "100vh", + viewBox: `0 0 ${num * 5} 100`, + preserveAspectRatio: "none", + convert: true, + }, + swatchesH(colors, 5, 100) + ); // main UI $compile( - div( - {}, - // color swatches - $replace(main.map(svgSwatches)), - // theme controls in HUD UI - div( - ".z-1.fixed.top-0.left-0.bg-white-80.ma3-m.ma3-l.pa3.w-100.w-50-m.w-33-l", - {}, - // list of controls for each theme part - $list<[string, ColorThemePart]>( - debouncedParts.map((parts) => Object.entries(parts), { - id: "swatches", - }), - "div", - {}, - themePartControls - ), - // global controls: num swatches, variance, random seed, sorting - control( - "num swatches", - inputRange({ - min: 8, - max: 256, - list: "pow2", - value: num, - oninput: $inputNum(num), - }), - datalist( - "#pow2", - {}, - ...[8, 16, 32, 64, 128, 256].map((x) => - option({}, String(x)) - ) - ) - ), - control( - "variance", - inputRange({ - min: 0, - max: 0.2, - step: 0.005, - value: variance, - oninput: $inputNum(variance), - }) - ), - control( - "random seed", - inputRange({ - min: 0, - max: 1 << 30, - value: seed, - oninput: $inputNum(seed), - }) - ), - control( - "sort", - checkbox({ - checked: sorted, - onchange: $inputCheckbox(sorted), - }) - ), - button( - ".bg-black.white.bn.pa2.mr3", - { onclick: randomizeThemeParts }, - "randomize" - ), - button( - ".bg-black.white.bn.pa2", - { - onclick: $inputTrigger(downloadTrigger), - }, - "download (ACT)" - ) - ) - ) + div( + {}, + // color swatches + $replace(main.map(svgSwatches)), + // theme controls in HUD UI + div( + ".z-1.fixed.top-0.left-0.bg-white-80.ma3-m.ma3-l.pa3.w-100.w-50-m.w-33-l", + {}, + // list of controls for each theme part + $list<[string, ColorThemePart]>( + debouncedParts.map((parts) => Object.entries(parts), { + id: "swatches", + }), + "div", + {}, + themePartControls + ), + // global controls: num swatches, variance, random seed, sorting + control( + "num swatches", + inputRange({ + min: 8, + max: 256, + list: "pow2", + value: num, + oninput: $inputNum(num), + }), + datalist( + "#pow2", + {}, + ...[8, 16, 32, 64, 128, 256].map((x) => + option({}, String(x)) + ) + ) + ), + control( + "variance", + inputRange({ + min: 0, + max: 0.2, + step: 0.005, + value: variance, + oninput: $inputNum(variance), + }) + ), + control( + "random seed", + inputRange({ + min: 0, + max: 1 << 30, + value: seed, + oninput: $inputNum(seed), + }) + ), + control( + "sort", + checkbox({ + checked: sorted, + onchange: $inputCheckbox(sorted), + }) + ), + button( + ".bg-black.white.bn.pa2.mr3", + { onclick: randomizeThemeParts }, + "randomize" + ), + button( + ".bg-black.white.bn.pa2", + { + onclick: $inputTrigger(downloadTrigger), + }, + "download (ACT)" + ) + ) + ) ).mount(document.getElementById("app")!); diff --git a/examples/color-themes/src/palette.ts b/examples/color-themes/src/palette.ts index 1af6426be9..5f2fddf9ad 100644 --- a/examples/color-themes/src/palette.ts +++ b/examples/color-themes/src/palette.ts @@ -4,20 +4,20 @@ import { srgbIntArgb32 } from "@thi.ng/color/srgb/srgb-int"; import { downloadWithMime } from "@thi.ng/dl-asset/raw"; export const downloadACT = (colors: ReadonlyColor[]) => { - const num = colors.length; - const buf = new Uint8Array(772); - // http://www.selapa.net/swatches/colors/fileformats.php - // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 - buf[768] = num >> 8; - buf[769] = num & 0xff; - // palette index 0 reserved for transparent slot - for (let i = 0, j = 3; i < num; i++, j += 3) { - const rgb = srgbIntArgb32(srgb(colors[i])); - buf[j] = (rgb >> 16) & 0xff; - buf[j + 1] = (rgb >> 8) & 0xff; - buf[j + 2] = rgb & 0xff; - } - downloadWithMime("palette.act", buf, { - mime: "application/octet-stream", - }); + const num = colors.length; + const buf = new Uint8Array(772); + // http://www.selapa.net/swatches/colors/fileformats.php + // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 + buf[768] = num >> 8; + buf[769] = num & 0xff; + // palette index 0 reserved for transparent slot + for (let i = 0, j = 3; i < num; i++, j += 3) { + const rgb = srgbIntArgb32(srgb(colors[i])); + buf[j] = (rgb >> 16) & 0xff; + buf[j + 1] = (rgb >> 8) & 0xff; + buf[j + 2] = rgb & 0xff; + } + downloadWithMime("palette.act", buf, { + mime: "application/octet-stream", + }); }; diff --git a/examples/color-themes/src/serialize.ts b/examples/color-themes/src/serialize.ts index 5dd8d45416..32f0dcbbca 100644 --- a/examples/color-themes/src/serialize.ts +++ b/examples/color-themes/src/serialize.ts @@ -5,27 +5,27 @@ import type { ISubscribable, ISubscriber } from "@thi.ng/rstream"; import { type MainOutputs, NUM_STATE_TOKENS } from "./api"; export const initFromHash = ( - parts: IObjectOf>, - seed: ISubscriber, - num: ISubscriber, - variance: ISubscriber + parts: IObjectOf>, + seed: ISubscriber, + num: ISubscriber, + variance: ISubscriber ) => { - // attempt to restore state from hash fragment - if (location.hash.length > 1) { - const tokens = window.atob(location.hash.substring(1)).split("|"); - if (tokens.length === NUM_STATE_TOKENS) { - seed.next(parseInt(tokens[0])); - num.next(parseInt(tokens[1])); - variance.next(parseFloat(tokens[2])); - for (let i = 3, j = 0; j < 4; i += 3, j++) { - parts[j].next({ - range: tokens[i], - base: lch(JSON.parse(tokens[i + 1])), - weight: parseFloat(tokens[i + 2]), - }); - } - } - } + // attempt to restore state from hash fragment + if (location.hash.length > 1) { + const tokens = window.atob(location.hash.substring(1)).split("|"); + if (tokens.length === NUM_STATE_TOKENS) { + seed.next(parseInt(tokens[0])); + num.next(parseInt(tokens[1])); + variance.next(parseFloat(tokens[2])); + for (let i = 3, j = 0; j < 4; i += 3, j++) { + parts[j].next({ + range: tokens[i], + base: lch(JSON.parse(tokens[i + 1])), + weight: parseFloat(tokens[i + 2]), + }); + } + } + } }; /** @@ -34,16 +34,16 @@ export const initFromHash = ( * @param parent - */ export const attachSerializer = (parent: ISubscribable) => - parent.subscribe({ - next({ parts, num, variance, seed }) { - const res = [ - seed, - num, - variance, - ...Object.values(parts).map( - (p) => `${p.range}|${p.base}|${p.weight}` - ), - ].join("|"); - location.hash = btoa(res); - }, - }); + parent.subscribe({ + next({ parts, num, variance, seed }) { + const res = [ + seed, + num, + variance, + ...Object.values(parts).map( + (p) => `${p.range}|${p.base}|${p.weight}` + ), + ].join("|"); + location.hash = btoa(res); + }, + }); diff --git a/examples/color-themes/src/state.ts b/examples/color-themes/src/state.ts index 91777178a5..73608055d2 100644 --- a/examples/color-themes/src/state.ts +++ b/examples/color-themes/src/state.ts @@ -2,9 +2,9 @@ import type { IObjectOf } from "@thi.ng/api"; import { isMobile } from "@thi.ng/checks/is-mobile"; import { isString } from "@thi.ng/checks/is-string"; import type { - ColorRangePreset, - ColorThemePart, - CSSColorName, + ColorRangePreset, + ColorThemePart, + CSSColorName, } from "@thi.ng/color"; import { colorsFromTheme } from "@thi.ng/color/color-range"; import { distCIEDE2000 } from "@thi.ng/color/distance"; @@ -23,24 +23,24 @@ import { attachSerializer, initFromHash } from "./serialize"; // import { toDot, walk } from "@thi.ng/rstream-dot"; const themePart = ( - range: ColorRangePreset, - base: LCH | CSSColorName, - weight = 1 + range: ColorRangePreset, + base: LCH | CSSColorName, + weight = 1 ) => - reactive({ - range, - base: isString(base) ? lch(base) : base, - weight, - }); + reactive({ + range, + base: isString(base) ? lch(base) : base, + weight, + }); export const randomizeThemeParts = () => { - for (let part of Object.values(parts)) { - part.next({ - range: RANGE_IDs[SYSTEM.int() % RANGE_IDs.length], - base: lch.random(), - weight: SYSTEM.float(), - }); - } + for (let part of Object.values(parts)) { + part.next({ + range: RANGE_IDs[SYSTEM.int() % RANGE_IDs.length], + base: lch.random(), + weight: SYSTEM.float(), + }); + } }; /** @@ -48,34 +48,34 @@ export const randomizeThemeParts = () => { * proximity to white) and adds them to the state for further downstream * processing. * - * @param state - + * @param state - */ export const computeSwatches = (state: MainInputs) => { - const { parts, num, variance, seed, sorted } = state; - const colors = [ - ...colorsFromTheme(Object.values(parts), { - num, - variance, - rnd: new XsAdd(seed), - }), - ]; - if (sorted) { - sort(colors, proximity(lch(1, 0, 0), distCIEDE2000())); - } - return { ...state, colors }; + const { parts, num, variance, seed, sorted } = state; + const colors = [ + ...colorsFromTheme(Object.values(parts), { + num, + variance, + rnd: new XsAdd(seed), + }), + ]; + if (sorted) { + sort(colors, proximity(lch(1, 0, 0), distCIEDE2000())); + } + return { ...state, colors }; }; // setup streams of color theme parts export const parts: IObjectOf> = { - 0: themePart("bright", "goldenrod"), - 1: themePart("hard", "turquoise", 0.33), - 2: themePart("cool", "fuchsia", 0.5), - 3: themePart("warm", "seagreen", 0.1), + 0: themePart("bright", "goldenrod"), + 1: themePart("hard", "turquoise", 0.33), + 2: themePart("cool", "fuchsia", 0.5), + 3: themePart("warm", "seagreen", 0.1), }; // debounce needed to avoid triggering extraneous updates via randomizeTheme() export const debouncedParts = sync({ src: parts, id: "parts" }).subscribe( - debounce(1) + debounce(1) ); // streams for other user controls @@ -88,28 +88,28 @@ export const seed = reactive(0xdecafbad, { id: "seed" }); initFromHash(parts, seed, num, variance); const mainInputs = { - parts: debouncedParts, - num, - variance, - seed, - sorted, + parts: debouncedParts, + num, + variance, + seed, + sorted, }; // stream combinator export const main = sync({ - src: mainInputs, - xform: map(computeSwatches), - id: "main", + src: mainInputs, + xform: map(computeSwatches), + id: "main", }); attachSerializer(main); export const downloadTrigger = stream(); sync({ - src: { main, trigger: downloadTrigger }, - reset: true, + src: { main, trigger: downloadTrigger }, + reset: true, }).subscribe({ - next: (state) => downloadACT(state.main.colors), + next: (state) => downloadACT(state.main.colors), }); // traverse dataflow graph from given roots, produce Graphviz DOT output diff --git a/examples/color-themes/tsconfig.json b/examples/color-themes/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/color-themes/tsconfig.json +++ b/examples/color-themes/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/commit-heatmap/package.json b/examples/commit-heatmap/package.json index 25df74e372..2910cbbf12 100644 --- a/examples/commit-heatmap/package.json +++ b/examples/commit-heatmap/package.json @@ -1,46 +1,46 @@ { - "name": "@example/commit-heatmap", - "private": true, - "version": "0.0.1", - "description": "Heatmap visualization of this mono-repo's commits", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "type": "module", - "scripts": { - "clean": "rimraf .cache build out", - "build": "tools:node-esm src/index.ts" - }, - "dependencies": { - "@thi.ng/associative": "workspace:^", - "@thi.ng/color": "workspace:^", - "@thi.ng/compose": "workspace:^", - "@thi.ng/date": "workspace:^", - "@thi.ng/hiccup": "workspace:^", - "@thi.ng/hiccup-svg": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "thi.ng": { - "online": false, - "readme": [ - "associative", - "color", - "date", - "hiccup", - "hiccup-svg", - "transducers" - ], - "screenshot": "examples/commit-heatmap.png" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "tools": "workspace:^", - "typescript": "^4.7.4" - } + "name": "@example/commit-heatmap", + "private": true, + "version": "0.0.1", + "description": "Heatmap visualization of this mono-repo's commits", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "type": "module", + "scripts": { + "clean": "rimraf .cache build out", + "build": "tools:node-esm src/index.ts" + }, + "dependencies": { + "@thi.ng/associative": "workspace:^", + "@thi.ng/color": "workspace:^", + "@thi.ng/compose": "workspace:^", + "@thi.ng/date": "workspace:^", + "@thi.ng/hiccup": "workspace:^", + "@thi.ng/hiccup-svg": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "thi.ng": { + "online": false, + "readme": [ + "associative", + "color", + "date", + "hiccup", + "hiccup-svg", + "transducers" + ], + "screenshot": "examples/commit-heatmap.png" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "tools": "workspace:^", + "typescript": "^4.7.4" + } } diff --git a/examples/commit-heatmap/src/index.ts b/examples/commit-heatmap/src/index.ts index 5061694914..6c935d6cd5 100644 --- a/examples/commit-heatmap/src/index.ts +++ b/examples/commit-heatmap/src/index.ts @@ -1,7 +1,7 @@ import { withoutKeysObj } from "@thi.ng/associative/without-keys"; import { - cosineGradient, - COSINE_GRADIENTS, + cosineGradient, + COSINE_GRADIENTS, } from "@thi.ng/color/cosine-gradients"; import { threadLast } from "@thi.ng/compose/thread-last"; import { DAY } from "@thi.ng/date/api"; @@ -34,13 +34,13 @@ import * as fs from "fs"; import { resolve } from "path"; interface Commit { - name: string; - epoch: number; - date: string; - msg: string; - files: number; - adds: number; - dels: number; + name: string; + epoch: number; + date: string; + msg: string; + files: number; + adds: number; + dels: number; } const BASE_DIR = ".."; @@ -51,12 +51,12 @@ const RE_PKG = /\(([a-z-]+)\):/; // invalid / misspelled package names to exclude const IGNORE_PACKAGES = [ - "all", - "cso", - "example", - "exmples", - "shadertoy", - "transducer", + "all", + "cso", + "example", + "exmples", + "shadertoy", + "transducer", ]; // heatmap gradient @@ -66,8 +66,8 @@ const MIN_DATE = Date.parse("2018-01-01T00:00:00+00:00"); const MAX_DATE = Date.now(); const enum LogItem { - COMMIT, - STATS, + COMMIT, + STATS, } type ClassifiedCommit = [LogItem, string | string[]]; @@ -75,72 +75,72 @@ type ClassifiedCommit = [LogItem, string | string[]]; /** * Retrieves raw git log from given repo path. * - * @param repoPath - + * @param repoPath - */ const gitLog = (repoPath: string) => - execSync( - `git log --pretty=format:"%ad${SEP}%s" --date=iso-strict --shortstat`, - { - cwd: resolve(repoPath), - } - ) - .toString() - .trim(); + execSync( + `git log --pretty=format:"%ad${SEP}%s" --date=iso-strict --shortstat`, + { + cwd: resolve(repoPath), + } + ) + .toString() + .trim(); /** * Attempts to split commit line with field separator and classifies * line as COMMIT or STATS based on outcome. * - * @param line - + * @param line - */ const classifyCommitLine = (line: string): ClassifiedCommit => { - const parts = line.split(SEP); - return parts.length > 1 ? [LogItem.COMMIT, parts] : [LogItem.STATS, line]; + const parts = line.split(SEP); + return parts.length > 1 ? [LogItem.COMMIT, parts] : [LogItem.STATS, line]; }; /** * Filter predicate. Returns false if given line is empty. * - * @param line - + * @param line - */ const removeEmpty = (line: string) => line.length > 0; /** * Filter predicate. Returns false if commit is a merge. * - * @param x - + * @param x - */ const removeMergeCommits = (x: ClassifiedCommit) => - x[0] == LogItem.STATS || !x[1][1].startsWith("Merge"); + x[0] == LogItem.STATS || !x[1][1].startsWith("Merge"); /** * Takes a tuple of `[commit, stats]` and attempts to parse it into a * `Commit` object. Returns undefined if commit message is not package * specific (based on Conventional Commits format). * - * @param tuple - + * @param tuple - */ const parseCommitTuple = (tuple: ClassifiedCommit[]) => { - const [date, msg] = tuple[0][1]; - const stats = tuple[1][1]; - const match = RE_PKG.exec(msg); - if (match) { - const [files, adds, dels] = [ - ...map( - (x: RegExpMatchArray) => parseInt(x[0]), - stats.matchAll(/\d+/g) - ), - ]; - return { - name: match[1], - epoch: Date.parse(date), - date, - msg, - files, - adds, - dels, - }; - } + const [date, msg] = tuple[0][1]; + const stats = tuple[1][1]; + const match = RE_PKG.exec(msg); + if (match) { + const [files, adds, dels] = [ + ...map( + (x: RegExpMatchArray) => parseInt(x[0]), + stats.matchAll(/\d+/g) + ), + ]; + return { + name: match[1], + epoch: Date.parse(date), + date, + msg, + files, + adds, + dels, + }; + } }; /** @@ -149,46 +149,46 @@ const parseCommitTuple = (tuple: ClassifiedCommit[]) => { * removes commits for ignored packages. */ const commitsByPackage = withoutKeysObj( - transduce( - comp( - filter(removeEmpty), - map(classifyCommitLine), - filter(removeMergeCommits), - partition(2), - map(parseCommitTuple), - keep() - ), - groupByObj({ - group: pushSort((a, b) => a.epoch - b.epoch), - key: (x) => x.name, - }), - gitLog(BASE_DIR).split("\n") - ), - IGNORE_PACKAGES + transduce( + comp( + filter(removeEmpty), + map(classifyCommitLine), + filter(removeMergeCommits), + partition(2), + map(parseCommitTuple), + keep() + ), + groupByObj({ + group: pushSort((a, b) => a.epoch - b.epoch), + key: (x) => x.name, + }), + gitLog(BASE_DIR).split("\n") + ), + IGNORE_PACKAGES ); /** * Computes max value for given statistics key. * - * @param key - + * @param key - */ const maxStat = (key: "files" | "adds" | "dels") => - transduce( - comp( - mapcat((x) => x), - map((x) => x[key]) - ), - max(), - vals(commitsByPackage) - ); + transduce( + comp( + mapcat((x) => x), + map((x) => x[key]) + ), + max(), + vals(commitsByPackage) + ); /** * Total number of commits across all packages. */ const totalCommits = transduce( - map((x) => x.length), - add(), - vals(commitsByPackage) + map((x) => x.length), + add(), + vals(commitsByPackage) ); const maxFiles = maxStat("files"); @@ -204,58 +204,58 @@ const HEIGHT = NUM_PKG * 10 + 20; /** * Computes X coord for given epoch (based on above config). * - * @param epoch - + * @param epoch - */ const mapEpoch = (epoch: number) => - fit(epoch, MIN_DATE, MAX_DATE, PKG_WIDTH, WIDTH - 1); + fit(epoch, MIN_DATE, MAX_DATE, PKG_WIDTH, WIDTH - 1); /** * Returns log-mapped color from `GRAD` based on given `x` and `max` value. * - * @param x - - * @param max - + * @param x - + * @param max - */ const mapColor = (x: number, max: number) => - GRAD[fit(Math.log(x), 0, Math.log(max), 0, GRAD.length - 1) | 0]; + GRAD[fit(Math.log(x), 0, Math.log(max), 0, GRAD.length - 1) | 0]; /** * Returns iterator of quarterly timeline axis labels. */ const timeLineLabels = () => - map((t) => { - const x = mapEpoch(t); - const d = new Date(t); - return group( - {}, - line([x, 0], [x, NUM_PKG * 10 + 20], { - stroke: "#999", - "stroke-dasharray": 1, - }), - text([x + 5, 8], `${d.getFullYear()}-${Z2(d.getMonth() + 1)}`) - ); - }, quarters(MIN_DATE, MAX_DATE)); + map((t) => { + const x = mapEpoch(t); + const d = new Date(t); + return group( + {}, + line([x, 0], [x, NUM_PKG * 10 + 20], { + stroke: "#999", + "stroke-dasharray": 1, + }), + text([x + 5, 8], `${d.getFullYear()}-${Z2(d.getMonth() + 1)}`) + ); + }, quarters(MIN_DATE, MAX_DATE)); /** * Main visualization. Returns SVG group of commits for given package * name and index. See usage below. * - * @param i - - * @param pkg - + * @param i - + * @param pkg - */ const packageCommits = (i: number, pkg: string) => - group( - { - transform: `translate(0, ${(i + 2) * 10})`, - "stroke-width": 2, - }, - text([0, 8], pkg, { stroke: "none" }), - map((commit) => { - const x = mapEpoch(commit.epoch); - return line([x, 0], [x, 8], { - stroke: mapColor(commit.adds, maxAdds), - }); - }, commitsByPackage[pkg]) - ); + group( + { + transform: `translate(0, ${(i + 2) * 10})`, + "stroke-width": 2, + }, + text([0, 8], pkg, { stroke: "none" }), + map((commit) => { + const x = mapEpoch(commit.epoch); + return line([x, 0], [x, 8], { + stroke: mapColor(commit.adds, maxAdds), + }); + }, commitsByPackage[pkg]) + ); /** * Assemble & output full SVG document using hiccup-svg primitives. @@ -263,28 +263,28 @@ const packageCommits = (i: number, pkg: string) => * See: https://docs.thi.ng/umbrella/compose/modules.html#threadLast */ threadLast( - commitsByPackage, - sortedKeys, - [mapIndexed, packageCommits], - [ - svg, - { - width: WIDTH, - height: HEIGHT, - viewBox: `-10 -10 ${WIDTH + 20} ${HEIGHT + 20}`, - "font-family": "Inconsolata", - "font-size": 9, - fill: "white", - }, - defs([ - "style", - { type: "text/css" }, - "", - ]), - // background - rect([-10, -10], WIDTH + 20, HEIGHT + 20, { fill: "black" }), - timeLineLabels(), - ], - serialize, - [fs.writeFileSync, "heatmap.svg"] + commitsByPackage, + sortedKeys, + [mapIndexed, packageCommits], + [ + svg, + { + width: WIDTH, + height: HEIGHT, + viewBox: `-10 -10 ${WIDTH + 20} ${HEIGHT + 20}`, + "font-family": "Inconsolata", + "font-size": 9, + fill: "white", + }, + defs([ + "style", + { type: "text/css" }, + "", + ]), + // background + rect([-10, -10], WIDTH + 20, HEIGHT + 20, { fill: "black" }), + timeLineLabels(), + ], + serialize, + [fs.writeFileSync, "heatmap.svg"] ); diff --git a/examples/commit-heatmap/tsconfig.json b/examples/commit-heatmap/tsconfig.json index 4ee43ac513..b4bceb5b17 100644 --- a/examples/commit-heatmap/tsconfig.json +++ b/examples/commit-heatmap/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["src/**/*"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["src/**/*"] } diff --git a/examples/commit-table-ssr/package.json b/examples/commit-table-ssr/package.json index 4ddec7bd57..aec57f280a 100644 --- a/examples/commit-table-ssr/package.json +++ b/examples/commit-table-ssr/package.json @@ -1,47 +1,47 @@ { - "name": "@example/commit-table-ssr", - "private": true, - "version": "0.0.1", - "description": "Filterable commit log UI w/ minimal server to provide commit history", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "clean": "rimraf node_modules/.cache build out", - "build-static": "ts-node src/server/static.ts", - "start": "ts-node src/server/index.ts", - "preview": "vite preview --host --open" - }, - "devDependencies": { - "@types/node": "^17.0.41", - "ts-node": "^10.8.2", - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/associative": "workspace:^", - "@thi.ng/cache": "workspace:^", - "@thi.ng/hiccup": "workspace:^", - "@thi.ng/resolve-map": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^", - "@types/express": "^4.17.13", - "express": "^4.18.1" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "cache", - "hiccup", - "resolve-map", - "transducers" - ], - "screenshot": "examples/commit-table-ssr.png" - } + "name": "@example/commit-table-ssr", + "private": true, + "version": "0.0.1", + "description": "Filterable commit log UI w/ minimal server to provide commit history", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "clean": "rimraf node_modules/.cache build out", + "build-static": "ts-node src/server/static.ts", + "start": "ts-node src/server/index.ts", + "preview": "vite preview --host --open" + }, + "devDependencies": { + "@types/node": "^17.0.41", + "ts-node": "^10.8.2", + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/associative": "workspace:^", + "@thi.ng/cache": "workspace:^", + "@thi.ng/hiccup": "workspace:^", + "@thi.ng/resolve-map": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^", + "@types/express": "^4.17.13", + "express": "^4.18.1" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "cache", + "hiccup", + "resolve-map", + "transducers" + ], + "screenshot": "examples/commit-table-ssr.png" + } } diff --git a/examples/commit-table-ssr/src/client/index.ts b/examples/commit-table-ssr/src/client/index.ts index 281472c054..531adcc6c6 100644 --- a/examples/commit-table-ssr/src/client/index.ts +++ b/examples/commit-table-ssr/src/client/index.ts @@ -17,92 +17,92 @@ import { repoTable } from "../common/components/repo-table"; import { ctx } from "../common/config"; const COMMITS_URL = - process.env.NODE_ENV === "production" ? "./commits.json" : "/commits"; + process.env.NODE_ENV === "production" ? "./commits.json" : "/commits"; // UI root component const app = (state: any) => [ - "div", - [header, ctx.repo.name], - [stats, state], - [repoTable, state.commits], + "div", + [header, ctx.repo.name], + [stats, state], + [repoTable, state.commits], ]; // stats container component const stats = (ctx: AppContext, state: any) => [ - "div", - ctx.ui.stats.root, - ["div.tl", ctx.ui.stats.col, [searchFilter, state]], - [ - "div.tc", - ctx.ui.stats.col, - ["div", `Authors: ${state.authors}`], - ["div", `Total adds: ${state.adds} (${state.avgAdds} avg / commit)`], - ["div", `Total dels: ${state.dels} (${state.avgDels} avg / commit)`], - ], - [ - "div.tr", - ctx.ui.stats.col, - [link, { ...ctx.ui.stats.link, href: ctx.repo.url }, ctx.repo.url], - ], + "div", + ctx.ui.stats.root, + ["div.tl", ctx.ui.stats.col, [searchFilter, state]], + [ + "div.tc", + ctx.ui.stats.col, + ["div", `Authors: ${state.authors}`], + ["div", `Total adds: ${state.adds} (${state.avgAdds} avg / commit)`], + ["div", `Total dels: ${state.dels} (${state.avgDels} avg / commit)`], + ], + [ + "div.tr", + ctx.ui.stats.col, + [link, { ...ctx.ui.stats.link, href: ctx.repo.url }, ctx.repo.url], + ], ]; // search filter input component const searchFilter = ( - ctx: AppContext, - state: { commits: Commit[]; search: string } + ctx: AppContext, + state: { commits: Commit[]; search: string } ) => [ - "div", - "Filter:", - [ - "input", - { - ...ctx.ui.search, - type: "text", - value: state.search, - // emit changes on `search` stream - oninput: (e: any) => search.next(e.target.value.toLowerCase()), - }, - ], - `(${state.commits.length} commits)`, + "div", + "Filter:", + [ + "input", + { + ...ctx.ui.search, + type: "text", + value: state.search, + // emit changes on `search` stream + oninput: (e: any) => search.next(e.target.value.toLowerCase()), + }, + ], + `(${state.commits.length} commits)`, ]; // transformation function to filter commits with search string // doesn't apply filter if search term is empty const filterCommits = ({ - commits, - search, + commits, + search, }: { - commits: Commit[]; - search: string; + commits: Commit[]; + search: string; }) => ({ - search, - commits: search - ? commits.filter((x) => x.msg.toLowerCase().indexOf(search) !== -1) - : commits, + search, + commits: search + ? commits.filter((x) => x.msg.toLowerCase().indexOf(search) !== -1) + : commits, }); // transformation function to compute stats of filtered commits // uses `resolve-map` package to execute given functions in dependency order const computeStats = (state: any) => - resolveMap({ - ...state, - adds: ({ commits }: any) => - transduce( - map((x: Commit) => x.add || 0), - add(), - commits - ), - dels: ({ commits }: any) => - transduce( - map((x: Commit) => x.del || 0), - add(), - commits - ), - authors: ({ commits }: any) => - transduce(pluck("author"), conj(), commits).size, - avgAdds: ({ commits, adds }: any) => (adds / commits.length) | 0, - avgDels: ({ commits, dels }: any) => (dels / commits.length) | 0, - }); + resolveMap({ + ...state, + adds: ({ commits }: any) => + transduce( + map((x: Commit) => x.add || 0), + add(), + commits + ), + dels: ({ commits }: any) => + transduce( + map((x: Commit) => x.del || 0), + add(), + commits + ), + authors: ({ commits }: any) => + transduce(pluck("author"), conj(), commits).size, + avgAdds: ({ commits, adds }: any) => (adds / commits.length) | 0, + avgDels: ({ commits, dels }: any) => (dels / commits.length) | 0, + }); // error stream & handler const error = stream(); @@ -110,20 +110,20 @@ error.subscribe({ next: (e) => alert(`An error occurred:\n${e}`) }); // commit log stream, reloads every 1h const commits = fromInterval(60 * 60 * 1000) - // fetch commits from server - .transform( - map(() => - fetch(COMMITS_URL).then( - (res) => - res.ok ? res.json() : error.next("error loading commits"), - (e) => error.next(e.message) - ) - ) - ) - // the above transducer returns a promise - // this next subscription resolves it and only then - // passes the result downstream - .subscribe(resolvePromise({ fail: (e) => error.next(e.message) })); + // fetch commits from server + .transform( + map(() => + fetch(COMMITS_URL).then( + (res) => + res.ok ? res.json() : error.next("error loading commits"), + (e) => error.next(e.message) + ) + ) + ) + // the above transducer returns a promise + // this next subscription resolves it and only then + // passes the result downstream + .subscribe(resolvePromise({ fail: (e) => error.next(e.message) })); // stream of commit filter terms const search = reactive(""); @@ -131,21 +131,21 @@ const search = reactive(""); // stream combinator & transformation into UI / DOM update // will only execute once all of its inputs have delivered a value. sync({ - // streams to synchronize - src: { - commits, - // throttle search stream @ 10Hz (100ms) to minimize - // UI lag for fast typists - search: search.transform(throttleTime(100)), - }, + // streams to synchronize + src: { + commits, + // throttle search stream @ 10Hz (100ms) to minimize + // UI lag for fast typists + search: search.transform(throttleTime(100)), + }, }) - // now transform the combined stream - // each value is an object tuple of: `{ commits, search }` - .transform( - map(filterCommits), - map(computeStats), - // apply root component - map(app), - // apply hdom tree to real DOM - updateDOM({ ctx }) - ); + // now transform the combined stream + // each value is an object tuple of: `{ commits, search }` + .transform( + map(filterCommits), + map(computeStats), + // apply root component + map(app), + // apply hdom tree to real DOM + updateDOM({ ctx }) + ); diff --git a/examples/commit-table-ssr/src/common/api.ts b/examples/commit-table-ssr/src/common/api.ts index 894f8e4d3e..15497dc6ee 100644 --- a/examples/commit-table-ssr/src/common/api.ts +++ b/examples/commit-table-ssr/src/common/api.ts @@ -3,21 +3,21 @@ * See ./html.ts for usage */ export interface HTMLDoc { - lang?: string; - head?: Partial; - body: any[]; - /** - * This object will be passed to all component functions. - */ - ctx?: AppContext; + lang?: string; + head?: Partial; + body: any[]; + /** + * This object will be passed to all component functions. + */ + ctx?: AppContext; } export interface HTMLHead { - title: string; - meta: any[]; - links: { rel: string; href: string }[]; - scripts: { src: string; type?: string }[]; - styles: string[]; + title: string; + meta: any[]; + links: { rel: string; href: string }[]; + scripts: { src: string; type?: string }[]; + styles: string[]; } /** @@ -25,32 +25,32 @@ export interface HTMLHead { * Contains repo information & component styles */ export interface AppContext { - repo: Repo; - ui: { - body: any; - link: any; - header: any; - table: any; - stats: any; - search: any; - }; + repo: Repo; + ui: { + body: any; + link: any; + header: any; + table: any; + stats: any; + search: any; + }; } export interface Repo { - name: string; - path: string; - url: string; + name: string; + path: string; + url: string; } /** * Data structure of a single commit. */ export interface Commit { - sha: string; - date: string; - author: string; - msg: string; - files: number; - add: number; - del: number; + sha: string; + date: string; + author: string; + msg: string; + files: number; + add: number; + del: number; } diff --git a/examples/commit-table-ssr/src/common/components/commit-link.ts b/examples/commit-table-ssr/src/common/components/commit-link.ts index 51cbded4a7..06514ea523 100644 --- a/examples/commit-table-ssr/src/common/components/commit-link.ts +++ b/examples/commit-table-ssr/src/common/components/commit-link.ts @@ -5,12 +5,12 @@ import { link } from "./link"; * Link component which links to given SHA commit hash using the * context's repo URL. * - * @param ctx - - * @param sha - - * @param body - + * @param ctx - + * @param sha - + * @param body - */ export const commitLink = (ctx: AppContext, sha: string, body: string) => [ - link, - { ...ctx.ui.link, href: `${ctx.repo.url}/commit/${sha}` }, - body, + link, + { ...ctx.ui.link, href: `${ctx.repo.url}/commit/${sha}` }, + body, ]; diff --git a/examples/commit-table-ssr/src/common/components/header.ts b/examples/commit-table-ssr/src/common/components/header.ts index 367e9e79a4..62aad50157 100644 --- a/examples/commit-table-ssr/src/common/components/header.ts +++ b/examples/commit-table-ssr/src/common/components/header.ts @@ -1,7 +1,7 @@ import type { AppContext } from "../api"; export const header = (ctx: AppContext, title: string) => [ - "section", - ctx.ui.header.root, - ["h1", ctx.ui.header.title, title], + "section", + ctx.ui.header.root, + ["h1", ctx.ui.header.title, title], ]; diff --git a/examples/commit-table-ssr/src/common/components/link.ts b/examples/commit-table-ssr/src/common/components/link.ts index 945d33953f..9d3f7ffe23 100644 --- a/examples/commit-table-ssr/src/common/components/link.ts +++ b/examples/commit-table-ssr/src/common/components/link.ts @@ -3,12 +3,12 @@ import type { AppContext } from "../api"; /** * Generic HTML link component. * - * @param ctx - - * @param href - - * @param body - + * @param ctx - + * @param href - + * @param body - */ export const link = (_: AppContext, attribs: any, body: string) => [ - "a", - attribs, - body, + "a", + attribs, + body, ]; diff --git a/examples/commit-table-ssr/src/common/components/repo-table.ts b/examples/commit-table-ssr/src/common/components/repo-table.ts index 49d16e714a..cf35c86436 100644 --- a/examples/commit-table-ssr/src/common/components/repo-table.ts +++ b/examples/commit-table-ssr/src/common/components/repo-table.ts @@ -16,28 +16,28 @@ import { table } from "./table"; * @param commits - */ export const repoTable = (_: AppContext, commits: Iterable) => [ - table, - ["15%", "15%", "55%", "5%", "5%", "5%"], - ["Date", "Author", "Description", "Files", "Adds", "Dels"], - iterator( - comp( - // convert commit into tuple, one value per table cell - map((x: Commit) => [ - x.date.substring(0, 10), - x.author, - [commitLink, x.sha, x.msg], - x.files, - x.add ? ["span.green", `+${x.add}`] : null, - x.del ? ["span.red", `-${x.del}`] : null, - ]), - // partition rows by month - partitionBy((row: any[]) => row[0].split("-")[1]), - // insert month headers (but not in 1st chunk) - mapIndexed((i, month) => [ - i > 0 ? [month[0][0].substring(0, 7), ...repeat("", 5)] : null, - month, - ]) - ), - commits - ), + table, + ["15%", "15%", "55%", "5%", "5%", "5%"], + ["Date", "Author", "Description", "Files", "Adds", "Dels"], + iterator( + comp( + // convert commit into tuple, one value per table cell + map((x: Commit) => [ + x.date.substring(0, 10), + x.author, + [commitLink, x.sha, x.msg], + x.files, + x.add ? ["span.green", `+${x.add}`] : null, + x.del ? ["span.red", `-${x.del}`] : null, + ]), + // partition rows by month + partitionBy((row: any[]) => row[0].split("-")[1]), + // insert month headers (but not in 1st chunk) + mapIndexed((i, month) => [ + i > 0 ? [month[0][0].substring(0, 7), ...repeat("", 5)] : null, + month, + ]) + ), + commits + ), ]; diff --git a/examples/commit-table-ssr/src/common/components/table.ts b/examples/commit-table-ssr/src/common/components/table.ts index 61e18908a2..91ad124b74 100644 --- a/examples/commit-table-ssr/src/common/components/table.ts +++ b/examples/commit-table-ssr/src/common/components/table.ts @@ -3,18 +3,18 @@ import { mapcat } from "@thi.ng/transducers/mapcat"; import type { AppContext } from "../api"; const thead = (ctx: AppContext, head: Iterable) => [ - "thead", - [ - row, - ctx.ui.table.head.row, - map((x) => ["th", ctx.ui.table.head.cell, x], head), - ], + "thead", + [ + row, + ctx.ui.table.head.row, + map((x) => ["th", ctx.ui.table.head.cell, x], head), + ], ]; const row = (ctx: AppContext, attribs: any, body: Iterable) => [ - "tr", - { ...ctx.ui.table.row, ...attribs }, - ...body, + "tr", + { ...ctx.ui.table.row, ...attribs }, + ...body, ]; /** @@ -43,36 +43,36 @@ const row = (ctx: AppContext, attribs: any, body: Iterable) => [ * ] * ``` * - * @param ctx - + * @param ctx - * @param layout - olumn sizes * @param head - eader cell values * @param body - ow chunks */ export const table = ( - ctx: AppContext, - layout: (string | number)[], - head: Iterable, - body: Iterable> + ctx: AppContext, + layout: (string | number)[], + head: Iterable, + body: Iterable> ) => [ - "table", - ctx.ui.table.root, - map((x) => ["col", { style: { width: x } }], layout || []), - [thead, head], - mapcat( - ([hd, rows]) => [ - hd ? [thead, hd] : null, - [ - "tbody", - map( - (cols: any) => [ - row, - null, - map((x) => ["td", ctx.ui.table.cell, x], cols), - ], - rows - ), - ], - ], - body - ), + "table", + ctx.ui.table.root, + map((x) => ["col", { style: { width: x } }], layout || []), + [thead, head], + mapcat( + ([hd, rows]) => [ + hd ? [thead, hd] : null, + [ + "tbody", + map( + (cols: any) => [ + row, + null, + map((x) => ["td", ctx.ui.table.cell, x], cols), + ], + rows + ), + ], + ], + body + ), ]; diff --git a/examples/commit-table-ssr/src/common/config.ts b/examples/commit-table-ssr/src/common/config.ts index 33a92a6045..86ee853da3 100644 --- a/examples/commit-table-ssr/src/common/config.ts +++ b/examples/commit-table-ssr/src/common/config.ts @@ -1,66 +1,66 @@ import type { AppContext, HTMLDoc } from "./api"; export const DEFAULT_DOC: HTMLDoc = { - head: { - meta: [ - { - "http-equiv": "Content-Type", - content: "text/html;charset=UTF-8", - }, - { "http-equiv": "X-UA-Compatible", content: "ie=edge" }, - ], - links: [ - { - rel: "stylesheet", - href: "https://unpkg.com/tachyons@4.11.1/css/tachyons.min.css", - }, - { - rel: "stylesheet", - href: "https://fonts.googleapis.com/css?family=Inconsolata", - }, - ], - scripts: [], - styles: [], - title: "", - }, - body: [], + head: { + meta: [ + { + "http-equiv": "Content-Type", + content: "text/html;charset=UTF-8", + }, + { "http-equiv": "X-UA-Compatible", content: "ie=edge" }, + ], + links: [ + { + rel: "stylesheet", + href: "https://unpkg.com/tachyons@4.11.1/css/tachyons.min.css", + }, + { + rel: "stylesheet", + href: "https://fonts.googleapis.com/css?family=Inconsolata", + }, + ], + scripts: [], + styles: [], + title: "", + }, + body: [], }; /** * Main app config. */ export const ctx: AppContext = { - repo: { - name: "thi.ng/umbrella", - path: ".", - url: "https://github.com/thi-ng/umbrella", - }, - ui: { - body: { class: "sans-serif vh-100" }, - link: { class: "link blue hover-light-blue" }, - header: { - root: { class: "bg-dark-gray white pa3 ma0 w-100" }, - title: { class: "tc ma0 pa0 fw1" }, - }, - table: { - root: { - class: "w-100 collapse ba br2 b--black-10 pv2 ph3 f7 f6-ns", - style: { "font-family": "Inconsolata, monospace" }, - }, - head: { - row: { class: "tl bg-black white" }, - cell: { class: "pv1 pv2-ns ph2 ph3-ns" }, - }, - row: { class: "striped--light-gray" }, - cell: { class: "pv1 pv2-ns ph2 ph3-ns" }, - }, - stats: { - root: { - class: "flex items-center pa2 bg-light-green dark-gray f7", - }, - col: { class: "w-33" }, - link: { class: "link dark-gray" }, - }, - search: { class: "pa1 mh2" }, - }, + repo: { + name: "thi.ng/umbrella", + path: ".", + url: "https://github.com/thi-ng/umbrella", + }, + ui: { + body: { class: "sans-serif vh-100" }, + link: { class: "link blue hover-light-blue" }, + header: { + root: { class: "bg-dark-gray white pa3 ma0 w-100" }, + title: { class: "tc ma0 pa0 fw1" }, + }, + table: { + root: { + class: "w-100 collapse ba br2 b--black-10 pv2 ph3 f7 f6-ns", + style: { "font-family": "Inconsolata, monospace" }, + }, + head: { + row: { class: "tl bg-black white" }, + cell: { class: "pv1 pv2-ns ph2 ph3-ns" }, + }, + row: { class: "striped--light-gray" }, + cell: { class: "pv1 pv2-ns ph2 ph3-ns" }, + }, + stats: { + root: { + class: "flex items-center pa2 bg-light-green dark-gray f7", + }, + col: { class: "w-33" }, + link: { class: "link dark-gray" }, + }, + search: { class: "pa1 mh2" }, + }, }; diff --git a/examples/commit-table-ssr/src/server/build-table.ts b/examples/commit-table-ssr/src/server/build-table.ts index 85be2290be..a05dc1f29d 100644 --- a/examples/commit-table-ssr/src/server/build-table.ts +++ b/examples/commit-table-ssr/src/server/build-table.ts @@ -9,10 +9,10 @@ import { html } from "./html"; * generation. Returns serialized HTML string of commit table. */ export const buildRepoTableHTML = (commits: Iterable) => - html({ - ctx, - body: [ - [header, ctx.repo.name], - [repoTable, commits], - ], - }); + html({ + ctx, + body: [ + [header, ctx.repo.name], + [repoTable, commits], + ], + }); diff --git a/examples/commit-table-ssr/src/server/git.ts b/examples/commit-table-ssr/src/server/git.ts index 5c41ddc135..522089caa8 100644 --- a/examples/commit-table-ssr/src/server/git.ts +++ b/examples/commit-table-ssr/src/server/git.ts @@ -14,77 +14,77 @@ import type { Commit } from "../common/api"; /** * Calls out to git to retrieve raw log string. * - * @param repoPath - + * @param repoPath - */ const gitLog = (repoPath: string) => - execSync( - `git log --pretty=format:"%ad~~%an~~%h~~%s" --shortstat --date=iso-strict`, - { cwd: resolve(repoPath) } - ) - .toString() - .trim(); + execSync( + `git log --pretty=format:"%ad~~%an~~%h~~%s" --shortstat --date=iso-strict`, + { cwd: resolve(repoPath) } + ) + .toString() + .trim(); /** * Transforms 1st line of a raw commit log into a partial commit * object. * - * @param log - + * @param log - */ const parseLog = ([log]: string[]): Partial => { - const [date, author, sha, msg] = log.split("~~"); - return { date, author, sha, msg }; + const [date, author, sha, msg] = log.split("~~"); + return { date, author, sha, msg }; }; /** * Transforms 2nd line (if present) of a raw commit log into a partial * commit object. * - * @param log - + * @param log - */ const parseStats = ([_, stats]: string[]): Partial | null => - stats - ? transduce( - map(([k, v]) => <[string, number]>[k, parseInt(v)]), - assocObj(), - zip(["files", "add", "del"], stats.split(",")) - ) - : null; + stats + ? transduce( + map(([k, v]) => <[string, number]>[k, parseInt(v)]), + assocObj(), + zip(["files", "add", "del"], stats.split(",")) + ) + : null; /** * Retrieves git log for given `repoPath` and transforms it into an * iterable of `Commit` objects. * - * @param repoPath - + * @param repoPath - */ export const repoCommits = (repoPath: string) => - iterator( - comp( - // get raw log - map(gitLog), - // split into lines - mapcat((x: string) => x.split("\n")), - // group related lines: - // normal commits have 2 lines + 1 empty - // merge commits have only 1 line - // pick a random number for merge commits - // in case there're successive ones - partitionBy((x) => - x.indexOf("~~Merge ") !== -1 - ? Math.random() - : x.length > 0 - ? 1 - : 0 - ), - // remove empty lines - filter((x) => x[0].length > 0), - // parse commit details - map( - (commit) => - { - ...parseLog(commit), - ...parseStats(commit), - } - ) - ), - [repoPath] - ); + iterator( + comp( + // get raw log + map(gitLog), + // split into lines + mapcat((x: string) => x.split("\n")), + // group related lines: + // normal commits have 2 lines + 1 empty + // merge commits have only 1 line + // pick a random number for merge commits + // in case there're successive ones + partitionBy((x) => + x.indexOf("~~Merge ") !== -1 + ? Math.random() + : x.length > 0 + ? 1 + : 0 + ), + // remove empty lines + filter((x) => x[0].length > 0), + // parse commit details + map( + (commit) => + { + ...parseLog(commit), + ...parseStats(commit), + } + ) + ), + [repoPath] + ); diff --git a/examples/commit-table-ssr/src/server/html.ts b/examples/commit-table-ssr/src/server/html.ts index 920b560c22..daf7c15c9b 100644 --- a/examples/commit-table-ssr/src/server/html.ts +++ b/examples/commit-table-ssr/src/server/html.ts @@ -16,29 +16,29 @@ import { DEFAULT_DOC } from "../common/config"; * See here for more reference: * https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup * - * @param doc - + * @param doc - */ export const html = (doc: HTMLDoc) => { - doc = mergeDeepObj(DEFAULT_DOC, doc); - return `${serialize( - [ - "html", - { lang: doc.lang || "en" }, - [ - "head", - map((meta) => ["meta", meta], doc.head!.meta || []), - map((s) => script(null, s), doc.head!.scripts || []), - map((link) => ["link", link], doc.head!.links || []), - map((css) => ["style", css], doc.head!.styles || []), - ["title", doc.head!.title || ""], - ], - ["body", doc.ctx!.ui.body, ...doc.body], - ], - doc.ctx - )}`; + doc = mergeDeepObj(DEFAULT_DOC, doc); + return `${serialize( + [ + "html", + { lang: doc.lang || "en" }, + [ + "head", + map((meta) => ["meta", meta], doc.head!.meta || []), + map((s) => script(null, s), doc.head!.scripts || []), + map((link) => ["link", link], doc.head!.links || []), + map((css) => ["style", css], doc.head!.styles || []), + ["title", doc.head!.title || ""], + ], + ["body", doc.ctx!.ui.body, ...doc.body], + ], + doc.ctx + )}`; }; export const script = ( - _: Nullable, - script: { src: string; type?: string } + _: Nullable, + script: { src: string; type?: string } ) => ["script", { type: "text/javascript", ...script }]; diff --git a/examples/commit-table-ssr/src/server/index.ts b/examples/commit-table-ssr/src/server/index.ts index 0571991804..6ff1419c2b 100644 --- a/examples/commit-table-ssr/src/server/index.ts +++ b/examples/commit-table-ssr/src/server/index.ts @@ -11,21 +11,23 @@ import { repoCommits } from "./git"; // building the repo commit table takes quite some time // therefore we cache results with 1h expiry time // (which is also the default) -// prettier-ignore -const rawCache = new TLRUCache(undefined, { ttl: 60 * 60 * 1000 }); -// prettier-ignore -const htmlCache = new TLRUCache(undefined, { ttl: 60 * 60 * 1000 }); +const rawCache = new TLRUCache(undefined, { + ttl: 60 * 60 * 1000, +}); +const htmlCache = new TLRUCache(undefined, { + ttl: 60 * 60 * 1000, +}); const bundler = new Bundler("index.html", { - outDir: "./out", - outFile: "index.html", - publicUrl: "/out", + outDir: "./out", + outFile: "index.html", + publicUrl: "/out", }); const getCommits = async () => { - const commits = [...repoCommits(ctx.repo.path)]; - fs.writeFileSync("commits.json", JSON.stringify(commits)); - return commits; + const commits = [...repoCommits(ctx.repo.path)]; + fs.writeFileSync("commits.json", JSON.stringify(commits)); + return commits; }; const app = express(); @@ -33,28 +35,28 @@ const app = express(); // route for browser version // here we simply redirect to the Parcel managed client version app.get("/", (_, res) => { - res.redirect("/out/"); + res.redirect("/out/"); }); // route for the client to retrieve the commit log as JSON app.get("/commits", (_, res) => { - // retrieve raw commit log from cache or - // (re)create if missing... - rawCache - .getSet(ctx.repo.path, getCommits) - .then((commits) => res.type("json").send(commits)); + // retrieve raw commit log from cache or + // (re)create if missing... + rawCache + .getSet(ctx.repo.path, getCommits) + .then((commits) => res.type("json").send(commits)); }); // route for server-side rendering // uses both caches app.get("/ssr", (_, res) => { - // retrieve rendered html from cache or - // (re)create if missing... - htmlCache - .getSet(ctx.repo.path, async () => - buildRepoTableHTML(await rawCache.getSet(ctx.repo.path, getCommits)) - ) - .then((doc) => res.send(doc)); + // retrieve rendered html from cache or + // (re)create if missing... + htmlCache + .getSet(ctx.repo.path, async () => + buildRepoTableHTML(await rawCache.getSet(ctx.repo.path, getCommits)) + ) + .then((doc) => res.send(doc)); }); app.use(express.static(".")); diff --git a/examples/commit-table-ssr/tsconfig.json b/examples/commit-table-ssr/tsconfig.json index 428408eef1..d505faae06 100644 --- a/examples/commit-table-ssr/tsconfig.json +++ b/examples/commit-table-ssr/tsconfig.json @@ -1,10 +1,10 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./build", - "module": "commonjs", - "target": "es2017", - "sourceMap": true - }, - "include": ["src/**/*"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./build", + "module": "commonjs", + "target": "es2017", + "sourceMap": true + }, + "include": ["src/**/*"] } diff --git a/examples/crypto-chart/ohlc.json b/examples/crypto-chart/ohlc.json index 3347ad31b3..034b58de40 100644 --- a/examples/crypto-chart/ohlc.json +++ b/examples/crypto-chart/ohlc.json @@ -1,1535 +1,1535 @@ { - "Response": "Success", - "Type": 100, - "Aggregated": false, - "Data": [ - { - "time": 1531054800, - "close": 6736.36, - "high": 6747.33, - "low": 6712.34, - "open": 6731.21, - "volumefrom": 1808.95, - "volumeto": 12241907.1 - }, - { - "time": 1531058400, - "close": 6751.25, - "high": 6762.05, - "low": 6724.56, - "open": 6736.36, - "volumefrom": 2054.76, - "volumeto": 13903333.45 - }, - { - "time": 1531062000, - "close": 6743.55, - "high": 6768.96, - "low": 6742.58, - "open": 6751.4, - "volumefrom": 2015.38, - "volumeto": 13683443.72 - }, - { - "time": 1531065600, - "close": 6751.56, - "high": 6758.84, - "low": 6742.12, - "open": 6744.22, - "volumefrom": 1085.39, - "volumeto": 7381082.14 - }, - { - "time": 1531069200, - "close": 6752.21, - "high": 6764.38, - "low": 6745.59, - "open": 6751.59, - "volumefrom": 1274.54, - "volumeto": 8656031.41 - }, - { - "time": 1531072800, - "close": 6752.81, - "high": 6763.28, - "low": 6745.05, - "open": 6752.21, - "volumefrom": 1080.71, - "volumeto": 7331549.32 - }, - { - "time": 1531076400, - "close": 6758, - "high": 6760.83, - "low": 6742.41, - "open": 6752.81, - "volumefrom": 1043.28, - "volumeto": 7073338.91 - }, - { - "time": 1531080000, - "close": 6748.79, - "high": 6765.02, - "low": 6748.46, - "open": 6758, - "volumefrom": 1370.51, - "volumeto": 9343071.48 - }, - { - "time": 1531083600, - "close": 6755, - "high": 6761.91, - "low": 6743.97, - "open": 6748.79, - "volumefrom": 1123.85, - "volumeto": 7634206.13 - }, - { - "time": 1531087200, - "close": 6751.18, - "high": 6758.82, - "low": 6742.34, - "open": 6755.03, - "volumefrom": 1205.95, - "volumeto": 8191445.01 - }, - { - "time": 1531090800, - "close": 6707.38, - "high": 6752.81, - "low": 6684.15, - "open": 6751.94, - "volumefrom": 3513.7, - "volumeto": 23623766.17 - }, - { - "time": 1531094400, - "close": 6716.19, - "high": 6727.06, - "low": 6692.62, - "open": 6707.46, - "volumefrom": 1595.85, - "volumeto": 10764835.22 - }, - { - "time": 1531098000, - "close": 6704.18, - "high": 6717.29, - "low": 6694.84, - "open": 6716.19, - "volumefrom": 1298.23, - "volumeto": 8729204.25 - }, - { - "time": 1531101600, - "close": 6701.34, - "high": 6706, - "low": 6690.61, - "open": 6704.18, - "volumefrom": 1135.75, - "volumeto": 7632998.18 - }, - { - "time": 1531105200, - "close": 6712.3, - "high": 6717.81, - "low": 6699.16, - "open": 6701.3, - "volumefrom": 1233.99, - "volumeto": 8303151.48 - }, - { - "time": 1531108800, - "close": 6702.38, - "high": 6715.55, - "low": 6696.35, - "open": 6712.3, - "volumefrom": 1377.04, - "volumeto": 9269485.43 - }, - { - "time": 1531112400, - "close": 6702.77, - "high": 6706.54, - "low": 6694.53, - "open": 6702.58, - "volumefrom": 1233.8, - "volumeto": 8294586.37 - }, - { - "time": 1531116000, - "close": 6714.61, - "high": 6720.46, - "low": 6699.55, - "open": 6702.81, - "volumefrom": 1775.97, - "volumeto": 11958344.77 - }, - { - "time": 1531119600, - "close": 6725.73, - "high": 6739.25, - "low": 6706.88, - "open": 6714.66, - "volumefrom": 1529.24, - "volumeto": 10316826.29 - }, - { - "time": 1531123200, - "close": 6721.48, - "high": 6729.42, - "low": 6715.41, - "open": 6725.74, - "volumefrom": 1359.6, - "volumeto": 9174151.01 - }, - { - "time": 1531126800, - "close": 6690.8, - "high": 6721.65, - "low": 6670.9, - "open": 6721.48, - "volumefrom": 3344.4, - "volumeto": 22431823.64 - }, - { - "time": 1531130400, - "close": 6696.14, - "high": 6702.51, - "low": 6680.06, - "open": 6690.94, - "volumefrom": 1682.41, - "volumeto": 11306630.17 - }, - { - "time": 1531134000, - "close": 6723, - "high": 6746.24, - "low": 6694.68, - "open": 6695.2, - "volumefrom": 2631.14, - "volumeto": 17723558.62 - }, - { - "time": 1531137600, - "close": 6711.4, - "high": 6725.63, - "low": 6705.89, - "open": 6723.14, - "volumefrom": 1459.97, - "volumeto": 9866328.98 - }, - { - "time": 1531141200, - "close": 6727.41, - "high": 6739.11, - "low": 6709.93, - "open": 6711.49, - "volumefrom": 1868.24, - "volumeto": 12614000.31 - }, - { - "time": 1531144800, - "close": 6728.75, - "high": 6729.07, - "low": 6717, - "open": 6727.41, - "volumefrom": 1481.69, - "volumeto": 10032564.2 - }, - { - "time": 1531148400, - "close": 6722.57, - "high": 6737.7, - "low": 6721.18, - "open": 6728.75, - "volumefrom": 1594.78, - "volumeto": 10776229.52 - }, - { - "time": 1531152000, - "close": 6710.13, - "high": 6725, - "low": 6678.93, - "open": 6722.57, - "volumefrom": 2713.74, - "volumeto": 18305957.1 - }, - { - "time": 1531155600, - "close": 6697.09, - "high": 6712.33, - "low": 6677.61, - "open": 6710.13, - "volumefrom": 3340.27, - "volumeto": 22447322.98 - }, - { - "time": 1531159200, - "close": 6703.53, - "high": 6711.76, - "low": 6687.13, - "open": 6697.11, - "volumefrom": 1600.65, - "volumeto": 10753710.89 - }, - { - "time": 1531162800, - "close": 6716.41, - "high": 6719.86, - "low": 6698.36, - "open": 6703.53, - "volumefrom": 1347.06, - "volumeto": 9095142.98 - }, - { - "time": 1531166400, - "close": 6702.32, - "high": 6718.25, - "low": 6696.07, - "open": 6716.46, - "volumefrom": 1168.77, - "volumeto": 7905232.9 - }, - { - "time": 1531170000, - "close": 6722.62, - "high": 6724.47, - "low": 6696.55, - "open": 6702.49, - "volumefrom": 1400.96, - "volumeto": 9462854.68 - }, - { - "time": 1531173600, - "close": 6702.5, - "high": 6803.1, - "low": 6678.35, - "open": 6722.62, - "volumefrom": 5907.23, - "volumeto": 39855256.37 - }, - { - "time": 1531177200, - "close": 6668.84, - "high": 6707.67, - "low": 6625.58, - "open": 6702.5, - "volumefrom": 4680.63, - "volumeto": 31295949.44 - }, - { - "time": 1531180800, - "close": 6672.88, - "high": 6683.61, - "low": 6643.58, - "open": 6668.84, - "volumefrom": 2285.78, - "volumeto": 15281083.7 - }, - { - "time": 1531184400, - "close": 6655.67, - "high": 6678.06, - "low": 6655.67, - "open": 6672.88, - "volumefrom": 1541.5, - "volumeto": 10329286.72 - }, - { - "time": 1531188000, - "close": 6638.49, - "high": 6661.26, - "low": 6628.32, - "open": 6655.29, - "volumefrom": 2451.5, - "volumeto": 16344607.49 - }, - { - "time": 1531191600, - "close": 6639.2, - "high": 6656.36, - "low": 6635.24, - "open": 6638.53, - "volumefrom": 1457.85, - "volumeto": 9726365.65 - }, - { - "time": 1531195200, - "close": 6647.33, - "high": 6656.37, - "low": 6630.67, - "open": 6639.2, - "volumefrom": 1374.69, - "volumeto": 9185559.77 - }, - { - "time": 1531198800, - "close": 6629.46, - "high": 6652.19, - "low": 6628.04, - "open": 6647.33, - "volumefrom": 1614.18, - "volumeto": 10779743.34 - }, - { - "time": 1531202400, - "close": 6605.14, - "high": 6630.69, - "low": 6577.59, - "open": 6629.46, - "volumefrom": 5507.95, - "volumeto": 36449029.53 - }, - { - "time": 1531206000, - "close": 6606.67, - "high": 6612.45, - "low": 6592.53, - "open": 6604.38, - "volumefrom": 1739.22, - "volumeto": 11570214.54 - }, - { - "time": 1531209600, - "close": 6599.58, - "high": 6619.62, - "low": 6589.65, - "open": 6606.67, - "volumefrom": 1706.34, - "volumeto": 11336289.39 - }, - { - "time": 1531213200, - "close": 6448.91, - "high": 6599.53, - "low": 6443, - "open": 6599.53, - "volumefrom": 8469.1, - "volumeto": 55216106.52 - }, - { - "time": 1531216800, - "close": 6477.34, - "high": 6482.96, - "low": 6436.38, - "open": 6448.91, - "volumefrom": 4240.7, - "volumeto": 27484743.41 - }, - { - "time": 1531220400, - "close": 6366.8, - "high": 6477.35, - "low": 6331.28, - "open": 6477.32, - "volumefrom": 7837.09, - "volumeto": 50283889.36 - }, - { - "time": 1531224000, - "close": 6375.2, - "high": 6380.64, - "low": 6340.4, - "open": 6366.71, - "volumefrom": 3752.26, - "volumeto": 24006341.09 - }, - { - "time": 1531227600, - "close": 6389.33, - "high": 6399.65, - "low": 6362.89, - "open": 6375.47, - "volumefrom": 4272.56, - "volumeto": 27394449.92 - }, - { - "time": 1531231200, - "close": 6385.13, - "high": 6392.75, - "low": 6350.99, - "open": 6388.8, - "volumefrom": 3016.77, - "volumeto": 19301321.15 - }, - { - "time": 1531234800, - "close": 6386.13, - "high": 6392.51, - "low": 6374.96, - "open": 6384.5, - "volumefrom": 1937.58, - "volumeto": 12453402.15 - }, - { - "time": 1531238400, - "close": 6362.18, - "high": 6411.34, - "low": 6348.68, - "open": 6386.17, - "volumefrom": 3047.89, - "volumeto": 19482722.44 - }, - { - "time": 1531242000, - "close": 6374.69, - "high": 6382.17, - "low": 6350.48, - "open": 6362.18, - "volumefrom": 1884.61, - "volumeto": 12053801.31 - }, - { - "time": 1531245600, - "close": 6384.1, - "high": 6389.53, - "low": 6370.05, - "open": 6374.54, - "volumefrom": 1408.74, - "volumeto": 9045142.44 - }, - { - "time": 1531249200, - "close": 6377.46, - "high": 6385.16, - "low": 6366.94, - "open": 6384.1, - "volumefrom": 1585.99, - "volumeto": 10175354.9 - }, - { - "time": 1531252800, - "close": 6393.3, - "high": 6404.51, - "low": 6376.35, - "open": 6377.46, - "volumefrom": 2121.66, - "volumeto": 13609905.66 - }, - { - "time": 1531256400, - "close": 6376.49, - "high": 6409.04, - "low": 6375.92, - "open": 6393.3, - "volumefrom": 1721.38, - "volumeto": 11082330.97 - }, - { - "time": 1531260000, - "close": 6374.55, - "high": 6389.03, - "low": 6360.85, - "open": 6376.49, - "volumefrom": 1622.16, - "volumeto": 10390880.53 - }, - { - "time": 1531263600, - "close": 6306.85, - "high": 6374.98, - "low": 6277.23, - "open": 6374.57, - "volumefrom": 5915.82, - "volumeto": 37449570.74 - }, - { - "time": 1531267200, - "close": 6390.82, - "high": 6400.78, - "low": 6293.68, - "open": 6306.87, - "volumefrom": 4756.67, - "volumeto": 30336519.52 - }, - { - "time": 1531270800, - "close": 6371.22, - "high": 6395.35, - "low": 6363.64, - "open": 6391.5, - "volumefrom": 1582.43, - "volumeto": 10216055.54 - }, - { - "time": 1531274400, - "close": 6371.71, - "high": 6377.77, - "low": 6359.73, - "open": 6371.22, - "volumefrom": 996.81, - "volumeto": 6384469.16 - }, - { - "time": 1531278000, - "close": 6344.1, - "high": 6375.17, - "low": 6340.58, - "open": 6371.71, - "volumefrom": 1562.72, - "volumeto": 10012551.1 - }, - { - "time": 1531281600, - "close": 6354.55, - "high": 6365.17, - "low": 6328.48, - "open": 6344.1, - "volumefrom": 1924.77, - "volumeto": 12271764.03 - }, - { - "time": 1531285200, - "close": 6343.63, - "high": 6354.65, - "low": 6299.48, - "open": 6354.55, - "volumefrom": 2660.57, - "volumeto": 16960966.21 - }, - { - "time": 1531288800, - "close": 6322.76, - "high": 6346.18, - "low": 6293.82, - "open": 6343.7, - "volumefrom": 2773.56, - "volumeto": 17637160.77 - }, - { - "time": 1531292400, - "close": 6349.21, - "high": 6366.5, - "low": 6319.82, - "open": 6322.76, - "volumefrom": 2453.37, - "volumeto": 15690691.92 - }, - { - "time": 1531296000, - "close": 6378.34, - "high": 6379.27, - "low": 6348.49, - "open": 6349.19, - "volumefrom": 1655.21, - "volumeto": 10628920.08 - }, - { - "time": 1531299600, - "close": 6347, - "high": 6381.38, - "low": 6331.11, - "open": 6378.34, - "volumefrom": 2429.16, - "volumeto": 15529373.41 - }, - { - "time": 1531303200, - "close": 6352.68, - "high": 6357.32, - "low": 6334.49, - "open": 6347, - "volumefrom": 1985.1, - "volumeto": 12770865.32 - }, - { - "time": 1531306800, - "close": 6382.28, - "high": 6405.59, - "low": 6352.68, - "open": 6352.68, - "volumefrom": 2995.55, - "volumeto": 19354627.86 - }, - { - "time": 1531310400, - "close": 6382.92, - "high": 6394.94, - "low": 6367.72, - "open": 6382.28, - "volumefrom": 1714.28, - "volumeto": 11127145.13 - }, - { - "time": 1531314000, - "close": 6382.87, - "high": 6400.76, - "low": 6376.36, - "open": 6382.86, - "volumefrom": 3433.89, - "volumeto": 22184264.2 - }, - { - "time": 1531317600, - "close": 6379.44, - "high": 6386.68, - "low": 6353.64, - "open": 6382.87, - "volumefrom": 2410.65, - "volumeto": 15685628.46 - }, - { - "time": 1531321200, - "close": 6372.94, - "high": 6381.12, - "low": 6362.13, - "open": 6379.49, - "volumefrom": 1567.77, - "volumeto": 10133170.75 - }, - { - "time": 1531324800, - "close": 6358.27, - "high": 6377.44, - "low": 6343.04, - "open": 6373.01, - "volumefrom": 2425.19, - "volumeto": 15567078.81 - }, - { - "time": 1531328400, - "close": 6362.57, - "high": 6381.78, - "low": 6357.6, - "open": 6358.27, - "volumefrom": 1693.04, - "volumeto": 10895956.52 - }, - { - "time": 1531332000, - "close": 6362.43, - "high": 6364.89, - "low": 6349.89, - "open": 6362.72, - "volumefrom": 1000.74, - "volumeto": 6493713.28 - }, - { - "time": 1531335600, - "close": 6362.08, - "high": 6362.79, - "low": 6309.63, - "open": 6361.53, - "volumefrom": 3141.68, - "volumeto": 19980278.9 - }, - { - "time": 1531339200, - "close": 6370.26, - "high": 6387.51, - "low": 6358.77, - "open": 6362.08, - "volumefrom": 1514.63, - "volumeto": 9699597.33 - }, - { - "time": 1531342800, - "close": 6375.24, - "high": 6388.94, - "low": 6359.13, - "open": 6370.26, - "volumefrom": 1777.67, - "volumeto": 11389235.23 - }, - { - "time": 1531346400, - "close": 6380.98, - "high": 6390.48, - "low": 6363.56, - "open": 6375.24, - "volumefrom": 1208.06, - "volumeto": 7777066.7 - }, - { - "time": 1531350000, - "close": 6394.36, - "high": 6396.41, - "low": 6378.7, - "open": 6380.98, - "volumefrom": 1391.16, - "volumeto": 8940654.12 - }, - { - "time": 1531353600, - "close": 6380.65, - "high": 6394.93, - "low": 6368.83, - "open": 6394.36, - "volumefrom": 2531.03, - "volumeto": 16230024.11 - }, - { - "time": 1531357200, - "close": 6346.36, - "high": 6382.71, - "low": 6346.07, - "open": 6380.65, - "volumefrom": 1982.19, - "volumeto": 12695222.72 - }, - { - "time": 1531360800, - "close": 6343.99, - "high": 6356.45, - "low": 6319.1, - "open": 6346.36, - "volumefrom": 2057.78, - "volumeto": 13077618.21 - }, - { - "time": 1531364400, - "close": 6359.09, - "high": 6360.86, - "low": 6342.23, - "open": 6343.99, - "volumefrom": 897.82, - "volumeto": 5749807.24 - }, - { - "time": 1531368000, - "close": 6347.94, - "high": 6362.39, - "low": 6338.79, - "open": 6359.67, - "volumefrom": 1021.31, - "volumeto": 6544094.25 - }, - { - "time": 1531371600, - "close": 6340.93, - "high": 6348.42, - "low": 6315.71, - "open": 6348.04, - "volumefrom": 1119.58, - "volumeto": 7150597.29 - }, - { - "time": 1531375200, - "close": 6227.43, - "high": 6345.24, - "low": 6205.65, - "open": 6340.87, - "volumefrom": 8625.26, - "volumeto": 53848908.56 - }, - { - "time": 1531378800, - "close": 6190.11, - "high": 6233.46, - "low": 6167.51, - "open": 6227.43, - "volumefrom": 4634.39, - "volumeto": 28782141.54 - }, - { - "time": 1531382400, - "close": 6208.6, - "high": 6208.65, - "low": 6148.56, - "open": 6190.11, - "volumefrom": 5437.93, - "volumeto": 33669660.83 - }, - { - "time": 1531386000, - "close": 6196.07, - "high": 6214.48, - "low": 6188.93, - "open": 6208.6, - "volumefrom": 2931.86, - "volumeto": 18251378.85 - }, - { - "time": 1531389600, - "close": 6181.98, - "high": 6196.11, - "low": 6166.89, - "open": 6196.07, - "volumefrom": 2777.28, - "volumeto": 17214048.32 - }, - { - "time": 1531393200, - "close": 6174.11, - "high": 6191.39, - "low": 6149.38, - "open": 6181.47, - "volumefrom": 2418.54, - "volumeto": 14950407.61 - }, - { - "time": 1531396800, - "close": 6189.47, - "high": 6199.55, - "low": 6166.78, - "open": 6174.06, - "volumefrom": 2325.16, - "volumeto": 14428307.44 - }, - { - "time": 1531400400, - "close": 6191.18, - "high": 6196.09, - "low": 6171.38, - "open": 6189.47, - "volumefrom": 2226.22, - "volumeto": 13834016.16 - }, - { - "time": 1531404000, - "close": 6187.27, - "high": 6200.23, - "low": 6178.99, - "open": 6191.56, - "volumefrom": 2298.68, - "volumeto": 14317623.73 - }, - { - "time": 1531407600, - "close": 6168.96, - "high": 6191.39, - "low": 6166.56, - "open": 6187.43, - "volumefrom": 1457.68, - "volumeto": 9068058.02 - }, - { - "time": 1531411200, - "close": 6190.81, - "high": 6205.73, - "low": 6168.62, - "open": 6169, - "volumefrom": 2844.64, - "volumeto": 17702858.89 - }, - { - "time": 1531414800, - "close": 6190.09, - "high": 6201.86, - "low": 6174.34, - "open": 6190.81, - "volumefrom": 1676.25, - "volumeto": 10445786.42 - }, - { - "time": 1531418400, - "close": 6177.49, - "high": 6198.26, - "low": 6173, - "open": 6190.09, - "volumefrom": 1166.57, - "volumeto": 7312438.43 - }, - { - "time": 1531422000, - "close": 6183.09, - "high": 6188.18, - "low": 6160.27, - "open": 6177.53, - "volumefrom": 1704.06, - "volumeto": 10630764.8 - }, - { - "time": 1531425600, - "close": 6184.27, - "high": 6192.99, - "low": 6177.94, - "open": 6183.09, - "volumefrom": 1370.22, - "volumeto": 8563525.07 - }, - { - "time": 1531429200, - "close": 6175.03, - "high": 6185.31, - "low": 6163.74, - "open": 6184.27, - "volumefrom": 1293.66, - "volumeto": 8029608.45 - }, - { - "time": 1531432800, - "close": 6157.64, - "high": 6182.18, - "low": 6133.93, - "open": 6175.11, - "volumefrom": 2096.36, - "volumeto": 12961149.43 - }, - { - "time": 1531436400, - "close": 6253.6, - "high": 6270.29, - "low": 6084, - "open": 6157.64, - "volumefrom": 8660.9, - "volumeto": 53620136.26 - }, - { - "time": 1531440000, - "close": 6250.92, - "high": 6285.48, - "low": 6226.02, - "open": 6253.66, - "volumefrom": 3581.27, - "volumeto": 22511975.13 - }, - { - "time": 1531443600, - "close": 6234.36, - "high": 6257.81, - "low": 6233.23, - "open": 6250.92, - "volumefrom": 1133.42, - "volumeto": 7134076.13 - }, - { - "time": 1531447200, - "close": 6244.85, - "high": 6263.2, - "low": 6231.57, - "open": 6234.36, - "volumefrom": 1323.34, - "volumeto": 8326560.53 - }, - { - "time": 1531450800, - "close": 6252.59, - "high": 6257.13, - "low": 6244.34, - "open": 6244.85, - "volumefrom": 1213.84, - "volumeto": 7642556.22 - }, - { - "time": 1531454400, - "close": 6257.74, - "high": 6267.53, - "low": 6245.41, - "open": 6252.59, - "volumefrom": 2125.95, - "volumeto": 13403443.97 - }, - { - "time": 1531458000, - "close": 6256.03, - "high": 6263.1, - "low": 6247.57, - "open": 6257.74, - "volumefrom": 1300.98, - "volumeto": 8224538.73 - }, - { - "time": 1531461600, - "close": 6247.29, - "high": 6262, - "low": 6226.3, - "open": 6256.03, - "volumefrom": 1921.77, - "volumeto": 12119063.12 - }, - { - "time": 1531465200, - "close": 6251.84, - "high": 6259.07, - "low": 6243.49, - "open": 6247.29, - "volumefrom": 1528.39, - "volumeto": 9613110.57 - }, - { - "time": 1531468800, - "close": 6251.88, - "high": 6262.37, - "low": 6241.74, - "open": 6252.19, - "volumefrom": 1208.78, - "volumeto": 7617196.44 - }, - { - "time": 1531472400, - "close": 6247.12, - "high": 6259.72, - "low": 6245.05, - "open": 6251.88, - "volumefrom": 1194.4, - "volumeto": 7523559.74 - }, - { - "time": 1531476000, - "close": 6244.47, - "high": 6259.23, - "low": 6241.27, - "open": 6247.51, - "volumefrom": 1142.29, - "volumeto": 7201680.55 - }, - { - "time": 1531479600, - "close": 6253.97, - "high": 6264.53, - "low": 6241.99, - "open": 6244.47, - "volumefrom": 1027.83, - "volumeto": 6480971.52 - }, - { - "time": 1531483200, - "close": 6285.5, - "high": 6297.63, - "low": 6250.63, - "open": 6253.97, - "volumefrom": 2246.02, - "volumeto": 14149718.59 - }, - { - "time": 1531486800, - "close": 6249.46, - "high": 6286.98, - "low": 6246.99, - "open": 6285.5, - "volumefrom": 2180.22, - "volumeto": 13725967.18 - }, - { - "time": 1531490400, - "close": 6262.33, - "high": 6275.05, - "low": 6244.57, - "open": 6249.53, - "volumefrom": 1881.19, - "volumeto": 11834310.25 - }, - { - "time": 1531494000, - "close": 6271.75, - "high": 6277.86, - "low": 6253.82, - "open": 6262.43, - "volumefrom": 1777.67, - "volumeto": 11186281.6 - }, - { - "time": 1531497600, - "close": 6274.02, - "high": 6287.75, - "low": 6255.21, - "open": 6271.81, - "volumefrom": 1816.52, - "volumeto": 11447557.29 - }, - { - "time": 1531501200, - "close": 6279.63, - "high": 6284.74, - "low": 6264.41, - "open": 6274.02, - "volumefrom": 1391.67, - "volumeto": 8793588.78 - }, - { - "time": 1531504800, - "close": 6252.29, - "high": 6349.21, - "low": 6233.57, - "open": 6279.69, - "volumefrom": 5448.74, - "volumeto": 34330129.18 - }, - { - "time": 1531508400, - "close": 6184.59, - "high": 6255.66, - "low": 6180.03, - "open": 6252.27, - "volumefrom": 4946.27, - "volumeto": 30731212.69 - }, - { - "time": 1531512000, - "close": 6199.62, - "high": 6224.61, - "low": 6131.54, - "open": 6184.68, - "volumefrom": 5467.67, - "volumeto": 33890975.97 - }, - { - "time": 1531515600, - "close": 6228.26, - "high": 6240.15, - "low": 6196.46, - "open": 6199.62, - "volumefrom": 2459.39, - "volumeto": 15348000.07 - }, - { - "time": 1531519200, - "close": 6233.29, - "high": 6233.95, - "low": 6196.09, - "open": 6228.29, - "volumefrom": 1499.92, - "volumeto": 9371373.05 - }, - { - "time": 1531522800, - "close": 6229.83, - "high": 6255.45, - "low": 6212.3, - "open": 6233.29, - "volumefrom": 1146.89, - "volumeto": 7194424.68 - }, - { - "time": 1531526400, - "close": 6253.41, - "high": 6282.41, - "low": 6227.32, - "open": 6229.61, - "volumefrom": 1432.5, - "volumeto": 9020030.19 - }, - { - "time": 1531530000, - "close": 6246.63, - "high": 6288.44, - "low": 6230.07, - "open": 6253.41, - "volumefrom": 1279.28, - "volumeto": 8083202.45 - }, - { - "time": 1531533600, - "close": 6245.91, - "high": 6254.14, - "low": 6232.99, - "open": 6246.63, - "volumefrom": 581.84, - "volumeto": 3674209.8 - }, - { - "time": 1531537200, - "close": 6219.48, - "high": 6246.19, - "low": 6212.58, - "open": 6245.98, - "volumefrom": 859.69, - "volumeto": 5398704.65 - }, - { - "time": 1531540800, - "close": 6232.08, - "high": 6236.64, - "low": 6218.27, - "open": 6219.52, - "volumefrom": 573.05, - "volumeto": 3616448.09 - }, - { - "time": 1531544400, - "close": 6228.16, - "high": 6241.88, - "low": 6227.64, - "open": 6232.11, - "volumefrom": 670.38, - "volumeto": 4224030.4 - }, - { - "time": 1531548000, - "close": 6222.79, - "high": 6238.98, - "low": 6217.55, - "open": 6228.21, - "volumefrom": 579.04, - "volumeto": 3641279.22 - }, - { - "time": 1531551600, - "close": 6227.17, - "high": 6238.36, - "low": 6214.88, - "open": 6222.85, - "volumefrom": 882.9, - "volumeto": 5547597.69 - }, - { - "time": 1531555200, - "close": 6228.26, - "high": 6232.91, - "low": 6214.66, - "open": 6227.17, - "volumefrom": 988.93, - "volumeto": 6196216.22 - }, - { - "time": 1531558800, - "close": 6209.03, - "high": 6232.2, - "low": 6190.18, - "open": 6228.21, - "volumefrom": 1519.58, - "volumeto": 9477712.13 - }, - { - "time": 1531562400, - "close": 6226.68, - "high": 6228.3, - "low": 6208.77, - "open": 6209.03, - "volumefrom": 949.22, - "volumeto": 5943855.26 - }, - { - "time": 1531566000, - "close": 6243.66, - "high": 6253.98, - "low": 6226.6, - "open": 6226.69, - "volumefrom": 1139.81, - "volumeto": 7155449.92 - }, - { - "time": 1531569600, - "close": 6241.15, - "high": 6259.7, - "low": 6240.4, - "open": 6243.66, - "volumefrom": 1060.82, - "volumeto": 6674241.01 - }, - { - "time": 1531573200, - "close": 6246.82, - "high": 6246.93, - "low": 6234.35, - "open": 6241.15, - "volumefrom": 852.81, - "volumeto": 5362837.9 - }, - { - "time": 1531576800, - "close": 6248.17, - "high": 6256.15, - "low": 6240.57, - "open": 6246.82, - "volumefrom": 1054.42, - "volumeto": 6632178.53 - }, - { - "time": 1531580400, - "close": 6252.82, - "high": 6253.26, - "low": 6231.3, - "open": 6248.17, - "volumefrom": 1021.31, - "volumeto": 6443906.59 - }, - { - "time": 1531584000, - "close": 6275.9, - "high": 6277.6, - "low": 6250.64, - "open": 6252.94, - "volumefrom": 1887.92, - "volumeto": 11907239.67 - }, - { - "time": 1531587600, - "close": 6274.19, - "high": 6278.79, - "low": 6263.6, - "open": 6275.9, - "volumefrom": 1506.54, - "volumeto": 9519611.21 - }, - { - "time": 1531591200, - "close": 6250.67, - "high": 6300.03, - "low": 6234.92, - "open": 6274.19, - "volumefrom": 1357.48, - "volumeto": 8555230.38 - }, - { - "time": 1531594800, - "close": 6281.33, - "high": 6284.31, - "low": 6222.2, - "open": 6249.52, - "volumefrom": 1739.34, - "volumeto": 10926973.25 - }, - { - "time": 1531598400, - "close": 6286.29, - "high": 6332.46, - "low": 6236.22, - "open": 6281.79, - "volumefrom": 2968.3, - "volumeto": 18687874.52 - }, - { - "time": 1531602000, - "close": 6274.44, - "high": 6297.88, - "low": 6272.27, - "open": 6286.29, - "volumefrom": 887.98, - "volumeto": 5622374.02 - }, - { - "time": 1531605600, - "close": 6269.34, - "high": 6291.68, - "low": 6265.69, - "open": 6274.44, - "volumefrom": 1147.69, - "volumeto": 7235699.7 - }, - { - "time": 1531609200, - "close": 6268.75, - "high": 6280.66, - "low": 6268.3, - "open": 6269.52, - "volumefrom": 772.35, - "volumeto": 4869883.56 - }, - { - "time": 1531612800, - "close": 6264.31, - "high": 6275.51, - "low": 6245.75, - "open": 6268.32, - "volumefrom": 1234.34, - "volumeto": 7767086.98 - }, - { - "time": 1531616400, - "close": 6257.61, - "high": 6264.48, - "low": 6250.13, - "open": 6263.27, - "volumefrom": 840.15, - "volumeto": 5296830.09 - }, - { - "time": 1531620000, - "close": 6260.11, - "high": 6266.9, - "low": 6252.77, - "open": 6257.61, - "volumefrom": 811.65, - "volumeto": 5115797.52 - }, - { - "time": 1531623600, - "close": 6271.27, - "high": 6271.58, - "low": 6252.1, - "open": 6260.11, - "volumefrom": 877.38, - "volumeto": 5525653.33 - }, - { - "time": 1531627200, - "close": 6283.29, - "high": 6294.66, - "low": 6263.26, - "open": 6271.27, - "volumefrom": 922.72, - "volumeto": 5831628.28 - }, - { - "time": 1531630800, - "close": 6294.22, - "high": 6298.76, - "low": 6281, - "open": 6283.35, - "volumefrom": 857.09, - "volumeto": 5409531.91 - }, - { - "time": 1531634400, - "close": 6297.09, - "high": 6305.07, - "low": 6288.47, - "open": 6294.1, - "volumefrom": 857.95, - "volumeto": 5435064.44 - }, - { - "time": 1531638000, - "close": 6293.2, - "high": 6300.17, - "low": 6284.61, - "open": 6297.09, - "volumefrom": 944.41, - "volumeto": 5987349.78 - }, - { - "time": 1531641600, - "close": 6330.76, - "high": 6358.54, - "low": 6293.03, - "open": 6293.2, - "volumefrom": 2931.66, - "volumeto": 18574867.99 - }, - { - "time": 1531645200, - "close": 6318.81, - "high": 6344.93, - "low": 6311.71, - "open": 6329.99, - "volumefrom": 1429.16, - "volumeto": 9093020.53 - }, - { - "time": 1531648800, - "close": 6351.18, - "high": 6394.67, - "low": 6318.79, - "open": 6318.79, - "volumefrom": 3128.86, - "volumeto": 19933132.96 - }, - { - "time": 1531652400, - "close": 6365.58, - "high": 6374.77, - "low": 6348.07, - "open": 6351.18, - "volumefrom": 1806.75, - "volumeto": 11669063.36 - }, - { - "time": 1531656000, - "close": 6355.99, - "high": 6366.62, - "low": 6347.32, - "open": 6365.58, - "volumefrom": 1176.05, - "volumeto": 7516629.3 - }, - { - "time": 1531659600, - "close": 6348.13, - "high": 6355.99, - "low": 6348.13, - "open": 6355.99, - "volumefrom": 0, - "volumeto": 0 - } - ], - "TimeTo": 1531659600, - "TimeFrom": 1531054800, - "FirstValueInArray": true, - "ConversionType": { - "type": "direct", - "conversionSymbol": "" - } -} \ No newline at end of file + "Response": "Success", + "Type": 100, + "Aggregated": false, + "Data": [ + { + "time": 1531054800, + "close": 6736.36, + "high": 6747.33, + "low": 6712.34, + "open": 6731.21, + "volumefrom": 1808.95, + "volumeto": 12241907.1 + }, + { + "time": 1531058400, + "close": 6751.25, + "high": 6762.05, + "low": 6724.56, + "open": 6736.36, + "volumefrom": 2054.76, + "volumeto": 13903333.45 + }, + { + "time": 1531062000, + "close": 6743.55, + "high": 6768.96, + "low": 6742.58, + "open": 6751.4, + "volumefrom": 2015.38, + "volumeto": 13683443.72 + }, + { + "time": 1531065600, + "close": 6751.56, + "high": 6758.84, + "low": 6742.12, + "open": 6744.22, + "volumefrom": 1085.39, + "volumeto": 7381082.14 + }, + { + "time": 1531069200, + "close": 6752.21, + "high": 6764.38, + "low": 6745.59, + "open": 6751.59, + "volumefrom": 1274.54, + "volumeto": 8656031.41 + }, + { + "time": 1531072800, + "close": 6752.81, + "high": 6763.28, + "low": 6745.05, + "open": 6752.21, + "volumefrom": 1080.71, + "volumeto": 7331549.32 + }, + { + "time": 1531076400, + "close": 6758, + "high": 6760.83, + "low": 6742.41, + "open": 6752.81, + "volumefrom": 1043.28, + "volumeto": 7073338.91 + }, + { + "time": 1531080000, + "close": 6748.79, + "high": 6765.02, + "low": 6748.46, + "open": 6758, + "volumefrom": 1370.51, + "volumeto": 9343071.48 + }, + { + "time": 1531083600, + "close": 6755, + "high": 6761.91, + "low": 6743.97, + "open": 6748.79, + "volumefrom": 1123.85, + "volumeto": 7634206.13 + }, + { + "time": 1531087200, + "close": 6751.18, + "high": 6758.82, + "low": 6742.34, + "open": 6755.03, + "volumefrom": 1205.95, + "volumeto": 8191445.01 + }, + { + "time": 1531090800, + "close": 6707.38, + "high": 6752.81, + "low": 6684.15, + "open": 6751.94, + "volumefrom": 3513.7, + "volumeto": 23623766.17 + }, + { + "time": 1531094400, + "close": 6716.19, + "high": 6727.06, + "low": 6692.62, + "open": 6707.46, + "volumefrom": 1595.85, + "volumeto": 10764835.22 + }, + { + "time": 1531098000, + "close": 6704.18, + "high": 6717.29, + "low": 6694.84, + "open": 6716.19, + "volumefrom": 1298.23, + "volumeto": 8729204.25 + }, + { + "time": 1531101600, + "close": 6701.34, + "high": 6706, + "low": 6690.61, + "open": 6704.18, + "volumefrom": 1135.75, + "volumeto": 7632998.18 + }, + { + "time": 1531105200, + "close": 6712.3, + "high": 6717.81, + "low": 6699.16, + "open": 6701.3, + "volumefrom": 1233.99, + "volumeto": 8303151.48 + }, + { + "time": 1531108800, + "close": 6702.38, + "high": 6715.55, + "low": 6696.35, + "open": 6712.3, + "volumefrom": 1377.04, + "volumeto": 9269485.43 + }, + { + "time": 1531112400, + "close": 6702.77, + "high": 6706.54, + "low": 6694.53, + "open": 6702.58, + "volumefrom": 1233.8, + "volumeto": 8294586.37 + }, + { + "time": 1531116000, + "close": 6714.61, + "high": 6720.46, + "low": 6699.55, + "open": 6702.81, + "volumefrom": 1775.97, + "volumeto": 11958344.77 + }, + { + "time": 1531119600, + "close": 6725.73, + "high": 6739.25, + "low": 6706.88, + "open": 6714.66, + "volumefrom": 1529.24, + "volumeto": 10316826.29 + }, + { + "time": 1531123200, + "close": 6721.48, + "high": 6729.42, + "low": 6715.41, + "open": 6725.74, + "volumefrom": 1359.6, + "volumeto": 9174151.01 + }, + { + "time": 1531126800, + "close": 6690.8, + "high": 6721.65, + "low": 6670.9, + "open": 6721.48, + "volumefrom": 3344.4, + "volumeto": 22431823.64 + }, + { + "time": 1531130400, + "close": 6696.14, + "high": 6702.51, + "low": 6680.06, + "open": 6690.94, + "volumefrom": 1682.41, + "volumeto": 11306630.17 + }, + { + "time": 1531134000, + "close": 6723, + "high": 6746.24, + "low": 6694.68, + "open": 6695.2, + "volumefrom": 2631.14, + "volumeto": 17723558.62 + }, + { + "time": 1531137600, + "close": 6711.4, + "high": 6725.63, + "low": 6705.89, + "open": 6723.14, + "volumefrom": 1459.97, + "volumeto": 9866328.98 + }, + { + "time": 1531141200, + "close": 6727.41, + "high": 6739.11, + "low": 6709.93, + "open": 6711.49, + "volumefrom": 1868.24, + "volumeto": 12614000.31 + }, + { + "time": 1531144800, + "close": 6728.75, + "high": 6729.07, + "low": 6717, + "open": 6727.41, + "volumefrom": 1481.69, + "volumeto": 10032564.2 + }, + { + "time": 1531148400, + "close": 6722.57, + "high": 6737.7, + "low": 6721.18, + "open": 6728.75, + "volumefrom": 1594.78, + "volumeto": 10776229.52 + }, + { + "time": 1531152000, + "close": 6710.13, + "high": 6725, + "low": 6678.93, + "open": 6722.57, + "volumefrom": 2713.74, + "volumeto": 18305957.1 + }, + { + "time": 1531155600, + "close": 6697.09, + "high": 6712.33, + "low": 6677.61, + "open": 6710.13, + "volumefrom": 3340.27, + "volumeto": 22447322.98 + }, + { + "time": 1531159200, + "close": 6703.53, + "high": 6711.76, + "low": 6687.13, + "open": 6697.11, + "volumefrom": 1600.65, + "volumeto": 10753710.89 + }, + { + "time": 1531162800, + "close": 6716.41, + "high": 6719.86, + "low": 6698.36, + "open": 6703.53, + "volumefrom": 1347.06, + "volumeto": 9095142.98 + }, + { + "time": 1531166400, + "close": 6702.32, + "high": 6718.25, + "low": 6696.07, + "open": 6716.46, + "volumefrom": 1168.77, + "volumeto": 7905232.9 + }, + { + "time": 1531170000, + "close": 6722.62, + "high": 6724.47, + "low": 6696.55, + "open": 6702.49, + "volumefrom": 1400.96, + "volumeto": 9462854.68 + }, + { + "time": 1531173600, + "close": 6702.5, + "high": 6803.1, + "low": 6678.35, + "open": 6722.62, + "volumefrom": 5907.23, + "volumeto": 39855256.37 + }, + { + "time": 1531177200, + "close": 6668.84, + "high": 6707.67, + "low": 6625.58, + "open": 6702.5, + "volumefrom": 4680.63, + "volumeto": 31295949.44 + }, + { + "time": 1531180800, + "close": 6672.88, + "high": 6683.61, + "low": 6643.58, + "open": 6668.84, + "volumefrom": 2285.78, + "volumeto": 15281083.7 + }, + { + "time": 1531184400, + "close": 6655.67, + "high": 6678.06, + "low": 6655.67, + "open": 6672.88, + "volumefrom": 1541.5, + "volumeto": 10329286.72 + }, + { + "time": 1531188000, + "close": 6638.49, + "high": 6661.26, + "low": 6628.32, + "open": 6655.29, + "volumefrom": 2451.5, + "volumeto": 16344607.49 + }, + { + "time": 1531191600, + "close": 6639.2, + "high": 6656.36, + "low": 6635.24, + "open": 6638.53, + "volumefrom": 1457.85, + "volumeto": 9726365.65 + }, + { + "time": 1531195200, + "close": 6647.33, + "high": 6656.37, + "low": 6630.67, + "open": 6639.2, + "volumefrom": 1374.69, + "volumeto": 9185559.77 + }, + { + "time": 1531198800, + "close": 6629.46, + "high": 6652.19, + "low": 6628.04, + "open": 6647.33, + "volumefrom": 1614.18, + "volumeto": 10779743.34 + }, + { + "time": 1531202400, + "close": 6605.14, + "high": 6630.69, + "low": 6577.59, + "open": 6629.46, + "volumefrom": 5507.95, + "volumeto": 36449029.53 + }, + { + "time": 1531206000, + "close": 6606.67, + "high": 6612.45, + "low": 6592.53, + "open": 6604.38, + "volumefrom": 1739.22, + "volumeto": 11570214.54 + }, + { + "time": 1531209600, + "close": 6599.58, + "high": 6619.62, + "low": 6589.65, + "open": 6606.67, + "volumefrom": 1706.34, + "volumeto": 11336289.39 + }, + { + "time": 1531213200, + "close": 6448.91, + "high": 6599.53, + "low": 6443, + "open": 6599.53, + "volumefrom": 8469.1, + "volumeto": 55216106.52 + }, + { + "time": 1531216800, + "close": 6477.34, + "high": 6482.96, + "low": 6436.38, + "open": 6448.91, + "volumefrom": 4240.7, + "volumeto": 27484743.41 + }, + { + "time": 1531220400, + "close": 6366.8, + "high": 6477.35, + "low": 6331.28, + "open": 6477.32, + "volumefrom": 7837.09, + "volumeto": 50283889.36 + }, + { + "time": 1531224000, + "close": 6375.2, + "high": 6380.64, + "low": 6340.4, + "open": 6366.71, + "volumefrom": 3752.26, + "volumeto": 24006341.09 + }, + { + "time": 1531227600, + "close": 6389.33, + "high": 6399.65, + "low": 6362.89, + "open": 6375.47, + "volumefrom": 4272.56, + "volumeto": 27394449.92 + }, + { + "time": 1531231200, + "close": 6385.13, + "high": 6392.75, + "low": 6350.99, + "open": 6388.8, + "volumefrom": 3016.77, + "volumeto": 19301321.15 + }, + { + "time": 1531234800, + "close": 6386.13, + "high": 6392.51, + "low": 6374.96, + "open": 6384.5, + "volumefrom": 1937.58, + "volumeto": 12453402.15 + }, + { + "time": 1531238400, + "close": 6362.18, + "high": 6411.34, + "low": 6348.68, + "open": 6386.17, + "volumefrom": 3047.89, + "volumeto": 19482722.44 + }, + { + "time": 1531242000, + "close": 6374.69, + "high": 6382.17, + "low": 6350.48, + "open": 6362.18, + "volumefrom": 1884.61, + "volumeto": 12053801.31 + }, + { + "time": 1531245600, + "close": 6384.1, + "high": 6389.53, + "low": 6370.05, + "open": 6374.54, + "volumefrom": 1408.74, + "volumeto": 9045142.44 + }, + { + "time": 1531249200, + "close": 6377.46, + "high": 6385.16, + "low": 6366.94, + "open": 6384.1, + "volumefrom": 1585.99, + "volumeto": 10175354.9 + }, + { + "time": 1531252800, + "close": 6393.3, + "high": 6404.51, + "low": 6376.35, + "open": 6377.46, + "volumefrom": 2121.66, + "volumeto": 13609905.66 + }, + { + "time": 1531256400, + "close": 6376.49, + "high": 6409.04, + "low": 6375.92, + "open": 6393.3, + "volumefrom": 1721.38, + "volumeto": 11082330.97 + }, + { + "time": 1531260000, + "close": 6374.55, + "high": 6389.03, + "low": 6360.85, + "open": 6376.49, + "volumefrom": 1622.16, + "volumeto": 10390880.53 + }, + { + "time": 1531263600, + "close": 6306.85, + "high": 6374.98, + "low": 6277.23, + "open": 6374.57, + "volumefrom": 5915.82, + "volumeto": 37449570.74 + }, + { + "time": 1531267200, + "close": 6390.82, + "high": 6400.78, + "low": 6293.68, + "open": 6306.87, + "volumefrom": 4756.67, + "volumeto": 30336519.52 + }, + { + "time": 1531270800, + "close": 6371.22, + "high": 6395.35, + "low": 6363.64, + "open": 6391.5, + "volumefrom": 1582.43, + "volumeto": 10216055.54 + }, + { + "time": 1531274400, + "close": 6371.71, + "high": 6377.77, + "low": 6359.73, + "open": 6371.22, + "volumefrom": 996.81, + "volumeto": 6384469.16 + }, + { + "time": 1531278000, + "close": 6344.1, + "high": 6375.17, + "low": 6340.58, + "open": 6371.71, + "volumefrom": 1562.72, + "volumeto": 10012551.1 + }, + { + "time": 1531281600, + "close": 6354.55, + "high": 6365.17, + "low": 6328.48, + "open": 6344.1, + "volumefrom": 1924.77, + "volumeto": 12271764.03 + }, + { + "time": 1531285200, + "close": 6343.63, + "high": 6354.65, + "low": 6299.48, + "open": 6354.55, + "volumefrom": 2660.57, + "volumeto": 16960966.21 + }, + { + "time": 1531288800, + "close": 6322.76, + "high": 6346.18, + "low": 6293.82, + "open": 6343.7, + "volumefrom": 2773.56, + "volumeto": 17637160.77 + }, + { + "time": 1531292400, + "close": 6349.21, + "high": 6366.5, + "low": 6319.82, + "open": 6322.76, + "volumefrom": 2453.37, + "volumeto": 15690691.92 + }, + { + "time": 1531296000, + "close": 6378.34, + "high": 6379.27, + "low": 6348.49, + "open": 6349.19, + "volumefrom": 1655.21, + "volumeto": 10628920.08 + }, + { + "time": 1531299600, + "close": 6347, + "high": 6381.38, + "low": 6331.11, + "open": 6378.34, + "volumefrom": 2429.16, + "volumeto": 15529373.41 + }, + { + "time": 1531303200, + "close": 6352.68, + "high": 6357.32, + "low": 6334.49, + "open": 6347, + "volumefrom": 1985.1, + "volumeto": 12770865.32 + }, + { + "time": 1531306800, + "close": 6382.28, + "high": 6405.59, + "low": 6352.68, + "open": 6352.68, + "volumefrom": 2995.55, + "volumeto": 19354627.86 + }, + { + "time": 1531310400, + "close": 6382.92, + "high": 6394.94, + "low": 6367.72, + "open": 6382.28, + "volumefrom": 1714.28, + "volumeto": 11127145.13 + }, + { + "time": 1531314000, + "close": 6382.87, + "high": 6400.76, + "low": 6376.36, + "open": 6382.86, + "volumefrom": 3433.89, + "volumeto": 22184264.2 + }, + { + "time": 1531317600, + "close": 6379.44, + "high": 6386.68, + "low": 6353.64, + "open": 6382.87, + "volumefrom": 2410.65, + "volumeto": 15685628.46 + }, + { + "time": 1531321200, + "close": 6372.94, + "high": 6381.12, + "low": 6362.13, + "open": 6379.49, + "volumefrom": 1567.77, + "volumeto": 10133170.75 + }, + { + "time": 1531324800, + "close": 6358.27, + "high": 6377.44, + "low": 6343.04, + "open": 6373.01, + "volumefrom": 2425.19, + "volumeto": 15567078.81 + }, + { + "time": 1531328400, + "close": 6362.57, + "high": 6381.78, + "low": 6357.6, + "open": 6358.27, + "volumefrom": 1693.04, + "volumeto": 10895956.52 + }, + { + "time": 1531332000, + "close": 6362.43, + "high": 6364.89, + "low": 6349.89, + "open": 6362.72, + "volumefrom": 1000.74, + "volumeto": 6493713.28 + }, + { + "time": 1531335600, + "close": 6362.08, + "high": 6362.79, + "low": 6309.63, + "open": 6361.53, + "volumefrom": 3141.68, + "volumeto": 19980278.9 + }, + { + "time": 1531339200, + "close": 6370.26, + "high": 6387.51, + "low": 6358.77, + "open": 6362.08, + "volumefrom": 1514.63, + "volumeto": 9699597.33 + }, + { + "time": 1531342800, + "close": 6375.24, + "high": 6388.94, + "low": 6359.13, + "open": 6370.26, + "volumefrom": 1777.67, + "volumeto": 11389235.23 + }, + { + "time": 1531346400, + "close": 6380.98, + "high": 6390.48, + "low": 6363.56, + "open": 6375.24, + "volumefrom": 1208.06, + "volumeto": 7777066.7 + }, + { + "time": 1531350000, + "close": 6394.36, + "high": 6396.41, + "low": 6378.7, + "open": 6380.98, + "volumefrom": 1391.16, + "volumeto": 8940654.12 + }, + { + "time": 1531353600, + "close": 6380.65, + "high": 6394.93, + "low": 6368.83, + "open": 6394.36, + "volumefrom": 2531.03, + "volumeto": 16230024.11 + }, + { + "time": 1531357200, + "close": 6346.36, + "high": 6382.71, + "low": 6346.07, + "open": 6380.65, + "volumefrom": 1982.19, + "volumeto": 12695222.72 + }, + { + "time": 1531360800, + "close": 6343.99, + "high": 6356.45, + "low": 6319.1, + "open": 6346.36, + "volumefrom": 2057.78, + "volumeto": 13077618.21 + }, + { + "time": 1531364400, + "close": 6359.09, + "high": 6360.86, + "low": 6342.23, + "open": 6343.99, + "volumefrom": 897.82, + "volumeto": 5749807.24 + }, + { + "time": 1531368000, + "close": 6347.94, + "high": 6362.39, + "low": 6338.79, + "open": 6359.67, + "volumefrom": 1021.31, + "volumeto": 6544094.25 + }, + { + "time": 1531371600, + "close": 6340.93, + "high": 6348.42, + "low": 6315.71, + "open": 6348.04, + "volumefrom": 1119.58, + "volumeto": 7150597.29 + }, + { + "time": 1531375200, + "close": 6227.43, + "high": 6345.24, + "low": 6205.65, + "open": 6340.87, + "volumefrom": 8625.26, + "volumeto": 53848908.56 + }, + { + "time": 1531378800, + "close": 6190.11, + "high": 6233.46, + "low": 6167.51, + "open": 6227.43, + "volumefrom": 4634.39, + "volumeto": 28782141.54 + }, + { + "time": 1531382400, + "close": 6208.6, + "high": 6208.65, + "low": 6148.56, + "open": 6190.11, + "volumefrom": 5437.93, + "volumeto": 33669660.83 + }, + { + "time": 1531386000, + "close": 6196.07, + "high": 6214.48, + "low": 6188.93, + "open": 6208.6, + "volumefrom": 2931.86, + "volumeto": 18251378.85 + }, + { + "time": 1531389600, + "close": 6181.98, + "high": 6196.11, + "low": 6166.89, + "open": 6196.07, + "volumefrom": 2777.28, + "volumeto": 17214048.32 + }, + { + "time": 1531393200, + "close": 6174.11, + "high": 6191.39, + "low": 6149.38, + "open": 6181.47, + "volumefrom": 2418.54, + "volumeto": 14950407.61 + }, + { + "time": 1531396800, + "close": 6189.47, + "high": 6199.55, + "low": 6166.78, + "open": 6174.06, + "volumefrom": 2325.16, + "volumeto": 14428307.44 + }, + { + "time": 1531400400, + "close": 6191.18, + "high": 6196.09, + "low": 6171.38, + "open": 6189.47, + "volumefrom": 2226.22, + "volumeto": 13834016.16 + }, + { + "time": 1531404000, + "close": 6187.27, + "high": 6200.23, + "low": 6178.99, + "open": 6191.56, + "volumefrom": 2298.68, + "volumeto": 14317623.73 + }, + { + "time": 1531407600, + "close": 6168.96, + "high": 6191.39, + "low": 6166.56, + "open": 6187.43, + "volumefrom": 1457.68, + "volumeto": 9068058.02 + }, + { + "time": 1531411200, + "close": 6190.81, + "high": 6205.73, + "low": 6168.62, + "open": 6169, + "volumefrom": 2844.64, + "volumeto": 17702858.89 + }, + { + "time": 1531414800, + "close": 6190.09, + "high": 6201.86, + "low": 6174.34, + "open": 6190.81, + "volumefrom": 1676.25, + "volumeto": 10445786.42 + }, + { + "time": 1531418400, + "close": 6177.49, + "high": 6198.26, + "low": 6173, + "open": 6190.09, + "volumefrom": 1166.57, + "volumeto": 7312438.43 + }, + { + "time": 1531422000, + "close": 6183.09, + "high": 6188.18, + "low": 6160.27, + "open": 6177.53, + "volumefrom": 1704.06, + "volumeto": 10630764.8 + }, + { + "time": 1531425600, + "close": 6184.27, + "high": 6192.99, + "low": 6177.94, + "open": 6183.09, + "volumefrom": 1370.22, + "volumeto": 8563525.07 + }, + { + "time": 1531429200, + "close": 6175.03, + "high": 6185.31, + "low": 6163.74, + "open": 6184.27, + "volumefrom": 1293.66, + "volumeto": 8029608.45 + }, + { + "time": 1531432800, + "close": 6157.64, + "high": 6182.18, + "low": 6133.93, + "open": 6175.11, + "volumefrom": 2096.36, + "volumeto": 12961149.43 + }, + { + "time": 1531436400, + "close": 6253.6, + "high": 6270.29, + "low": 6084, + "open": 6157.64, + "volumefrom": 8660.9, + "volumeto": 53620136.26 + }, + { + "time": 1531440000, + "close": 6250.92, + "high": 6285.48, + "low": 6226.02, + "open": 6253.66, + "volumefrom": 3581.27, + "volumeto": 22511975.13 + }, + { + "time": 1531443600, + "close": 6234.36, + "high": 6257.81, + "low": 6233.23, + "open": 6250.92, + "volumefrom": 1133.42, + "volumeto": 7134076.13 + }, + { + "time": 1531447200, + "close": 6244.85, + "high": 6263.2, + "low": 6231.57, + "open": 6234.36, + "volumefrom": 1323.34, + "volumeto": 8326560.53 + }, + { + "time": 1531450800, + "close": 6252.59, + "high": 6257.13, + "low": 6244.34, + "open": 6244.85, + "volumefrom": 1213.84, + "volumeto": 7642556.22 + }, + { + "time": 1531454400, + "close": 6257.74, + "high": 6267.53, + "low": 6245.41, + "open": 6252.59, + "volumefrom": 2125.95, + "volumeto": 13403443.97 + }, + { + "time": 1531458000, + "close": 6256.03, + "high": 6263.1, + "low": 6247.57, + "open": 6257.74, + "volumefrom": 1300.98, + "volumeto": 8224538.73 + }, + { + "time": 1531461600, + "close": 6247.29, + "high": 6262, + "low": 6226.3, + "open": 6256.03, + "volumefrom": 1921.77, + "volumeto": 12119063.12 + }, + { + "time": 1531465200, + "close": 6251.84, + "high": 6259.07, + "low": 6243.49, + "open": 6247.29, + "volumefrom": 1528.39, + "volumeto": 9613110.57 + }, + { + "time": 1531468800, + "close": 6251.88, + "high": 6262.37, + "low": 6241.74, + "open": 6252.19, + "volumefrom": 1208.78, + "volumeto": 7617196.44 + }, + { + "time": 1531472400, + "close": 6247.12, + "high": 6259.72, + "low": 6245.05, + "open": 6251.88, + "volumefrom": 1194.4, + "volumeto": 7523559.74 + }, + { + "time": 1531476000, + "close": 6244.47, + "high": 6259.23, + "low": 6241.27, + "open": 6247.51, + "volumefrom": 1142.29, + "volumeto": 7201680.55 + }, + { + "time": 1531479600, + "close": 6253.97, + "high": 6264.53, + "low": 6241.99, + "open": 6244.47, + "volumefrom": 1027.83, + "volumeto": 6480971.52 + }, + { + "time": 1531483200, + "close": 6285.5, + "high": 6297.63, + "low": 6250.63, + "open": 6253.97, + "volumefrom": 2246.02, + "volumeto": 14149718.59 + }, + { + "time": 1531486800, + "close": 6249.46, + "high": 6286.98, + "low": 6246.99, + "open": 6285.5, + "volumefrom": 2180.22, + "volumeto": 13725967.18 + }, + { + "time": 1531490400, + "close": 6262.33, + "high": 6275.05, + "low": 6244.57, + "open": 6249.53, + "volumefrom": 1881.19, + "volumeto": 11834310.25 + }, + { + "time": 1531494000, + "close": 6271.75, + "high": 6277.86, + "low": 6253.82, + "open": 6262.43, + "volumefrom": 1777.67, + "volumeto": 11186281.6 + }, + { + "time": 1531497600, + "close": 6274.02, + "high": 6287.75, + "low": 6255.21, + "open": 6271.81, + "volumefrom": 1816.52, + "volumeto": 11447557.29 + }, + { + "time": 1531501200, + "close": 6279.63, + "high": 6284.74, + "low": 6264.41, + "open": 6274.02, + "volumefrom": 1391.67, + "volumeto": 8793588.78 + }, + { + "time": 1531504800, + "close": 6252.29, + "high": 6349.21, + "low": 6233.57, + "open": 6279.69, + "volumefrom": 5448.74, + "volumeto": 34330129.18 + }, + { + "time": 1531508400, + "close": 6184.59, + "high": 6255.66, + "low": 6180.03, + "open": 6252.27, + "volumefrom": 4946.27, + "volumeto": 30731212.69 + }, + { + "time": 1531512000, + "close": 6199.62, + "high": 6224.61, + "low": 6131.54, + "open": 6184.68, + "volumefrom": 5467.67, + "volumeto": 33890975.97 + }, + { + "time": 1531515600, + "close": 6228.26, + "high": 6240.15, + "low": 6196.46, + "open": 6199.62, + "volumefrom": 2459.39, + "volumeto": 15348000.07 + }, + { + "time": 1531519200, + "close": 6233.29, + "high": 6233.95, + "low": 6196.09, + "open": 6228.29, + "volumefrom": 1499.92, + "volumeto": 9371373.05 + }, + { + "time": 1531522800, + "close": 6229.83, + "high": 6255.45, + "low": 6212.3, + "open": 6233.29, + "volumefrom": 1146.89, + "volumeto": 7194424.68 + }, + { + "time": 1531526400, + "close": 6253.41, + "high": 6282.41, + "low": 6227.32, + "open": 6229.61, + "volumefrom": 1432.5, + "volumeto": 9020030.19 + }, + { + "time": 1531530000, + "close": 6246.63, + "high": 6288.44, + "low": 6230.07, + "open": 6253.41, + "volumefrom": 1279.28, + "volumeto": 8083202.45 + }, + { + "time": 1531533600, + "close": 6245.91, + "high": 6254.14, + "low": 6232.99, + "open": 6246.63, + "volumefrom": 581.84, + "volumeto": 3674209.8 + }, + { + "time": 1531537200, + "close": 6219.48, + "high": 6246.19, + "low": 6212.58, + "open": 6245.98, + "volumefrom": 859.69, + "volumeto": 5398704.65 + }, + { + "time": 1531540800, + "close": 6232.08, + "high": 6236.64, + "low": 6218.27, + "open": 6219.52, + "volumefrom": 573.05, + "volumeto": 3616448.09 + }, + { + "time": 1531544400, + "close": 6228.16, + "high": 6241.88, + "low": 6227.64, + "open": 6232.11, + "volumefrom": 670.38, + "volumeto": 4224030.4 + }, + { + "time": 1531548000, + "close": 6222.79, + "high": 6238.98, + "low": 6217.55, + "open": 6228.21, + "volumefrom": 579.04, + "volumeto": 3641279.22 + }, + { + "time": 1531551600, + "close": 6227.17, + "high": 6238.36, + "low": 6214.88, + "open": 6222.85, + "volumefrom": 882.9, + "volumeto": 5547597.69 + }, + { + "time": 1531555200, + "close": 6228.26, + "high": 6232.91, + "low": 6214.66, + "open": 6227.17, + "volumefrom": 988.93, + "volumeto": 6196216.22 + }, + { + "time": 1531558800, + "close": 6209.03, + "high": 6232.2, + "low": 6190.18, + "open": 6228.21, + "volumefrom": 1519.58, + "volumeto": 9477712.13 + }, + { + "time": 1531562400, + "close": 6226.68, + "high": 6228.3, + "low": 6208.77, + "open": 6209.03, + "volumefrom": 949.22, + "volumeto": 5943855.26 + }, + { + "time": 1531566000, + "close": 6243.66, + "high": 6253.98, + "low": 6226.6, + "open": 6226.69, + "volumefrom": 1139.81, + "volumeto": 7155449.92 + }, + { + "time": 1531569600, + "close": 6241.15, + "high": 6259.7, + "low": 6240.4, + "open": 6243.66, + "volumefrom": 1060.82, + "volumeto": 6674241.01 + }, + { + "time": 1531573200, + "close": 6246.82, + "high": 6246.93, + "low": 6234.35, + "open": 6241.15, + "volumefrom": 852.81, + "volumeto": 5362837.9 + }, + { + "time": 1531576800, + "close": 6248.17, + "high": 6256.15, + "low": 6240.57, + "open": 6246.82, + "volumefrom": 1054.42, + "volumeto": 6632178.53 + }, + { + "time": 1531580400, + "close": 6252.82, + "high": 6253.26, + "low": 6231.3, + "open": 6248.17, + "volumefrom": 1021.31, + "volumeto": 6443906.59 + }, + { + "time": 1531584000, + "close": 6275.9, + "high": 6277.6, + "low": 6250.64, + "open": 6252.94, + "volumefrom": 1887.92, + "volumeto": 11907239.67 + }, + { + "time": 1531587600, + "close": 6274.19, + "high": 6278.79, + "low": 6263.6, + "open": 6275.9, + "volumefrom": 1506.54, + "volumeto": 9519611.21 + }, + { + "time": 1531591200, + "close": 6250.67, + "high": 6300.03, + "low": 6234.92, + "open": 6274.19, + "volumefrom": 1357.48, + "volumeto": 8555230.38 + }, + { + "time": 1531594800, + "close": 6281.33, + "high": 6284.31, + "low": 6222.2, + "open": 6249.52, + "volumefrom": 1739.34, + "volumeto": 10926973.25 + }, + { + "time": 1531598400, + "close": 6286.29, + "high": 6332.46, + "low": 6236.22, + "open": 6281.79, + "volumefrom": 2968.3, + "volumeto": 18687874.52 + }, + { + "time": 1531602000, + "close": 6274.44, + "high": 6297.88, + "low": 6272.27, + "open": 6286.29, + "volumefrom": 887.98, + "volumeto": 5622374.02 + }, + { + "time": 1531605600, + "close": 6269.34, + "high": 6291.68, + "low": 6265.69, + "open": 6274.44, + "volumefrom": 1147.69, + "volumeto": 7235699.7 + }, + { + "time": 1531609200, + "close": 6268.75, + "high": 6280.66, + "low": 6268.3, + "open": 6269.52, + "volumefrom": 772.35, + "volumeto": 4869883.56 + }, + { + "time": 1531612800, + "close": 6264.31, + "high": 6275.51, + "low": 6245.75, + "open": 6268.32, + "volumefrom": 1234.34, + "volumeto": 7767086.98 + }, + { + "time": 1531616400, + "close": 6257.61, + "high": 6264.48, + "low": 6250.13, + "open": 6263.27, + "volumefrom": 840.15, + "volumeto": 5296830.09 + }, + { + "time": 1531620000, + "close": 6260.11, + "high": 6266.9, + "low": 6252.77, + "open": 6257.61, + "volumefrom": 811.65, + "volumeto": 5115797.52 + }, + { + "time": 1531623600, + "close": 6271.27, + "high": 6271.58, + "low": 6252.1, + "open": 6260.11, + "volumefrom": 877.38, + "volumeto": 5525653.33 + }, + { + "time": 1531627200, + "close": 6283.29, + "high": 6294.66, + "low": 6263.26, + "open": 6271.27, + "volumefrom": 922.72, + "volumeto": 5831628.28 + }, + { + "time": 1531630800, + "close": 6294.22, + "high": 6298.76, + "low": 6281, + "open": 6283.35, + "volumefrom": 857.09, + "volumeto": 5409531.91 + }, + { + "time": 1531634400, + "close": 6297.09, + "high": 6305.07, + "low": 6288.47, + "open": 6294.1, + "volumefrom": 857.95, + "volumeto": 5435064.44 + }, + { + "time": 1531638000, + "close": 6293.2, + "high": 6300.17, + "low": 6284.61, + "open": 6297.09, + "volumefrom": 944.41, + "volumeto": 5987349.78 + }, + { + "time": 1531641600, + "close": 6330.76, + "high": 6358.54, + "low": 6293.03, + "open": 6293.2, + "volumefrom": 2931.66, + "volumeto": 18574867.99 + }, + { + "time": 1531645200, + "close": 6318.81, + "high": 6344.93, + "low": 6311.71, + "open": 6329.99, + "volumefrom": 1429.16, + "volumeto": 9093020.53 + }, + { + "time": 1531648800, + "close": 6351.18, + "high": 6394.67, + "low": 6318.79, + "open": 6318.79, + "volumefrom": 3128.86, + "volumeto": 19933132.96 + }, + { + "time": 1531652400, + "close": 6365.58, + "high": 6374.77, + "low": 6348.07, + "open": 6351.18, + "volumefrom": 1806.75, + "volumeto": 11669063.36 + }, + { + "time": 1531656000, + "close": 6355.99, + "high": 6366.62, + "low": 6347.32, + "open": 6365.58, + "volumefrom": 1176.05, + "volumeto": 7516629.3 + }, + { + "time": 1531659600, + "close": 6348.13, + "high": 6355.99, + "low": 6348.13, + "open": 6355.99, + "volumefrom": 0, + "volumeto": 0 + } + ], + "TimeTo": 1531659600, + "TimeFrom": 1531054800, + "FirstValueInArray": true, + "ConversionType": { + "type": "direct", + "conversionSymbol": "" + } +} diff --git a/examples/crypto-chart/package.json b/examples/crypto-chart/package.json index 59f39d3432..bb86eff3a1 100644 --- a/examples/crypto-chart/package.json +++ b/examples/crypto-chart/package.json @@ -1,49 +1,49 @@ { - "name": "@example/crypto-chart", - "private": true, - "version": "0.0.1", - "description": "Basic crypto-currency candle chart with multiple moving averages plots", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/hiccup-svg": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/resolve-map": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^", - "@thi.ng/transducers-stats": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom-components", - "hiccup-svg", - "math", - "resolve-map", - "rstream", - "strings", - "transducers", - "transducers-hdom", - "transducers-stats" - ], - "screenshot": "examples/crypto-chart.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/crypto-chart", + "private": true, + "version": "0.0.1", + "description": "Basic crypto-currency candle chart with multiple moving averages plots", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/hiccup-svg": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/resolve-map": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^", + "@thi.ng/transducers-stats": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom-components", + "hiccup-svg", + "math", + "resolve-map", + "rstream", + "strings", + "transducers", + "transducers-hdom", + "transducers-stats" + ], + "screenshot": "examples/crypto-chart.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/crypto-chart/src/index.ts b/examples/crypto-chart/src/index.ts index 6d6376db72..85f9d5426f 100644 --- a/examples/crypto-chart/src/index.ts +++ b/examples/crypto-chart/src/index.ts @@ -1,7 +1,7 @@ import type { Fn, IObjectOf } from "@thi.ng/api"; import { - dropdown, - type DropDownOption, + dropdown, + type DropDownOption, } from "@thi.ng/hdom-components/dropdown"; import { group } from "@thi.ng/hiccup-svg/group"; import { line } from "@thi.ng/hiccup-svg/line"; @@ -48,54 +48,54 @@ import { transduce } from "@thi.ng/transducers/transduce"; // were any relevant upstream value changes. interface OHLC { - open: number; - high: number; - low: number; - close: number; - time: number; + open: number; + high: number; + low: number; + close: number; + time: number; } interface MarketResponse { - ohlc: OHLC[]; + ohlc: OHLC[]; } interface Stats { - ohlc: OHLC[]; - period: number; - min: number; - max: number; - tbounds: number[]; - sma: [number, number[]][]; + ohlc: OHLC[]; + period: number; + min: number; + max: number; + tbounds: number[]; + sma: [number, number[]][]; } // constant definitions // supported chart (and API) timeframes const TIMEFRAMES: IObjectOf = { - 1: "Minute", - 60: "Hour", - 1440: "Day", + 1: "Minute", + 60: "Hour", + 1440: "Day", }; // supported symbol pairs const SYMBOL_PAIRS: DropDownOption[] = [ - ["ADAUSD", "ADA-USD"], - ["BTCUSD", "BTC-USD"], - ["ETHUSD", "ETH-USD"], - ["LTCUSD", "LTC-USD"], - ["XLMUSD", "XLM-USD"], - ["XMRUSD", "XMR-USD"], - ["XTZUSD", "XTZ-USD"], + ["ADAUSD", "ADA-USD"], + ["BTCUSD", "BTC-USD"], + ["ETHUSD", "ETH-USD"], + ["LTCUSD", "LTC-USD"], + ["XLMUSD", "XLM-USD"], + ["XMRUSD", "XMR-USD"], + ["XTZUSD", "XTZ-USD"], ]; const MA_MODES: IObjectOf<{ - fn: Fn>; - label: string; + fn: Fn>; + label: string; }> = { - ema: { fn: ema, label: "Exponential" }, - hma: { fn: hma, label: "Hull" }, - sma: { fn: sma, label: "Simple" }, - wma: { fn: wma, label: "Weighted" }, + ema: { fn: ema, label: "Exponential" }, + hma: { fn: hma, label: "Hull" }, + sma: { fn: sma, label: "Simple" }, + wma: { fn: wma, label: "Weighted" }, }; // chart settings @@ -104,100 +104,100 @@ const MARGIN_Y = 60; const DAY = 60 * 60 * 24; const TIME_TICKS: IObjectOf = { - 1: 15 * 60, - 60: DAY, - 1440: DAY * 14, + 1: 15 * 60, + 60: DAY, + 1440: DAY * 14, }; const TIME_FORMATS: IObjectOf> = { - 1: (t: number) => { - const d = new Date(t * 1000); - return `${Z2(d.getUTCHours())}:${Z2(d.getUTCMinutes())}`; - }, - 60: (t: number) => { - const d = new Date(t * 1000); - return `${d.getUTCFullYear()}-${Z2(d.getUTCMonth() + 1)}-${Z2( - d.getUTCDate() - )}`; - }, - 1440: (t: number) => { - const d = new Date(t * 1000); - return `${d.getUTCFullYear()}-${Z2(d.getUTCMonth() + 1)}-${Z2( - d.getUTCDate() - )}`; - }, + 1: (t: number) => { + const d = new Date(t * 1000); + return `${Z2(d.getUTCHours())}:${Z2(d.getUTCMinutes())}`; + }, + 60: (t: number) => { + const d = new Date(t * 1000); + return `${d.getUTCFullYear()}-${Z2(d.getUTCMonth() + 1)}-${Z2( + d.getUTCDate() + )}`; + }, + 1440: (t: number) => { + const d = new Date(t * 1000); + return `${d.getUTCFullYear()}-${Z2(d.getUTCMonth() + 1)}-${Z2( + d.getUTCDate() + )}`; + }, }; // UI theme presets const THEMES: any = { - light: { - id: "light", - label: "Light", - bg: "white", - body: "black", - chart: { - axis: "#000", - price: "#006", - pricelabel: "#fff", - bull: "#6c0", - bear: "#f04", - sma12: "#f90", - sma24: "#0ff", - sma50: "#06f", - sma72: "#00f", - gridMajor: "#666", - gridMinor: "#ccc", - }, - }, - dark: { - id: "dark", - label: "Dark", - bg: "black", - body: "white", - chart: { - axis: "#eee", - price: "#f0f", - pricelabel: "#fff", - bull: "#0c4", - bear: "#f02", - sma12: "#ff0", - sma24: "#0ff", - sma50: "#06f", - sma72: "#00f", - gridMajor: "#666", - gridMinor: "#333", - }, - }, + light: { + id: "light", + label: "Light", + bg: "white", + body: "black", + chart: { + axis: "#000", + price: "#006", + pricelabel: "#fff", + bull: "#6c0", + bear: "#f04", + sma12: "#f90", + sma24: "#0ff", + sma50: "#06f", + sma72: "#00f", + gridMajor: "#666", + gridMinor: "#ccc", + }, + }, + dark: { + id: "dark", + label: "Dark", + bg: "black", + body: "white", + chart: { + axis: "#eee", + price: "#f0f", + pricelabel: "#fff", + bull: "#0c4", + bear: "#f02", + sma12: "#ff0", + sma24: "#0ff", + sma50: "#06f", + sma72: "#00f", + gridMajor: "#666", + gridMinor: "#333", + }, + }, }; // constructs request URL from given inputs // API docs: https://min-api.cryptocompare.com/ const API_URL = (market: string, symbol: string, period: number) => - `https://min-api.cryptocompare.com/data/histo${TIMEFRAMES[ - period - ].toLowerCase()}?fsym=${symbol.substring(0, 3)}&tsym=${symbol.substring( - 3 - )}&limit=168&aggregate=1&e=${market}`; + `https://min-api.cryptocompare.com/data/histo${TIMEFRAMES[ + period + ].toLowerCase()}?fsym=${symbol.substring(0, 3)}&tsym=${symbol.substring( + 3 + )}&limit=168&aggregate=1&e=${market}`; // stub for local testing // const API_URL = (..._) => `ohlc.json`; const emitOnStream = (stream: ISubscriber) => (e: Event) => - stream.next((e.target).value); + stream.next((e.target).value); const menu = ( - stream: ISubscriber, - title: string, - items: DropDownOption[] + stream: ISubscriber, + title: string, + items: DropDownOption[] ) => - map((x: any) => - dropdown( - null, - { class: "w-100", onchange: emitOnStream(stream) }, - [[null, title, true], ...items], - String(x) - ) - ); + map((x: any) => + dropdown( + null, + { class: "w-100", onchange: emitOnStream(stream) }, + [[null, title, true], ...items], + String(x) + ) + ); // pre-seeded streams/reactive values const market = reactive("CCCAGG"); @@ -215,300 +215,300 @@ const refresh = fromInterval(60000).subscribe(trace("refresh")); // this stream combinator performs API requests to obtain OHLC data const response = sync({ - src: { market, symbol, period, refresh }, - xform: map((inst) => - fetch(API_URL(inst.market, inst.symbol, inst.period)) - .then( - (res) => - res.ok ? res.json() : error.next("error loading OHLC data"), - (e) => error.next(e.message) - ) - .then((json) => ({ ...inst, ohlc: json ? json.Data : null })) - ), + src: { market, symbol, period, refresh }, + xform: map((inst) => + fetch(API_URL(inst.market, inst.symbol, inst.period)) + .then( + (res) => + res.ok ? res.json() : error.next("error loading OHLC data"), + (e) => error.next(e.message) + ) + .then((json) => ({ ...inst, ohlc: json ? json.Data : null })) + ), }).subscribe(resolvePromise({ fail: (e) => error.next(e.message) })); // this stream combinator computes a number of statistics on incoming OHLC data // including calculation of moving averages (based on current mode selection) const data = sync({ - src: { - response, - avg: avgMode.transform(map((id: string) => MA_MODES[id].fn)), - }, - xform: comp( - // bail if response value has no OHLC data - filter(({ response }) => !!response.ohlc), - // use @thi.ng/resolve-map to compute bounds & moving averages - map(({ response, avg }: any) => - resolve({ - ...response, - min: ({ ohlc }: MarketResponse) => - transduce(pluck("low"), min(), ohlc), - max: ({ ohlc }: MarketResponse) => - transduce(pluck("high"), max(), ohlc), - tbounds: ({ ohlc }: MarketResponse) => [ - ohlc[0].time, - ohlc[ohlc.length - 1].time, - ], - sma: ({ ohlc }: MarketResponse) => - transduce( - map((period: number) => [ - period, - transduce( - comp(pluck("close"), avg(period)), - push(), - ohlc - ), - ]), - push(), - [12, 24, 50, 72] - ), - }) - ) - ), + src: { + response, + avg: avgMode.transform(map((id: string) => MA_MODES[id].fn)), + }, + xform: comp( + // bail if response value has no OHLC data + filter(({ response }) => !!response.ohlc), + // use @thi.ng/resolve-map to compute bounds & moving averages + map(({ response, avg }: any) => + resolve({ + ...response, + min: ({ ohlc }: MarketResponse) => + transduce(pluck("low"), min(), ohlc), + max: ({ ohlc }: MarketResponse) => + transduce(pluck("high"), max(), ohlc), + tbounds: ({ ohlc }: MarketResponse) => [ + ohlc[0].time, + ohlc[ohlc.length - 1].time, + ], + sma: ({ ohlc }: MarketResponse) => + transduce( + map((period: number) => [ + period, + transduce( + comp(pluck("close"), avg(period)), + push(), + ohlc + ), + ]), + push(), + [12, 24, 50, 72] + ), + }) + ) + ), }); // this stream combinator (re)computes the SVG chart // updates whenever data, theme or window size has changed const chart = sync({ - src: { - data, - theme, - window: fromEvent(window, "resize").transform( - map(() => [window.innerWidth, window.innerHeight]) - ), - }, - xform: map(({ data, window, theme }) => { - let [width, height] = window; - const ohlc: OHLC[] = data.ohlc; - const chartW = width - 2 * MARGIN_X; - const chartH = height - 2 * MARGIN_Y; - const bw = Math.max(3, chartW / ohlc.length); - const by = height - MARGIN_Y; + src: { + data, + theme, + window: fromEvent(window, "resize").transform( + map(() => [window.innerWidth, window.innerHeight]) + ), + }, + xform: map(({ data, window, theme }) => { + let [width, height] = window; + const ohlc: OHLC[] = data.ohlc; + const chartW = width - 2 * MARGIN_X; + const chartH = height - 2 * MARGIN_Y; + const bw = Math.max(3, chartW / ohlc.length); + const by = height - MARGIN_Y; - const mapX = (x: number) => - fit(x, 0, ohlc.length, MARGIN_X, width - MARGIN_X); - const mapY = (y: number) => fit(y, data.min, data.max, by, MARGIN_Y); - // helper fn for plotting moving averages - const sma = (vals: number[], col: string) => - polyline( - vals.map((y, x) => [ - mapX(x + (ohlc.length - vals.length) + 0.5), - mapY(y), - ]), - { stroke: col, fill: "none" } - ); + const mapX = (x: number) => + fit(x, 0, ohlc.length, MARGIN_X, width - MARGIN_X); + const mapY = (y: number) => fit(y, data.min, data.max, by, MARGIN_Y); + // helper fn for plotting moving averages + const sma = (vals: number[], col: string) => + polyline( + vals.map((y, x) => [ + mapX(x + (ohlc.length - vals.length) + 0.5), + mapY(y), + ]), + { stroke: col, fill: "none" } + ); - // use preset time precisions based on current chart period - let tickX: number = TIME_TICKS[data.period]; - const timeRange = data.tbounds[1] - data.tbounds[0]; - while (chartW / (timeRange / tickX) < 60) { - tickX *= 2; - } - const fmtTime: (t: number) => string = TIME_FORMATS[data.period]; + // use preset time precisions based on current chart period + let tickX: number = TIME_TICKS[data.period]; + const timeRange = data.tbounds[1] - data.tbounds[0]; + while (chartW / (timeRange / tickX) < 60) { + tickX *= 2; + } + const fmtTime: (t: number) => string = TIME_FORMATS[data.period]; - // price tick resolution estimation based on actual OHLC interval & window height - const domain = data.max - data.min; - // min tick in currency - const minTickY = 0.0025; - // min tick in screen coords - const minProjTickY = Math.max(chartH / 8, 50); - let tickY = - Math.pow(10, Math.floor(Math.log(domain) / Math.log(10))) / 2; - while (tickY > minTickY && chartH / (domain / tickY) > minProjTickY) { - tickY /= 2; - } - while (chartH / (domain / tickY) < minProjTickY) { - tickY *= 2; - } + // price tick resolution estimation based on actual OHLC interval & window height + const domain = data.max - data.min; + // min tick in currency + const minTickY = 0.0025; + // min tick in screen coords + const minProjTickY = Math.max(chartH / 8, 50); + let tickY = + Math.pow(10, Math.floor(Math.log(domain) / Math.log(10))) / 2; + while (tickY > minTickY && chartH / (domain / tickY) > minProjTickY) { + tickY /= 2; + } + while (chartH / (domain / tickY) < minProjTickY) { + tickY *= 2; + } - const lastPrice = ohlc[ohlc.length - 1].close; - const closeX = width - MARGIN_X; - const closeY = mapY(lastPrice); - // inline definition of SVG chart - return svg( - { width, height, "font-family": "Arial", "font-size": "10px" }, - // XY axes incl. tick markers & labels - group( - { - stroke: theme.chart.axis, - fill: theme.chart.axis, - "text-anchor": "end", - }, - line([MARGIN_X, MARGIN_Y], [MARGIN_X, by]), - line([MARGIN_X, by], [width - MARGIN_X, by]), - // Y axis ticks - mapcat((price: number) => { - const y = mapY(price); - return [ - line([MARGIN_X - 10, y], [MARGIN_X, y]), - line([MARGIN_X, y], [width - MARGIN_X, y], { - stroke: - price % 100 < 1 - ? theme.chart.gridMajor - : theme.chart.gridMinor, - "stroke-dasharray": 2, - }), - text([MARGIN_X - 15, y + 4], price.toFixed(4), { - stroke: "none", - }), - ]; - }, range(Math.ceil(data.min / tickY) * tickY, data.max, tickY)), - // X axis ticks - mapcat((t: number) => { - const x = fit( - t, - data.tbounds[0], - data.tbounds[1], - MARGIN_X + bw / 2, - width - MARGIN_X - bw / 2 - ); - return [ - line([x, by], [x, by + 10]), - line([x, MARGIN_Y], [x, by], { - stroke: theme.chart.gridMinor, - "stroke-dasharray": 2, - }), - text([x, by + 20], fmtTime(t), { - stroke: "none", - "text-anchor": "middle", - }), - ]; - }, range(Math.ceil(data.tbounds[0] / tickX) * tickX, data.tbounds[1], tickX)) - ), - // moving averages - map( - ([period, vals]: [number, number[]]) => - sma(vals, theme.chart[`sma${period}`]), - data.sma - ), - // candles - mapIndexed((i, candle: OHLC) => { - const isBullish = candle.open < candle.close; - let y, h; - let col; - if (isBullish) { - col = theme.chart.bull; - y = mapY(candle.close); - h = mapY(candle.open) - y; - } else { - col = theme.chart.bear; - y = mapY(candle.open); - h = mapY(candle.close) - y; - } - return group( - { fill: col, stroke: col }, - line( - [mapX(i + 0.5), mapY(candle.low)], - [mapX(i + 0.5), mapY(candle.high)] - ), - rect([mapX(i) + 1, y], bw - 2, h) - ); - }, ohlc), - // price line - line([MARGIN_X, closeY], [closeX, closeY], { - stroke: theme.chart.price, - }), - // closing price tag - polygon( - [ - [closeX, closeY], - [closeX + 10, closeY - 8], - [width, closeY - 8], - [width, closeY + 8], - [closeX + 10, closeY + 8], - ], - { fill: theme.chart.price } - ), - text([closeX + 12, closeY + 4], lastPrice.toFixed(4), { - fill: theme.chart.pricelabel, - }) - ); - }), + const lastPrice = ohlc[ohlc.length - 1].close; + const closeX = width - MARGIN_X; + const closeY = mapY(lastPrice); + // inline definition of SVG chart + return svg( + { width, height, "font-family": "Arial", "font-size": "10px" }, + // XY axes incl. tick markers & labels + group( + { + stroke: theme.chart.axis, + fill: theme.chart.axis, + "text-anchor": "end", + }, + line([MARGIN_X, MARGIN_Y], [MARGIN_X, by]), + line([MARGIN_X, by], [width - MARGIN_X, by]), + // Y axis ticks + mapcat((price: number) => { + const y = mapY(price); + return [ + line([MARGIN_X - 10, y], [MARGIN_X, y]), + line([MARGIN_X, y], [width - MARGIN_X, y], { + stroke: + price % 100 < 1 + ? theme.chart.gridMajor + : theme.chart.gridMinor, + "stroke-dasharray": 2, + }), + text([MARGIN_X - 15, y + 4], price.toFixed(4), { + stroke: "none", + }), + ]; + }, range(Math.ceil(data.min / tickY) * tickY, data.max, tickY)), + // X axis ticks + mapcat((t: number) => { + const x = fit( + t, + data.tbounds[0], + data.tbounds[1], + MARGIN_X + bw / 2, + width - MARGIN_X - bw / 2 + ); + return [ + line([x, by], [x, by + 10]), + line([x, MARGIN_Y], [x, by], { + stroke: theme.chart.gridMinor, + "stroke-dasharray": 2, + }), + text([x, by + 20], fmtTime(t), { + stroke: "none", + "text-anchor": "middle", + }), + ]; + }, range(Math.ceil(data.tbounds[0] / tickX) * tickX, data.tbounds[1], tickX)) + ), + // moving averages + map( + ([period, vals]: [number, number[]]) => + sma(vals, theme.chart[`sma${period}`]), + data.sma + ), + // candles + mapIndexed((i, candle: OHLC) => { + const isBullish = candle.open < candle.close; + let y, h; + let col; + if (isBullish) { + col = theme.chart.bull; + y = mapY(candle.close); + h = mapY(candle.open) - y; + } else { + col = theme.chart.bear; + y = mapY(candle.open); + h = mapY(candle.close) - y; + } + return group( + { fill: col, stroke: col }, + line( + [mapX(i + 0.5), mapY(candle.low)], + [mapX(i + 0.5), mapY(candle.high)] + ), + rect([mapX(i) + 1, y], bw - 2, h) + ); + }, ohlc), + // price line + line([MARGIN_X, closeY], [closeX, closeY], { + stroke: theme.chart.price, + }), + // closing price tag + polygon( + [ + [closeX, closeY], + [closeX + 10, closeY - 8], + [width, closeY - 8], + [width, closeY + 8], + [closeX + 10, closeY + 8], + ], + { fill: theme.chart.price } + ), + text([closeX + 12, closeY + 4], lastPrice.toFixed(4), { + fill: theme.chart.pricelabel, + }) + ); + }), }); // stream construct to perform UI update sync({ - src: { - chart, - theme, - // the following input streams are each transformed - // into a dropdown component - symbol: symbol.transform(menu(symbol, "Symbol pair", SYMBOL_PAIRS)), - period: period.transform( - menu(period, "Time frame", [...pairs(TIMEFRAMES)]) - ), - avg: avgMode.transform( - menu(avgMode, "Moving average", [ - ...map( - ([id, mode]) => [id, mode.label], - pairs(MA_MODES) - ), - ]) - ), - themeSel: theme.transform( - map((x) => x.id), - menu(theme, "Theme", [ - ...map( - ([id, theme]: [string, any]) => - [id, theme.label], - pairs(THEMES) - ), - ]) - ), - }, - xform: comp( - // combines all inputs into a single root component - map(({ theme, themeSel, chart, symbol, period, avg }) => [ - "div", - { class: `sans-serif f7 bg-${theme.bg} ${theme.body}` }, - chart, - [ - "div.fixed", - { - style: { - top: `1rem`, - right: `${MARGIN_X}px`, - width: `calc(100vw - 2 * ${MARGIN_X}px)`, - }, - }, - [ - "div.flex", - ...map( - (x) => ["div.w-25.ph2", x], - [symbol, period, avg, themeSel] - ), - ], - ], - [ - "div.fixed.tc", - { - style: { - bottom: `1rem`, - left: `${MARGIN_X}px`, - width: `calc(100vw - 2 * ${MARGIN_X}px)`, - }, - }, - [ - "a", - { - class: `mr3 b link ${theme.body}`, - href: "https://min-api.cryptocompare.com/", - }, - "Data by cyptocompare.com", - ], - [ - "a", - { - class: `mr3 b link ${theme.body}`, - href: "https://github.com/thi-ng/umbrella/tree/develop/examples/crypto-chart/", - }, - "Source", - ], - ], - ]), - // perform hdom update / diffing - updateDOM() - ), + src: { + chart, + theme, + // the following input streams are each transformed + // into a dropdown component + symbol: symbol.transform(menu(symbol, "Symbol pair", SYMBOL_PAIRS)), + period: period.transform( + menu(period, "Time frame", [...pairs(TIMEFRAMES)]) + ), + avg: avgMode.transform( + menu(avgMode, "Moving average", [ + ...map( + ([id, mode]) => [id, mode.label], + pairs(MA_MODES) + ), + ]) + ), + themeSel: theme.transform( + map((x) => x.id), + menu(theme, "Theme", [ + ...map( + ([id, theme]: [string, any]) => + [id, theme.label], + pairs(THEMES) + ), + ]) + ), + }, + xform: comp( + // combines all inputs into a single root component + map(({ theme, themeSel, chart, symbol, period, avg }) => [ + "div", + { class: `sans-serif f7 bg-${theme.bg} ${theme.body}` }, + chart, + [ + "div.fixed", + { + style: { + top: `1rem`, + right: `${MARGIN_X}px`, + width: `calc(100vw - 2 * ${MARGIN_X}px)`, + }, + }, + [ + "div.flex", + ...map( + (x) => ["div.w-25.ph2", x], + [symbol, period, avg, themeSel] + ), + ], + ], + [ + "div.fixed.tc", + { + style: { + bottom: `1rem`, + left: `${MARGIN_X}px`, + width: `calc(100vw - 2 * ${MARGIN_X}px)`, + }, + }, + [ + "a", + { + class: `mr3 b link ${theme.body}`, + href: "https://min-api.cryptocompare.com/", + }, + "Data by cyptocompare.com", + ], + [ + "a", + { + class: `mr3 b link ${theme.body}`, + href: "https://github.com/thi-ng/umbrella/tree/develop/examples/crypto-chart/", + }, + "Source", + ], + ], + ]), + // perform hdom update / diffing + updateDOM() + ), }); window.dispatchEvent(new CustomEvent("resize")); diff --git a/examples/crypto-chart/tsconfig.json b/examples/crypto-chart/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/crypto-chart/tsconfig.json +++ b/examples/crypto-chart/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/devcards/package.json b/examples/devcards/package.json index 7241596908..2ca45a1606 100644 --- a/examples/devcards/package.json +++ b/examples/devcards/package.json @@ -1,34 +1,34 @@ { - "name": "@example/devcards", - "private": true, - "description": "BMI calculator in a devcards format", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "hdom" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/devcards", + "private": true, + "description": "BMI calculator in a devcards format", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "hdom" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/devcards/src/index.ts b/examples/devcards/src/index.ts index 8159b28287..b6b39e5e60 100644 --- a/examples/devcards/src/index.ts +++ b/examples/devcards/src/index.ts @@ -27,49 +27,49 @@ let CARD_ID = 1; * As with the original Devcards, the client root component is wrapped * in a card container, which too displays the app state in JSON format. * - * @param card - - * @param state - - * @param title - - * @param parent - + * @param card - + * @param state - + * @param title - + * @param parent - */ function defcard( - card: CardFn, - state?: IAtom, - title?: string, - parent?: string | Element + card: CardFn, + state?: IAtom, + title?: string, + parent?: string | Element ) { - state = state || defAtom({}); - title = title || `devcard-${CARD_ID++}`; - - // create new parent element if not provided - if (!parent) { - parent = document.createElement("div"); - document.body.appendChild(parent); - } - - // Create a derived view for entire atom (using empty path `[]`) - // this updates the JSON body only when state has changed - const json = defView(state, [], (state) => JSON.stringify(state, null, 2)); - - // instantiate the component with supplied state - const root = card(state); - - // kick off hdom renderloop - start( - () => ["div.card", ["h3", title], ["div.body", root, ["pre", json]]], - { root: parent } - ); + state = state || defAtom({}); + title = title || `devcard-${CARD_ID++}`; + + // create new parent element if not provided + if (!parent) { + parent = document.createElement("div"); + document.body.appendChild(parent); + } + + // Create a derived view for entire atom (using empty path `[]`) + // this updates the JSON body only when state has changed + const json = defView(state, [], (state) => JSON.stringify(state, null, 2)); + + // instantiate the component with supplied state + const root = card(state); + + // kick off hdom renderloop + start( + () => ["div.card", ["h3", title], ["div.body", root, ["pre", json]]], + { root: parent } + ); } /** * Value slider component options (see function below) */ interface SliderOpts { - min: number; - max: number; - step?: number; - label: Fn; - onchange?: EventListener; + min: number; + max: number; + step?: number; + label: Fn; + onchange?: EventListener; } /** @@ -80,30 +80,30 @@ interface SliderOpts { * resets cursor's value and calls user `onchange` * function (if provided). * - * @param state - - * @param opts - + * @param state - + * @param opts - */ function slider(state: IAtom, opts: SliderOpts) { - // prep attribs to avoid extra work during render - const attribs = { - ...opts, - type: "range", - oninput: (e: Event) => { - state.reset((e.target).value); - opts.onchange && opts.onchange(e); - }, - }; - return () => [ - "div", - ["div", opts.label(state.deref())], - ["input", { ...attribs, value: state.deref() }], - ]; + // prep attribs to avoid extra work during render + const attribs = { + ...opts, + type: "range", + oninput: (e: Event) => { + state.reset((e.target).value); + opts.onchange && opts.onchange(e); + }, + }; + return () => [ + "div", + ["div", opts.label(state.deref())], + ["input", { ...attribs, value: state.deref() }], + ]; } interface BMIState { - height: number; - weight: number; - bmi: number; + height: number; + weight: number; + bmi: number; } /** @@ -113,95 +113,95 @@ interface BMIState { * slider components used and finally return a component * function to be shown in the card wrapper. * - * @param state - + * @param state - */ function bmi(state: IAtom) { - // state update function - // computes new BMI value (if weight was changed) or - // new weight value (if BMI was changed by user) - const calc = (updateWeight = false) => { - let { height, weight, bmi } = state.deref() || { - height: 0, - weight: 0, - bmi: 0, - }; - height *= 0.01; - if (updateWeight) { - state.resetIn(["weight"], bmi * height * height); - } else { - state.resetIn(["bmi"], weight / (height * height)); - } - }; - - // define BMI thresholds - const thresh: [number, string, string][] = [ - [10, "underweight", "#cf3"], - [18.5, "normal", "#7f0"], - [25, "overweight", "#f90"], - [30, "obese", "#f00"], - ]; - - // derived view of bmi value to translate it into english - const bmiClass = defView(state, ["bmi"], (bmi) => - bmi > thresh[3][0] - ? thresh[3][1] - : bmi > thresh[2][0] - ? thresh[2][1] - : bmi > thresh[1][0] - ? thresh[1][1] - : thresh[0][1] - ); - - // another derived view to create SVG visualization - const bmiScale = (x: number) => ((x - 10) / 30) * 100 + "%"; - const bmiViz = defView(state, ["bmi"], (bmi: number) => [ - "div", - [ - "svg", - { width: "100%", height: 30, style: { "font-size": "10px" } }, - ...thresh.map(([t, _, col]) => [ - "rect", - { x: bmiScale(t), y: 0, width: "100%", height: 30, fill: col }, - ]), - ...thresh.map(([t, label]) => [ - "text", - { x: bmiScale(t + 0.5), y: 12 }, - label, - ]), - ["circle", { cx: bmiScale(bmi), cy: 20, r: 5 }], - ], - ]); - - // define slider components - // note how each uses a cursor to their respective - // target values in the app state - const height = slider(defCursor(state, ["height"]), { - min: 100, - max: 220, - label: (v) => `Height: ${~~v}cm`, - onchange: () => calc(), - }); - const weight = slider(defCursor(state, ["weight"]), { - min: 10, - max: 150, - label: (v) => `Weight: ${~~v}kg`, - onchange: () => calc(), - }); - const bmi = slider(defCursor(state, ["bmi"]), { - min: 10, - max: 50, - label: (v) => [ - "span", - { class: bmiClass.deref() }, - `BMI: ${~~v} (${bmiClass.deref()})`, - ], - onchange: () => calc(true), - }); - - // perform initial calculation - calc(); - - return () => ["div", height, weight, bmi, ["div", bmiViz]]; + // state update function + // computes new BMI value (if weight was changed) or + // new weight value (if BMI was changed by user) + const calc = (updateWeight = false) => { + let { height, weight, bmi } = state.deref() || { + height: 0, + weight: 0, + bmi: 0, + }; + height *= 0.01; + if (updateWeight) { + state.resetIn(["weight"], bmi * height * height); + } else { + state.resetIn(["bmi"], weight / (height * height)); + } + }; + + // define BMI thresholds + const thresh: [number, string, string][] = [ + [10, "underweight", "#cf3"], + [18.5, "normal", "#7f0"], + [25, "overweight", "#f90"], + [30, "obese", "#f00"], + ]; + + // derived view of bmi value to translate it into english + const bmiClass = defView(state, ["bmi"], (bmi) => + bmi > thresh[3][0] + ? thresh[3][1] + : bmi > thresh[2][0] + ? thresh[2][1] + : bmi > thresh[1][0] + ? thresh[1][1] + : thresh[0][1] + ); + + // another derived view to create SVG visualization + const bmiScale = (x: number) => ((x - 10) / 30) * 100 + "%"; + const bmiViz = defView(state, ["bmi"], (bmi: number) => [ + "div", + [ + "svg", + { width: "100%", height: 30, style: { "font-size": "10px" } }, + ...thresh.map(([t, _, col]) => [ + "rect", + { x: bmiScale(t), y: 0, width: "100%", height: 30, fill: col }, + ]), + ...thresh.map(([t, label]) => [ + "text", + { x: bmiScale(t + 0.5), y: 12 }, + label, + ]), + ["circle", { cx: bmiScale(bmi), cy: 20, r: 5 }], + ], + ]); + + // define slider components + // note how each uses a cursor to their respective + // target values in the app state + const height = slider(defCursor(state, ["height"]), { + min: 100, + max: 220, + label: (v) => `Height: ${~~v}cm`, + onchange: () => calc(), + }); + const weight = slider(defCursor(state, ["weight"]), { + min: 10, + max: 150, + label: (v) => `Weight: ${~~v}kg`, + onchange: () => calc(), + }); + const bmi = slider(defCursor(state, ["bmi"]), { + min: 10, + max: 50, + label: (v) => [ + "span", + { class: bmiClass.deref() }, + `BMI: ${~~v} (${bmiClass.deref()})`, + ], + onchange: () => calc(true), + }); + + // perform initial calculation + calc(); + + return () => ["div", height, weight, bmi, ["div", bmiViz]]; } /** @@ -213,25 +213,25 @@ defcard(bmi, defAtom({ weight: 75, height: 194 }), "BMI calculator"); defcard(bmi); defcard( - () => - "The cards below are all attached to the same atom, but use cursors to subscribe to different branches within the nested state." + () => + "The cards below are all attached to the same atom, but use cursors to subscribe to different branches within the nested state." ); /** * Option 2: defcard() instances using shared central state */ const db = defAtom>>({ - card1: { weight: 75, height: 194 }, + card1: { weight: 75, height: 194 }, }); defcard(bmi, defCursor(db, ["card1"]), "BMI calculator (shared)"); defcard(bmi, defCursor(db, ["card2"])); defcard((state) => { - // just some random task to populate another part of the app state - setInterval( - () => state.resetIn(["stats", "now"], new Date().toISOString()), - 1000 - ); - return ["div", "The full shared state:"]; + // just some random task to populate another part of the app state + setInterval( + () => state.resetIn(["stats", "now"], new Date().toISOString()), + 1000 + ); + return ["div", "The full shared state:"]; }, db); diff --git a/examples/devcards/tsconfig.json b/examples/devcards/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/devcards/tsconfig.json +++ b/examples/devcards/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/dominant-colors/package.json b/examples/dominant-colors/package.json index efd4053b30..fc77f5c4b0 100644 --- a/examples/dominant-colors/package.json +++ b/examples/dominant-colors/package.json @@ -1,55 +1,55 @@ { - "name": "@example/dominant-colors", - "private": true, - "version": "0.0.1", - "description": "Color palette generation via dominant color extraction from uploaded images", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/bench": "workspace:^", - "@thi.ng/checks": "workspace:^", - "@thi.ng/color": "workspace:^", - "@thi.ng/compare": "workspace:^", - "@thi.ng/dl-asset": "workspace:^", - "@thi.ng/hiccup-html": "workspace:^", - "@thi.ng/hiccup-svg": "workspace:^", - "@thi.ng/pixel": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/rdom-components": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "color", - "compare", - "dl-asset", - "hiccup-html", - "hiccup-svg", - "k-means", - "pixel", - "rdom", - "rdom-components", - "rstream", - "transducers" - ], - "screenshot": "examples/dominant-colors.png" - } + "name": "@example/dominant-colors", + "private": true, + "version": "0.0.1", + "description": "Color palette generation via dominant color extraction from uploaded images", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/bench": "workspace:^", + "@thi.ng/checks": "workspace:^", + "@thi.ng/color": "workspace:^", + "@thi.ng/compare": "workspace:^", + "@thi.ng/dl-asset": "workspace:^", + "@thi.ng/hiccup-html": "workspace:^", + "@thi.ng/hiccup-svg": "workspace:^", + "@thi.ng/pixel": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/rdom-components": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "color", + "compare", + "dl-asset", + "hiccup-html", + "hiccup-svg", + "k-means", + "pixel", + "rdom", + "rdom-components", + "rstream", + "transducers" + ], + "screenshot": "examples/dominant-colors.png" + } } diff --git a/examples/dominant-colors/src/api.ts b/examples/dominant-colors/src/api.ts index 7a8b2d221a..8d0e9a73a2 100644 --- a/examples/dominant-colors/src/api.ts +++ b/examples/dominant-colors/src/api.ts @@ -6,18 +6,18 @@ import { compareNumDesc } from "@thi.ng/compare/numeric"; export type SortMode = "hue" | "luma" | "area"; export interface DominantColor { - col: LCH; - area: number; + col: LCH; + area: number; } export const SORT_MODES: Record> = { - hue: compareByKeys2( - (x) => x.col.h, - (x) => x.col.l - ), - luma: compareByKeys2( - (x) => x.col.l, - (x) => x.col.h - ), - area: compareByKey("area", compareNumDesc), + hue: compareByKeys2( + (x) => x.col.h, + (x) => x.col.l + ), + luma: compareByKeys2( + (x) => x.col.l, + (x) => x.col.h + ), + area: compareByKey("area", compareNumDesc), }; diff --git a/examples/dominant-colors/src/components/css.ts b/examples/dominant-colors/src/components/css.ts index c33f82da86..312b7647d6 100644 --- a/examples/dominant-colors/src/components/css.ts +++ b/examples/dominant-colors/src/components/css.ts @@ -3,29 +3,29 @@ import { li, ul } from "@thi.ng/hiccup-html/lists"; import type { DominantColor } from "../api"; export const cssPalette = (colors: DominantColor[]) => - ul( - ".dib.w-25.ma0.pa0.list", - {}, - ...colors - .map((c) => css(c.col)) - .map((c) => - li( - ".db.relative", - { style: { color: c, height: "2.25rem" } }, - [ - "i.absolute", - { style: { top: "-0.25rem" } }, - [ - "svg", - { - viewBox: "0 0 1 1", - width: "2rem", - height: "2rem", - }, - ["circle", { fill: c, cx: 0.5, cy: 0.5, r: 0.5 }], - ], - ], - ["span.ml5", {}, c] - ) - ) - ); + ul( + ".dib.w-25.ma0.pa0.list", + {}, + ...colors + .map((c) => css(c.col)) + .map((c) => + li( + ".db.relative", + { style: { color: c, height: "2.25rem" } }, + [ + "i.absolute", + { style: { top: "-0.25rem" } }, + [ + "svg", + { + viewBox: "0 0 1 1", + width: "2rem", + height: "2rem", + }, + ["circle", { fill: c, cx: 0.5, cy: 0.5, r: 0.5 }], + ], + ], + ["span.ml5", {}, c] + ) + ) + ); diff --git a/examples/dominant-colors/src/components/pixelcanvas.ts b/examples/dominant-colors/src/components/pixelcanvas.ts index 43fff14863..b6f6f57ba9 100644 --- a/examples/dominant-colors/src/components/pixelcanvas.ts +++ b/examples/dominant-colors/src/components/pixelcanvas.ts @@ -7,20 +7,20 @@ import { Component } from "@thi.ng/rdom/component"; // when the component mounts export class PixelCanvas extends Component { - constructor(protected buffer: IntBuffer) { - super(); - } + constructor(protected buffer: IntBuffer) { + super(); + } - async mount(parent: Element, index?: NumOrElement) { - const buf = this.buffer; - this.el = this.$el( - "canvas", - { width: buf.width, height: buf.height, class: "dib v-top" }, - null, - parent, - index - ); - buf.blitCanvas(this.el); - return this.el; - } + async mount(parent: Element, index?: NumOrElement) { + const buf = this.buffer; + this.el = this.$el( + "canvas", + { width: buf.width, height: buf.height, class: "dib v-top" }, + null, + parent, + index + ); + buf.blitCanvas(this.el); + return this.el; + } } diff --git a/examples/dominant-colors/src/components/slider.ts b/examples/dominant-colors/src/components/slider.ts index 0c3b267755..b701b9977a 100644 --- a/examples/dominant-colors/src/components/slider.ts +++ b/examples/dominant-colors/src/components/slider.ts @@ -4,20 +4,20 @@ import { inputNumeric } from "@thi.ng/rdom-components/input"; import type { ISubscription } from "@thi.ng/rstream"; export const slider = ( - dest: ISubscription, - desc: string, - attribs?: any + dest: ISubscription, + desc: string, + attribs?: any ) => - div( - null, - label( - { class: "dib w-50 w-25-ns", for: `input-${desc}` }, - `${desc}: `, - dest - ), - inputNumeric(dest, { - class: "dib w-50 w-25-ns", - type: "range", - ...attribs, - }) - ); + div( + null, + label( + { class: "dib w-50 w-25-ns", for: `input-${desc}` }, + `${desc}: `, + dest + ), + inputNumeric(dest, { + class: "dib w-50 w-25-ns", + type: "range", + ...attribs, + }) + ); diff --git a/examples/dominant-colors/src/components/swatches.ts b/examples/dominant-colors/src/components/swatches.ts index 2fd85a1796..9b77429bff 100644 --- a/examples/dominant-colors/src/components/swatches.ts +++ b/examples/dominant-colors/src/components/swatches.ts @@ -3,13 +3,13 @@ import { dotsH } from "@thi.ng/color/swatches"; import { svg } from "@thi.ng/hiccup-svg/svg"; export const svgSwatches = (colors: ReadonlyColor[], size: number) => - svg( - { - width: colors.length * (size * 2 + 2), - height: size * 2, - convert: true, - }, - dotsH(colors, size - 1, 2, { - translate: [size, size], - }) - ); + svg( + { + width: colors.length * (size * 2 + 2), + height: size * 2, + convert: true, + }, + dotsH(colors, size - 1, 2, { + translate: [size, size], + }) + ); diff --git a/examples/dominant-colors/src/index.ts b/examples/dominant-colors/src/index.ts index a92fdcc110..bfc0cb83b1 100644 --- a/examples/dominant-colors/src/index.ts +++ b/examples/dominant-colors/src/index.ts @@ -36,101 +36,101 @@ const size = (isMobile() ? 0.5 : 1) * 25; // stream combinator & image processor const main = sync({ - src: { - image, - num, - minChroma, - _: update, - }, - xform: map((params) => - processImage(params.image, params.num, params.minChroma * 0.01) - ), + src: { + image, + num, + minChroma, + _: update, + }, + xform: map((params) => + processImage(params.image, params.num, params.minChroma * 0.01) + ), }); // final result combinator & post-analysis/filtering const result = sync({ - src: { main, minArea, sortMode }, - xform: map(({ main: { buf, colors }, minArea, sortMode }) => ({ - buf, - ...postProcess(colors, minArea, sortMode), - })), + src: { main, minArea, sortMode }, + xform: map(({ main: { buf, colors }, minArea, sortMode }) => ({ + buf, + ...postProcess(colors, minArea, sortMode), + })), }); // new values pushed into `file` will trigger reading file as an image // once ready, puts image into `image` stream for further processing file.subscribe({ - next(file) { - const url = URL.createObjectURL(file); - const img = new Image(); - img.onload = () => { - image.next(img); - URL.revokeObjectURL(url); // house keeping! - }; - img.src = url; - }, + next(file) { + const url = URL.createObjectURL(file); + const img = new Image(); + img.onload = () => { + image.next(img); + URL.revokeObjectURL(url); // house keeping! + }; + img.src = url; + }, }); // main UI $compile( - div( - ".lh-copy.f6.f5-ns", - {}, - h1(".ma0", {}, "Dominant colors"), - inputFile(".db.mv3", { - accept: ["image/jpg", "image/png", "image/gif", "image/webp"], - multiple: false, - onchange: $inputFile(file), - }), - slider(num, "max. colors", { - min: 2, - max: 16, - step: 1, - }), - slider(minChroma, "min. chroma", { - min: 0, - max: 100, - step: 5, - }), - slider(minArea, "min. area", { - min: 0, - max: 25, - step: 0.5, - }), - div(".mv3", {}, "Sort colors by:"), - staticRadio(["hue", "luma", "area"], sortMode, { - label: (id, radio) => - label( - ".db", - { for: id }, - span(".dib.w-50.w-25-ns", {}, id), - radio - ), - }), - button(".db.mv3", { onclick: $inputTrigger(update) }, "update"), - // this part of the UI will be replaced for each new processed image - $replace( - result.map((res) => - div( - {}, - cssPalette(res.colors), - // resized image as canvas - new PixelCanvas(res.buf), - // swatches of dominant colors - div( - {}, - `hue range: ${res.hues.map(float(3)).join(" .. ")}` - ), - // download palette button - button( - ".db.mv3", - { - onclick: () => - downloadACT(res.colors.map((c) => c.col)), - }, - "download .act palette" - ) - ) - ) - ) - ) + div( + ".lh-copy.f6.f5-ns", + {}, + h1(".ma0", {}, "Dominant colors"), + inputFile(".db.mv3", { + accept: ["image/jpg", "image/png", "image/gif", "image/webp"], + multiple: false, + onchange: $inputFile(file), + }), + slider(num, "max. colors", { + min: 2, + max: 16, + step: 1, + }), + slider(minChroma, "min. chroma", { + min: 0, + max: 100, + step: 5, + }), + slider(minArea, "min. area", { + min: 0, + max: 25, + step: 0.5, + }), + div(".mv3", {}, "Sort colors by:"), + staticRadio(["hue", "luma", "area"], sortMode, { + label: (id, radio) => + label( + ".db", + { for: id }, + span(".dib.w-50.w-25-ns", {}, id), + radio + ), + }), + button(".db.mv3", { onclick: $inputTrigger(update) }, "update"), + // this part of the UI will be replaced for each new processed image + $replace( + result.map((res) => + div( + {}, + cssPalette(res.colors), + // resized image as canvas + new PixelCanvas(res.buf), + // swatches of dominant colors + div( + {}, + `hue range: ${res.hues.map(float(3)).join(" .. ")}` + ), + // download palette button + button( + ".db.mv3", + { + onclick: () => + downloadACT(res.colors.map((c) => c.col)), + }, + "download .act palette" + ) + ) + ) + ) + ) ).mount(document.getElementById("app")!); diff --git a/examples/dominant-colors/src/palette.ts b/examples/dominant-colors/src/palette.ts index 1af6426be9..5f2fddf9ad 100644 --- a/examples/dominant-colors/src/palette.ts +++ b/examples/dominant-colors/src/palette.ts @@ -4,20 +4,20 @@ import { srgbIntArgb32 } from "@thi.ng/color/srgb/srgb-int"; import { downloadWithMime } from "@thi.ng/dl-asset/raw"; export const downloadACT = (colors: ReadonlyColor[]) => { - const num = colors.length; - const buf = new Uint8Array(772); - // http://www.selapa.net/swatches/colors/fileformats.php - // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 - buf[768] = num >> 8; - buf[769] = num & 0xff; - // palette index 0 reserved for transparent slot - for (let i = 0, j = 3; i < num; i++, j += 3) { - const rgb = srgbIntArgb32(srgb(colors[i])); - buf[j] = (rgb >> 16) & 0xff; - buf[j + 1] = (rgb >> 8) & 0xff; - buf[j + 2] = rgb & 0xff; - } - downloadWithMime("palette.act", buf, { - mime: "application/octet-stream", - }); + const num = colors.length; + const buf = new Uint8Array(772); + // http://www.selapa.net/swatches/colors/fileformats.php + // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 + buf[768] = num >> 8; + buf[769] = num & 0xff; + // palette index 0 reserved for transparent slot + for (let i = 0, j = 3; i < num; i++, j += 3) { + const rgb = srgbIntArgb32(srgb(colors[i])); + buf[j] = (rgb >> 16) & 0xff; + buf[j + 1] = (rgb >> 8) & 0xff; + buf[j + 2] = rgb & 0xff; + } + downloadWithMime("palette.act", buf, { + mime: "application/octet-stream", + }); }; diff --git a/examples/dominant-colors/src/process.ts b/examples/dominant-colors/src/process.ts index e2bdbd98d4..3c8748fd46 100644 --- a/examples/dominant-colors/src/process.ts +++ b/examples/dominant-colors/src/process.ts @@ -15,38 +15,38 @@ import { type DominantColor, type SortMode, SORT_MODES } from "./api"; * and applies k-means clustering to obtain dominant colors and their coverage. * Returns object of pixel buffer and result colors (in LCH space). * - * @param img - - * @param num - - * @param minChroma - + * @param img - + * @param num - + * @param minChroma - */ export const processImage = ( - img: HTMLImageElement, - num: number, - minChroma: number + img: HTMLImageElement, + num: number, + minChroma: number ) => - timed(() => { - let buf = intBufferFromImage(img); - buf = buf.scale(256 / Math.max(buf.width, buf.height), "nearest"); - const colors = dominantColors(floatBuffer(buf, FLOAT_RGB), num, { - // use min chroma as pre-filter criteria - filter: (p) => lch(srgb(p)).c >= minChroma, - }).map((c) => { col: lch(srgb(c.color)), area: c.area }); - return { buf, colors }; - }); + timed(() => { + let buf = intBufferFromImage(img); + buf = buf.scale(256 / Math.max(buf.width, buf.height), "nearest"); + const colors = dominantColors(floatBuffer(buf, FLOAT_RGB), num, { + // use min chroma as pre-filter criteria + filter: (p) => lch(srgb(p)).c >= minChroma, + }).map((c) => { col: lch(srgb(c.color)), area: c.area }); + return { buf, colors }; + }); export const postProcess = ( - colors: DominantColor[], - minArea: number, - sortMode: SortMode + colors: DominantColor[], + minArea: number, + sortMode: SortMode ) => { - minArea *= 0.01; - // min area as post-filter, sort colors by selected mode - colors = colors.filter((c) => c.area >= minArea).sort(SORT_MODES[sortMode]); - // determine hue range - const hues = transduce( - map((c) => c.col.h), - minMax(), - colors - ); - return { colors, hues }; + minArea *= 0.01; + // min area as post-filter, sort colors by selected mode + colors = colors.filter((c) => c.area >= minArea).sort(SORT_MODES[sortMode]); + // determine hue range + const hues = transduce( + map((c) => c.col.h), + minMax(), + colors + ); + return { colors, hues }; }; diff --git a/examples/dominant-colors/tsconfig.json b/examples/dominant-colors/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/dominant-colors/tsconfig.json +++ b/examples/dominant-colors/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/ellipse-proximity/package.json b/examples/ellipse-proximity/package.json index f0376a3803..112ea7abe5 100644 --- a/examples/ellipse-proximity/package.json +++ b/examples/ellipse-proximity/package.json @@ -1,43 +1,43 @@ { - "name": "@example/ellipse-proximity", - "private": true, - "version": "0.0.1", - "description": "Interactive visualization of closest points on ellipses", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/geom-closest-point": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/rdom-canvas": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom-closest-point", - "rdom", - "rdom-canvas", - "rstream-gestures", - "transducers", - "vectors" - ], - "screenshot": "examples/ellipse-proximity.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/ellipse-proximity", + "private": true, + "version": "0.0.1", + "description": "Interactive visualization of closest points on ellipses", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/geom-closest-point": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/rdom-canvas": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom-closest-point", + "rdom", + "rdom-canvas", + "rstream-gestures", + "transducers", + "vectors" + ], + "screenshot": "examples/ellipse-proximity.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/ellipse-proximity/src/index.ts b/examples/ellipse-proximity/src/index.ts index 338f010f63..4b4f17dff0 100644 --- a/examples/ellipse-proximity/src/index.ts +++ b/examples/ellipse-proximity/src/index.ts @@ -14,51 +14,51 @@ const W = 600; // define random ellipses ([origin, radius] tuples) const ELLIPSES = [ - ...repeatedly(() => [random2([], 50, W - 50), random2([], 10, W / 2)], 5), + ...repeatedly(() => [random2([], 50, W - 50), random2([], 10, W / 2)], 5), ]; // compile & mount reactive canvas component $compile( - $canvas( - // stream merge - merge({ - src: [ - // #1 initial call to action... - reactive([ - "g", - {}, - [ - "text", - { align: "center", fill: "black" }, - [W / 2, W / 2], - "Move your mouse / finger!", - ], - ]), - // #2 stream of mouse/touch coordinates (main) - gestureStream(document.body).map((e) => [ - "g", - // disable canvas clearing if no mouse buttons pressed - { fill: "none", __clear: !!e.buttons }, - // semi-transparent white rect to fade previous frame - ["rect", { fill: [1, 1, 1, 0.2] }, [0, 0], W, W], - // declare ellipses, closest points and tangents - ...mapcat(([o, r]) => { - const p = closestPointEllipse(e.pos, o, r); - return [ - ["ellipse", { stroke: "#ccc" }, o, r], - ["circle", { stroke: "#f0f" }, p, 5], - ["line", { stroke: "#666" }, e.pos, p], - [ - "line", - { stroke: "#6c0" }, - p, - add2(null, normalCCW([], p, e.pos, 100), p), - ], - ]; - }, ELLIPSES), - ]), - ], - }), - [W, W] - ) + $canvas( + // stream merge + merge({ + src: [ + // #1 initial call to action... + reactive([ + "g", + {}, + [ + "text", + { align: "center", fill: "black" }, + [W / 2, W / 2], + "Move your mouse / finger!", + ], + ]), + // #2 stream of mouse/touch coordinates (main) + gestureStream(document.body).map((e) => [ + "g", + // disable canvas clearing if no mouse buttons pressed + { fill: "none", __clear: !!e.buttons }, + // semi-transparent white rect to fade previous frame + ["rect", { fill: [1, 1, 1, 0.2] }, [0, 0], W, W], + // declare ellipses, closest points and tangents + ...mapcat(([o, r]) => { + const p = closestPointEllipse(e.pos, o, r); + return [ + ["ellipse", { stroke: "#ccc" }, o, r], + ["circle", { stroke: "#f0f" }, p, 5], + ["line", { stroke: "#666" }, e.pos, p], + [ + "line", + { stroke: "#6c0" }, + p, + add2(null, normalCCW([], p, e.pos, 100), p), + ], + ]; + }, ELLIPSES), + ]), + ], + }), + [W, W] + ) ).mount(document.getElementById("app")!); diff --git a/examples/ellipse-proximity/tsconfig.json b/examples/ellipse-proximity/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/ellipse-proximity/tsconfig.json +++ b/examples/ellipse-proximity/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/fft-synth/package.json b/examples/fft-synth/package.json index 6344745c54..996969b93e 100644 --- a/examples/fft-synth/package.json +++ b/examples/fft-synth/package.json @@ -1,53 +1,53 @@ { - "name": "@example/fft-synth", - "private": true, - "version": "0.0.1", - "description": "Interactive inverse FFT toy synth", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/dsp": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/imgui": "workspace:^", - "@thi.ng/layout": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/random": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "dsp", - "hdom-canvas", - "imgui", - "layout", - "random", - "rstream", - "rstream-gestures", - "transducers", - "transducers-hdom" - ], - "screenshot": "examples/fft-synth.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/fft-synth", + "private": true, + "version": "0.0.1", + "description": "Interactive inverse FFT toy synth", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/dsp": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/imgui": "workspace:^", + "@thi.ng/layout": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/random": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "dsp", + "hdom-canvas", + "imgui", + "layout", + "random", + "rstream", + "rstream-gestures", + "transducers", + "transducers-hdom" + ], + "screenshot": "examples/fft-synth.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/fft-synth/src/audio.ts b/examples/fft-synth/src/audio.ts index 30233e1b08..6c1de3a3b1 100644 --- a/examples/fft-synth/src/audio.ts +++ b/examples/fft-synth/src/audio.ts @@ -14,43 +14,43 @@ let delay = new Delay(80, makeBins()); export const isAudioActive = () => !!actx; export const initAudio = (size: number) => { - if (actx) return; - actx = new AudioContext(); - buf = actx.createBuffer(1, size, actx.sampleRate); - src = actx.createBufferSource(); - src.buffer = buf; - src.loop = true; - src.connect(actx.destination); - src.start(); - updateAudio(); + if (actx) return; + actx = new AudioContext(); + buf = actx.createBuffer(1, size, actx.sampleRate); + src = actx.createBufferSource(); + src.buffer = buf; + src.loop = true; + src.connect(actx.destination); + src.start(); + updateAudio(); }; export const stopAudio = () => { - src.stop(); - actx!.suspend(); - actx = undefined; + src.stop(); + actx!.suspend(); + actx = undefined; }; export const updateAudio = () => { - let { auto, bins, gain, feedback } = DB.value; - gain *= BIN_AMP; - if (auto != null) { - const pbins = [0, ...delay.deref().slice(0, NUM_BINS - 1)]; - bins = bins.map((x, i) => x * gain + pbins[i] * feedback); - } else { - bins = bins.map((x) => x * gain); - } - delay.next(bins); - const wave = ifft(conjugate(bins))[0]; - DB.resetIn(["wave"], wave); + let { auto, bins, gain, feedback } = DB.value; + gain *= BIN_AMP; + if (auto != null) { + const pbins = [0, ...delay.deref().slice(0, NUM_BINS - 1)]; + bins = bins.map((x, i) => x * gain + pbins[i] * feedback); + } else { + bins = bins.map((x) => x * gain); + } + delay.next(bins); + const wave = ifft(conjugate(bins))[0]; + DB.resetIn(["wave"], wave); - if (!actx) return; - const left = buf.getChannelData(0); - if (PITCH_SCALE > 1) { - for (let i = 0, j = 0; i < wave.length; i++, j += PITCH_SCALE) { - left.fill(wave[i], j, j + PITCH_SCALE); - } - } else { - left.set(wave); - } + if (!actx) return; + const left = buf.getChannelData(0); + if (PITCH_SCALE > 1) { + for (let i = 0, j = 0; i < wave.length; i++, j += PITCH_SCALE) { + left.fill(wave[i], j, j + PITCH_SCALE); + } + } else { + left.set(wave); + } }; diff --git a/examples/fft-synth/src/automode.ts b/examples/fft-synth/src/automode.ts index e90e8a97fa..767aead386 100644 --- a/examples/fft-synth/src/automode.ts +++ b/examples/fft-synth/src/automode.ts @@ -6,42 +6,42 @@ import { NUM_BINS } from "./config"; import { DB, updateSpectrumBin } from "./state"; const weights = [ - 0, - ...map( - (x) => 1 - Math.pow((x - 1) / NUM_BINS, 0.15) * 0.99, - range(1, NUM_BINS) - ), + 0, + ...map( + (x) => 1 - Math.pow((x - 1) / NUM_BINS, 0.15) * 0.99, + range(1, NUM_BINS) + ), ]; const rnd = weightedRandom([...range(0, NUM_BINS)], weights); const startAutoMode = () => { - let i = 0; - DB.resetIn( - ["auto"], - setInterval(() => { - let { decay, attenuate, interval } = DB.value; - attenuate = 1 + attenuate; - DB.swapIn(["bins"], (buf: number[]) => - buf.map((x, i) => (x * decay) / attenuate ** i) - ); - if (i % interval === 0) { - const bin = rnd(); - updateSpectrumBin( - bin, - Math.random() * (1 - Math.pow((bin - 1) / NUM_BINS, 0.8)) - ); - } - updateAudio(); - i++; - }, 16) - ); + let i = 0; + DB.resetIn( + ["auto"], + setInterval(() => { + let { decay, attenuate, interval } = DB.value; + attenuate = 1 + attenuate; + DB.swapIn(["bins"], (buf: number[]) => + buf.map((x, i) => (x * decay) / attenuate ** i) + ); + if (i % interval === 0) { + const bin = rnd(); + updateSpectrumBin( + bin, + Math.random() * (1 - Math.pow((bin - 1) / NUM_BINS, 0.8)) + ); + } + updateAudio(); + i++; + }, 16) + ); }; const stopAutoMode = () => { - clearInterval(DB.value.auto); - DB.resetIn(["auto"], null); + clearInterval(DB.value.auto); + DB.resetIn(["auto"], null); }; export const toggleAutoMode = () => - DB.value.auto == null ? startAutoMode() : stopAutoMode(); + DB.value.auto == null ? startAutoMode() : stopAutoMode(); diff --git a/examples/fft-synth/src/config.ts b/examples/fft-synth/src/config.ts index 4d47840a8a..9644a9686d 100644 --- a/examples/fft-synth/src/config.ts +++ b/examples/fft-synth/src/config.ts @@ -14,14 +14,14 @@ export const FMT = float(3); export const FMT_PERCENT = percent(3); export const PRESETS: any[] = [ - () => [0, 1, ...repeat(0, NUM_BINS - 2)], - () => [0, ...map((i) => 1 / i, range(1, NUM_BINS))], - () => [...mapcat((i) => [0, 1 / i], range(1, NUM_BINS + 1, 2))], + () => [0, 1, ...repeat(0, NUM_BINS - 2)], + () => [0, ...map((i) => 1 / i, range(1, NUM_BINS))], + () => [...mapcat((i) => [0, 1 / i], range(1, NUM_BINS + 1, 2))], ]; export const BIN_LABELS = [ - ...map( - (i) => (binFreq(i, 48000 / PITCH_SCALE, WINDOW_LEN) | 0) + " Hz", - range(NUM_BINS) - ), + ...map( + (i) => (binFreq(i, 48000 / PITCH_SCALE, WINDOW_LEN) | 0) + " Hz", + range(NUM_BINS) + ), ]; diff --git a/examples/fft-synth/src/gui.ts b/examples/fft-synth/src/gui.ts index ec4d97fe60..3c35bbf4fd 100644 --- a/examples/fft-synth/src/gui.ts +++ b/examples/fft-synth/src/gui.ts @@ -9,176 +9,176 @@ import { gridLayout, GridLayout } from "@thi.ng/layout/grid-layout"; import { initAudio, isAudioActive, stopAudio } from "./audio"; import { toggleAutoMode } from "./automode"; import { - BIN_LABELS, - FMT, - FMT_PERCENT, - NUM_BINS, - PITCH_SCALE, - WINDOW_LEN, + BIN_LABELS, + FMT, + FMT_PERCENT, + NUM_BINS, + PITCH_SCALE, + WINDOW_LEN, } from "./config"; import { - clearSpectrum, - DB, - setGain, - setSpectrumPreset, - updateSpectrumBin, + clearSpectrum, + DB, + setGain, + setSpectrumPreset, + updateSpectrumBin, } from "./state"; export const gui = new IMGUI({ - theme: { - ...DEFAULT_THEME, - globalBg: "#ccc", - font: "10px Inconsolata", - charWidth: 5, - }, + theme: { + ...DEFAULT_THEME, + globalBg: "#ccc", + font: "10px Inconsolata", + charWidth: 5, + }, }); export const updateGUI = (draw: boolean) => { - const state = DB.deref(); - const width = state.size[0] - 20; - const grid = gridLayout(10, 10, width, 1, 16, 4); - let inner: GridLayout; - let res: any; - - gui.begin(draw); - - // audio ctrls - - inner = grid.nest(2); - - if ( - buttonH( - gui, - inner, - "audioToggle", - `Turn audio ${isAudioActive() ? "off" : "on"}` - ) - ) { - isAudioActive() ? stopAudio() : initAudio(WINDOW_LEN * PITCH_SCALE); - } - - gui.beginDisabled(!isAudioActive()); - - res = sliderH( - gui, - inner, - "gain", - 0, - 1, - 0.001, - state.gain, - "Gain", - FMT, - "Master volume" - ); - res !== undefined && setGain(res); - - gui.endDisabled(); - - // auto sequencer mode - - grid.next(); - inner = grid.nest(5); - toggle( - gui, - inner, - "autoMode", - !!state.auto, - false, - "Auto mode", - "Randomized spectrum sequencer" - ) !== undefined && toggleAutoMode(); - - gui.beginDisabled(!state.auto); - - res = sliderH( - gui, - inner, - "decay", - 0.5, - 0.999, - 0.001, - state.decay, - "Decay", - FMT_PERCENT, - "Bin gain decay factor" - ); - res !== undefined && DB.resetIn(["decay"], res); - - res = sliderH( - gui, - inner, - "attenuate", - 0, - 0.05, - 0.0001, - state.attenuate, - "Attentuation", - FMT_PERCENT, - "Bin decay attenuation factor" - ); - res !== undefined && DB.resetIn(["attenuate"], res); - - res = sliderH( - gui, - inner, - "feedback", - 0, - 0.95, - 0.01, - state.feedback, - "Feedback", - FMT_PERCENT, - "Delay line feedback" - ); - res !== undefined && DB.resetIn(["feedback"], res); - - res = sliderH( - gui, - inner, - "delay", - 1, - 100, - 1, - state.interval, - "Interval", - FMT, - "Trigger interval" - ); - res !== undefined && DB.resetIn(["interval"], res); - - gui.endDisabled(); - - // presets - - inner = grid.nest(4); - buttonH(gui, inner, "clear", "Clear", undefined, "Clear all bins") && - clearSpectrum(); - buttonH(gui, inner, "presetSin", "Sine", undefined, "Apply preset") && - setSpectrumPreset(0); - buttonH(gui, inner, "presetSaw", "Saw", undefined, "Apply preset") && - setSpectrumPreset(1); - buttonH(gui, inner, "presetSq", "Square", undefined, "Apply preset") && - setSpectrumPreset(2); - - // FFT bins - - textLabel(gui, grid, "Frequency bins"); - res = sliderVGroup( - gui, - grid, - "bins", - 0, - 1, - 0.001, - state.bins.slice(0, Math.min(width / 16, NUM_BINS)), - 8, - [], - FMT, - BIN_LABELS - ); - res && updateSpectrumBin(res[0], res[1]); - - textLabel(gui, grid, "Waveform"); - gui.end(); + const state = DB.deref(); + const width = state.size[0] - 20; + const grid = gridLayout(10, 10, width, 1, 16, 4); + let inner: GridLayout; + let res: any; + + gui.begin(draw); + + // audio ctrls + + inner = grid.nest(2); + + if ( + buttonH( + gui, + inner, + "audioToggle", + `Turn audio ${isAudioActive() ? "off" : "on"}` + ) + ) { + isAudioActive() ? stopAudio() : initAudio(WINDOW_LEN * PITCH_SCALE); + } + + gui.beginDisabled(!isAudioActive()); + + res = sliderH( + gui, + inner, + "gain", + 0, + 1, + 0.001, + state.gain, + "Gain", + FMT, + "Master volume" + ); + res !== undefined && setGain(res); + + gui.endDisabled(); + + // auto sequencer mode + + grid.next(); + inner = grid.nest(5); + toggle( + gui, + inner, + "autoMode", + !!state.auto, + false, + "Auto mode", + "Randomized spectrum sequencer" + ) !== undefined && toggleAutoMode(); + + gui.beginDisabled(!state.auto); + + res = sliderH( + gui, + inner, + "decay", + 0.5, + 0.999, + 0.001, + state.decay, + "Decay", + FMT_PERCENT, + "Bin gain decay factor" + ); + res !== undefined && DB.resetIn(["decay"], res); + + res = sliderH( + gui, + inner, + "attenuate", + 0, + 0.05, + 0.0001, + state.attenuate, + "Attentuation", + FMT_PERCENT, + "Bin decay attenuation factor" + ); + res !== undefined && DB.resetIn(["attenuate"], res); + + res = sliderH( + gui, + inner, + "feedback", + 0, + 0.95, + 0.01, + state.feedback, + "Feedback", + FMT_PERCENT, + "Delay line feedback" + ); + res !== undefined && DB.resetIn(["feedback"], res); + + res = sliderH( + gui, + inner, + "delay", + 1, + 100, + 1, + state.interval, + "Interval", + FMT, + "Trigger interval" + ); + res !== undefined && DB.resetIn(["interval"], res); + + gui.endDisabled(); + + // presets + + inner = grid.nest(4); + buttonH(gui, inner, "clear", "Clear", undefined, "Clear all bins") && + clearSpectrum(); + buttonH(gui, inner, "presetSin", "Sine", undefined, "Apply preset") && + setSpectrumPreset(0); + buttonH(gui, inner, "presetSaw", "Saw", undefined, "Apply preset") && + setSpectrumPreset(1); + buttonH(gui, inner, "presetSq", "Square", undefined, "Apply preset") && + setSpectrumPreset(2); + + // FFT bins + + textLabel(gui, grid, "Frequency bins"); + res = sliderVGroup( + gui, + grid, + "bins", + 0, + 1, + 0.001, + state.bins.slice(0, Math.min(width / 16, NUM_BINS)), + 8, + [], + FMT, + BIN_LABELS + ); + res && updateSpectrumBin(res[0], res[1]); + + textLabel(gui, grid, "Waveform"); + gui.end(); }; diff --git a/examples/fft-synth/src/index.ts b/examples/fft-synth/src/index.ts index 343eb8cbb3..dfc25e3c84 100644 --- a/examples/fft-synth/src/index.ts +++ b/examples/fft-synth/src/index.ts @@ -14,70 +14,70 @@ import { gui, updateGUI } from "./gui"; import { DB } from "./state"; const main = sync({ - src: { - state: fromAtom(DB), - }, + src: { + state: fromAtom(DB), + }, }); const app = () => { - const _canvas = { - ...canvas, - init(canv: HTMLCanvasElement) { - main.add( - merge({ - src: [ - gestureStream(canv, {}).subscribe({ - next(e) { - gui.setMouse(e.pos, e.buttons); - }, - }), - fromDOMEvent(window, "resize").subscribe({ - next() { - DB.resetIn( - ["size"], - [window.innerWidth, window.innerHeight] - ); - }, - }), - ], - }) - ); - }, - }; + const _canvas = { + ...canvas, + init(canv: HTMLCanvasElement) { + main.add( + merge({ + src: [ + gestureStream(canv, {}).subscribe({ + next(e) { + gui.setMouse(e.pos, e.buttons); + }, + }), + fromDOMEvent(window, "resize").subscribe({ + next() { + DB.resetIn( + ["size"], + [window.innerWidth, window.innerHeight] + ); + }, + }), + ], + }) + ); + }, + }; - return () => { - const width = window.innerWidth; - const height = 500; - const iwidth = width - 10; + return () => { + const width = window.innerWidth; + const height = 500; + const iwidth = width - 10; - updateGUI(false); - updateGUI(true); + updateGUI(false); + updateGUI(true); - return [ - _canvas, - { - width, - height, - style: { background: gui.theme.globalBg, cursor: gui.cursor }, - ...gui.attribs, - }, - gui, - // waveform display - [ - "polyline", - { stroke: "red" }, - [ - ...mapIndexed( - (i, y) => [ - fit(i, 0, WINDOW_LEN - 1, 10, iwidth), - fitClamped(y, -1, 1, 490, 290), - ], - DB.value.wave - ), - ], - ], - ]; - }; + return [ + _canvas, + { + width, + height, + style: { background: gui.theme.globalBg, cursor: gui.cursor }, + ...gui.attribs, + }, + gui, + // waveform display + [ + "polyline", + { stroke: "red" }, + [ + ...mapIndexed( + (i, y) => [ + fit(i, 0, WINDOW_LEN - 1, 10, iwidth), + fitClamped(y, -1, 1, 490, 290), + ], + DB.value.wave + ), + ], + ], + ]; + }; }; // subscription & transformation of app state stream. uses a RAF diff --git a/examples/fft-synth/src/state.ts b/examples/fft-synth/src/state.ts index 61f4d691fd..29787459b6 100644 --- a/examples/fft-synth/src/state.ts +++ b/examples/fft-synth/src/state.ts @@ -4,33 +4,33 @@ import { makeBins, updateAudio } from "./audio"; import { NUM_BINS, PRESETS } from "./config"; export const DB = defAtom({ - auto: null, - gain: 0.5, - decay: 0.999, - attenuate: 0.004, - interval: 70, - feedback: 0.66, - bins: [...repeat(0, NUM_BINS)], - wave: new Float64Array(NUM_BINS * 2), - size: [window.innerWidth, window.innerHeight], + auto: null, + gain: 0.5, + decay: 0.999, + attenuate: 0.004, + interval: 70, + feedback: 0.66, + bins: [...repeat(0, NUM_BINS)], + wave: new Float64Array(NUM_BINS * 2), + size: [window.innerWidth, window.innerHeight], }); export const setGain = (gain: number) => { - DB.resetIn(["gain"], gain); - updateAudio(); + DB.resetIn(["gain"], gain); + updateAudio(); }; export const clearSpectrum = () => { - DB.resetIn(["bins"], makeBins()); - updateAudio(); + DB.resetIn(["bins"], makeBins()); + updateAudio(); }; export const setSpectrumPreset = (id: number) => { - DB.swapIn(["bins"], PRESETS[id]); - updateAudio(); + DB.swapIn(["bins"], PRESETS[id]); + updateAudio(); }; export const updateSpectrumBin = (bin: number, amp: number) => { - DB.resetIn(["bins", bin], amp); - updateAudio(); + DB.resetIn(["bins", bin], amp); + updateAudio(); }; diff --git a/examples/fft-synth/tsconfig.json b/examples/fft-synth/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/fft-synth/tsconfig.json +++ b/examples/fft-synth/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/geom-convex-hull/package.json b/examples/geom-convex-hull/package.json index d9a14250a6..95b5432e49 100644 --- a/examples/geom-convex-hull/package.json +++ b/examples/geom-convex-hull/package.json @@ -1,37 +1,37 @@ { - "name": "@example/geom-convex-hull", - "private": true, - "description": "Convex hull & shape clipping of 2D polygons", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/geom": "workspace:^", - "@thi.ng/geom-api": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom", - "geom-hull", - "hdom-canvas" - ], - "screenshot": "examples/geom-convex-hull.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/geom-convex-hull", + "private": true, + "description": "Convex hull & shape clipping of 2D polygons", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/geom": "workspace:^", + "@thi.ng/geom-api": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom", + "geom-hull", + "hdom-canvas" + ], + "screenshot": "examples/geom-convex-hull.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/geom-convex-hull/src/index.ts b/examples/geom-convex-hull/src/index.ts index 682c99a1e6..c72c96af35 100644 --- a/examples/geom-convex-hull/src/index.ts +++ b/examples/geom-convex-hull/src/index.ts @@ -36,25 +36,25 @@ const COL3 = (a: number) => `rgba(102,102,102,${a})`; // render shapes with thi.ng/hdom & thi.ng/hdom-canvas renderOnce([ - canvas, - { width, height }, - points(pts1, { shape: "circle", fill: COL1(1), size: 5 }), - points(pts2, { shape: "circle", fill: COL2(1), size: 5 }), - withAttribs(hull1, { fill: COL1(0.5) }), - withAttribs(hull2, { fill: COL2(0.5) }), - withAttribs(clip, { - fill: COL3(0.5), - stroke: COL3(1), - weight: 3, - }), - ...(<[IShape, string][]>[ - [hull1, COL1(1)], - [hull2, COL2(1)], - [clip, COL3(1)], - ]).map(([shape, col]) => [ - "text", - { fill: col }, - centroid(shape), - area(shape).toFixed(2), - ]), + canvas, + { width, height }, + points(pts1, { shape: "circle", fill: COL1(1), size: 5 }), + points(pts2, { shape: "circle", fill: COL2(1), size: 5 }), + withAttribs(hull1, { fill: COL1(0.5) }), + withAttribs(hull2, { fill: COL2(0.5) }), + withAttribs(clip, { + fill: COL3(0.5), + stroke: COL3(1), + weight: 3, + }), + ...(<[IShape, string][]>[ + [hull1, COL1(1)], + [hull2, COL2(1)], + [clip, COL3(1)], + ]).map(([shape, col]) => [ + "text", + { fill: col }, + centroid(shape), + area(shape).toFixed(2), + ]), ]); diff --git a/examples/geom-convex-hull/tsconfig.json b/examples/geom-convex-hull/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/geom-convex-hull/tsconfig.json +++ b/examples/geom-convex-hull/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/geom-fuzz-basics/package.json b/examples/geom-fuzz-basics/package.json index 8d10dc94ff..98adf1e740 100644 --- a/examples/geom-fuzz-basics/package.json +++ b/examples/geom-fuzz-basics/package.json @@ -1,38 +1,38 @@ { - "name": "@example/geom-fuzz-basics", - "private": true, - "version": "0.0.1", - "description": "geom-fuzz basic shape & fill examples", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/adapt-dpi": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/geom-fuzz": "workspace:^", - "@thi.ng/hiccup-canvas": "workspace:^", - "@thi.ng/rstream": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom", - "geom-fuzz", - "hiccup-canvas" - ], - "screenshot": "geom/geom-fuzz.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/geom-fuzz-basics", + "private": true, + "version": "0.0.1", + "description": "geom-fuzz basic shape & fill examples", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/adapt-dpi": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/geom-fuzz": "workspace:^", + "@thi.ng/hiccup-canvas": "workspace:^", + "@thi.ng/rstream": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom", + "geom-fuzz", + "hiccup-canvas" + ], + "screenshot": "geom/geom-fuzz.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/geom-fuzz-basics/src/index.ts b/examples/geom-fuzz-basics/src/index.ts index 4c235d89fd..da82f6bb44 100644 --- a/examples/geom-fuzz-basics/src/index.ts +++ b/examples/geom-fuzz-basics/src/index.ts @@ -1,10 +1,10 @@ import { adaptDPI } from "@thi.ng/adapt-dpi"; import { - compFill, - defDots, - defHatchPen, - fuzzyPoly, - jitterPoints, + compFill, + defDots, + defHatchPen, + fuzzyPoly, + jitterPoints, } from "@thi.ng/geom-fuzz"; import { circle } from "@thi.ng/geom/circle"; import { group } from "@thi.ng/geom/group"; @@ -16,29 +16,29 @@ import { fromInterval } from "@thi.ng/rstream/interval"; const W = 300; const SHAPES = { - tri: vertices(circle(100), 3), - hex: vertices(circle(50), 6), - star: vertices(star(50, 6, [0.5, 1])), - spike: jitterPoints(vertices(circle(40), 12), 15), + tri: vertices(circle(100), 3), + hex: vertices(circle(50), 6), + star: vertices(star(50, 6, [0.5, 1])), + spike: jitterPoints(vertices(circle(40), 12), 15), }; const PEN1 = defHatchPen([0, 0.8, 1, 0.5]); const PEN2 = compFill( - defHatchPen("#f3f", "d", 1, 3), - defHatchPen("#f3f", "v", 1, 3) + defHatchPen("#f3f", "d", 1, 3), + defHatchPen("#f3f", "v", 1, 3) ); const PEN3 = defHatchPen([1, 1, 0, 0.5], "h", 4, 1.2); const PEN4 = defDots({ - jitter: 1, - attribs: { size: 1.5, fill: [0, 1, 0], stroke: "none" }, + jitter: 1, + attribs: { size: 1.5, fill: [0, 1, 0], stroke: "none" }, }); const curvePos = ( - t: number, - fx: number, - fy: number, - ax: number, - ay: number + t: number, + fx: number, + fy: number, + ax: number, + ay: number ) => [Math.sin(t * fx) * ax + W / 2, Math.cos(t * fy) * ay + W / 2]; const canvas: HTMLCanvasElement = document.createElement("canvas"); @@ -48,50 +48,50 @@ adaptDPI(canvas, W, W); const ctx = canvas.getContext("2d")!; fromInterval(1000 / 15).subscribe({ - next(t: number) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - draw( - ctx, - group({ stroke: "black", scale: window.devicePixelRatio || 1 }, [ - fuzzyPoly( - SHAPES.tri, - { translate: [150, 150], rotate: t * 0.04 }, - { - fill: PEN1, - curveScale: 0.05, - jitter: 3, - } - ), - fuzzyPoly( - SHAPES.star, - { - translate: curvePos(t, 0.02, 0.03, 100, 50), - }, - { - fill: PEN2, - curveScale: 0.3, - } - ), - fuzzyPoly( - SHAPES.hex, - { - translate: curvePos(t + 100, 0.03, 0.02, 100, 50), - }, - { - fill: PEN3, - curveScale: 0.1, - } - ), - fuzzyPoly( - SHAPES.spike, - { - translate: curvePos(t, 0.04, 0.03, 50, 100), - }, - { - fill: PEN4, - } - ), - ]) - ); - }, + next(t: number) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + draw( + ctx, + group({ stroke: "black", scale: window.devicePixelRatio || 1 }, [ + fuzzyPoly( + SHAPES.tri, + { translate: [150, 150], rotate: t * 0.04 }, + { + fill: PEN1, + curveScale: 0.05, + jitter: 3, + } + ), + fuzzyPoly( + SHAPES.star, + { + translate: curvePos(t, 0.02, 0.03, 100, 50), + }, + { + fill: PEN2, + curveScale: 0.3, + } + ), + fuzzyPoly( + SHAPES.hex, + { + translate: curvePos(t + 100, 0.03, 0.02, 100, 50), + }, + { + fill: PEN3, + curveScale: 0.1, + } + ), + fuzzyPoly( + SHAPES.spike, + { + translate: curvePos(t, 0.04, 0.03, 50, 100), + }, + { + fill: PEN4, + } + ), + ]) + ); + }, }); diff --git a/examples/geom-fuzz-basics/tsconfig.json b/examples/geom-fuzz-basics/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/geom-fuzz-basics/tsconfig.json +++ b/examples/geom-fuzz-basics/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/geom-knn/package.json b/examples/geom-knn/package.json index ce7eed0b7f..6b39d78e6b 100644 --- a/examples/geom-knn/package.json +++ b/examples/geom-knn/package.json @@ -1,44 +1,44 @@ { - "name": "@example/geom-knn", - "private": true, - "description": "Doodle w/ K-nearest neighbor search result visualization", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/bench": "workspace:^", - "@thi.ng/geom-accel": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "bench", - "geom-accel", - "hdom-canvas", - "rstream-gestures", - "transducers-hdom", - "vectors" - ], - "screenshot": "examples/geom-knn.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/geom-knn", + "private": true, + "description": "Doodle w/ K-nearest neighbor search result visualization", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/bench": "workspace:^", + "@thi.ng/geom-accel": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "bench", + "geom-accel", + "hdom-canvas", + "rstream-gestures", + "transducers-hdom", + "vectors" + ], + "screenshot": "examples/geom-knn.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/geom-knn/src/index.ts b/examples/geom-knn/src/index.ts index 0f5bde2f2d..ad4370b8c2 100644 --- a/examples/geom-knn/src/index.ts +++ b/examples/geom-knn/src/index.ts @@ -11,86 +11,86 @@ import { mapcat } from "@thi.ng/transducers/mapcat"; import type { Vec } from "@thi.ng/vectors"; const app = (main: StreamSync) => { - // augment hdom-canvas component w/ `init` lifecycle method: this is - // method is called when the canvas DOM element is first created and - // used to attach a mouse & touch event stream to it. this stream is - // then transformed using a transducer to only select the mouse - // position and then added as new input to the `main` stream - // combinator below... - const _canvas = { - ...canvas, - init: (el: HTMLCanvasElement) => - main.add(gestureStream(el).transform(map((g) => g.pos)), "mpos"), - }; - // initialize 1st point & store in tree for fast KNN searches - const width = window.innerWidth; - const height = window.innerHeight; - let tree = new KdTreeMap(2); + // augment hdom-canvas component w/ `init` lifecycle method: this is + // method is called when the canvas DOM element is first created and + // used to attach a mouse & touch event stream to it. this stream is + // then transformed using a transducer to only select the mouse + // position and then added as new input to the `main` stream + // combinator below... + const _canvas = { + ...canvas, + init: (el: HTMLCanvasElement) => + main.add(gestureStream(el).transform(map((g) => g.pos)), "mpos"), + }; + // initialize 1st point & store in tree for fast KNN searches + const width = window.innerWidth; + const height = window.innerHeight; + let tree = new KdTreeMap(2); - // return root component function, triggered by each new mouse / touch event - return ({ mpos }: { mpos: Vec }) => { - // recreate tree every 500 points (in lieu of re-balancing) - if (!(tree.size % 500)) { - tree = tree.copy(); - } - // the 1st invocation of this function will be via the - // `trigger()` stream defined further below. that means - // initially, there will be no valid `mpos` and so we insert a - // default point instead - mpos = mpos || [width / 2, height / 2]; - // record new pos in tree - tree.set(mpos, 1.5 + Math.random() * 5); - // even though we only create 2d vectors, we store a 3rd value - // in the backing array, which will be later used as radius when - // the point has been selected as part of a KNN query and is - // visualized as circle. - // mpos.push(1.5 + Math.random() * 5); + // return root component function, triggered by each new mouse / touch event + return ({ mpos }: { mpos: Vec }) => { + // recreate tree every 500 points (in lieu of re-balancing) + if (!(tree.size % 500)) { + tree = tree.copy(); + } + // the 1st invocation of this function will be via the + // `trigger()` stream defined further below. that means + // initially, there will be no valid `mpos` and so we insert a + // default point instead + mpos = mpos || [width / 2, height / 2]; + // record new pos in tree + tree.set(mpos, 1.5 + Math.random() * 5); + // even though we only create 2d vectors, we store a 3rd value + // in the backing array, which will be later used as radius when + // the point has been selected as part of a KNN query and is + // visualized as circle. + // mpos.push(1.5 + Math.random() * 5); - // select max. 200 neighbors for given mouse position, - // measure execution time... - let [selected, t1] = timedResult(() => - tree.query(mpos, width / 4, 200) - ); - // for each selected neighbor, perform another KNN search and - // create line segments to each of these secondary matches - // use `mapcat` to yield a flat array of lines - let [neighbors, t2] = timedResult(() => [ - ...mapcat( - (p) => - tree - .queryKeys(p[0], width / 4, 8) - .map((q) => ["line", {}, p[0], q]), - selected - ), - ]); - return [ - "div.overflow-hidden.sans-serif.f7", - // tree stats - [ - "div", - `Points: ${tree.size}, Sel: ${selected.length}, `, - `Neighbors: ${neighbors.length}, Q1: ${t1}ms, Q2: ${t2}ms, `, - `Height: ${tree.height}, Ratio: ${tree.ratio.toFixed(2)}`, - ], - // visualize - // the __diff & __normalize control attribs are used to optimize drawing perf - // see: https://github.com/thi-ng/umbrella/tree/develop/packages/hdom#behavior-control-attributes - [ - _canvas, - { width, height, __diff: false, __normalize: false }, - // point cloud - ["points", { fill: "black", size: 2 }, tree.keys()], - // selected points as circles (using 3rd array item as radius) - [ - "g", - { fill: "rgba(0,192,255,0.5)" }, - ...selected.map((p) => ["circle", {}, p[0], p[1]]), - ], - // secondary neighbor connections - ["g", { stroke: "rgba(0,0,0,0.25)" }, ...neighbors], - ], - ]; - }; + // select max. 200 neighbors for given mouse position, + // measure execution time... + let [selected, t1] = timedResult(() => + tree.query(mpos, width / 4, 200) + ); + // for each selected neighbor, perform another KNN search and + // create line segments to each of these secondary matches + // use `mapcat` to yield a flat array of lines + let [neighbors, t2] = timedResult(() => [ + ...mapcat( + (p) => + tree + .queryKeys(p[0], width / 4, 8) + .map((q) => ["line", {}, p[0], q]), + selected + ), + ]); + return [ + "div.overflow-hidden.sans-serif.f7", + // tree stats + [ + "div", + `Points: ${tree.size}, Sel: ${selected.length}, `, + `Neighbors: ${neighbors.length}, Q1: ${t1}ms, Q2: ${t2}ms, `, + `Height: ${tree.height}, Ratio: ${tree.ratio.toFixed(2)}`, + ], + // visualize + // the __diff & __normalize control attribs are used to optimize drawing perf + // see: https://github.com/thi-ng/umbrella/tree/develop/packages/hdom#behavior-control-attributes + [ + _canvas, + { width, height, __diff: false, __normalize: false }, + // point cloud + ["points", { fill: "black", size: 2 }, tree.keys()], + // selected points as circles (using 3rd array item as radius) + [ + "g", + { fill: "rgba(0,192,255,0.5)" }, + ...selected.map((p) => ["circle", {}, p[0], p[1]]), + ], + // secondary neighbor connections + ["g", { stroke: "rgba(0,0,0,0.25)" }, ...neighbors], + ], + ]; + }; }; // main stream combinator: initially only a single dummy `trigger` input @@ -99,8 +99,8 @@ const app = (main: StreamSync) => { // stream dynamically. the entire UI then only updates when there are new // user interactions... const main = sync({ - src: { trigger: trigger() }, - closeIn: CloseMode.NEVER, + src: { trigger: trigger() }, + closeIn: CloseMode.NEVER, }); // transform result stream using the // root component fn and the hdom differential diff --git a/examples/geom-knn/tsconfig.json b/examples/geom-knn/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/geom-knn/tsconfig.json +++ b/examples/geom-knn/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/geom-tessel/package.json b/examples/geom-tessel/package.json index 5b343f7c2f..c614273c60 100644 --- a/examples/geom-tessel/package.json +++ b/examples/geom-tessel/package.json @@ -1,42 +1,42 @@ { - "name": "@example/geom-tessel", - "private": true, - "description": "Animated, recursive polygon tessellations", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/compose": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/geom-api": "workspace:^", - "@thi.ng/geom-tessellate": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom", - "geom-tessellate" - ], - "screenshot": "geom/tessel.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/geom-tessel", + "private": true, + "description": "Animated, recursive polygon tessellations", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/compose": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/geom-api": "workspace:^", + "@thi.ng/geom-tessellate": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom", + "geom-tessellate" + ], + "screenshot": "geom/tessel.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/geom-tessel/src/index.ts b/examples/geom-tessel/src/index.ts index 896fbc5a6a..a260f5df50 100644 --- a/examples/geom-tessel/src/index.ts +++ b/examples/geom-tessel/src/index.ts @@ -40,11 +40,11 @@ const W2 = W / 2; * space to HSL. */ const centroidToHSL = (p: IShape) => { - const c = polar(null, centroid(p)!); - const h = deg(c[1]); - const s = fit01(c[0] / W2, 0, 100); - const l = fit01(c[0] / W2, 100, 50); - return `hsl(${h},${s}%,${l}%)`; + const c = polar(null, centroid(p)!); + const h = deg(c[1]); + const s = fit01(c[0] / W2, 0, 100); + const l = fit01(c[0] / W2, 100, 50); + return `hsl(${h},${s}%,${l}%)`; }; /** @@ -52,66 +52,66 @@ const centroidToHSL = (p: IShape) => { * shape. */ const arclengthToHSL = (max: number, p: IShape) => - `hsl(${fit01(arcLength(p) / max, 0, 360)},100%,50%)`; + `hsl(${fit01(arcLength(p) / max, 0, 360)},100%,50%)`; /** * Converts given point array into a polygon and computes fill color * with provided `tint` function. */ const tintedPoly = (tint: Tint, points: Vec[]) => { - const p = polygon(points); - p.attribs = { - fill: tint(p), - // stroke: tint(p), - }; - return p; + const p = polygon(points); + p.attribs = { + fill: tint(p), + // stroke: tint(p), + }; + return p; }; /** * Creates a regular polygon, then recursively subdivides it and tints */ const tessellation = (t: number, tessel: Tessellator[], tint: Tint) => { - return tessellate( - asPolygon( - circle([0, 0], W2), - Math.floor(fit11(Math.sin(t), MIN_RES, MAX_RES)) - ), - tessel - ).map(partial(tintedPoly, tint)); + return tessellate( + asPolygon( + circle([0, 0], W2), + Math.floor(fit11(Math.sin(t), MIN_RES, MAX_RES)) + ), + tessel + ).map(partial(tintedPoly, tint)); }; const main = sync({ - src: { - time: fromInterval(16), - }, + src: { + time: fromInterval(16), + }, }).transform( - // root component function - map(({ time }) => { - time *= 0.1; - // create tessellation - // resulting array contains Polygon2 values - // Polygon2 implements the .toHiccup() method for - // auto-conversion during hdom tree normalization - const cells = tessellation(time, SUBDIVS, partial(arclengthToHSL, 250)); - return [ - "div.ma2.sans-serif", - ["div", `Cells: ${cells.length}`], - [ - canvas, - { width: 600, height: 600 }, - // ["polygon", { stroke: "black" }, vertices(asPolygon(circle([300, 300], 300), 3))], - [ - "g", - { - translate: [300, 300], - // rotate: (time / 10) % TAU, - stroke: "#000", - weight: 0.25, - }, - ...cells, - ], - ], - ]; - }), - updateDOM() + // root component function + map(({ time }) => { + time *= 0.1; + // create tessellation + // resulting array contains Polygon2 values + // Polygon2 implements the .toHiccup() method for + // auto-conversion during hdom tree normalization + const cells = tessellation(time, SUBDIVS, partial(arclengthToHSL, 250)); + return [ + "div.ma2.sans-serif", + ["div", `Cells: ${cells.length}`], + [ + canvas, + { width: 600, height: 600 }, + // ["polygon", { stroke: "black" }, vertices(asPolygon(circle([300, 300], 300), 3))], + [ + "g", + { + translate: [300, 300], + // rotate: (time / 10) % TAU, + stroke: "#000", + weight: 0.25, + }, + ...cells, + ], + ], + ]; + }), + updateDOM() ); diff --git a/examples/geom-tessel/tsconfig.json b/examples/geom-tessel/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/geom-tessel/tsconfig.json +++ b/examples/geom-tessel/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/geom-voronoi-mst/package.json b/examples/geom-voronoi-mst/package.json index e4c866a4b1..9925fdf7ae 100644 --- a/examples/geom-voronoi-mst/package.json +++ b/examples/geom-voronoi-mst/package.json @@ -1,48 +1,48 @@ { - "name": "@example/geom-voronoi-mst", - "private": true, - "version": "0.0.1", - "description": "Poisson-disk shape-aware sampling, Voronoi & Minimum Spanning Tree visualization", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/adjacency": "workspace:^", - "@thi.ng/bench": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/geom-accel": "workspace:^", - "@thi.ng/geom-voronoi": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/poisson": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "adjacency", - "bench", - "geom", - "geom-accel", - "geom-voronoi", - "hdom-canvas", - "poisson" - ], - "screenshot": "examples/geom-voronoi-mst.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/geom-voronoi-mst", + "private": true, + "version": "0.0.1", + "description": "Poisson-disk shape-aware sampling, Voronoi & Minimum Spanning Tree visualization", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/adjacency": "workspace:^", + "@thi.ng/bench": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/geom-accel": "workspace:^", + "@thi.ng/geom-voronoi": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/poisson": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "adjacency", + "bench", + "geom", + "geom-accel", + "geom-voronoi", + "hdom-canvas", + "poisson" + ], + "screenshot": "examples/geom-voronoi-mst.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/geom-voronoi-mst/src/index.ts b/examples/geom-voronoi-mst/src/index.ts index 22daf3015b..6c7c577587 100644 --- a/examples/geom-voronoi-mst/src/index.ts +++ b/examples/geom-voronoi-mst/src/index.ts @@ -34,66 +34,66 @@ const poly = star(R, 8, [0.7, 1, 1, 0.7]); const bounds = center(rect(W))!; const pts = timed(() => - samplePoisson({ - points: () => scatter(poly, 1)![0], - density: (p) => fit(dist(p, closestPoint(poly, p)!), 0, R, 1, 20), - max: 10000, - quality: 1000, - index: new KdTreeSet(2), - }) + samplePoisson({ + points: () => scatter(poly, 1)![0], + density: (p) => fit(dist(p, closestPoint(poly, p)!), 0, R, 1, 20), + max: 10000, + quality: 1000, + index: new KdTreeSet(2), + }) ); const mesh = timed(() => new DVMesh(pts, 1e4)); const _mst = timed(() => { - const edges = [ - ...map( - (e) => [floor(null, e[0]), floor(null, e[1])], - mesh.edges(false, [ - [-R, -R], - [R, R], - ]) - ), - ]; + const edges = [ + ...map( + (e) => [floor(null, e[0]), floor(null, e[1])], + mesh.edges(false, [ + [-R, -R], + [R, R], + ]) + ), + ]; - const idx = new KdTreeMap(2); - const rawVerts = transduce( - comp( - mapcat((e) => e), - mapIndexed((i, v) => <[Vec, number]>[v, i]) - ), - push<[Vec, number]>(), - edges - ); - idx.into(rawVerts, 0); + const idx = new KdTreeMap(2); + const rawVerts = transduce( + comp( + mapcat((e) => e), + mapIndexed((i, v) => <[Vec, number]>[v, i]) + ), + push<[Vec, number]>(), + edges + ); + idx.into(rawVerts, 0); - const edgeVertexIDs: [number, number, Vec, Vec, number][] = edges.map( - ([a, b]) => { - const ia = idx.queryValues(a, Infinity, 1)[0]; - const ib = idx.queryValues(b, Infinity, 1)[0]; - return [ia, ib, a, b, dist(a, b)]; - } - ); + const edgeVertexIDs: [number, number, Vec, Vec, number][] = edges.map( + ([a, b]) => { + const ia = idx.queryValues(a, Infinity, 1)[0]; + const ib = idx.queryValues(b, Infinity, 1)[0]; + return [ia, ib, a, b, dist(a, b)]; + } + ); - return mst( - edgeVertexIDs, - rawVerts.length, - (e) => e[4], - (e) => [e[0], e[1]] - ).map((e) => line(e[2], e[3])); + return mst( + edgeVertexIDs, + rawVerts.length, + (e) => e[4], + (e) => [e[0], e[1]] + ).map((e) => line(e[2], e[3])); }); clearDOM(document.getElementById("app")!); renderOnce([ - canvas, - { width: W, height: W }, - group({ translate: [R, R] }, [ - group( - { stroke: "#9dd", weight: 0.25 }, - mesh.voronoi(vertices(bounds)).map((p) => polygon(p)) - ), - points(pts, { fill: "#f09", size: 2 }), - group({ stroke: "#000", weight: 1 }, _mst), - ]), + canvas, + { width: W, height: W }, + group({ translate: [R, R] }, [ + group( + { stroke: "#9dd", weight: 0.25 }, + mesh.voronoi(vertices(bounds)).map((p) => polygon(p)) + ), + points(pts, { fill: "#f09", size: 2 }), + group({ stroke: "#000", weight: 1 }, _mst), + ]), ]); diff --git a/examples/geom-voronoi-mst/tsconfig.json b/examples/geom-voronoi-mst/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/geom-voronoi-mst/tsconfig.json +++ b/examples/geom-voronoi-mst/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/gesture-analysis/package.json b/examples/gesture-analysis/package.json index d5848e101b..c12315cb56 100644 --- a/examples/gesture-analysis/package.json +++ b/examples/gesture-analysis/package.json @@ -1,44 +1,44 @@ { - "name": "@example/gesture-analysis", - "private": true, - "version": "0.0.1", - "description": "Mouse gesture / stroke analysis, simplification, corner detection", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/arrays": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/hiccup-svg": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom", - "hiccup-svg", - "rstream", - "rstream-gestures", - "transducers-hdom", - "vectors" - ], - "screenshot": "examples/gesture-analysis.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/gesture-analysis", + "private": true, + "version": "0.0.1", + "description": "Mouse gesture / stroke analysis, simplification, corner detection", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/arrays": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/hiccup-svg": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom", + "hiccup-svg", + "rstream", + "rstream-gestures", + "transducers-hdom", + "vectors" + ], + "screenshot": "examples/gesture-analysis.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/gesture-analysis/src/index.ts b/examples/gesture-analysis/src/index.ts index edef3b96de..0479dee8a4 100644 --- a/examples/gesture-analysis/src/index.ts +++ b/examples/gesture-analysis/src/index.ts @@ -29,32 +29,32 @@ import { CTA } from "./config"; * Root component function, attached to rstream (see further below). * Receives raw & processed gesture paths to visualize as SVG. * - * @param raw - - * @param processed - + * @param raw - + * @param processed - */ const app = ({ - raw, - processed, + raw, + processed, }: { - raw: Vec[]; - processed: { path: Vec[]; corners: Vec[] }; + raw: Vec[]; + processed: { path: Vec[]; corners: Vec[] }; }) => [ - "div", - svg( - { - width: window.innerWidth, - height: window.innerHeight, - stroke: "none", - fill: "none", - }, - path(raw || [], processed.path, processed.corners || []) - ), - [ - "div.fixed.top-0.left-0.ma3", - ["div", `raw: ${(raw && raw.length) || 0}`], - ["div", `resampled: ${(processed && processed.path.length) || 0}`], - ["div", `corners: ${(processed && processed.corners.length) || 0}`], - ], + "div", + svg( + { + width: window.innerWidth, + height: window.innerHeight, + stroke: "none", + fill: "none", + }, + path(raw || [], processed.path, processed.corners || []) + ), + [ + "div.fixed.top-0.left-0.ma3", + ["div", `raw: ${(raw && raw.length) || 0}`], + ["div", `resampled: ${(processed && processed.path.length) || 0}`], + ["div", `corners: ${(processed && processed.corners.length) || 0}`], + ], ]; /** @@ -66,40 +66,40 @@ const app = ({ * @param corners - rray of corner points */ const path = (raw: Vec[], sampled: Vec[], corners: Vec[]) => - group( - { __diff: false }, - polyline(raw, { stroke: "#444" }), - map((p) => circle(p, 2, { fill: "#444" }), raw), - polyline(sampled, { stroke: "#fff" }), - map((p) => circle(p, 2, { fill: "#fff" }), sampled), - map((p) => circle(p, 6, { fill: "#cf0" }), corners), - circle(sampled[0], 6, { fill: "#f0c" }), - circle(peek(sampled), 6, { fill: "#0cf" }) - ); + group( + { __diff: false }, + polyline(raw, { stroke: "#444" }), + map((p) => circle(p, 2, { fill: "#444" }), raw), + polyline(sampled, { stroke: "#fff" }), + map((p) => circle(p, 2, { fill: "#fff" }), sampled), + map((p) => circle(p, 6, { fill: "#cf0" }), corners), + circle(sampled[0], 6, { fill: "#f0c" }), + circle(peek(sampled), 6, { fill: "#0cf" }) + ); /** * Re-samples given polyline at given uniform distance. Returns array of * interpolated points (does not modify original). * * @param step - ample distance - * @param pts - + * @param pts - */ const sampleUniform = (step: number, pts: Vec[]) => - vertices(resample(gPolyline(pts), { dist: step })); + vertices(resample(gPolyline(pts), { dist: step })); /** * Applies low-pass filter to given polyline. I.e. Each point in the * array (apart from the 1st) is interpolated towards the last point in * the result array. Returns new array of smoothed points. * - * @param path - + * @param path - */ const smoothPath = (smooth: number, path: Vec[]) => { - const res: Vec[] = [path[0]]; - for (let i = 1, n = path.length; i < n; i++) { - res.push(mixN2([], path[i], res[i - 1], smooth)); - } - return res; + const res: Vec[] = [path[0]]; + for (let i = 1, n = path.length; i < n; i++) { + res.push(mixN2([], path[i], res[i - 1], smooth)); + } + return res; }; /** @@ -109,45 +109,45 @@ const smoothPath = (smooth: number, path: Vec[]) => { * @param thresh - ormalized angle threshold */ const isCorner = - (thresh: number) => - ([a, b, c]: Vec[]) => - angleBetween2(sub2([], b, a), sub2([], b, c), true) < thresh; + (thresh: number) => + ([a, b, c]: Vec[]) => + angleBetween2(sub2([], b, a), sub2([], b, c), true) < thresh; /** * Gesture event processor. Collects gesture event positions into an * array of Vec2. */ const collectPath = () => { - let pts: Vec[] = []; - return (g: GestureEvent) => { - console.log(g); - switch (g.type) { - case "start": - pts = [g.pos]; - break; - case "drag": - pts.push(g.pos); - break; - } - return pts; - }; + let pts: Vec[] = []; + return (g: GestureEvent) => { + console.log(g); + switch (g.type) { + case "start": + pts = [g.pos]; + break; + case "drag": + pts.push(g.pos); + break; + } + return pts; + }; }; // gesture input stream(s) const gesture = merge({ - src: [ - // the initial CTA (call-to-action) gesture (see config.ts) - // will be shown prior to first user interaction. - // this stream only emits this one single gesture path, - // then closes and will be removed from the stream merge - fromIterable([CTA]), - // mouse & touch event stream attached to document.body - // we're filtering out move & zoom events to avoid extraneous work - gestureStream(document.body).transform( - filter((g) => !(g.type === "move" || g.type === "zoom")), - map(collectPath()) - ), - ], + src: [ + // the initial CTA (call-to-action) gesture (see config.ts) + // will be shown prior to first user interaction. + // this stream only emits this one single gesture path, + // then closes and will be removed from the stream merge + fromIterable([CTA]), + // mouse & touch event stream attached to document.body + // we're filtering out move & zoom events to avoid extraneous work + gestureStream(document.body).transform( + filter((g) => !(g.type === "move" || g.type === "zoom")), + map(collectPath()) + ), + ], }); // main gesture processor @@ -157,32 +157,32 @@ const gesture = merge({ // the resulting stream will emit tuple objects of this structure: // `{ raw: Vec2[], processed: { path: Vec2[], corners: Vec2[] } } sync({ - src: { - raw: gesture, - processed: gesture.transform( - comp( - map((pts: Vec[]) => smoothPath(3 / 4, pts)), - map((pts: Vec[]) => sampleUniform(20, pts)), - multiplexObj({ - path: noop(), - corners: map((pts) => - transduce( - comp( - partition(3, 1), - filter(isCorner((Math.PI * 2) / 3)), - map((x) => x[1]) - ), - push(), - pts - ) - ), - }) - ) - ), - }, + src: { + raw: gesture, + processed: gesture.transform( + comp( + map((pts: Vec[]) => smoothPath(3 / 4, pts)), + map((pts: Vec[]) => sampleUniform(20, pts)), + multiplexObj({ + path: noop(), + corners: map((pts) => + transduce( + comp( + partition(3, 1), + filter(isCorner((Math.PI * 2) / 3)), + map((x) => x[1]) + ), + push(), + pts + ) + ), + }) + ) + ), + }, }).transform( - // transform result tuples into HDOM components - map(app), - // update UI diff - updateDOM() + // transform result tuples into HDOM components + map(app), + // update UI diff + updateDOM() ); diff --git a/examples/gesture-analysis/tsconfig.json b/examples/gesture-analysis/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/gesture-analysis/tsconfig.json +++ b/examples/gesture-analysis/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/grid-iterators/package.json b/examples/grid-iterators/package.json index 39ba22b031..55d204f3c2 100644 --- a/examples/grid-iterators/package.json +++ b/examples/grid-iterators/package.json @@ -1,37 +1,37 @@ { - "name": "@example/grid-iterators", - "private": true, - "description": "Visualization of different grid iterator strategies", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/color": "workspace:^", - "@thi.ng/grid-iterators": "workspace:^", - "@thi.ng/pixel": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "color", - "grid-iterators", - "transducers" - ], - "screenshot": "examples/grid-iterators.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/grid-iterators", + "private": true, + "description": "Visualization of different grid iterator strategies", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/color": "workspace:^", + "@thi.ng/grid-iterators": "workspace:^", + "@thi.ng/pixel": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "color", + "grid-iterators", + "transducers" + ], + "screenshot": "examples/grid-iterators.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/grid-iterators/src/index.ts b/examples/grid-iterators/src/index.ts index e7f994dacf..8989b4ee6f 100644 --- a/examples/grid-iterators/src/index.ts +++ b/examples/grid-iterators/src/index.ts @@ -1,16 +1,16 @@ import { hueRgb } from "@thi.ng/color/rgb/hue-rgb"; import { srgbCss } from "@thi.ng/color/srgb/srgb-css"; import { - diagonal2d, - hilbert2d, - interleaveColumns2d, - interleaveRows2d, - random2d, - spiral2d, - zcurve2d, - zigzagColumns2d, - zigzagDiagonal2d, - zigzagRows2d, + diagonal2d, + hilbert2d, + interleaveColumns2d, + interleaveRows2d, + random2d, + spiral2d, + zcurve2d, + zigzagColumns2d, + zigzagDiagonal2d, + zigzagRows2d, } from "@thi.ng/grid-iterators"; import { canvas2d } from "@thi.ng/pixel/canvas"; import { concat } from "@thi.ng/transducers/concat"; @@ -25,26 +25,26 @@ const BW = Math.ceil(W / NB); const BH = Math.ceil(H / NB); // create infinite sequence of all grid iterators const buckets = cycle( - concat( - diagonal2d(NB, NB), - zigzagRows2d(NB), - hilbert2d(NB), - zigzagColumns2d(NB), - spiral2d(NB), - zigzagDiagonal2d(NB), - interleaveColumns2d(NB, NB, 4), - interleaveRows2d(NB, NB, 4), - zcurve2d(NB), - random2d(NB) - ) + concat( + diagonal2d(NB, NB), + zigzagRows2d(NB), + hilbert2d(NB), + zigzagColumns2d(NB), + spiral2d(NB), + zigzagDiagonal2d(NB), + interleaveColumns2d(NB, NB, 4), + interleaveRows2d(NB, NB, 4), + zcurve2d(NB), + random2d(NB) + ) ); let frame = 0; setInterval(() => { - const b = buckets.next(); - let [x, y] = b.value; - x *= BW; - y *= BH; - ctx.fillStyle = srgbCss(hueRgb([], frame++ / (NB * NB))); - ctx.fillRect(x, y, BW, BH); + const b = buckets.next(); + let [x, y] = b.value; + x *= BW; + y *= BH; + ctx.fillStyle = srgbCss(hueRgb([], frame++ / (NB * NB))); + ctx.fillRect(x, y, BW, BH); }, 16); diff --git a/examples/grid-iterators/tsconfig.json b/examples/grid-iterators/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/grid-iterators/tsconfig.json +++ b/examples/grid-iterators/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-basics/package.json b/examples/hdom-basics/package.json index 1fcbc73fa5..9ac843b6b7 100644 --- a/examples/hdom-basics/package.json +++ b/examples/hdom-basics/package.json @@ -1,26 +1,26 @@ { - "name": "@example/hdom-basics", - "private": true, - "description": "Minimal hdom usage example", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hdom-basics", + "private": true, + "description": "Minimal hdom usage example", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hdom-basics/src/index.ts b/examples/hdom-basics/src/index.ts index 7844d164f7..5130a7b87f 100644 --- a/examples/hdom-basics/src/index.ts +++ b/examples/hdom-basics/src/index.ts @@ -7,14 +7,14 @@ const greeter = (_: any, name: string) => ["h1.title", "hello ", name]; // component w/ local state const counter = (i = 0) => { - return () => ["button", { onclick: () => i++ }, `clicks: ${i}`]; + return () => ["button", { onclick: () => i++ }, `clicks: ${i}`]; }; const app = () => { - // initialization steps - // ... - // root component is just a static array - return ["div#app", [greeter, "world"], counter(), counter(100)]; + // initialization steps + // ... + // root component is just a static array + return ["div#app", [greeter, "world"], counter(), counter(100)]; }; // start update loop (browser only, see diagram below) diff --git a/examples/hdom-basics/tsconfig.json b/examples/hdom-basics/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-basics/tsconfig.json +++ b/examples/hdom-basics/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-benchmark/package.json b/examples/hdom-benchmark/package.json index 3c3abede0b..8d7537e11f 100644 --- a/examples/hdom-benchmark/package.json +++ b/examples/hdom-benchmark/package.json @@ -1,33 +1,33 @@ { - "name": "@example/hdom-benchmark", - "private": true, - "description": "hdom update performance benchmark (old version)", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/hex": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "screenshot": "examples/hdom-benchmark.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hdom-benchmark", + "private": true, + "description": "hdom update performance benchmark (old version)", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/hex": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "screenshot": "examples/hdom-benchmark.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hdom-benchmark/src/index.ts b/examples/hdom-benchmark/src/index.ts index be7ffd62d0..fa5a2912ab 100644 --- a/examples/hdom-benchmark/src/index.ts +++ b/examples/hdom-benchmark/src/index.ts @@ -15,17 +15,17 @@ import { range } from "@thi.ng/transducers/range"; * Single box component. Uses given id to switch between using `div` or * `box` element types, computes color and body text. * - * @param id - + * @param id - */ const box = (index: number, id: number) => [ - id & 1 ? "div" : "box", - { - key: index, - style: { - background: "#" + U24(((id & 0x1ff) << 15) | (id << 10) | id), - }, - }, - U16(id & 0xffff), + id & 1 ? "div" : "box", + { + key: index, + style: { + background: "#" + U24(((id & 0x1ff) << 15) | (id << 10) | id), + }, + }, + U16(id & 0xffff), ]; /** @@ -34,97 +34,97 @@ const box = (index: number, id: number) => [ * uses the time interval between received values. If not given, * attaches itself to a new RAF event stream. * - * @param src - - * @param width - - * @param height - - * @param period - - * @param col - - * @param txtCol - + * @param src - + * @param width - + * @param height - + * @param period - + * @param col - + * @param txtCol - */ const fpsCounter = ( - src: Stream | null, - width = 100, - height = 30, - period = 50, - col = "#09f", - txtCol = "#000" + src: Stream | null, + width = 100, + height = 30, + period = 50, + col = "#09f", + txtCol = "#000" ) => { - let ctx: CanvasRenderingContext2D; - let scale = height / 60; - (src || fromRAF()).subscribe( - { - next(samples: number[]) { - ctx.clearRect(0, 0, width, height); - ctx.fillStyle = col; - ctx.beginPath(); - ctx.moveTo(0, height); - for (let i = 0; i < width; i++) { - ctx.lineTo(i, height - samples[i] * scale); - } - ctx.lineTo(width - 1, height); - ctx.fill(); - ctx.fillStyle = txtCol; - ctx.fillText( - `SMA(${period}): ${samples[width - 1].toFixed(1)} fps`, - 2, - height - 4 - ); - }, - }, - { - // stream transducer to compute the windowed moving avarage - xform: comp( - benchmark(), - movingAverage(period), - map((x) => 1000 / x), - partition(width, 1, true) - ), - } - ); - return [ - { - init: (el: HTMLCanvasElement) => { - ctx = el.getContext("2d")!; - ctx.fillStyle = txtCol; - ctx.fillText("sampling...", 2, height - 4); - }, - render: () => ["canvas", { width, height }], - }, - ]; + let ctx: CanvasRenderingContext2D; + let scale = height / 60; + (src || fromRAF()).subscribe( + { + next(samples: number[]) { + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = col; + ctx.beginPath(); + ctx.moveTo(0, height); + for (let i = 0; i < width; i++) { + ctx.lineTo(i, height - samples[i] * scale); + } + ctx.lineTo(width - 1, height); + ctx.fill(); + ctx.fillStyle = txtCol; + ctx.fillText( + `SMA(${period}): ${samples[width - 1].toFixed(1)} fps`, + 2, + height - 4 + ); + }, + }, + { + // stream transducer to compute the windowed moving avarage + xform: comp( + benchmark(), + movingAverage(period), + map((x) => 1000 / x), + partition(width, 1, true) + ), + } + ); + return [ + { + init: (el: HTMLCanvasElement) => { + ctx = el.getContext("2d")!; + ctx.fillStyle = txtCol; + ctx.fillText("sampling...", 2, height - 4); + }, + render: () => ["canvas", { width, height }], + }, + ]; }; /** * Main app root component */ const app = () => { - // initialize local state - let i = 0, - num = 128; - const fps = fpsCounter(null, 100, 60); - const menu = dropdown( - null, - { - onchange: (e: Event) => { - num = parseInt((e.target).value); - }, - }, - [ - [128, 128], - [192, 192], - [256, 256], - [384, 384], - [512, 512], - ] - ); + // initialize local state + let i = 0, + num = 128; + const fps = fpsCounter(null, 100, 60); + const menu = dropdown( + null, + { + onchange: (e: Event) => { + num = parseInt((e.target).value); + }, + }, + [ + [128, 128], + [192, 192], + [256, 256], + [384, 384], + [512, 512], + ] + ); - return () => { - let j = ++i & 0x1ff; - return [ - "div", - ["div#stats", fps, menu], - ["grid", mapIndexed(box, range(j, j + num))], - ]; - }; + return () => { + let j = ++i & 0x1ff; + return [ + "div", + ["div#stats", fps, menu], + ["grid", mapIndexed(box, range(j, j + num))], + ]; + }; }; start(app(), { span: false }); diff --git a/examples/hdom-benchmark/tsconfig.json b/examples/hdom-benchmark/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-benchmark/tsconfig.json +++ b/examples/hdom-benchmark/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-benchmark2/package.json b/examples/hdom-benchmark2/package.json index 28ca4cfe3c..8921ffb604 100644 --- a/examples/hdom-benchmark2/package.json +++ b/examples/hdom-benchmark2/package.json @@ -1,41 +1,41 @@ { - "name": "@example/hdom-benchmark2", - "private": true, - "description": "hdom update performance benchmark w/ config options", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/binary": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/hiccup-css": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "binary", - "hdom", - "hdom-components", - "hiccup-css", - "transducers" - ], - "screenshot": "examples/hdom-benchmark2.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hdom-benchmark2", + "private": true, + "description": "hdom update performance benchmark w/ config options", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/binary": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/hiccup-css": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "binary", + "hdom", + "hdom-components", + "hiccup-css", + "transducers" + ], + "screenshot": "examples/hdom-benchmark2.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hdom-benchmark2/src/index.ts b/examples/hdom-benchmark2/src/index.ts index b0e29a6604..b59d7d2b44 100644 --- a/examples/hdom-benchmark2/src/index.ts +++ b/examples/hdom-benchmark2/src/index.ts @@ -16,129 +16,129 @@ import { transduce } from "@thi.ng/transducers/transduce"; const SIZE = "0.5rem"; injectStyleSheet( - css([ - map( - (x: number) => [ - `.cell-${x}`, - { - background: `#${U24(splat4_24(x))}`, - }, - ], - range(16) - ), - map( - (x: number) => [ - `.xcell-${x}`, - { - background: `#${U24(splat4_24(x) | 0x00ff00)}`, - }, - ], - range(16) - ), - [ - ".cell", - { - display: "inline-block", - width: SIZE, - height: SIZE, - }, - ], - [ - ".row", - { - height: SIZE, - }, - ], - ]) + css([ + map( + (x: number) => [ + `.cell-${x}`, + { + background: `#${U24(splat4_24(x))}`, + }, + ], + range(16) + ), + map( + (x: number) => [ + `.xcell-${x}`, + { + background: `#${U24(splat4_24(x) | 0x00ff00)}`, + }, + ], + range(16) + ), + [ + ".cell", + { + display: "inline-block", + width: SIZE, + height: SIZE, + }, + ], + [ + ".row", + { + height: SIZE, + }, + ], + ]) ); const grid = { - render( - _: any, - cells: number[], - w: number, - numChanges: number, - frame: number - ) { - if (!frame) { - this.prevChanged = null; - this.prevChangedRows = null; - return ["div"]; - } - const isFirst = !this.prevChanged; - const num = w * w; - const changed = new Set(); - const changedRows = new Set(); - for (let i = 0; i < numChanges; i++) { - const idx = (Math.random() * num) | 0; - changed.add(idx); - changedRows.add((idx / w) | 0); - cells[idx] = (cells[idx] + 1) % 16; - } - const body = transduce( - comp( - mapIndexed((i, x) => [ - "span", - { - key: "c" + i, - class: `cell ${changed.has(i) ? "xcell" : "cell"}-${x}`, - }, - ]), - partition(w), - mapIndexed((i, row) => [ - "div.row", - { - key: "r" + i, - __skip: - !isFirst && - !( - this.prevChangedRows.has(i) || - changedRows.has(i) - ), - }, - row, - ]) - ), - push(), - ["div", {}], - cells - ); - let mergedCells = new Set(changed); - if (this.prevChanged) { - for (let x of this.prevChanged) { - mergedCells.add(x); - } - } - const mergedRows = new Set(changedRows); - if (this.prevChangedRows) { - for (let x of this.prevChangedRows) { - mergedRows.add(x); - } - } - this.stats = { - cells: mergedCells.size, - rows: mergedRows.size, - total: mergedCells.size + mergedRows.size, - }; - this.prevChanged = changed; - this.prevChangedRows = changedRows; - return body; - }, + render( + _: any, + cells: number[], + w: number, + numChanges: number, + frame: number + ) { + if (!frame) { + this.prevChanged = null; + this.prevChangedRows = null; + return ["div"]; + } + const isFirst = !this.prevChanged; + const num = w * w; + const changed = new Set(); + const changedRows = new Set(); + for (let i = 0; i < numChanges; i++) { + const idx = (Math.random() * num) | 0; + changed.add(idx); + changedRows.add((idx / w) | 0); + cells[idx] = (cells[idx] + 1) % 16; + } + const body = transduce( + comp( + mapIndexed((i, x) => [ + "span", + { + key: "c" + i, + class: `cell ${changed.has(i) ? "xcell" : "cell"}-${x}`, + }, + ]), + partition(w), + mapIndexed((i, row) => [ + "div.row", + { + key: "r" + i, + __skip: + !isFirst && + !( + this.prevChangedRows.has(i) || + changedRows.has(i) + ), + }, + row, + ]) + ), + push(), + ["div", {}], + cells + ); + let mergedCells = new Set(changed); + if (this.prevChanged) { + for (let x of this.prevChanged) { + mergedCells.add(x); + } + } + const mergedRows = new Set(changedRows); + if (this.prevChangedRows) { + for (let x of this.prevChangedRows) { + mergedRows.add(x); + } + } + this.stats = { + cells: mergedCells.size, + rows: mergedRows.size, + total: mergedCells.size + mergedRows.size, + }; + this.prevChanged = changed; + this.prevChangedRows = changedRows; + return body; + }, }; const domStats = (_: any, grid: any, res: number, _static: number) => - grid && grid.stats - ? [ - "div", - ["div", ["span.pink", grid.stats.cells], " cells updated"], - ["div", ["span.pink", grid.stats.rows], " rows updated"], - [ - "div", - ["span.pink", res * res + res + _static], - " DOM nodes total", - ], - ] - : null; + grid && grid.stats + ? [ + "div", + ["div", ["span.pink", grid.stats.cells], " cells updated"], + ["div", ["span.pink", grid.stats.rows], " rows updated"], + [ + "div", + ["span.pink", res * res + res + _static], + " DOM nodes total", + ], + ] + : null; const newCells = (res: number) => new Array(res * res).fill(0); @@ -150,70 +150,70 @@ let frame = -1; let cells = newCells(res); const resOpts = [ - [24, 24], - [32, 32], - [40, 40], - [48, 48], - [56, 56], - [64, 64], + [24, 24], + [32, 32], + [40, 40], + [48, 48], + [56, 56], + [64, 64], ]; const deltaOpts = [ - ...map((i) => [i, i], [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]), + ...map((i) => [i, i], [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]), ]; start(() => { - frame++; - return [ - "div.ma3.code.f7", - [ - "div.measure.lh-copy", - `Each grid cell is one element. Each frame ${delta} random cell states + frame++; + return [ + "div.ma3.code.f7", + [ + "div.measure.lh-copy", + `Each grid cell is one element. Each frame ${delta} random cell states will be updated (highlighted in green), resulting in approx. twice as many DOM updates (due to resetting of updated cells from previous frame).`, - ], - ["div.mt3", [grid, cells, res, delta, frame]], - ["div.mt3", [domStats, grid, res, 46]], - ["div.mt3", [stats]], - [ - "div.mt3", - ["span.w5.dib", "Resolution: "], - [ - dropdown, - { - class: "w3 code", - onchange: (e: Event) => ( - (res = parseInt((e.target).value)), - (frame = -1), - (cells = newCells(res)) - ), - }, - resOpts, - res, - ], - ], - [ - "div.mt3", - ["span.w5.dib", "Random updates/frame: "], - [ - dropdown, - { - class: "w3 code", - onchange: (e: Event) => - (delta = parseInt((e.target).value)), - }, - deltaOpts, - delta, - ], - ], - [ - "div.mt3", - [ - "a", - { - href: "https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-benchmark2", - }, - "Source", - ], - ], - ]; + ], + ["div.mt3", [grid, cells, res, delta, frame]], + ["div.mt3", [domStats, grid, res, 46]], + ["div.mt3", [stats]], + [ + "div.mt3", + ["span.w5.dib", "Resolution: "], + [ + dropdown, + { + class: "w3 code", + onchange: (e: Event) => ( + (res = parseInt((e.target).value)), + (frame = -1), + (cells = newCells(res)) + ), + }, + resOpts, + res, + ], + ], + [ + "div.mt3", + ["span.w5.dib", "Random updates/frame: "], + [ + dropdown, + { + class: "w3 code", + onchange: (e: Event) => + (delta = parseInt((e.target).value)), + }, + deltaOpts, + delta, + ], + ], + [ + "div.mt3", + [ + "a", + { + href: "https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-benchmark2", + }, + "Source", + ], + ], + ]; }); diff --git a/examples/hdom-benchmark2/tsconfig.json b/examples/hdom-benchmark2/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-benchmark2/tsconfig.json +++ b/examples/hdom-benchmark2/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-canvas-clock/package.json b/examples/hdom-canvas-clock/package.json index 3b7fa0b411..5a9ca257e3 100644 --- a/examples/hdom-canvas-clock/package.json +++ b/examples/hdom-canvas-clock/package.json @@ -1,40 +1,40 @@ { - "name": "@example/hdom-canvas-clock", - "private": true, - "version": "0.0.1", - "description": "Realtime analog clock demo", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom", - "hdom-canvas", - "hiccup-canvas", - "transducers", - "vectors" - ], - "screenshot": "examples/hdom-canvas-clock.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hdom-canvas-clock", + "private": true, + "version": "0.0.1", + "description": "Realtime analog clock demo", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom", + "hdom-canvas", + "hiccup-canvas", + "transducers", + "vectors" + ], + "screenshot": "examples/hdom-canvas-clock.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hdom-canvas-clock/src/index.ts b/examples/hdom-canvas-clock/src/index.ts index 87a454d92d..67c2463b80 100644 --- a/examples/hdom-canvas-clock/src/index.ts +++ b/examples/hdom-canvas-clock/src/index.ts @@ -8,119 +8,119 @@ import { cartesian2 } from "@thi.ng/vectors/cartesian"; const WEEKDAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; const tick = (i: number, r1: number, r2: number) => { - const theta = (i / 12) * TAU - HALF_PI; - return [ - [ - "line", - {}, - cartesian2(null, [r1, theta]), - cartesian2(null, [r2, theta]), - ], - i % 3 == 0 - ? [ - "text", - { stroke: "none" }, - cartesian2(null, [r1 - 10, theta]), - i > 0 ? i : 12, - ] - : null, - ]; + const theta = (i / 12) * TAU - HALF_PI; + return [ + [ + "line", + {}, + cartesian2(null, [r1, theta]), + cartesian2(null, [r2, theta]), + ], + i % 3 == 0 + ? [ + "text", + { stroke: "none" }, + cartesian2(null, [r1 - 10, theta]), + i > 0 ? i : 12, + ] + : null, + ]; }; const hand = ( - r1: number, - r2: number, - theta: number, - attribs = {}, - eps = 0.5 + r1: number, + r2: number, + theta: number, + attribs = {}, + eps = 0.5 ) => { - theta = theta * TAU - HALF_PI; - return [ - "polygon", - attribs, - [ - [r1, theta - eps], - [r2, theta], - [r1, theta + eps], - ].map((p) => cartesian2(null, p)), - ]; + theta = theta * TAU - HALF_PI; + return [ + "polygon", + attribs, + [ + [r1, theta - eps], + [r2, theta], + [r1, theta + eps], + ].map((p) => cartesian2(null, p)), + ]; }; start(() => { - const now = new Date(); - const t = now.getTime() / 1000 - now.getTimezoneOffset() * 60; - const sec = (t % 60) / 60; - const min = ((t % 3600) + sec / 60) / 3600; - const hour = ((t % (12 * 3600)) + min / 60) / (12 * 3600); - return [ - "div.vh-100.flex.flex-column.justify-center.items-center.code.f7", - ["div", now.toLocaleTimeString()], - [ - canvas, - { class: "ma2", width: 200, height: 200 }, - // these canvas-inner elements use SVG-like hiccup syntax, - // but are NOT real DOM elements. they will be processed by - // the canvas component's branch-local hdom update - // operations and translated into canvas drawing commands - // - // shapes can be grouped using ["g"...] elements as in SVG - // attribs declared at group level will be shared by all - // children but can be overridden individually. groups can - // be nested. also note, that nested `transform`, - // `translate`, `rotate` and `scale` attribs are indeed - // applied in a nested manner... - // - // see here for a list of all supported attribs: - // https://docs.thi.ng/umbrella/hiccup-canvas/#attributes - [ - "g", - { - translate: [100, 100], - stroke: "black", - fill: "none", - align: "center", - baseline: "middle", - __normalize: false, - }, - // rim - ["circle", {}, [0, 0], 99], - ["text", { font: "24px Menlo" }, [0, -33], "thi.ng"], - // hour tick marks & weekday inset - [ - "g", - { fill: "black" }, - ...mapcat((i) => tick(i, 90, 99), range(12)), - ["rect", { fill: "none" }, [40, -8], 30, 16], - [ - "text", - { stroke: "none" }, - [55, 0], - WEEKDAYS[now.getDay()], - ], - ], - // hands - [ - "g", - { fill: "black", stroke: "none" }, - hand(5, 60, hour), - hand(5, 90, min), - hand(5, 80, sec, { - fill: "red", - shadowX: 2, - shadowY: 2, - shadowBlur: 5, - shadowColor: "rgba(0,0,0,0.4)", - }), - ["circle", {}, [0, 0], 5], - ], - ], - ], - [ - "a.link", - { - href: "https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-clock", - }, - "Source code", - ], - ]; + const now = new Date(); + const t = now.getTime() / 1000 - now.getTimezoneOffset() * 60; + const sec = (t % 60) / 60; + const min = ((t % 3600) + sec / 60) / 3600; + const hour = ((t % (12 * 3600)) + min / 60) / (12 * 3600); + return [ + "div.vh-100.flex.flex-column.justify-center.items-center.code.f7", + ["div", now.toLocaleTimeString()], + [ + canvas, + { class: "ma2", width: 200, height: 200 }, + // these canvas-inner elements use SVG-like hiccup syntax, + // but are NOT real DOM elements. they will be processed by + // the canvas component's branch-local hdom update + // operations and translated into canvas drawing commands + // + // shapes can be grouped using ["g"...] elements as in SVG + // attribs declared at group level will be shared by all + // children but can be overridden individually. groups can + // be nested. also note, that nested `transform`, + // `translate`, `rotate` and `scale` attribs are indeed + // applied in a nested manner... + // + // see here for a list of all supported attribs: + // https://docs.thi.ng/umbrella/hiccup-canvas/#attributes + [ + "g", + { + translate: [100, 100], + stroke: "black", + fill: "none", + align: "center", + baseline: "middle", + __normalize: false, + }, + // rim + ["circle", {}, [0, 0], 99], + ["text", { font: "24px Menlo" }, [0, -33], "thi.ng"], + // hour tick marks & weekday inset + [ + "g", + { fill: "black" }, + ...mapcat((i) => tick(i, 90, 99), range(12)), + ["rect", { fill: "none" }, [40, -8], 30, 16], + [ + "text", + { stroke: "none" }, + [55, 0], + WEEKDAYS[now.getDay()], + ], + ], + // hands + [ + "g", + { fill: "black", stroke: "none" }, + hand(5, 60, hour), + hand(5, 90, min), + hand(5, 80, sec, { + fill: "red", + shadowX: 2, + shadowY: 2, + shadowBlur: 5, + shadowColor: "rgba(0,0,0,0.4)", + }), + ["circle", {}, [0, 0], 5], + ], + ], + ], + [ + "a.link", + { + href: "https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-clock", + }, + "Source code", + ], + ]; }); diff --git a/examples/hdom-canvas-clock/tsconfig.json b/examples/hdom-canvas-clock/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-canvas-clock/tsconfig.json +++ b/examples/hdom-canvas-clock/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-canvas-draw/package.json b/examples/hdom-canvas-draw/package.json index c2f22e7d17..123ec0ca42 100644 --- a/examples/hdom-canvas-draw/package.json +++ b/examples/hdom-canvas-draw/package.json @@ -1,44 +1,44 @@ { - "name": "@example/hdom-canvas-draw", - "private": true, - "version": "0.0.1", - "description": "Interactive pattern drawing demo using transducers", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom-canvas", - "hiccup-canvas", - "rstream", - "rstream-gestures", - "transducers", - "transducers-hdom", - "vectors" - ], - "screenshot": "examples/hdom-canvas-draw.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hdom-canvas-draw", + "private": true, + "version": "0.0.1", + "description": "Interactive pattern drawing demo using transducers", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom-canvas", + "hiccup-canvas", + "rstream", + "rstream-gestures", + "transducers", + "transducers-hdom", + "vectors" + ], + "screenshot": "examples/hdom-canvas-draw.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hdom-canvas-draw/src/index.ts b/examples/hdom-canvas-draw/src/index.ts index 94778f6400..320abaec2e 100644 --- a/examples/hdom-canvas-draw/src/index.ts +++ b/examples/hdom-canvas-draw/src/index.ts @@ -20,114 +20,114 @@ const W = 480; // higher order line/shape component function // takes a tuple of 2 points and returns a component fn const line = - ([a, b]: number[][]) => - (_: any, attribs: any) => - ["line", { ...attribs, weight: dist(a, b) / 4 }, a, b]; + ([a, b]: number[][]) => + (_: any, attribs: any) => + ["line", { ...attribs, weight: dist(a, b) / 4 }, a, b]; // higher order root component function. takes a @thi.ng/rstream // `StreamSync` instance as argument to dynamically add a new input // stream to later const app = (main: StreamSync) => { - // augment hdom-canvas component w/ `init` lifecycle method: this is - // method is called when the canvas DOM element is first created and - // used to attach a mouse & touch event stream to it. this stream is - // then transformed using a number of transducers and eventually - // outputs a series of `line` components. furthermore this - // transformed stream is added as new input to the `main` stream - // combinator below... - const _canvas = { - ...canvas, - init: (el: HTMLCanvasElement) => - main.add( - gestureStream(el).transform( - // only interested in some gesture types - filter( - (e: GestureEvent) => - e.type === "start" || e.type === "drag" - ), - // get current mouse / touch position - map((e) => e.pos), - // form consecutive line pairs - partition(2, 1), - // transform into pre-curried line component - map(line) - ), - // name of the new input - "gesture" - ), - }; - // this root component function will produce the actual UI and - // will be attached to the `main` stream combinator below and executes - // each time any inputs have changed... - // the only input used here is the above stream of mouse events - // transformed into line components - return ({ gesture }: any) => [ - "div.sans-serif.ma2", - "Click & draw in the box below...", - // all child elements of the canvas component - // are NOT real DOM elements, but are translated into - // canvas API draw calls - [ - _canvas, - { class: "mv2 db", width: W, height: W, __clear: false }, - // semi-transparent bg - [ - "rect", - { - fill: "rgba(255,255,255,0.1)", - stroke: "black", - dash: [1, 1], - }, - [0, 0], - W, - W, - ], - // use a group node to assign attributes common to all children - [ - "g", - { lineCap: "round" }, - // `mapcat` here returns an iterator of fully - // configured line components - mapcat( - (attribs) => - map( - ([[h, translate, theta], delta]: any[]) => - // the `gesture` value is a pre-curried component fn - // (built by the the above transducer chain). we can - // configure it further using additional attributes to - // draw multiple instances. - [ - gesture, - { - stroke: `hsl(${ - h + (delta - 0.5) * 40 - },100%,50%)`, - rotate: theta + (delta - 0.5) * 0.3, - translate, - }, - ], - // iterator which forms tuples of `[attribs, counter]` - zip(repeat(attribs), normRange(6)) - ), - // configurations for the replicated strokes - [ - [30, [0, 0], 0], - [120, [W, W], PI], - [210, [W, 0], HALF_PI], - [300, [0, W], -HALF_PI], - ] - ), - ], - ], - // back in normal DOM - [ - "a.db.link", - { - href: "https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-draw", - }, - "Source code", - ], - ]; + // augment hdom-canvas component w/ `init` lifecycle method: this is + // method is called when the canvas DOM element is first created and + // used to attach a mouse & touch event stream to it. this stream is + // then transformed using a number of transducers and eventually + // outputs a series of `line` components. furthermore this + // transformed stream is added as new input to the `main` stream + // combinator below... + const _canvas = { + ...canvas, + init: (el: HTMLCanvasElement) => + main.add( + gestureStream(el).transform( + // only interested in some gesture types + filter( + (e: GestureEvent) => + e.type === "start" || e.type === "drag" + ), + // get current mouse / touch position + map((e) => e.pos), + // form consecutive line pairs + partition(2, 1), + // transform into pre-curried line component + map(line) + ), + // name of the new input + "gesture" + ), + }; + // this root component function will produce the actual UI and + // will be attached to the `main` stream combinator below and executes + // each time any inputs have changed... + // the only input used here is the above stream of mouse events + // transformed into line components + return ({ gesture }: any) => [ + "div.sans-serif.ma2", + "Click & draw in the box below...", + // all child elements of the canvas component + // are NOT real DOM elements, but are translated into + // canvas API draw calls + [ + _canvas, + { class: "mv2 db", width: W, height: W, __clear: false }, + // semi-transparent bg + [ + "rect", + { + fill: "rgba(255,255,255,0.1)", + stroke: "black", + dash: [1, 1], + }, + [0, 0], + W, + W, + ], + // use a group node to assign attributes common to all children + [ + "g", + { lineCap: "round" }, + // `mapcat` here returns an iterator of fully + // configured line components + mapcat( + (attribs) => + map( + ([[h, translate, theta], delta]: any[]) => + // the `gesture` value is a pre-curried component fn + // (built by the the above transducer chain). we can + // configure it further using additional attributes to + // draw multiple instances. + [ + gesture, + { + stroke: `hsl(${ + h + (delta - 0.5) * 40 + },100%,50%)`, + rotate: theta + (delta - 0.5) * 0.3, + translate, + }, + ], + // iterator which forms tuples of `[attribs, counter]` + zip(repeat(attribs), normRange(6)) + ), + // configurations for the replicated strokes + [ + [30, [0, 0], 0], + [120, [W, W], PI], + [210, [W, 0], HALF_PI], + [300, [0, W], -HALF_PI], + ] + ), + ], + ], + // back in normal DOM + [ + "a.db.link", + { + href: "https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-draw", + }, + "Source code", + ], + ]; }; // main stream combinator: initially only a single dummy `trigger` input @@ -136,8 +136,8 @@ const app = (main: StreamSync) => { // stream dynamically. the entire UI then only updates when there are new // user interactions... const main = sync({ - src: { trigger: trigger() }, - closeIn: CloseMode.NEVER, + src: { trigger: trigger() }, + closeIn: CloseMode.NEVER, }); // transform result stream using the // root component fn and the hdom differential diff --git a/examples/hdom-canvas-draw/tsconfig.json b/examples/hdom-canvas-draw/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-canvas-draw/tsconfig.json +++ b/examples/hdom-canvas-draw/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-canvas-particles/package.json b/examples/hdom-canvas-particles/package.json index c7f453e97a..8826c9af89 100644 --- a/examples/hdom-canvas-particles/package.json +++ b/examples/hdom-canvas-particles/package.json @@ -1,45 +1,45 @@ { - "name": "@example/hdom-canvas-particles", - "private": true, - "description": "2D Bezier curve-guided particle system", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/compose": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/random": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom", - "hdom", - "hdom-canvas", - "hiccup-canvas", - "math", - "random", - "vectors" - ], - "screenshot": "examples/hdom-canvas-particles.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hdom-canvas-particles", + "private": true, + "description": "2D Bezier curve-guided particle system", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/compose": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/random": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom", + "hdom", + "hdom-canvas", + "hiccup-canvas", + "math", + "random", + "vectors" + ], + "screenshot": "examples/hdom-canvas-particles.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hdom-canvas-particles/src/index.ts b/examples/hdom-canvas-particles/src/index.ts index 32c3e013a4..2475e34e08 100644 --- a/examples/hdom-canvas-particles/src/index.ts +++ b/examples/hdom-canvas-particles/src/index.ts @@ -18,119 +18,119 @@ const NUMC = 21; const NUMP = 100; interface Particle { - curve: Cubic; - pos: number; - vel: number; + curve: Cubic; + pos: number; + vel: number; } const makeCurve = (i: number) => - cubic( - [0, i * 30], - [100 - Math.abs(i) * 10, 0], - [300, i * 6], - [600, i * 6], - { id: i } - ); + cubic( + [0, i * 30], + [100 - Math.abs(i) * 10, 0], + [300, i * 6], + [600, i * 6], + { id: i } + ); const initCurves = () => [ - ...map(makeCurve, range(-(NUMC >> 1), (NUMC >> 1) + 1)), + ...map(makeCurve, range(-(NUMC >> 1), (NUMC >> 1) + 1)), ]; const updateCurves = (curves: Cubic[], t: number) => { - for (let i = curves.length; i-- > 0; ) { - const crv = curves[i]; - const pts = crv.points; - const id = crv.attribs!.id; - const [c, s] = cossin(t + id * 0.07); - const c2 = Math.cos(t * 0.25 - id * 0.05); - pts[0][1] = (1 - Math.abs(c2)) * id * 30; - pts[2][0] = s * 100 + 300; - pts[2][1] = c * 150 + id * 6; - pts[3][1] = id * Math.abs(c * 12); - } + for (let i = curves.length; i-- > 0; ) { + const crv = curves[i]; + const pts = crv.points; + const id = crv.attribs!.id; + const [c, s] = cossin(t + id * 0.07); + const c2 = Math.cos(t * 0.25 - id * 0.05); + pts[0][1] = (1 - Math.abs(c2)) * id * 30; + pts[2][0] = s * 100 + 300; + pts[2][1] = c * 150 + id * 6; + pts[3][1] = id * Math.abs(c * 12); + } }; const makeParticle = (curves: Cubic[]) => ({ - curve: curves[SYSTEM.int() % curves.length], - pos: 0, - vel: SYSTEM.minmax(0.002, 0.01), + curve: curves[SYSTEM.int() % curves.length], + pos: 0, + vel: SYSTEM.minmax(0.002, 0.01), }); const updateParticles = (particles: Particle[]) => { - for (let i = particles.length; i-- > 0; ) { - const p = particles[i]; - p.pos = wrap01(p.pos + p.vel); - } + for (let i = particles.length; i-- > 0; ) { + const p = particles[i]; + p.pos = wrap01(p.pos + p.vel); + } }; const particle = (p: Particle) => { - // compute point on cubic bezier - const pos = pointAt(p.curve, p.pos)!; - // need to use translate here only because of gradient - return [ - "line", - { translate: pos }, - // compute 2nd end point in local space - sub2(null, pointAt(p.curve, p.pos - 0.05)!, pos), - ZERO2, - ]; + // compute point on cubic bezier + const pos = pointAt(p.curve, p.pos)!; + // need to use translate here only because of gradient + return [ + "line", + { translate: pos }, + // compute 2nd end point in local space + sub2(null, pointAt(p.curve, p.pos - 0.05)!, pos), + ZERO2, + ]; }; // gradient definition (using RGBA) const GRAD = [ - "defs", - {}, - // gradient for curves - [ - "linearGradient", - { id: "curve", from: [0, 0], to: [600, 0] }, - [ - [0, [0.6, 0.01, 0]], - [1, [0.2, 0, 0.6]], - ], - ], - // for particles - [ - "linearGradient", - { id: "part", from: [-20, 0], to: [0, 0] }, - [ - [0, [1, 0, 0, 0]], - [1, [1, 0.8, 0.5, 0.8]], - ], - ], + "defs", + {}, + // gradient for curves + [ + "linearGradient", + { id: "curve", from: [0, 0], to: [600, 0] }, + [ + [0, [0.6, 0.01, 0]], + [1, [0.2, 0, 0.6]], + ], + ], + // for particles + [ + "linearGradient", + { id: "part", from: [-20, 0], to: [0, 0] }, + [ + [0, [1, 0, 0, 0]], + [1, [1, 0.8, 0.5, 0.8]], + ], + ], ]; const app = () => { - const curves = initCurves(); - const particles = [...map(partial(makeParticle, curves), range(NUMP))]; - let t = 0; - return () => { - updateCurves(curves, (t += 0.02)); - updateParticles(particles); - const width = window.innerWidth; - const height = window.innerHeight; - const scale = width / 600; - return [ - canvas, - { width, height, __diff: false }, - GRAD, - [ - "g", - { - translate: [0, height / 2], - scale, - stroke: "$curve", - compose: "screen", - }, - ...curves, - [ - "g", - { stroke: "$part", weight: 2 }, - ...map(particle, particles), - ], - ], - ]; - }; + const curves = initCurves(); + const particles = [...map(partial(makeParticle, curves), range(NUMP))]; + let t = 0; + return () => { + updateCurves(curves, (t += 0.02)); + updateParticles(particles); + const width = window.innerWidth; + const height = window.innerHeight; + const scale = width / 600; + return [ + canvas, + { width, height, __diff: false }, + GRAD, + [ + "g", + { + translate: [0, height / 2], + scale, + stroke: "$curve", + compose: "screen", + }, + ...curves, + [ + "g", + { stroke: "$part", weight: 2 }, + ...map(particle, particles), + ], + ], + ]; + }; }; start(app()); diff --git a/examples/hdom-canvas-particles/tsconfig.json b/examples/hdom-canvas-particles/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-canvas-particles/tsconfig.json +++ b/examples/hdom-canvas-particles/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-canvas-shapes/package.json b/examples/hdom-canvas-shapes/package.json index e57aa54bea..6ded77e10b 100644 --- a/examples/hdom-canvas-shapes/package.json +++ b/examples/hdom-canvas-shapes/package.json @@ -1,56 +1,56 @@ { - "name": "@example/hdom-canvas-shapes", - "private": true, - "description": "Various hdom-canvas shape drawing examples & SVG conversion / export", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/color": "workspace:^", - "@thi.ng/dl-asset": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/hiccup": "workspace:^", - "@thi.ng/hiccup-svg": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/matrices": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "color", - "dl-asset", - "hdom", - "hdom-canvas", - "hdom-components", - "hiccup", - "hiccup-canvas", - "hiccup-svg", - "matrices", - "rstream", - "transducers", - "transducers-hdom", - "vectors" - ], - "screenshot": "hdom-canvas/hdom-canvas-shapes-results.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hdom-canvas-shapes", + "private": true, + "description": "Various hdom-canvas shape drawing examples & SVG conversion / export", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/color": "workspace:^", + "@thi.ng/dl-asset": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/hiccup": "workspace:^", + "@thi.ng/hiccup-svg": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/matrices": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "color", + "dl-asset", + "hdom", + "hdom-canvas", + "hdom-components", + "hiccup", + "hiccup-canvas", + "hiccup-svg", + "matrices", + "rstream", + "transducers", + "transducers-hdom", + "vectors" + ], + "screenshot": "hdom-canvas/hdom-canvas-shapes-results.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hdom-canvas-shapes/src/index.ts b/examples/hdom-canvas-shapes/src/index.ts index dc9bb482c6..e611334730 100644 --- a/examples/hdom-canvas-shapes/src/index.ts +++ b/examples/hdom-canvas-shapes/src/index.ts @@ -33,290 +33,290 @@ const W2 = W / 2; const randpos = () => [Math.random() * W - W2, Math.random() * W - W2]; const randdir = (n = 1) => [ - Math.random() * n * 2 - n, - Math.random() * n * 2 - n, + Math.random() * n * 2 - n, + Math.random() * n * 2 - n, ]; // various tests for different shapes & canvas drawing options // each test is a standalone component (only one used at a time) const TESTS: any = { - "dash offset": { - attribs: {}, - desc: "Simple path w/ animated stroke dash pattern", - body: () => - pathBuilder({ - fill: "blue", - stroke: "#000", - weight: 3, - dash: [4, 8], - dashOffset: (Date.now() * 0.01) % 12, - }) - .moveTo([10, 10]) - .quadraticTo([W2, W2], [W2, W - 10]) - .quadraticTo([W2, W2], [W - 10, 10]) - .quadraticTo([W2, W2], [10, 10]) - .current(), - }, + "dash offset": { + attribs: {}, + desc: "Simple path w/ animated stroke dash pattern", + body: () => + pathBuilder({ + fill: "blue", + stroke: "#000", + weight: 3, + dash: [4, 8], + dashOffset: (Date.now() * 0.01) % 12, + }) + .moveTo([10, 10]) + .quadraticTo([W2, W2], [W2, W - 10]) + .quadraticTo([W2, W2], [W - 10, 10]) + .quadraticTo([W2, W2], [10, 10]) + .current(), + }, - "shape morph": { - attribs: { __clear: false }, - desc: "Animated semi-transparent path, stroke dash pattern, transformed origin, non-clearing background", - body: () => { - const t = Date.now() * 0.01; - const a = 10 + 140 * (Math.sin(t * 0.33) * 0.5 + 0.5); - return pathBuilder({ - fill: [1, 1, 1, 0.05], - stroke: "#000", - weight: 3, - miterLimit: 1, - dash: [20, 20], - dashOffset: (t * 5) % 40, - translate: [W2, W2], - rotate: (t * 0.05) % (2 * Math.PI), - }) - .moveTo([-100, -100]) - .quadraticTo([-a, 0], [0, 100]) - .quadraticTo([a, 0], [100, -100]) - .quadraticTo([0, -a], [-100, -100]) - .current(); - }, - }, + "shape morph": { + attribs: { __clear: false }, + desc: "Animated semi-transparent path, stroke dash pattern, transformed origin, non-clearing background", + body: () => { + const t = Date.now() * 0.01; + const a = 10 + 140 * (Math.sin(t * 0.33) * 0.5 + 0.5); + return pathBuilder({ + fill: [1, 1, 1, 0.05], + stroke: "#000", + weight: 3, + miterLimit: 1, + dash: [20, 20], + dashOffset: (t * 5) % 40, + translate: [W2, W2], + rotate: (t * 0.05) % (2 * Math.PI), + }) + .moveTo([-100, -100]) + .quadraticTo([-a, 0], [0, 100]) + .quadraticTo([a, 0], [100, -100]) + .quadraticTo([0, -a], [-100, -100]) + .current(); + }, + }, - "points 1k": { - attribs: { __diff: false }, - desc: "1,000 random circles", - body: () => - points([...repeatedly(randpos, 1000)], { - fill: "#000", - stroke: "none", - size: 4, - shape: "circle", - translate: [W2, W2], - scale: 0.6 + 0.4 * Math.sin(Date.now() * 0.005), - }), - }, + "points 1k": { + attribs: { __diff: false }, + desc: "1,000 random circles", + body: () => + points([...repeatedly(randpos, 1000)], { + fill: "#000", + stroke: "none", + size: 4, + shape: "circle", + translate: [W2, W2], + scale: 0.6 + 0.4 * Math.sin(Date.now() * 0.005), + }), + }, - "points 10k": { - attribs: { __diff: false }, - desc: "10,000 random rects", - body: () => - points([...repeatedly(randpos, 10000)], { - fill: "#000", - stroke: "none", - translate: [W2, W2], - scale: 0.6 + 0.4 * Math.sin(Date.now() * 0.005), - }), - }, + "points 10k": { + attribs: { __diff: false }, + desc: "10,000 random rects", + body: () => + points([...repeatedly(randpos, 10000)], { + fill: "#000", + stroke: "none", + translate: [W2, W2], + scale: 0.6 + 0.4 * Math.sin(Date.now() * 0.005), + }), + }, - "points 50k": { - attribs: { __diff: false }, - desc: "50,000 random rects", - body: () => - points([...repeatedly(randpos, 50000)], { - fill: "#000", - stroke: "none", - translate: [W2, W2], - scale: 0.6 + 0.4 * Math.sin(Date.now() * 0.005), - }), - }, + "points 50k": { + attribs: { __diff: false }, + desc: "50,000 random rects", + body: () => + points([...repeatedly(randpos, 50000)], { + fill: "#000", + stroke: "none", + translate: [W2, W2], + scale: 0.6 + 0.4 * Math.sin(Date.now() * 0.005), + }), + }, - "rounded rects": { - attribs: {}, - desc: "Rounded rects w/ animated corner radii", - body: () => { - const t = Date.now() * 0.01; - const r = 100 * (Math.sin(t * 0.5) * 0.5 + 0.5); - return [ - "g", - { - weight: 1, - stroke: "#00f", - align: "center", - baseline: "middle", - font: "48px Menlo", - __normalize: false, - }, - ...map( - (i) => ["rect", null, [i, i], W - 2 * i, W - 2 * i, r], - range(10, 50, 5) - ), - // ...map((i) => normalizedPath(roundedRect([i, i], [W - 2 * i, W - 2 * i], r)), range(10, 50, 5)), - ["text", {}, [W2, W2], Math.round(r)], - ]; - }, - }, + "rounded rects": { + attribs: {}, + desc: "Rounded rects w/ animated corner radii", + body: () => { + const t = Date.now() * 0.01; + const r = 100 * (Math.sin(t * 0.5) * 0.5 + 0.5); + return [ + "g", + { + weight: 1, + stroke: "#00f", + align: "center", + baseline: "middle", + font: "48px Menlo", + __normalize: false, + }, + ...map( + (i) => ["rect", null, [i, i], W - 2 * i, W - 2 * i, r], + range(10, 50, 5) + ), + // ...map((i) => normalizedPath(roundedRect([i, i], [W - 2 * i, W - 2 * i], r)), range(10, 50, 5)), + ["text", {}, [W2, W2], Math.round(r)], + ]; + }, + }, - "linear gradient": { - attribs: {}, - desc: "Animated linear gradients", - body: () => [ - [ - "defs", - {}, - [ - "linearGradient", - { id: "grad1", from: [0, 0], to: [W, W] }, - [ - [0, "#fc0"], - [1, "#0ef"], - ], - ], - [ - "linearGradient", - { - id: "grad2", - from: [0, 0], - to: [W, W2 + W2 * Math.sin(Date.now() * 0.005)], - }, - [ - [0, "#700"], - [0.5, "#d0f"], - [1, "#fff"], - ], - ], - ], - ["circle", { fill: "$grad1" }, [W2, W2], W2 - 10], - ["rect", { fill: "$grad2" }, [125, 0], 50, W], - ["rect", { fill: "$grad2" }, [0, 125], W, 50], - ], - }, + "linear gradient": { + attribs: {}, + desc: "Animated linear gradients", + body: () => [ + [ + "defs", + {}, + [ + "linearGradient", + { id: "grad1", from: [0, 0], to: [W, W] }, + [ + [0, "#fc0"], + [1, "#0ef"], + ], + ], + [ + "linearGradient", + { + id: "grad2", + from: [0, 0], + to: [W, W2 + W2 * Math.sin(Date.now() * 0.005)], + }, + [ + [0, "#700"], + [0.5, "#d0f"], + [1, "#fff"], + ], + ], + ], + ["circle", { fill: "$grad1" }, [W2, W2], W2 - 10], + ["rect", { fill: "$grad2" }, [125, 0], 50, W], + ["rect", { fill: "$grad2" }, [0, 125], W, 50], + ], + }, - "radial gradient": { - attribs: {}, - desc: "Animated radial gradients (w/ alpha channel)", - body: () => { - const t = Date.now() * 0.01; - const x = W2 + 50 * Math.sin(t * 0.5); - const y = W2 + 20 * Math.sin(t * 0.3); - const spos = [110, 120]; - return [ - [ - "defs", - {}, - [ - "radialGradient", - { - id: "bg", - from: [x, W - 20], - to: [W2, W], - r1: W, - r2: 100, - }, - [ - [0, "#07f"], - [0.5, "#0ef"], - [0.8, "#efe"], - [1, "#af0"], - ], - ], - [ - "radialGradient", - { id: "sun", from: spos, to: spos, r1: 5, r2: 50 }, - [ - [0, [1, 1, 1]], - [1, [1, 1, 0.75, 0]], - ], - ], - ], - ["circle", { fill: "$bg" }, [W2, y], W2 - 20], - ["circle", { fill: "$sun" }, spos, 50], - ]; - }, - }, + "radial gradient": { + attribs: {}, + desc: "Animated radial gradients (w/ alpha channel)", + body: () => { + const t = Date.now() * 0.01; + const x = W2 + 50 * Math.sin(t * 0.5); + const y = W2 + 20 * Math.sin(t * 0.3); + const spos = [110, 120]; + return [ + [ + "defs", + {}, + [ + "radialGradient", + { + id: "bg", + from: [x, W - 20], + to: [W2, W], + r1: W, + r2: 100, + }, + [ + [0, "#07f"], + [0.5, "#0ef"], + [0.8, "#efe"], + [1, "#af0"], + ], + ], + [ + "radialGradient", + { id: "sun", from: spos, to: spos, r1: 5, r2: 50 }, + [ + [0, [1, 1, 1]], + [1, [1, 1, 0.75, 0]], + ], + ], + ], + ["circle", { fill: "$bg" }, [W2, y], W2 - 20], + ["circle", { fill: "$sun" }, spos, 50], + ]; + }, + }, - "images 1k": { - attribs: {}, - desc: "1,000 stateful image sprite components", - body: (() => { - const img = new Image(); - img.src = logo; - const w = W - 64; - const ball = () => { - const p = randpos(); - const v = randdir(4); - return () => { - let x = p[0] + v[0]; - let y = p[1] + v[1]; - x < 0 && ((x *= -1), (v[0] *= -1)); - y < 0 && ((y *= -1), (v[1] *= -1)); - x > w && ((x = w - (x - w)), (v[0] *= -1)); - y > w && ((y = w - (y - w)), (v[1] *= -1)); - p[0] = x; - p[1] = y; - return ["img", {}, img, p.slice()]; - }; - }; - const body = ["g", {}, ...repeatedly(ball, 1000)]; - return () => body; - })(), - }, + "images 1k": { + attribs: {}, + desc: "1,000 stateful image sprite components", + body: (() => { + const img = new Image(); + img.src = logo; + const w = W - 64; + const ball = () => { + const p = randpos(); + const v = randdir(4); + return () => { + let x = p[0] + v[0]; + let y = p[1] + v[1]; + x < 0 && ((x *= -1), (v[0] *= -1)); + y < 0 && ((y *= -1), (v[1] *= -1)); + x > w && ((x = w - (x - w)), (v[0] *= -1)); + y > w && ((y = w - (y - w)), (v[1] *= -1)); + p[0] = x; + p[1] = y; + return ["img", {}, img, p.slice()]; + }; + }; + const body = ["g", {}, ...repeatedly(ball, 1000)]; + return () => body; + })(), + }, - static: { - attribs: {}, - desc: "static scene (single draw) w/ skew matrix", - body: (() => { - const body = [ - "g", - { - transform: concat( - [], - translation23([], [150, 150]), - skewX23([], -Math.PI / 6) - ), - }, - ["rect", { fill: "#ff0" }, [-50, -50], 100, 100], - [ - "text", - { - fill: "#00f", - font: "18px Menlo", - align: "center", - baseline: "middle", - }, - [0, 0], - new Date().toISOString(), - ], - ]; - return () => body; - })(), - }, + static: { + attribs: {}, + desc: "static scene (single draw) w/ skew matrix", + body: (() => { + const body = [ + "g", + { + transform: concat( + [], + translation23([], [150, 150]), + skewX23([], -Math.PI / 6) + ), + }, + ["rect", { fill: "#ff0" }, [-50, -50], 100, 100], + [ + "text", + { + fill: "#00f", + font: "18px Menlo", + align: "center", + baseline: "middle", + }, + [0, 0], + new Date().toISOString(), + ], + ]; + return () => body; + })(), + }, - ellipse: { - attribs: {}, - desc: "ellipses", - body: () => { - const t = Date.now() * 0.005; - return [ - "g", - {}, - map( - (x) => [ - "ellipse", - { stroke: hsv(x / 20, 1, 1) }, - [150, 150], // pos - addN(null, sincos(t + x * 0.1, 75), 75), // radii - Math.sin(t * 0.25), // axis - ], - range(30) - ), - ]; - }, - }, + ellipse: { + attribs: {}, + desc: "ellipses", + body: () => { + const t = Date.now() * 0.005; + return [ + "g", + {}, + map( + (x) => [ + "ellipse", + { stroke: hsv(x / 20, 1, 1) }, + [150, 150], // pos + addN(null, sincos(t + x * 0.1, 75), 75), // radii + Math.sin(t * 0.25), // axis + ], + range(30) + ), + ]; + }, + }, }; // test case selection dropdown const choices = (_: any, target: ISubscriber, id: string) => [ - dropdown, - { - class: "w4 ma2", - onchange: (e: Event) => { - const val = (e.target).value; - window.location.hash = val.replace(/\s/g, "-"); - target.next(val); - }, - }, - Object.keys(TESTS).map((k) => [k, k]), - id, + dropdown, + { + class: "w4 ma2", + onchange: (e: Event) => { + const val = (e.target).value; + window.location.hash = val.replace(/\s/g, "-"); + target.next(val); + }, + }, + Object.keys(TESTS).map((k) => [k, k]), + id, ]; // event stream for triggering SVG conversion / export @@ -327,88 +327,88 @@ const selection = stream(); // stream combinator updating & normalizing selected test component tree // (one of the inputs is linked to RAF to trigger updates) const scene = sync({ - src: { - id: selection, - time: fromRAF(), - }, + src: { + id: selection, + time: fromRAF(), + }, }).transform( - map(({ id }) => ({ id, shapes: normalizeTree({}, TESTS[id].body()) })) + map(({ id }) => ({ id, shapes: normalizeTree({}, TESTS[id].body()) })) ); // stream transformer to produce & update main user interface root component scene.transform( - map(({ id, shapes }) => [ - "div.vh-100.flex.flex-column.justify-center.items-center.code.f7", - [ - "div", - [choices, selection, id], - [ - "button.ml2", - { onclick: () => trigger.next(true) }, - "convert & export", - ], - ], + map(({ id, shapes }) => [ + "div.vh-100.flex.flex-column.justify-center.items-center.code.f7", + [ + "div", + [choices, selection, id], + [ + "button.ml2", + { onclick: () => trigger.next(true) }, + "convert & export", + ], + ], - // hdom-canvas component w/ injected `scene` subtree - // turn __normalize off because `scene` already contains normalized tree - [ - canvas, - { - class: "ma2", - width: 300, - height: 300, - __normalize: false, - ...TESTS[id].attribs, - }, - shapes, - ], + // hdom-canvas component w/ injected `scene` subtree + // turn __normalize off because `scene` already contains normalized tree + [ + canvas, + { + class: "ma2", + width: 300, + height: 300, + __normalize: false, + ...TESTS[id].attribs, + }, + shapes, + ], - ["div.ma2.tc", TESTS[id].desc], - [ - "a.link", - { - href: "https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-shapes", - }, - "Source code", - ], - ]), - updateDOM() + ["div.ma2.tc", TESTS[id].desc], + [ + "a.link", + { + href: "https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-shapes", + }, + "Source code", + ], + ]), + updateDOM() ); // stream combinator which triggers SVG conversion and file download // when both inputs have triggered (one of them being linked to the export button) sync({ - src: { scene, trigger }, - reset: true, - xform: map(({ scene }) => - downloadWithMime( - new Date().toISOString().replace(/[:.-]/g, "") + ".svg", - serialize( - svg( - { - width: 300, - height: 300, - stroke: "none", - fill: "none", - convert: true, - }, - [ - COMMENT, - `generated by @thi.ng/hiccup-svg @ ${new Date()}`, - ], - scene.shapes - ) - ), - { mime: "image/svg+xml" } - ) - ), + src: { scene, trigger }, + reset: true, + xform: map(({ scene }) => + downloadWithMime( + new Date().toISOString().replace(/[:.-]/g, "") + ".svg", + serialize( + svg( + { + width: 300, + height: 300, + stroke: "none", + fill: "none", + convert: true, + }, + [ + COMMENT, + `generated by @thi.ng/hiccup-svg @ ${new Date()}`, + ], + scene.shapes + ) + ), + { mime: "image/svg+xml" } + ) + ), }); // seed initial test selection selection.next( - window.location.hash.length > 1 - ? window.location.hash.substring(1).replace(/-/g, " ") - : "shape morph" + window.location.hash.length > 1 + ? window.location.hash.substring(1).replace(/-/g, " ") + : "shape morph" ); // // HMR handling diff --git a/examples/hdom-canvas-shapes/tsconfig.json b/examples/hdom-canvas-shapes/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-canvas-shapes/tsconfig.json +++ b/examples/hdom-canvas-shapes/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-dropdown-fuzzy/package.json b/examples/hdom-dropdown-fuzzy/package.json index 8aa6e2a4ff..b9bbba4445 100644 --- a/examples/hdom-dropdown-fuzzy/package.json +++ b/examples/hdom-dropdown-fuzzy/package.json @@ -1,42 +1,42 @@ { - "name": "@example/hdom-dropdown-fuzzy", - "private": true, - "description": "Custom dropdown UI component w/ fuzzy search", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/checks": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/interceptors": "workspace:^", - "@thi.ng/paths": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "hdom", - "hdom-components", - "interceptors", - "transducers" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hdom-dropdown-fuzzy", + "private": true, + "description": "Custom dropdown UI component w/ fuzzy search", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/checks": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/interceptors": "workspace:^", + "@thi.ng/paths": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "hdom", + "hdom-components", + "interceptors", + "transducers" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hdom-dropdown-fuzzy/src/config.ts b/examples/hdom-dropdown-fuzzy/src/config.ts index 7001dc5459..3ab9dc86ab 100644 --- a/examples/hdom-dropdown-fuzzy/src/config.ts +++ b/examples/hdom-dropdown-fuzzy/src/config.ts @@ -1,247 +1,245 @@ export const state = { - countries: { - open: false, - filter: "", - items: [ - "Afghanistan", - "Albania", - "Algeria", - "Andorra", - "Angola", - "Antigua and Barbuda", - "Argentina", - "Armenia", - "Australia", - "Austria", - "Azerbaijan", - "Bahamas", - "Bahrain", - "Bangladesh", - "Barbados", - "Belarus", - "Belgium", - "Belize", - "Benin", - "Bhutan", - "Bolivia", - "Bosnia and Herzegovina", - "Botswana", - "Brazil", - "Brunei", - "Bulgaria", - "Burkina Faso", - "Burundi", - "Cabo Verde", - "Cambodia", - "Cameroon", - "Canada", - "Central African Republic (CAR)", - "Chad", - "Chile", - "China", - "Colombia", - "Comoros", - "Democratic Republic of the Congo", - "Republic of the Congo", - "Costa Rica", - "Cote d'Ivoire", - "Croatia", - "Cuba", - "Cyprus", - "Czech Republic", - "Denmark", - "Djibouti", - "Dominica", - "Dominican Republic", - "Ecuador", - "Egypt", - "El Salvador", - "Equatorial Guinea", - "Eritrea", - "Estonia", - "Eswatini (formerly Swaziland)", - "Ethiopia", - "Fiji", - "Finland", - "France", - "Gabon", - "Gambia", - "Georgia", - "Germany", - "Ghana", - "Greece", - "Grenada", - "Guatemala", - "Guinea", - "Guinea-Bissau", - "Guyana", - "Haiti", - "Honduras", - "Hungary", - "Iceland", - "India", - "Indonesia", - "Iran", - "Iraq", - "Ireland", - "Israel", - "Italy", - "Jamaica", - "Japan", - "Jordan", - "Kazakhstan", - "Kenya", - "Kiribati", - "Kosovo", - "Kuwait", - "Kyrgyzstan", - "Laos", - "Latvia", - "Lebanon", - "Lesotho", - "Liberia", - "Libya", - "Liechtenstein", - "Lithuania", - "Luxembourg", - "Macedonia (FYROM)", - "Madagascar", - "Malawi", - "Malaysia", - "Maldives", - "Mali", - "Malta", - "Marshall Islands", - "Mauritania", - "Mauritius", - "Mexico", - "Micronesia", - "Moldova", - "Monaco", - "Mongolia", - "Montenegro", - "Morocco", - "Mozambique", - "Myanmar (formerly Burma)", - "Namibia", - "Nauru", - "Nepal", - "Netherlands", - "New Zealand", - "Nicaragua", - "Niger", - "Nigeria", - "North Korea", - "Norway", - "Oman", - "Pakistan", - "Palau", - "Palestine", - "Panama", - "Papua New Guinea", - "Paraguay", - "Peru", - "Philippines", - "Poland", - "Portugal", - "Qatar", - "Romania", - "Russia", - "Rwanda", - "Saint Kitts and Nevis", - "Saint Lucia", - "Saint Vincent and the Grenadines", - "Samoa", - "San Marino", - "Sao Tome and Principe", - "Saudi Arabia", - "Senegal", - "Serbia", - "Seychelles", - "Sierra Leone", - "Singapore", - "Slovakia", - "Slovenia", - "Solomon Islands", - "Somalia", - "South Africa", - "South Korea", - "South Sudan", - "Spain", - "Sri Lanka", - "Sudan", - "Suriname", - "Sweden", - "Switzerland", - "Syria", - "Taiwan", - "Tajikistan", - "Tanzania", - "Thailand", - "Timor-Leste", - "Togo", - "Tonga", - "Trinidad and Tobago", - "Tunisia", - "Turkey", - "Turkmenistan", - "Tuvalu", - "Uganda", - "Ukraine", - "United Arab Emirates (UAE)", - "United Kingdom (UK)", - "United States of America (USA)", - "Uruguay", - "Uzbekistan", - "Vanuatu", - "Vatican City (Holy See)", - "Venezuela", - "Vietnam", - "Yemen", - "Zambia", - "Zimbabwe", - ].map((x, i) => [i, x]), - }, + countries: { + open: false, + filter: "", + items: [ + "Afghanistan", + "Albania", + "Algeria", + "Andorra", + "Angola", + "Antigua and Barbuda", + "Argentina", + "Armenia", + "Australia", + "Austria", + "Azerbaijan", + "Bahamas", + "Bahrain", + "Bangladesh", + "Barbados", + "Belarus", + "Belgium", + "Belize", + "Benin", + "Bhutan", + "Bolivia", + "Bosnia and Herzegovina", + "Botswana", + "Brazil", + "Brunei", + "Bulgaria", + "Burkina Faso", + "Burundi", + "Cabo Verde", + "Cambodia", + "Cameroon", + "Canada", + "Central African Republic (CAR)", + "Chad", + "Chile", + "China", + "Colombia", + "Comoros", + "Democratic Republic of the Congo", + "Republic of the Congo", + "Costa Rica", + "Cote d'Ivoire", + "Croatia", + "Cuba", + "Cyprus", + "Czech Republic", + "Denmark", + "Djibouti", + "Dominica", + "Dominican Republic", + "Ecuador", + "Egypt", + "El Salvador", + "Equatorial Guinea", + "Eritrea", + "Estonia", + "Eswatini (formerly Swaziland)", + "Ethiopia", + "Fiji", + "Finland", + "France", + "Gabon", + "Gambia", + "Georgia", + "Germany", + "Ghana", + "Greece", + "Grenada", + "Guatemala", + "Guinea", + "Guinea-Bissau", + "Guyana", + "Haiti", + "Honduras", + "Hungary", + "Iceland", + "India", + "Indonesia", + "Iran", + "Iraq", + "Ireland", + "Israel", + "Italy", + "Jamaica", + "Japan", + "Jordan", + "Kazakhstan", + "Kenya", + "Kiribati", + "Kosovo", + "Kuwait", + "Kyrgyzstan", + "Laos", + "Latvia", + "Lebanon", + "Lesotho", + "Liberia", + "Libya", + "Liechtenstein", + "Lithuania", + "Luxembourg", + "Macedonia (FYROM)", + "Madagascar", + "Malawi", + "Malaysia", + "Maldives", + "Mali", + "Malta", + "Marshall Islands", + "Mauritania", + "Mauritius", + "Mexico", + "Micronesia", + "Moldova", + "Monaco", + "Mongolia", + "Montenegro", + "Morocco", + "Mozambique", + "Myanmar (formerly Burma)", + "Namibia", + "Nauru", + "Nepal", + "Netherlands", + "New Zealand", + "Nicaragua", + "Niger", + "Nigeria", + "North Korea", + "Norway", + "Oman", + "Pakistan", + "Palau", + "Palestine", + "Panama", + "Papua New Guinea", + "Paraguay", + "Peru", + "Philippines", + "Poland", + "Portugal", + "Qatar", + "Romania", + "Russia", + "Rwanda", + "Saint Kitts and Nevis", + "Saint Lucia", + "Saint Vincent and the Grenadines", + "Samoa", + "San Marino", + "Sao Tome and Principe", + "Saudi Arabia", + "Senegal", + "Serbia", + "Seychelles", + "Sierra Leone", + "Singapore", + "Slovakia", + "Slovenia", + "Solomon Islands", + "Somalia", + "South Africa", + "South Korea", + "South Sudan", + "Spain", + "Sri Lanka", + "Sudan", + "Suriname", + "Sweden", + "Switzerland", + "Syria", + "Taiwan", + "Tajikistan", + "Tanzania", + "Thailand", + "Timor-Leste", + "Togo", + "Tonga", + "Trinidad and Tobago", + "Tunisia", + "Turkey", + "Turkmenistan", + "Tuvalu", + "Uganda", + "Ukraine", + "United Arab Emirates (UAE)", + "United Kingdom (UK)", + "United States of America (USA)", + "Uruguay", + "Uzbekistan", + "Vanuatu", + "Vatican City (Holy See)", + "Venezuela", + "Vietnam", + "Yemen", + "Zambia", + "Zimbabwe", + ].map((x, i) => [i, x]), + }, }; export const theme = { - root: { - class: "sans-serif", - }, - column: { - class: "fl w-100 w-50-ns w-33-l pa2", - }, - input: { - class: "bg-transparent w-100 bn pa2", - }, - dd: { - root: { class: "" }, - bodyClosed: { - style: { - "max-height": 0, - "overflow-y": "hidden", - opacity: 0, - }, - }, - bodyOpen: { - style: { - "max-height": "calc(11 * 1.8rem)", - "overflow-y": "scroll", - opacity: 1, - transition: "all 100ms ease-in", - }, - }, - item: { - class: - "pointer link db w-100 ph3 pv2 black hover-bg-washed-green bg-animate bb b--moon-gray", - }, - itemSelected: { - class: - "pointer link db w-100 ph3 pv2 black hover-bg-light-gray bg-animate bb b--moon-gray b", - }, - itemDisabled: { class: "db w-100 ph3 pv2 gray bb b--moon-gray" }, - }, - fuzzy: { - class: "b underline", - }, + root: { + class: "sans-serif", + }, + column: { + class: "fl w-100 w-50-ns w-33-l pa2", + }, + input: { + class: "bg-transparent w-100 bn pa2", + }, + dd: { + root: { class: "" }, + bodyClosed: { + style: { + "max-height": 0, + "overflow-y": "hidden", + opacity: 0, + }, + }, + bodyOpen: { + style: { + "max-height": "calc(11 * 1.8rem)", + "overflow-y": "scroll", + opacity: 1, + transition: "all 100ms ease-in", + }, + }, + item: { + class: "pointer link db w-100 ph3 pv2 black hover-bg-washed-green bg-animate bb b--moon-gray", + }, + itemSelected: { + class: "pointer link db w-100 ph3 pv2 black hover-bg-light-gray bg-animate bb b--moon-gray b", + }, + itemDisabled: { class: "db w-100 ph3 pv2 gray bb b--moon-gray" }, + }, + fuzzy: { + class: "b underline", + }, }; diff --git a/examples/hdom-dropdown-fuzzy/src/dropdown.ts b/examples/hdom-dropdown-fuzzy/src/dropdown.ts index 29a865fb06..210b98aed1 100644 --- a/examples/hdom-dropdown-fuzzy/src/dropdown.ts +++ b/examples/hdom-dropdown-fuzzy/src/dropdown.ts @@ -6,110 +6,110 @@ import { EventBus, EV_SET_VALUE, EV_TOGGLE_VALUE } from "@thi.ng/interceptors"; import { getInUnsafe } from "@thi.ng/paths/get-in"; export interface BaseContext { - bus: EventBus; - state: ReadonlyAtom; + bus: EventBus; + state: ReadonlyAtom; } export interface DropdownArgs { - state: DropdownState; - statePath: Path; - ontoggle: EventListener; - onchange: Fn; - attribs: IObjectOf; - hoverLabel: any; - openLabel: any; - noItems: any; - onmouseover: EventListener; - onmouseleave: EventListener; + state: DropdownState; + statePath: Path; + ontoggle: EventListener; + onchange: Fn; + attribs: IObjectOf; + hoverLabel: any; + openLabel: any; + noItems: any; + onmouseover: EventListener; + onmouseleave: EventListener; } export interface DropdownState { - open: boolean; - hover: boolean; - selected: any; - items: DropdownItem[]; + open: boolean; + hover: boolean; + selected: any; + items: DropdownItem[]; } export type DropdownItem = [any, any]; export interface DropdownTheme { - root: IObjectOf; - bodyOpen: IObjectOf; - bodyClosed: IObjectOf; - item: IObjectOf; - itemSelected: IObjectOf; - itemDisabled: IObjectOf; + root: IObjectOf; + bodyOpen: IObjectOf; + bodyClosed: IObjectOf; + item: IObjectOf; + itemSelected: IObjectOf; + itemDisabled: IObjectOf; } export function dropdown(themeCtxPath: Path) { - return (ctx: any, opts: Partial) => { - const ui: DropdownTheme = getInUnsafe(ctx, themeCtxPath)!; - const state = opts.statePath - ? getInUnsafe(ctx, opts.statePath) - : opts.state; - const hattribs = { - onmouseover: opts.onmouseover, - onmouseleave: opts.onmouseleave, - }; - return state.open - ? [ - "div", - { ...ui.root, onkeydown: (e: Event) => console.log(e) }, - [ - appLink, - { ...hattribs, ...ui.itemSelected }, - opts.ontoggle, - opts.openLabel || opts.hoverLabel, - ], - [ - "div", - ui.bodyOpen, - state.items.length - ? state.items.map((x: any) => [ - "a", - { - ...(x[0] === state.selected - ? ui.itemSelected - : ui.item), - href: "#", - onclick: opts.onchange!(x[0]), - }, - ...(isString(x[1]) ? [x[1]] : x[1]), - ]) - : ["span", ui.itemDisabled, opts.noItems], - ], - ] - : [ - "div", - ui.root, - [ - appLink, - { ...hattribs, ...ui.item }, - opts.ontoggle, - state.hover - ? opts.hoverLabel - : (state.items.find( - (x: any) => x[0] === state.selected - ) || [, opts.hoverLabel])[1], - ], - ["div", ui.bodyClosed], - ]; - }; + return (ctx: any, opts: Partial) => { + const ui: DropdownTheme = getInUnsafe(ctx, themeCtxPath)!; + const state = opts.statePath + ? getInUnsafe(ctx, opts.statePath) + : opts.state; + const hattribs = { + onmouseover: opts.onmouseover, + onmouseleave: opts.onmouseleave, + }; + return state.open + ? [ + "div", + { ...ui.root, onkeydown: (e: Event) => console.log(e) }, + [ + appLink, + { ...hattribs, ...ui.itemSelected }, + opts.ontoggle, + opts.openLabel || opts.hoverLabel, + ], + [ + "div", + ui.bodyOpen, + state.items.length + ? state.items.map((x: any) => [ + "a", + { + ...(x[0] === state.selected + ? ui.itemSelected + : ui.item), + href: "#", + onclick: opts.onchange!(x[0]), + }, + ...(isString(x[1]) ? [x[1]] : x[1]), + ]) + : ["span", ui.itemDisabled, opts.noItems], + ], + ] + : [ + "div", + ui.root, + [ + appLink, + { ...hattribs, ...ui.item }, + opts.ontoggle, + state.hover + ? opts.hoverLabel + : (state.items.find( + (x: any) => x[0] === state.selected + ) || [, opts.hoverLabel])[1], + ], + ["div", ui.bodyClosed], + ]; + }; } export const dropdownListeners = ( - ctx: BaseContext, - basePath: readonly NumOrString[] + ctx: BaseContext, + basePath: readonly NumOrString[] ) => ({ - onmouseover: () => - ctx.bus.dispatch([EV_SET_VALUE, [[...basePath, "hover"], true]]), - onmouseleave: () => - ctx.bus.dispatch([EV_SET_VALUE, [[...basePath, "hover"], false]]), - ontoggle: () => ctx.bus.dispatch([EV_TOGGLE_VALUE, [...basePath, "open"]]), - onchange: (x: any) => () => { - ctx.bus.dispatch( - [EV_SET_VALUE, [[...basePath, "selected"], x]], - [EV_SET_VALUE, [[...basePath, "open"], false]] - ); - }, + onmouseover: () => + ctx.bus.dispatch([EV_SET_VALUE, [[...basePath, "hover"], true]]), + onmouseleave: () => + ctx.bus.dispatch([EV_SET_VALUE, [[...basePath, "hover"], false]]), + ontoggle: () => ctx.bus.dispatch([EV_TOGGLE_VALUE, [...basePath, "open"]]), + onchange: (x: any) => () => { + ctx.bus.dispatch( + [EV_SET_VALUE, [[...basePath, "selected"], x]], + [EV_SET_VALUE, [[...basePath, "open"], false]] + ); + }, }); diff --git a/examples/hdom-dropdown-fuzzy/src/fuzzy.ts b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts index 16441a09ea..96899f0fdd 100644 --- a/examples/hdom-dropdown-fuzzy/src/fuzzy.ts +++ b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts @@ -6,96 +6,96 @@ import { filterFuzzy } from "@thi.ng/transducers/filter-fuzzy"; import { iterator } from "@thi.ng/transducers/iterator"; import { map } from "@thi.ng/transducers/map"; import { - dropdownListeners, - type DropdownItem, - type DropdownState, + dropdownListeners, + type DropdownItem, + type DropdownState, } from "./dropdown"; export interface FuzzyArgs { - state: IView; - filter: IView; - dropdown: any; - input: any; - hoverLabel: any; - placeholder: string; + state: IView; + filter: IView; + dropdown: any; + input: any; + hoverLabel: any; + placeholder: string; } export const fuzzyDropdown = (ctx: any, opts: FuzzyArgs) => { - const close = () => - ctx.bus.dispatch([EV_SET_VALUE, [opts.state.path + ".open", false]]); - const filterInput = [ - opts.input, - { - state: opts.filter.deref(), - placeholder: opts.placeholder, - oninput: (e: Event) => - ctx.bus.dispatch([ - EV_SET_VALUE, - [opts.filter.path, (e.target).value], - ]), - onclear: () => - ctx.bus.dispatch([EV_SET_VALUE, [opts.filter.path, ""]]), - oncancel: close, - onconfirm: close, - }, - ]; - return () => { - const state: any = { ...opts.state.deref() }; - const filter = opts.filter.deref()!.toLowerCase(); - if (filter && state.open) { - state.items = [ - ...iterator( - comp( - filterFuzzy(filter, { - key: (x: DropdownItem) => x[1].toLowerCase(), - }), - map( - ([id, x]) => - [ - id, - highlightMatches( - (y) => ["span", ctx.theme.fuzzy, y], - x, - filter - ), - ] - ) - ), - state.items - ), - ]; - } - return [ - opts.dropdown, - { - ...dropdownListeners(ctx, toPath(opts.state.path)), - openLabel: filterInput, - hoverLabel: opts.hoverLabel, - noItems: "no matches", - state, - }, - ]; - }; + const close = () => + ctx.bus.dispatch([EV_SET_VALUE, [opts.state.path + ".open", false]]); + const filterInput = [ + opts.input, + { + state: opts.filter.deref(), + placeholder: opts.placeholder, + oninput: (e: Event) => + ctx.bus.dispatch([ + EV_SET_VALUE, + [opts.filter.path, (e.target).value], + ]), + onclear: () => + ctx.bus.dispatch([EV_SET_VALUE, [opts.filter.path, ""]]), + oncancel: close, + onconfirm: close, + }, + ]; + return () => { + const state: any = { ...opts.state.deref() }; + const filter = opts.filter.deref()!.toLowerCase(); + if (filter && state.open) { + state.items = [ + ...iterator( + comp( + filterFuzzy(filter, { + key: (x: DropdownItem) => x[1].toLowerCase(), + }), + map( + ([id, x]) => + [ + id, + highlightMatches( + (y) => ["span", ctx.theme.fuzzy, y], + x, + filter + ), + ] + ) + ), + state.items + ), + ]; + } + return [ + opts.dropdown, + { + ...dropdownListeners(ctx, toPath(opts.state.path)), + openLabel: filterInput, + hoverLabel: opts.hoverLabel, + noItems: "no matches", + state, + }, + ]; + }; }; const highlightMatches = ( - fn: (x: string) => any, - x: string, - filter: string + fn: (x: string) => any, + x: string, + filter: string ) => { - const res: any[] = []; - let prev = -1, - n = x.length - 1, - m = filter.length; - for (let i = 0, j = 0; i <= n && j < m; i++) { - const c = x.charAt(i); - if (c.toLowerCase() === filter.charAt(j)) { - i - prev > 1 && res.push(x.substring(prev + 1, i)); - res.push(fn(c)); - prev = i; - j++; - } - } - prev < n && res.push(x.substring(prev + 1)); - return res; + const res: any[] = []; + let prev = -1, + n = x.length - 1, + m = filter.length; + for (let i = 0, j = 0; i <= n && j < m; i++) { + const c = x.charAt(i); + if (c.toLowerCase() === filter.charAt(j)) { + i - prev > 1 && res.push(x.substring(prev + 1, i)); + res.push(fn(c)); + prev = i; + j++; + } + } + prev < n && res.push(x.substring(prev + 1)); + return res; }; diff --git a/examples/hdom-dropdown-fuzzy/src/index.ts b/examples/hdom-dropdown-fuzzy/src/index.ts index 33b3c5d757..261c636224 100644 --- a/examples/hdom-dropdown-fuzzy/src/index.ts +++ b/examples/hdom-dropdown-fuzzy/src/index.ts @@ -11,44 +11,44 @@ const bus = new EventBus(defAtom(state)); bus.instrumentWith([trace]); const ctx = { - bus, - theme, - views: { - countries: defView(bus.state, ["countries"]), - filter: defView(bus.state, ["countries", "filter"]), - }, + bus, + theme, + views: { + countries: defView(bus.state, ["countries"]), + filter: defView(bus.state, ["countries", "filter"]), + }, }; const dd = dropdown("theme.dd"); const input = cancelableInput("theme.input"); start( - (ctx: any) => { - ctx.bus.processQueue(); - return [ - "div", - ctx.theme.root, - [ - "div", - ctx.theme.column, - [ - fuzzyDropdown, - { - state: ctx.views.countries, - filter: ctx.views.filter, - placeholder: "Start typing to fuzzy match", - hoverLabel: [ - ["span", "Choose a country..."], - ["i.fr.fas.fa-angle-down"], - ], - dropdown: dd, - input, - }, - ], - ], - ]; - }, - { ctx } + (ctx: any) => { + ctx.bus.processQueue(); + return [ + "div", + ctx.theme.root, + [ + "div", + ctx.theme.column, + [ + fuzzyDropdown, + { + state: ctx.views.countries, + filter: ctx.views.filter, + placeholder: "Start typing to fuzzy match", + hoverLabel: [ + ["span", "Choose a country..."], + ["i.fr.fas.fa-angle-down"], + ], + dropdown: dd, + input, + }, + ], + ], + ]; + }, + { ctx } ); // window["ctx"] = ctx; diff --git a/examples/hdom-dropdown-fuzzy/src/input.ts b/examples/hdom-dropdown-fuzzy/src/input.ts index cba431b74f..f521071e80 100644 --- a/examples/hdom-dropdown-fuzzy/src/input.ts +++ b/examples/hdom-dropdown-fuzzy/src/input.ts @@ -3,66 +3,66 @@ import type { IView } from "@thi.ng/atom"; import { getInUnsafe } from "@thi.ng/paths/get-in"; export interface InputArgs { - state: IView; - orig: IView; - attribs: any; - placeholder: string; - oninput: EventListener; - oncancel: EventListener; - onconfirm: EventListener; - onclear: EventListener; - onblur: EventListener; + state: IView; + orig: IView; + attribs: any; + placeholder: string; + oninput: EventListener; + oncancel: EventListener; + onconfirm: EventListener; + onclear: EventListener; + onblur: EventListener; } export function cancelableInput(themeCtxPath: Path) { - let input: HTMLElement; - return { - init: (el: HTMLElement) => (input = el.firstChild).focus(), - render: (ctx: any, args: InputArgs) => [ - "span.relative", - [ - "input", - { - ...getInUnsafe(ctx, themeCtxPath), - ...args.attribs, - type: "text", - oninput: args.oninput, - onblur: args.onblur, - onkeydown: (e: KeyboardEvent) => { - switch (e.key) { - case "Escape": - args.oncancel && args.oncancel(e); - (e.target).blur(); - break; - case "Enter": - // case "Tab": - args.onconfirm && args.onconfirm(e); - (e.target).blur(); - break; - default: - } - }, - placeholder: args.placeholder, - value: args.state, - }, - ], - args.onclear - ? [ - "a", - { - href: "#", - onclick: (e: Event) => { - e.stopPropagation(); - input.focus(); - args.onclear(e); - }, - }, - [ - "i.absolute.fas.fa-times-circle.gray.f7", - { style: { right: "0.5rem", top: "0.25rem" } }, - ], - ] - : undefined, - ], - }; + let input: HTMLElement; + return { + init: (el: HTMLElement) => (input = el.firstChild).focus(), + render: (ctx: any, args: InputArgs) => [ + "span.relative", + [ + "input", + { + ...getInUnsafe(ctx, themeCtxPath), + ...args.attribs, + type: "text", + oninput: args.oninput, + onblur: args.onblur, + onkeydown: (e: KeyboardEvent) => { + switch (e.key) { + case "Escape": + args.oncancel && args.oncancel(e); + (e.target).blur(); + break; + case "Enter": + // case "Tab": + args.onconfirm && args.onconfirm(e); + (e.target).blur(); + break; + default: + } + }, + placeholder: args.placeholder, + value: args.state, + }, + ], + args.onclear + ? [ + "a", + { + href: "#", + onclick: (e: Event) => { + e.stopPropagation(); + input.focus(); + args.onclear(e); + }, + }, + [ + "i.absolute.fas.fa-times-circle.gray.f7", + { style: { right: "0.5rem", top: "0.25rem" } }, + ], + ] + : undefined, + ], + }; } diff --git a/examples/hdom-dropdown-fuzzy/tsconfig.json b/examples/hdom-dropdown-fuzzy/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-dropdown-fuzzy/tsconfig.json +++ b/examples/hdom-dropdown-fuzzy/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-dropdown/package.json b/examples/hdom-dropdown/package.json index 6cc46e50d2..fab7abc165 100644 --- a/examples/hdom-dropdown/package.json +++ b/examples/hdom-dropdown/package.json @@ -1,39 +1,39 @@ { - "name": "@example/hdom-dropdown", - "private": true, - "description": "Custom dropdown UI component for hdom", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/interceptors": "workspace:^", - "@thi.ng/paths": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "thi.ng": { - "readme": [ - "atom", - "hdom", - "hdom-components", - "interceptors" - ] - } + "name": "@example/hdom-dropdown", + "private": true, + "description": "Custom dropdown UI component for hdom", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/interceptors": "workspace:^", + "@thi.ng/paths": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "thi.ng": { + "readme": [ + "atom", + "hdom", + "hdom-components", + "interceptors" + ] + } } diff --git a/examples/hdom-dropdown/src/config.ts b/examples/hdom-dropdown/src/config.ts index cb0f2470de..bf9bf5425b 100644 --- a/examples/hdom-dropdown/src/config.ts +++ b/examples/hdom-dropdown/src/config.ts @@ -1,71 +1,69 @@ export const state = { - foo: { - open: false, - items: [ - "Action", - "Animation", - "Comedy", - "Crime", - "Documentary", - "Drama", - "Fantasy", - "Horror", - "Kids", - "Romance", - "Sci-Fi", - "Sport", - "Thriller", - "War", - "Western", - ].map((x, i) => [i, x]), - }, - bar: { - open: false, - items: [ - "Africa", - "Asia", - "Caribbean", - "Central America", - "Europe", - "Middle East", - "North America", - "Oceania", - "South America", - ].map((x, i) => [i, x]), - }, + foo: { + open: false, + items: [ + "Action", + "Animation", + "Comedy", + "Crime", + "Documentary", + "Drama", + "Fantasy", + "Horror", + "Kids", + "Romance", + "Sci-Fi", + "Sport", + "Thriller", + "War", + "Western", + ].map((x, i) => [i, x]), + }, + bar: { + open: false, + items: [ + "Africa", + "Asia", + "Caribbean", + "Central America", + "Europe", + "Middle East", + "North America", + "Oceania", + "South America", + ].map((x, i) => [i, x]), + }, }; export const theme = { - root: { - class: "sans-serif", - }, - column: { - class: "fl w-100 w-50-ns w-33-l pa2", - }, - dd: { - root: { class: "" }, - bodyClosed: { - style: { - "max-height": 0, - "overflow-y": "hidden", - opacity: 0, - }, - }, - bodyOpen: { - style: { - "max-height": "calc(11 * 1.8rem)", - "overflow-y": "scroll", - opacity: 1, - transition: "all 100ms ease-in", - }, - }, - item: { - class: - "pointer link db w-100 ph3 pv2 black hover-bg-washed-green bg-animate bb b--moon-gray", - }, - itemSelected: { - class: - "pointer link db w-100 ph3 pv2 black hover-bg-light-gray bg-animate bb b--moon-gray b", - }, - }, + root: { + class: "sans-serif", + }, + column: { + class: "fl w-100 w-50-ns w-33-l pa2", + }, + dd: { + root: { class: "" }, + bodyClosed: { + style: { + "max-height": 0, + "overflow-y": "hidden", + opacity: 0, + }, + }, + bodyOpen: { + style: { + "max-height": "calc(11 * 1.8rem)", + "overflow-y": "scroll", + opacity: 1, + transition: "all 100ms ease-in", + }, + }, + item: { + class: "pointer link db w-100 ph3 pv2 black hover-bg-washed-green bg-animate bb b--moon-gray", + }, + itemSelected: { + class: "pointer link db w-100 ph3 pv2 black hover-bg-light-gray bg-animate bb b--moon-gray b", + }, + }, }; diff --git a/examples/hdom-dropdown/src/dropdown.ts b/examples/hdom-dropdown/src/dropdown.ts index 4a39b824a4..d8c605f3e7 100644 --- a/examples/hdom-dropdown/src/dropdown.ts +++ b/examples/hdom-dropdown/src/dropdown.ts @@ -5,102 +5,102 @@ import { EventBus, EV_SET_VALUE, EV_TOGGLE_VALUE } from "@thi.ng/interceptors"; import { getInUnsafe } from "@thi.ng/paths/get-in"; export interface BaseContext { - bus: EventBus; - state: ReadonlyAtom; + bus: EventBus; + state: ReadonlyAtom; } export interface DropdownArgs { - state: DropdownState; - statePath: Path; - ontoggle: EventListener; - onchange: Fn; - attribs: IObjectOf; - hoverLabel: any; - onmouseover: EventListener; - onmouseleave: EventListener; + state: DropdownState; + statePath: Path; + ontoggle: EventListener; + onchange: Fn; + attribs: IObjectOf; + hoverLabel: any; + onmouseover: EventListener; + onmouseleave: EventListener; } export interface DropdownState { - open: boolean; - hover: boolean; - selected: any; - items: [any, any][]; + open: boolean; + hover: boolean; + selected: any; + items: [any, any][]; } export interface DropdownTheme { - root: IObjectOf; - bodyOpen: IObjectOf; - bodyClosed: IObjectOf; - item: IObjectOf; - itemSelected: IObjectOf; + root: IObjectOf; + bodyOpen: IObjectOf; + bodyClosed: IObjectOf; + item: IObjectOf; + itemSelected: IObjectOf; } export function dropdown(themeCtxPath: Path) { - return (ctx: any, opts: Partial) => { - const ui: DropdownTheme = getInUnsafe(ctx, themeCtxPath)!; - const state = opts.statePath - ? getInUnsafe(ctx, opts.statePath) - : opts.state; - const hattribs = { - onmouseover: opts.onmouseover, - onmouseleave: opts.onmouseleave, - }; - return state.open - ? [ - "div", - ui.root, - [ - appLink, - { ...hattribs, ...ui.itemSelected }, - opts.ontoggle, - opts.hoverLabel, - ], - [ - "div", - ui.bodyOpen, - state.items.map((x: any) => - appLink( - null, - x[0] === state.selected - ? ui.itemSelected - : ui.item, - opts.onchange!(x[0]), - x[1] - ) - ), - ], - ] - : [ - "div", - ui.root, - [ - appLink, - { ...hattribs, ...ui.item }, - opts.ontoggle, - state.hover - ? opts.hoverLabel - : (state.items.find( - (x: any) => x[0] === state.selected - ) || [, opts.hoverLabel])[1], - ], - ["div", ui.bodyClosed], - ]; - }; + return (ctx: any, opts: Partial) => { + const ui: DropdownTheme = getInUnsafe(ctx, themeCtxPath)!; + const state = opts.statePath + ? getInUnsafe(ctx, opts.statePath) + : opts.state; + const hattribs = { + onmouseover: opts.onmouseover, + onmouseleave: opts.onmouseleave, + }; + return state.open + ? [ + "div", + ui.root, + [ + appLink, + { ...hattribs, ...ui.itemSelected }, + opts.ontoggle, + opts.hoverLabel, + ], + [ + "div", + ui.bodyOpen, + state.items.map((x: any) => + appLink( + null, + x[0] === state.selected + ? ui.itemSelected + : ui.item, + opts.onchange!(x[0]), + x[1] + ) + ), + ], + ] + : [ + "div", + ui.root, + [ + appLink, + { ...hattribs, ...ui.item }, + opts.ontoggle, + state.hover + ? opts.hoverLabel + : (state.items.find( + (x: any) => x[0] === state.selected + ) || [, opts.hoverLabel])[1], + ], + ["div", ui.bodyClosed], + ]; + }; } export const dropdownListeners = ( - ctx: BaseContext, - basePath: PropertyKey[] + ctx: BaseContext, + basePath: PropertyKey[] ) => ({ - onmouseover: () => - ctx.bus.dispatch([EV_SET_VALUE, [[...basePath, "hover"], true]]), - onmouseleave: () => - ctx.bus.dispatch([EV_SET_VALUE, [[...basePath, "hover"], false]]), - ontoggle: () => ctx.bus.dispatch([EV_TOGGLE_VALUE, [...basePath, "open"]]), - onchange: (x: any) => () => { - ctx.bus.dispatch( - [EV_SET_VALUE, [[...basePath, "selected"], x]], - [EV_SET_VALUE, [[...basePath, "open"], false]] - ); - }, + onmouseover: () => + ctx.bus.dispatch([EV_SET_VALUE, [[...basePath, "hover"], true]]), + onmouseleave: () => + ctx.bus.dispatch([EV_SET_VALUE, [[...basePath, "hover"], false]]), + ontoggle: () => ctx.bus.dispatch([EV_TOGGLE_VALUE, [...basePath, "open"]]), + onchange: (x: any) => () => { + ctx.bus.dispatch( + [EV_SET_VALUE, [[...basePath, "selected"], x]], + [EV_SET_VALUE, [[...basePath, "open"], false]] + ); + }, }); diff --git a/examples/hdom-dropdown/src/index.ts b/examples/hdom-dropdown/src/index.ts index c6007a84f0..a014a99022 100644 --- a/examples/hdom-dropdown/src/index.ts +++ b/examples/hdom-dropdown/src/index.ts @@ -10,42 +10,42 @@ bus.instrumentWith([trace]); const dd = dropdown("theme.dd"); start( - (ctx: any) => { - bus.processQueue(); - return [ - "div", - ctx.theme.root, - [ - "div", - ctx.theme.column, - [ - dd, - { - ...dropdownListeners(ctx, ["foo"]), - state: ctx.bus.state.deref().foo, - hoverLabel: [ - ["span", "Choose a genre..."], - ["i.fr.fas.fa-angle-down"], - ], - }, - ], - ], - [ - "div", - ctx.theme.column, - [ - dd, - { - ...dropdownListeners(ctx, ["bar"]), - state: ctx.bus.state.deref().bar, - hoverLabel: [ - ["span", "Region..."], - ["i.fr.fas.fa-angle-down"], - ], - }, - ], - ], - ]; - }, - { ctx: { bus, theme } } + (ctx: any) => { + bus.processQueue(); + return [ + "div", + ctx.theme.root, + [ + "div", + ctx.theme.column, + [ + dd, + { + ...dropdownListeners(ctx, ["foo"]), + state: ctx.bus.state.deref().foo, + hoverLabel: [ + ["span", "Choose a genre..."], + ["i.fr.fas.fa-angle-down"], + ], + }, + ], + ], + [ + "div", + ctx.theme.column, + [ + dd, + { + ...dropdownListeners(ctx, ["bar"]), + state: ctx.bus.state.deref().bar, + hoverLabel: [ + ["span", "Region..."], + ["i.fr.fas.fa-angle-down"], + ], + }, + ], + ], + ]; + }, + { ctx: { bus, theme } } ); diff --git a/examples/hdom-dropdown/tsconfig.json b/examples/hdom-dropdown/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-dropdown/tsconfig.json +++ b/examples/hdom-dropdown/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-dyn-context/package.json b/examples/hdom-dyn-context/package.json index dfd1205074..11d72a4316 100644 --- a/examples/hdom-dyn-context/package.json +++ b/examples/hdom-dyn-context/package.json @@ -1,33 +1,33 @@ { - "name": "@example/hdom-dyn-context", - "private": true, - "description": "Using custom hdom context for dynamic UI theming", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/atom": "workspace:^", - "@thi.ng/hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "thi.ng": { - "readme": [ - "atom", - "hdom" - ] - } + "name": "@example/hdom-dyn-context", + "private": true, + "description": "Using custom hdom context for dynamic UI theming", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/atom": "workspace:^", + "@thi.ng/hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "thi.ng": { + "readme": [ + "atom", + "hdom" + ] + } } diff --git a/examples/hdom-dyn-context/src/index.ts b/examples/hdom-dyn-context/src/index.ts index 9c09686bda..ddcf55a272 100644 --- a/examples/hdom-dyn-context/src/index.ts +++ b/examples/hdom-dyn-context/src/index.ts @@ -4,21 +4,21 @@ import { start } from "@thi.ng/hdom/start"; // theme definitions const THEMES = [ - { - id: "default", - root: { class: "w-100 ma0 pa3 sans-serif f6" }, - button: { class: "w4 pa2 bg-blue white" }, - }, - { - id: "alt", - root: { class: "w-100 ma0 pa3 serif f3 bg-washed-red" }, - button: { class: "w5 pa2 bg-red white" }, - }, - { - id: "mono", - root: { class: "w-100 ma0 pa3 courier f7 bg-light-gray" }, - button: { class: "w4 pa2 bg-black white" }, - }, + { + id: "default", + root: { class: "w-100 ma0 pa3 sans-serif f6" }, + button: { class: "w4 pa2 bg-blue white" }, + }, + { + id: "alt", + root: { class: "w-100 ma0 pa3 serif f3 bg-washed-red" }, + button: { class: "w5 pa2 bg-red white" }, + }, + { + id: "mono", + root: { class: "w-100 ma0 pa3 courier f7 bg-light-gray" }, + button: { class: "w4 pa2 bg-black white" }, + }, ]; // central app state atom @@ -30,8 +30,8 @@ const db = defAtom({ id: 0 }); // goes for any other data structure which implements the @thi.ng/api // `IDeref` interface (e.g. the above atom, rstream's etc.)... const ctx = { - theme: defView(db, ["id"], (id) => THEMES[id]), - themeID: defView(db, ["id"], (id) => THEMES[id].id.toUpperCase()), + theme: defView(db, ["id"], (id) => THEMES[id]), + themeID: defView(db, ["id"], (id) => THEMES[id].id.toUpperCase()), }; // state updater / event handler to cycle through all themes @@ -41,14 +41,14 @@ const toggle = () => db.swapIn(["id"], (id) => (id + 1) % THEMES.length); // the destructuring form is for the context object which is always // passed as 1st arg const app = ({ theme, themeID }: any) => [ - "div", - theme.root, - ["h1", `Current theme: ${themeID}`], - ["button", { ...theme.button, onclick: toggle }, "Toggle"], + "div", + theme.root, + ["h1", `Current theme: ${themeID}`], + ["button", { ...theme.button, onclick: toggle }, "Toggle"], ]; // kick off hdom render loop start(app, { - ctx, - autoDerefKeys: Object.keys(ctx), + ctx, + autoDerefKeys: Object.keys(ctx), }); diff --git a/examples/hdom-dyn-context/tsconfig.json b/examples/hdom-dyn-context/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-dyn-context/tsconfig.json +++ b/examples/hdom-dyn-context/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-elm/package.json b/examples/hdom-elm/package.json index 154d56f3df..3fa2dd7e62 100644 --- a/examples/hdom-elm/package.json +++ b/examples/hdom-elm/package.json @@ -1,36 +1,36 @@ { - "name": "@example/hdom-elm", - "private": true, - "version": "0.0.1", - "description": "Using hdom in an Elm-like manner", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hiccup": "workspace:^", - "@thi.ng/paths": "workspace:^", - "@thi.ng/strings": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom", - "paths" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hdom-elm", + "private": true, + "version": "0.0.1", + "description": "Using hdom in an Elm-like manner", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hiccup": "workspace:^", + "@thi.ng/paths": "workspace:^", + "@thi.ng/strings": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom", + "paths" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hdom-elm/src/api.ts b/examples/hdom-elm/src/api.ts index 30a17db515..ef556d9a5d 100644 --- a/examples/hdom-elm/src/api.ts +++ b/examples/hdom-elm/src/api.ts @@ -7,10 +7,10 @@ export const DEFER = "defer"; // alias of possible event structures/signatures export interface EventTypeMap { - [INC]: [typeof INC, number]; - [DEC]: [typeof DEC, number]; - [RANDOM]: [typeof RANDOM]; - [DEFER]: [typeof DEFER, Fn0, number]; + [INC]: [typeof INC, number]; + [DEC]: [typeof DEC, number]; + [RANDOM]: [typeof RANDOM]; + [DEFER]: [typeof DEFER, Fn0, number]; } export type EventType = keyof EventTypeMap; diff --git a/examples/hdom-elm/src/elm.ts b/examples/hdom-elm/src/elm.ts index f9ddd964df..c1bfb02022 100644 --- a/examples/hdom-elm/src/elm.ts +++ b/examples/hdom-elm/src/elm.ts @@ -6,38 +6,38 @@ import { derefContext } from "@thi.ng/hiccup/deref"; import type { Event, Signal } from "./api"; export const mount = ( - model: T, - update: Fn2, - view: Fn2, - subscriptions?: Fn, - opts: Partial = {}, - impl: HDOMImplementation = DEFAULT_IMPL + model: T, + update: Fn2, + view: Fn2, + subscriptions?: Fn, + opts: Partial = {}, + impl: HDOMImplementation = DEFAULT_IMPL ) => { - const _opts = { root: "app", ...opts }; - let prev: any[] = []; - const root = resolveRoot(_opts.root, impl); + const _opts = { root: "app", ...opts }; + let prev: any[] = []; + const root = resolveRoot(_opts.root, impl); - const render = () => { - _opts.ctx = derefContext(opts.ctx, _opts.autoDerefKeys); - const curr = impl.normalizeTree(_opts, view(model, signal)); - if (curr != null) { - if (_opts.hydrate) { - impl.hydrateTree(_opts, root, curr); - _opts.hydrate = false; - } else { - impl.diffTree(_opts, root, prev, curr); - } - prev = curr; - } - }; + const render = () => { + _opts.ctx = derefContext(opts.ctx, _opts.autoDerefKeys); + const curr = impl.normalizeTree(_opts, view(model, signal)); + if (curr != null) { + if (_opts.hydrate) { + impl.hydrateTree(_opts, root, curr); + _opts.hydrate = false; + } else { + impl.diffTree(_opts, root, prev, curr); + } + prev = curr; + } + }; - const signal = (event: Event) => { - return () => { - model = update(event, model); - render(); - }; - }; + const signal = (event: Event) => { + return () => { + model = update(event, model); + render(); + }; + }; - render(); - subscriptions && subscriptions(signal); + render(); + subscriptions && subscriptions(signal); }; diff --git a/examples/hdom-elm/src/index.ts b/examples/hdom-elm/src/index.ts index b53aade655..185762b22e 100644 --- a/examples/hdom-elm/src/index.ts +++ b/examples/hdom-elm/src/index.ts @@ -5,64 +5,64 @@ import { DEC, DEFER, INC, RANDOM } from "./api"; import { mount } from "./elm"; mount( - // state / model - { - value: 0, - }, - // update / event processor - (event, model) => { - switch (event[0]) { - case INC: - return updateIn(model, ["value"], (x) => - Math.min(x + event[1], 100) - ); - case DEC: - return updateIn(model, ["value"], (x) => - Math.max(x - event[1], 0) - ); - case RANDOM: - return setIn(model, ["value"], (Math.random() * 100) | 0); - case DEFER: - setTimeout(() => event[1](), event[2]); - break; - default: - } - return model; - }, - // view / root component - (model, signal) => [ - "div.w4.pa2.br3.tc.bg-red.white", - ["div", Z3(model.value)], - [ - "div.mv2", - ["button", { onclick: () => signal([DEC, 1])() }, "-"], - ["button", { onclick: () => signal([INC, 1])() }, "+"], - ], - ], - // other event handlers - (signal) => { - document.addEventListener("keypress", (e) => { - switch (e.key) { - case "-": - signal([DEC, 1])(); - break; - case "_": - signal([DEC, 10])(); - break; - case "=": - signal([INC, 1])(); - break; - case "+": - signal([INC, 10])(); - break; - case "r": - signal([RANDOM])(); - break; - case "d": - signal([DEFER, signal(["rnd"]), 500])(); - break; - } - }); - } - // other hdom options (see mount()) + // state / model + { + value: 0, + }, + // update / event processor + (event, model) => { + switch (event[0]) { + case INC: + return updateIn(model, ["value"], (x) => + Math.min(x + event[1], 100) + ); + case DEC: + return updateIn(model, ["value"], (x) => + Math.max(x - event[1], 0) + ); + case RANDOM: + return setIn(model, ["value"], (Math.random() * 100) | 0); + case DEFER: + setTimeout(() => event[1](), event[2]); + break; + default: + } + return model; + }, + // view / root component + (model, signal) => [ + "div.w4.pa2.br3.tc.bg-red.white", + ["div", Z3(model.value)], + [ + "div.mv2", + ["button", { onclick: () => signal([DEC, 1])() }, "-"], + ["button", { onclick: () => signal([INC, 1])() }, "+"], + ], + ], + // other event handlers + (signal) => { + document.addEventListener("keypress", (e) => { + switch (e.key) { + case "-": + signal([DEC, 1])(); + break; + case "_": + signal([DEC, 10])(); + break; + case "=": + signal([INC, 1])(); + break; + case "+": + signal([INC, 10])(); + break; + case "r": + signal([RANDOM])(); + break; + case "d": + signal([DEFER, signal(["rnd"]), 500])(); + break; + } + }); + } + // other hdom options (see mount()) ); diff --git a/examples/hdom-elm/tsconfig.json b/examples/hdom-elm/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-elm/tsconfig.json +++ b/examples/hdom-elm/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-inner-html/package.json b/examples/hdom-inner-html/package.json index eca3f19352..8745cfad05 100644 --- a/examples/hdom-inner-html/package.json +++ b/examples/hdom-inner-html/package.json @@ -1,31 +1,31 @@ { - "name": "@example/hdom-inner-html", - "private": true, - "description": "Higher-order component for rendering HTML strings", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "thi.ng": { - "readme": [ - "hdom" - ] - } + "name": "@example/hdom-inner-html", + "private": true, + "description": "Higher-order component for rendering HTML strings", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "thi.ng": { + "readme": [ + "hdom" + ] + } } diff --git a/examples/hdom-inner-html/src/index.ts b/examples/hdom-inner-html/src/index.ts index 5dbff2b48e..b7911ffd15 100644 --- a/examples/hdom-inner-html/src/index.ts +++ b/examples/hdom-inner-html/src/index.ts @@ -6,38 +6,38 @@ import { start } from "@thi.ng/hdom/start"; * time the given string has changed. */ const innerHtmlWrapper = () => - { - init(el: any, _: any, html: string) { - this.el = el; - this.prev = html; - el.innerHTML = html; - }, - render(_: any, html: string) { - if (this.el && this.prev != html) { - this.el.innerHTML = html; - this.prev = html; - } - return ["div"]; - }, - release() { - this.el.innerHTML = ""; - delete this.prev; - delete this.el; - }, - }; + { + init(el: any, _: any, html: string) { + this.el = el; + this.prev = html; + el.innerHTML = html; + }, + render(_: any, html: string) { + if (this.el && this.prev != html) { + this.el.innerHTML = html; + this.prev = html; + } + return ["div"]; + }, + release() { + this.el.innerHTML = ""; + delete this.prev; + delete this.el; + }, + }; /** * Root component. */ const app = () => { - // instantiate HTML wrapper - const wrapper = innerHtmlWrapper(); - return () => [ - wrapper, - new Date().getSeconds() & 1 - ? `
Time now:
` - : `
${new Date().toLocaleTimeString()}
`, - ]; + // instantiate HTML wrapper + const wrapper = innerHtmlWrapper(); + return () => [ + wrapper, + new Date().getSeconds() & 1 + ? `
Time now:
` + : `
${new Date().toLocaleTimeString()}
`, + ]; }; // kick off diff --git a/examples/hdom-inner-html/tsconfig.json b/examples/hdom-inner-html/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-inner-html/tsconfig.json +++ b/examples/hdom-inner-html/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-local-render/package.json b/examples/hdom-local-render/package.json index 98ec790206..880b544b61 100644 --- a/examples/hdom-local-render/package.json +++ b/examples/hdom-local-render/package.json @@ -1,38 +1,38 @@ { - "name": "@example/hdom-local-render", - "private": true, - "description": "Isolated, component-local DOM updates", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/memoize": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "thi.ng": { - "readme": [ - "hdom", - "memoize", - "transducers", - "transducers-hdom" - ] - } + "name": "@example/hdom-local-render", + "private": true, + "description": "Isolated, component-local DOM updates", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/memoize": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "thi.ng": { + "readme": [ + "hdom", + "memoize", + "transducers", + "transducers-hdom" + ] + } } diff --git a/examples/hdom-local-render/src/index.ts b/examples/hdom-local-render/src/index.ts index 6bc1cd4ff8..10ef5b46b2 100644 --- a/examples/hdom-local-render/src/index.ts +++ b/examples/hdom-local-render/src/index.ts @@ -15,75 +15,75 @@ const COLORS = cycle(["red", "blue", "green", "orange", "light-blue"]); * re-render themselves on demand, without requiring a full DOM update. */ abstract class LocalReRenderable { - el?: HTMLElement; + el?: HTMLElement; - /** - * Init lifecycle method. In the base case, this is only used to - * cache this component's actual root DOM element. - * - * @param el - - */ - init(el: HTMLElement) { - this.el = el; - } + /** + * Init lifecycle method. In the base case, this is only used to + * cache this component's actual root DOM element. + * + * @param el - + */ + init(el: HTMLElement) { + this.el = el; + } - abstract render(_: any, ...args: any[]): any[]; + abstract render(_: any, ...args: any[]): any[]; - /** - * Call this function with any args usually given to your component - * when a re-render of this component's sub-tree is required. The - * component should set the `__diff` hdom behavior control attribute - * of the root element to false, to avoid potential clashes with - * future diffs during a full DOM update. - * - * @param args - component args - */ - localRender(...args: any[]) { - const el = this.el!; - const children = el.parentElement!.children; - for (let i = children.length; i-- > 0; ) { - if (children[i] === el) { - replaceChild( - {}, - DEFAULT_IMPL, - this.el!.parentElement!, - i, - normalizeTree({}, [this, ...args]), - true - ); - return; - } - } - } + /** + * Call this function with any args usually given to your component + * when a re-render of this component's sub-tree is required. The + * component should set the `__diff` hdom behavior control attribute + * of the root element to false, to avoid potential clashes with + * future diffs during a full DOM update. + * + * @param args - component args + */ + localRender(...args: any[]) { + const el = this.el!; + const children = el.parentElement!.children; + for (let i = children.length; i-- > 0; ) { + if (children[i] === el) { + replaceChild( + {}, + DEFAULT_IMPL, + this.el!.parentElement!, + i, + normalizeTree({}, [this, ...args]), + true + ); + return; + } + } + } } // Dummy test component with local on-demand re-render class Foo extends LocalReRenderable { - time!: number; - col!: string; + time!: number; + col!: string; - constructor(public id: string) { - super(); - this.col = COLORS.next().value; - } + constructor(public id: string) { + super(); + this.col = COLORS.next().value; + } - render(_: any, time: number) { - this.time = time; - return [ - `div.dib.w4.br2.pa2.mr2.tc.nosel.bg-${this.col}`, - { - // important (see comment further above) - __diff: false, - // pick new color and immediately re-render this - // component's subtree - onclick: () => { - this.col = COLORS.next().value; - this.localRender(this.time); - }, - }, - `${this.id}: ${time}`, - ]; - } + render(_: any, time: number) { + this.time = time; + return [ + `div.dib.w4.br2.pa2.mr2.tc.nosel.bg-${this.col}`, + { + // important (see comment further above) + __diff: false, + // pick new color and immediately re-render this + // component's subtree + onclick: () => { + this.col = COLORS.next().value; + this.localRender(this.time); + }, + }, + `${this.id}: ${time}`, + ]; + } } // Memoized component factory. This is needed to preserve local state @@ -101,16 +101,16 @@ const foo = memoize1((id: string) => new Foo(id)); // trigger full DOM updates every 2 secs fromInterval(2000).transform( - map((time) => [ - "div", - {}, - // use memoized components (lazy invocation): the `foo(id)` calls - // merely return the memoized component (or, in the first frame, - // actually create the components, and which are then cached, i.e. - // memoized...) - [foo("a"), time], - [foo("b"), time], - [foo("c"), time], - ]), - updateDOM() + map((time) => [ + "div", + {}, + // use memoized components (lazy invocation): the `foo(id)` calls + // merely return the memoized component (or, in the first frame, + // actually create the components, and which are then cached, i.e. + // memoized...) + [foo("a"), time], + [foo("b"), time], + [foo("c"), time], + ]), + updateDOM() ); diff --git a/examples/hdom-local-render/tsconfig.json b/examples/hdom-local-render/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-local-render/tsconfig.json +++ b/examples/hdom-local-render/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-localstate/package.json b/examples/hdom-localstate/package.json index 051c117831..4e411106ce 100644 --- a/examples/hdom-localstate/package.json +++ b/examples/hdom-localstate/package.json @@ -1,33 +1,33 @@ { - "name": "@example/hdom-localstate", - "private": true, - "description": "UI component w/ local state stored in hdom context", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/paths": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "thi.ng": { - "readme": [ - "hdom", - "paths" - ] - } + "name": "@example/hdom-localstate", + "private": true, + "description": "UI component w/ local state stored in hdom context", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/paths": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "thi.ng": { + "readme": [ + "hdom", + "paths" + ] + } } diff --git a/examples/hdom-localstate/src/index.ts b/examples/hdom-localstate/src/index.ts index 575140dc91..90330e7ff1 100644 --- a/examples/hdom-localstate/src/index.ts +++ b/examples/hdom-localstate/src/index.ts @@ -3,47 +3,47 @@ import { getInUnsafe } from "@thi.ng/paths/get-in"; import { setInUnsafe } from "@thi.ng/paths/set-in"; interface ButtonAttribs { - // unique button id / local state path - id: string; - onclick?: EventListener; + // unique button id / local state path + id: string; + onclick?: EventListener; } // stateless button component which stores its local state under an // unique key in the global hdom user context. const button = ( - ctx: any, - attribs: ButtonAttribs, - label: string, - tooltip: string + ctx: any, + attribs: ButtonAttribs, + label: string, + tooltip: string ) => { - // attempt to read local state - let local = getInUnsafe(ctx.__local, attribs.id); - // create if not yet present - if (!local) { - ctx.__local = setInUnsafe( - ctx.__local, - attribs.id, - (local = { tooltip: false }) - ); - } - return [ - "a.dib.w4.pa2.mr3.white.bg-blue.hover-black.hover-bg-yellow.bg-animate", - { - onclick: attribs.onclick, - onmouseenter: () => (local.tooltip = true), - onmouseleave: () => (local.tooltip = false), - }, - local.tooltip ? tooltip : label, - ]; + // attempt to read local state + let local = getInUnsafe(ctx.__local, attribs.id); + // create if not yet present + if (!local) { + ctx.__local = setInUnsafe( + ctx.__local, + attribs.id, + (local = { tooltip: false }) + ); + } + return [ + "a.dib.w4.pa2.mr3.white.bg-blue.hover-black.hover-bg-yellow.bg-animate", + { + onclick: attribs.onclick, + onmouseenter: () => (local.tooltip = true), + onmouseleave: () => (local.tooltip = false), + }, + local.tooltip ? tooltip : label, + ]; }; const APP = [ - "div.ma3.sans-serif", - // the ID attrib defines the local state path for these buttons - // it will be used to lookup state in the hdom user context object - // passed to each component function - [button, { id: "button.help" }, "Help", "Whazzup?!"], - [button, { id: "button.logout" }, "Logout", "See ya!"], + "div.ma3.sans-serif", + // the ID attrib defines the local state path for these buttons + // it will be used to lookup state in the hdom user context object + // passed to each component function + [button, { id: "button.help" }, "Help", "Whazzup?!"], + [button, { id: "button.logout" }, "Logout", "See ya!"], ]; // start app and define context object skeleton diff --git a/examples/hdom-localstate/tsconfig.json b/examples/hdom-localstate/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-localstate/tsconfig.json +++ b/examples/hdom-localstate/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-skip-nested/package.json b/examples/hdom-skip-nested/package.json index 10cd1afd08..a7ad002f59 100644 --- a/examples/hdom-skip-nested/package.json +++ b/examples/hdom-skip-nested/package.json @@ -1,29 +1,29 @@ { - "name": "@example/hdom-skip-nested", - "private": true, - "description": "Skipping UI updates for nested component(s)", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "thi.ng": { - "readme": true - } + "name": "@example/hdom-skip-nested", + "private": true, + "description": "Skipping UI updates for nested component(s)", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "thi.ng": { + "readme": true + } } diff --git a/examples/hdom-skip-nested/src/index.ts b/examples/hdom-skip-nested/src/index.ts index 39b4500d18..22dfd77023 100644 --- a/examples/hdom-skip-nested/src/index.ts +++ b/examples/hdom-skip-nested/src/index.ts @@ -2,10 +2,10 @@ import type { ILifecycle } from "@thi.ng/hdom"; import { start } from "@thi.ng/hdom/start"; interface Counter extends ILifecycle { - id: number; - enabled: boolean; - previd: number; - prevenabled: boolean; + id: number; + enabled: boolean; + previd: number; + prevenabled: boolean; } /** * Button counter HOF component with __skip support @@ -14,37 +14,37 @@ interface Counter extends ILifecycle { * 500ms, and only then increments. */ const button = () => - { - init(_, __, id) { - this.enabled = true; - this.id = id; - }, - render(_, __) { - const body = [ - "button.dib.w3.pa2.bn", - { - __skip: - this.previd === this.id && - this.prevenabled === this.enabled, - disabled: this.enabled === false, - class: this.enabled - ? "bg-black white" - : "bg-moon-gray gray", - onclick: () => { - this.enabled = !this.enabled; - setTimeout(() => { - this.id++; - this.enabled = true; - }, 1000); - }, - }, - this.id, - ]; - this.previd = this.id; - this.prevenabled = this.enabled; - return body; - }, - }; + { + init(_, __, id) { + this.enabled = true; + this.id = id; + }, + render(_, __) { + const body = [ + "button.dib.w3.pa2.bn", + { + __skip: + this.previd === this.id && + this.prevenabled === this.enabled, + disabled: this.enabled === false, + class: this.enabled + ? "bg-black white" + : "bg-moon-gray gray", + onclick: () => { + this.enabled = !this.enabled; + setTimeout(() => { + this.id++; + this.enabled = true; + }, 1000); + }, + }, + this.id, + ]; + this.previd = this.id; + this.prevenabled = this.enabled; + return body; + }, + }; /** * Button wrapper HOF component (also with __skip support). @@ -54,35 +54,35 @@ const button = () => * counter still remains clickable). */ const wrapper = () => { - let skip = false; - let nextSkip = false; - const bt = button(); - return (_: any, id: number) => - skip - ? ["div", { __skip: true }] - : [ - `div.pv2.${nextSkip ? "bg-washed-red" : "bg-light-green"}`, - [ - "a.dib.w4.pa2.pointer", - { - onclick: () => { - nextSkip = !nextSkip; - requestAnimationFrame(() => (skip = nextSkip)); - }, - }, - nextSkip ? "unskip!" : "skip!", - ], - [bt, id], - nextSkip - ? ["div.dib.mh3.f7.red", "(counter updates not shown)"] - : null, - ]; + let skip = false; + let nextSkip = false; + const bt = button(); + return (_: any, id: number) => + skip + ? ["div", { __skip: true }] + : [ + `div.pv2.${nextSkip ? "bg-washed-red" : "bg-light-green"}`, + [ + "a.dib.w4.pa2.pointer", + { + onclick: () => { + nextSkip = !nextSkip; + requestAnimationFrame(() => (skip = nextSkip)); + }, + }, + nextSkip ? "unskip!" : "skip!", + ], + [bt, id], + nextSkip + ? ["div.dib.mh3.f7.red", "(counter updates not shown)"] + : null, + ]; }; const app = () => { - const bt1 = wrapper(); - const bt2 = wrapper(); - return ["div.sans-serif", [bt1, 0], [bt2, 100]]; + const bt1 = wrapper(); + const bt2 = wrapper(); + return ["div.sans-serif", [bt1, 0], [bt2, 100]]; }; start(app()); diff --git a/examples/hdom-skip-nested/tsconfig.json b/examples/hdom-skip-nested/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-skip-nested/tsconfig.json +++ b/examples/hdom-skip-nested/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-skip/package.json b/examples/hdom-skip/package.json index 96bb4a172b..9135de5fcb 100644 --- a/examples/hdom-skip/package.json +++ b/examples/hdom-skip/package.json @@ -1,29 +1,29 @@ { - "name": "@example/hdom-skip", - "private": true, - "description": "Skipping UI updates for selected component(s)", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "thi.ng": { - "readme": true - } + "name": "@example/hdom-skip", + "private": true, + "description": "Skipping UI updates for selected component(s)", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "thi.ng": { + "readme": true + } } diff --git a/examples/hdom-skip/src/index.ts b/examples/hdom-skip/src/index.ts index 0d0b6ab31c..c67ac6a1e8 100644 --- a/examples/hdom-skip/src/index.ts +++ b/examples/hdom-skip/src/index.ts @@ -1,56 +1,56 @@ import { start } from "@thi.ng/hdom/start"; const timer = (period: number, name = `${period}ms`) => { - return { - // life cycle init method - // called when the component is being added to the real DOM - init() { - this.inited = true; - this.val = 0; - }, - render() { - // Key part of this example: + return { + // life cycle init method + // called when the component is being added to the real DOM + init() { + this.inited = true; + this.val = 0; + }, + render() { + // Key part of this example: - // Here we check the current time stamp for timer - // `period` crossings and only return an actual new - // tree/content iff the time stamp is within 16ms of the - // period (i.e. in the 1 frame following the timer - // period). In all other cases, we return some dummy - // content with the root element using the hdom `__skip` - // control attribute to skip diffing of this branch and - // not apply the given tree/branch. + // Here we check the current time stamp for timer + // `period` crossings and only return an actual new + // tree/content iff the time stamp is within 16ms of the + // period (i.e. in the 1 frame following the timer + // period). In all other cases, we return some dummy + // content with the root element using the hdom `__skip` + // control attribute to skip diffing of this branch and + // not apply the given tree/branch. - // IMPORTANT: the element type of the skipped branch MUST - // match the type of the real content (e.g. here `div`) - const t = Date.now(); - return !this.inited || t % period < 16 - ? ["div.sans-serif", `${name} @ ${this.val++ || 0}`] - : // dummy content (could be an empty div) - ["div", { __skip: true }, "I should be never seen"]; - }, - }; + // IMPORTANT: the element type of the skipped branch MUST + // match the type of the real content (e.g. here `div`) + const t = Date.now(); + return !this.inited || t % period < 16 + ? ["div.sans-serif", `${name} @ ${this.val++ || 0}`] + : // dummy content (could be an empty div) + ["div", { __skip: true }, "I should be never seen"]; + }, + }; }; // root component object w/ life cycle methods const app = { - init() { - // create timer component instances - this.timers = [[timer(1000)], [timer(500)], [timer(250)]]; - }, - render() { - return [ - "div.ma3.sans-serif", - ["h1", "Selective component updates"], - this.timers, - [ - "a.db.mt3.link", - { - href: "https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-skip", - }, - "Source code", - ], - ]; - }, + init() { + // create timer component instances + this.timers = [[timer(1000)], [timer(500)], [timer(250)]]; + }, + render() { + return [ + "div.ma3.sans-serif", + ["h1", "Selective component updates"], + this.timers, + [ + "a.db.mt3.link", + { + href: "https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-skip", + }, + "Source code", + ], + ]; + }, }; // kick off diff --git a/examples/hdom-skip/tsconfig.json b/examples/hdom-skip/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-skip/tsconfig.json +++ b/examples/hdom-skip/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-theme-adr-0003/package.json b/examples/hdom-theme-adr-0003/package.json index c2dab11613..75090a2955 100644 --- a/examples/hdom-theme-adr-0003/package.json +++ b/examples/hdom-theme-adr-0003/package.json @@ -1,34 +1,34 @@ { - "name": "@example/hdom-theme", - "private": true, - "description": "Example for themed components proposal", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/paths": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "thi.ng": { - "readme": [ - "hdom", - "paths" - ] - } + "name": "@example/hdom-theme", + "private": true, + "description": "Example for themed components proposal", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/paths": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "thi.ng": { + "readme": [ + "hdom", + "paths" + ] + } } diff --git a/examples/hdom-theme-adr-0003/src/index.ts b/examples/hdom-theme-adr-0003/src/index.ts index 6aec38b0a5..7238310f75 100644 --- a/examples/hdom-theme-adr-0003/src/index.ts +++ b/examples/hdom-theme-adr-0003/src/index.ts @@ -3,191 +3,191 @@ import { start } from "@thi.ng/hdom/start"; import { getInUnsafe } from "@thi.ng/paths/get-in"; interface ButtonBehavior { - /** - * Element name to use for enabled buttons. - * Default: "a" - */ - tag: string; - /** - * Element name to use for disabled buttons. - * Default: "span" - */ - tagDisabled: string; - /** - * Default attribs, always injected for active button states - * and overridable at runtime. - * Default: `{ href: "#", role: "button" }` - */ - attribs: IObjectOf; + /** + * Element name to use for enabled buttons. + * Default: "a" + */ + tag: string; + /** + * Element name to use for disabled buttons. + * Default: "span" + */ + tagDisabled: string; + /** + * Default attribs, always injected for active button states + * and overridable at runtime. + * Default: `{ href: "#", role: "button" }` + */ + attribs: IObjectOf; } interface ButtonArgs { - /** - * Click event handler to be wrapped with preventDefault() call - */ - onclick: EventListener; - /** - * Disabled flag. Used to determine themed version. - */ - disabled: boolean; - /** - * Selected flag. Used to determine themed version. - */ - selected: boolean; - /** - * Link target. - */ - href: string; + /** + * Click event handler to be wrapped with preventDefault() call + */ + onclick: EventListener; + /** + * Disabled flag. Used to determine themed version. + */ + disabled: boolean; + /** + * Selected flag. Used to determine themed version. + */ + selected: boolean; + /** + * Link target. + */ + href: string; } const button = (themeCtxPath: Path, behavior?: Partial) => { - // init with defaults - behavior = { - tag: "a", - tagDisabled: "span", - ...behavior, - }; - behavior.attribs = { href: "#", role: "button", ...behavior.attribs }; - // return component function as closure - return (ctx: any, args: Partial, ...body: any[]) => { - // lookup component theme config in context - const theme = getInUnsafe(ctx, themeCtxPath); - if (args.disabled) { - return [ - behavior!.tagDisabled, - { - ...behavior!.attribs, - ...theme.disabled, - ...args, - }, - ...body, - ]; - } else { - const attribs = { - ...behavior!.attribs, - ...theme[args.selected ? "selected" : "default"], - ...args, - }; - if ( - args && - args.onclick && - (args.href == null || args.href === "#") - ) { - attribs.onclick = (e: Event) => ( - e.preventDefault(), args.onclick!(e) - ); - } - return [behavior!.tag, attribs, ...body]; - } - }; + // init with defaults + behavior = { + tag: "a", + tagDisabled: "span", + ...behavior, + }; + behavior.attribs = { href: "#", role: "button", ...behavior.attribs }; + // return component function as closure + return (ctx: any, args: Partial, ...body: any[]) => { + // lookup component theme config in context + const theme = getInUnsafe(ctx, themeCtxPath); + if (args.disabled) { + return [ + behavior!.tagDisabled, + { + ...behavior!.attribs, + ...theme.disabled, + ...args, + }, + ...body, + ]; + } else { + const attribs = { + ...behavior!.attribs, + ...theme[args.selected ? "selected" : "default"], + ...args, + }; + if ( + args && + args.onclick && + (args.href == null || args.href === "#") + ) { + attribs.onclick = (e: Event) => ( + e.preventDefault(), args.onclick!(e) + ); + } + return [behavior!.tag, attribs, ...body]; + } + }; }; const link = (ctx: any, href: string, body: any) => [ - "a", - { ...ctx.theme.link, href }, - body, + "a", + { ...ctx.theme.link, href }, + body, ]; const lightTheme = { - id: "light", - body: { - class: "vh-100 bg-white dark-gray pa3 sans-serif", - }, - link: { - class: "link dim b black", - }, - button: { - default: { - class: "dib link mr2 ph3 pv2 bg-lightest-blue blue hover-bg-blue hover-white bg-animate br-pill", - }, - selected: { - class: "dib link mr2 ph3 pv2 bg-gold washed-yellow hover-bg-orange hover-gold bg-animate br-pill", - }, - disabled: { - class: "dib mr2 ph3 pv2 bg-moon-gray gray br-pill", - }, - }, + id: "light", + body: { + class: "vh-100 bg-white dark-gray pa3 sans-serif", + }, + link: { + class: "link dim b black", + }, + button: { + default: { + class: "dib link mr2 ph3 pv2 bg-lightest-blue blue hover-bg-blue hover-white bg-animate br-pill", + }, + selected: { + class: "dib link mr2 ph3 pv2 bg-gold washed-yellow hover-bg-orange hover-gold bg-animate br-pill", + }, + disabled: { + class: "dib mr2 ph3 pv2 bg-moon-gray gray br-pill", + }, + }, }; const darkTheme = { - id: "dark", - body: { - class: "vh-100 bg-black moon-gray pa3 sans-serif", - }, - link: { - class: "link dim b light-silver", - }, - button: { - default: { - class: "dib link mr2 ph3 pv2 blue hover-lightest-blue hover-b--current br3 ba b--blue", - }, - selected: { - class: "dib link mr2 ph3 pv2 red hover-gold hover-b--current br3 ba b--red", - }, - disabled: { - class: "dib mr2 ph3 pv2 mid-gray br3 ba b--mid-gray", - }, - }, + id: "dark", + body: { + class: "vh-100 bg-black moon-gray pa3 sans-serif", + }, + link: { + class: "link dim b light-silver", + }, + button: { + default: { + class: "dib link mr2 ph3 pv2 blue hover-lightest-blue hover-b--current br3 ba b--blue", + }, + selected: { + class: "dib link mr2 ph3 pv2 red hover-gold hover-b--current br3 ba b--red", + }, + disabled: { + class: "dib mr2 ph3 pv2 mid-gray br3 ba b--mid-gray", + }, + }, }; // source: https://fontawesome.com const icon = [ - "svg", - { - class: "mr1", - width: "1rem", - viewBox: "0 0 576 512", - fill: "currentcolor", - }, - [ - "path", - { - d: "M576 24v127.984c0 21.461-25.96 31.98-40.971 16.971l-35.707-35.709-243.523 243.523c-9.373 9.373-24.568 9.373-33.941 0l-22.627-22.627c-9.373-9.373-9.373-24.569 0-33.941L442.756 76.676l-35.703-35.705C391.982 25.9 402.656 0 424.024 0H552c13.255 0 24 10.745 24 24zM407.029 270.794l-16 16A23.999 23.999 0 0 0 384 303.765V448H64V128h264a24.003 24.003 0 0 0 16.97-7.029l16-16C376.089 89.851 365.381 64 344 64H48C21.49 64 0 85.49 0 112v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V287.764c0-21.382-25.852-32.09-40.971-16.97z", - }, - ], + "svg", + { + class: "mr1", + width: "1rem", + viewBox: "0 0 576 512", + fill: "currentcolor", + }, + [ + "path", + { + d: "M576 24v127.984c0 21.461-25.96 31.98-40.971 16.971l-35.707-35.709-243.523 243.523c-9.373 9.373-24.568 9.373-33.941 0l-22.627-22.627c-9.373-9.373-9.373-24.569 0-33.941L442.756 76.676l-35.703-35.705C391.982 25.9 402.656 0 424.024 0H552c13.255 0 24 10.745 24 24zM407.029 270.794l-16 16A23.999 23.999 0 0 0 384 303.765V448H64V128h264a24.003 24.003 0 0 0 16.97-7.029l16-16C376.089 89.851 365.381 64 344 64H48C21.49 64 0 85.49 0 112v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V287.764c0-21.382-25.852-32.09-40.971-16.97z", + }, + ], ]; const ctx = { theme: darkTheme }; const toggleTheme = () => { - ctx.theme = ctx.theme === lightTheme ? darkTheme : lightTheme; + ctx.theme = ctx.theme === lightTheme ? darkTheme : lightTheme; }; const bt = button("theme.button"); const btFixed = button("theme.button", { - attribs: { style: { width: "8rem" } }, + attribs: { style: { width: "8rem" } }, }); const app = (ctx: any) => [ - "div", - ctx.theme.body, - "Current theme: ", - ctx.theme.id, - [ - "p", - [bt, { onclick: toggleTheme }, "Toggle"], - [bt, { href: "https://github.com/thi-ng/umbrella" }, icon, "External"], - [btFixed, { onclick: () => alert("hi"), selected: true }, "Selected"], - [btFixed, { disabled: true }, "Disabled"], - ], - [ - "p", - "Please see ", - [ - link, - "https://github.com/thi-ng/umbrella/blob/develop/packages/hdom-components/adr/0003-component-configuration-via-context.md", - "ADR-0003", - ], - " for details of this approach.", - ], - [ - "p", - [ - link, - "https://github.com/thi-ng/umbrella/blob/develop/examples/hdom-theme-adr-0003", - "Source", - ], - ], + "div", + ctx.theme.body, + "Current theme: ", + ctx.theme.id, + [ + "p", + [bt, { onclick: toggleTheme }, "Toggle"], + [bt, { href: "https://github.com/thi-ng/umbrella" }, icon, "External"], + [btFixed, { onclick: () => alert("hi"), selected: true }, "Selected"], + [btFixed, { disabled: true }, "Disabled"], + ], + [ + "p", + "Please see ", + [ + link, + "https://github.com/thi-ng/umbrella/blob/develop/packages/hdom-components/adr/0003-component-configuration-via-context.md", + "ADR-0003", + ], + " for details of this approach.", + ], + [ + "p", + [ + link, + "https://github.com/thi-ng/umbrella/blob/develop/examples/hdom-theme-adr-0003", + "Source", + ], + ], ]; start(app, { ctx }); diff --git a/examples/hdom-theme-adr-0003/tsconfig.json b/examples/hdom-theme-adr-0003/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-theme-adr-0003/tsconfig.json +++ b/examples/hdom-theme-adr-0003/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-toggle/package.json b/examples/hdom-toggle/package.json index bc5b269cf0..c9188733ba 100644 --- a/examples/hdom-toggle/package.json +++ b/examples/hdom-toggle/package.json @@ -1,31 +1,31 @@ { - "name": "@example/hdom-toggle", - "private": true, - "description": "Customizable slide toggle component demo", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true, - "screenshot": "examples/hdom-toggle.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hdom-toggle", + "private": true, + "description": "Customizable slide toggle component demo", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true, + "screenshot": "examples/hdom-toggle.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hdom-toggle/src/index.ts b/examples/hdom-toggle/src/index.ts index 955bfb99c0..e24b06c9b7 100644 --- a/examples/hdom-toggle/src/index.ts +++ b/examples/hdom-toggle/src/index.ts @@ -1,8 +1,8 @@ import { - slideToggleDot, - slideToggleRect, - type ToggleDotOpts, - type ToggleRectOpts + slideToggleDot, + slideToggleRect, + type ToggleDotOpts, + type ToggleRectOpts, } from "@thi.ng/hdom-components/toggle"; import { start } from "@thi.ng/hdom/start"; @@ -11,73 +11,73 @@ const state = [true, false, true, false, true]; const toggleState = (i: number) => (state[i] = !state[i]); const dotOpts: Partial = { - r: 8, - pad: 2, - margin: 2, + r: 8, + pad: 2, + margin: 2, }; const rectOpts: Partial = { - w: 16, - h: 8, - pad: 2, - margin: 2, + w: 16, + h: 8, + pad: 2, + margin: 2, }; const strokeOpts = { - bgOn: { stroke: "#00f", "stroke-width": 2, fill: "none" }, - bgOff: { stroke: "#99f", "stroke-width": 2, fill: "none" }, - fgOn: { fill: "#00f" }, - fgOff: { fill: "#99f" }, + bgOn: { stroke: "#00f", "stroke-width": 2, fill: "none" }, + bgOff: { stroke: "#99f", "stroke-width": 2, fill: "none" }, + fgOn: { fill: "#00f" }, + fgOff: { fill: "#99f" }, }; const toggleH = slideToggleDot(dotOpts); const toggleHStroke = slideToggleDot({ ...dotOpts, ...strokeOpts }); const toggleV = slideToggleDot({ ...dotOpts, vertical: true }); const toggleVStroke = slideToggleDot({ - ...dotOpts, - ...strokeOpts, - fgOn: { stroke: "#00f", fill: "#00f" }, - fgOff: { stroke: "#99f", fill: "none" }, - vertical: true, - pad: 3, - r: 7, + ...dotOpts, + ...strokeOpts, + fgOn: { stroke: "#00f", fill: "#00f" }, + fgOff: { stroke: "#99f", fill: "none" }, + vertical: true, + pad: 3, + r: 7, }); const toggleHSq = slideToggleRect({ ...rectOpts }); const toggleHSqStroke = slideToggleRect({ ...rectOpts, ...strokeOpts }); const toggleVSq = slideToggleRect({ ...rectOpts, vertical: true }); const toggleVSqStroke = slideToggleRect({ - ...rectOpts, - ...strokeOpts, - w: 8, - h: 20, - vertical: true, + ...rectOpts, + ...strokeOpts, + w: 8, + h: 20, + vertical: true, }); const toggleGroup = (_: any, toggle: any) => [ - "div.mb3", - ...state.map((x, i) => [ - "div.dib", - [ - toggle, - { - class: "pointer mr1", - onclick: () => toggleState(i), - }, - x, - ], - ["div.tc", i], - ]), + "div.mb3", + ...state.map((x, i) => [ + "div.dib", + [ + toggle, + { + class: "pointer mr1", + onclick: () => toggleState(i), + }, + x, + ], + ["div.tc", i], + ]), ]; start(() => [ - "div", - [toggleGroup, toggleH], - [toggleGroup, toggleHStroke], - [toggleGroup, toggleV], - [toggleGroup, toggleVStroke], - [toggleGroup, toggleHSq], - [toggleGroup, toggleHSqStroke], - [toggleGroup, toggleVSq], - [toggleGroup, toggleVSqStroke], + "div", + [toggleGroup, toggleH], + [toggleGroup, toggleHStroke], + [toggleGroup, toggleV], + [toggleGroup, toggleVStroke], + [toggleGroup, toggleHSq], + [toggleGroup, toggleHSqStroke], + [toggleGroup, toggleVSq], + [toggleGroup, toggleVSqStroke], ]); diff --git a/examples/hdom-toggle/tsconfig.json b/examples/hdom-toggle/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-toggle/tsconfig.json +++ b/examples/hdom-toggle/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hdom-vscroller/package.json b/examples/hdom-vscroller/package.json index c0ea7a8512..b7543d9f66 100644 --- a/examples/hdom-vscroller/package.json +++ b/examples/hdom-vscroller/package.json @@ -1,28 +1,28 @@ { - "name": "@example/hdom-vscroller", - "private": true, - "description": "Virtual scroller component for large tables / lists", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hdom-vscroller", + "private": true, + "description": "Virtual scroller component for large tables / lists", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hdom-vscroller/src/index.ts b/examples/hdom-vscroller/src/index.ts index 50644b474e..83575ddb25 100644 --- a/examples/hdom-vscroller/src/index.ts +++ b/examples/hdom-vscroller/src/index.ts @@ -22,80 +22,80 @@ let pkgOffset = 0; let pkgTop = 0; const filterCommit = (q: string) => (x: Commit) => - x[2].toLowerCase().indexOf(q) >= 0 || x[1].indexOf(q) >= 0; + x[2].toLowerCase().indexOf(q) >= 0 || x[1].indexOf(q) >= 0; const setQuery = (e: Event) => { - try { - query = (e.target).value; - filtered = COMMITS.filter(filterCommit(query)); - commitsOffset = 0; - commitsTop = 0; - } catch (e) {} + try { + query = (e.target).value; + filtered = COMMITS.filter(filterCommit(query)); + commitsOffset = 0; + commitsTop = 0; + } catch (e) {} }; const queryFilter = (_: any, query: EventListener, items: any[]) => [ - "div.pv2.ph3.bg-black.white", - "Filter: ", - ["input", { type: "text", oninput: setQuery, value: query }], - ` (${items.length})`, + "div.pv2.ph3.bg-black.white", + "Filter: ", + ["input", { type: "text", oninput: setQuery, value: query }], + ` (${items.length})`, ]; const repoLink = (_: any, sha: string, body: any) => [ - "a.link.blue", - { href: `${REPO_BASE}commit/${sha}` }, - body, + "a.link.blue", + { href: `${REPO_BASE}commit/${sha}` }, + body, ]; const packageLink = (_: any, name: any) => [ - "a.link.blue", - { href: `${REPO_BASE}tree/develop/packages/${name.substring(8)}` }, - name, + "a.link.blue", + { href: `${REPO_BASE}tree/develop/packages/${name.substring(8)}` }, + name, ]; const commit = (i: number, [sha, date, msg]: Commit) => [ - `div.f7.pv2.bg-${i & 1 ? "light-gray" : "transparent"}`, - ["div.dib.w-30.w-20-m.w-10-l.ph3", date], - [ - "div.dib.w-70.w-80-m.w-90-l.ph3.overflow-x-hidden.nowrap", - [repoLink, sha, msg], - ], + `div.f7.pv2.bg-${i & 1 ? "light-gray" : "transparent"}`, + ["div.dib.w-30.w-20-m.w-10-l.ph3", date], + [ + "div.dib.w-70.w-80-m.w-90-l.ph3.overflow-x-hidden.nowrap", + [repoLink, sha, msg], + ], ]; const pkgSummary = ([name, desc]: Package) => [ - "div.flex.items-center.lh-copy.pa3.ph0-l.bb.b--black-10", - { style: { height: "96px" } }, - ["img.w2.h2", { src: LOGO }], - ["div.pl3.flex-auto.f7", ["h3.ma0", [packageLink, name]], desc], + "div.flex.items-center.lh-copy.pa3.ph0-l.bb.b--black-10", + { style: { height: "96px" } }, + ["img.w2.h2", { src: LOGO }], + ["div.pl3.flex-auto.f7", ["h3.ma0", [packageLink, name]], desc], ]; const app = () => [ - "div.sans-serif.pa2", - [queryFilter, query, filtered], - virtualScroller({ - onscroll: (_top, _offset) => { - commitsTop = _top; - commitsOffset = _offset; - }, - start: commitsOffset, - top: commitsTop, - numVisible: 10, - numItems: filtered.length, - itemHeight: 31, - items: mapIndexed(commit, 0, filtered), - }), - ["h3.ph2", "Packages"], - virtualScroller({ - onscroll: (_top, _offset) => { - pkgTop = _top; - pkgOffset = _offset; - }, - start: pkgOffset, - top: pkgTop, - numVisible: 2, - numItems: PACKAGES.length, - itemHeight: 96, - items: map(pkgSummary, PACKAGES), - }), + "div.sans-serif.pa2", + [queryFilter, query, filtered], + virtualScroller({ + onscroll: (_top, _offset) => { + commitsTop = _top; + commitsOffset = _offset; + }, + start: commitsOffset, + top: commitsTop, + numVisible: 10, + numItems: filtered.length, + itemHeight: 31, + items: mapIndexed(commit, 0, filtered), + }), + ["h3.ph2", "Packages"], + virtualScroller({ + onscroll: (_top, _offset) => { + pkgTop = _top; + pkgOffset = _offset; + }, + start: pkgOffset, + top: pkgTop, + numVisible: 2, + numItems: PACKAGES.length, + itemHeight: 96, + items: map(pkgSummary, PACKAGES), + }), ]; start(app); diff --git a/examples/hdom-vscroller/src/vscroller.ts b/examples/hdom-vscroller/src/vscroller.ts index 5dac8f2e13..e3605ec2fd 100644 --- a/examples/hdom-vscroller/src/vscroller.ts +++ b/examples/hdom-vscroller/src/vscroller.ts @@ -5,71 +5,71 @@ import { iterator } from "@thi.ng/transducers/iterator"; import { take } from "@thi.ng/transducers/take"; interface VScrollOpts { - /** - * Current index of 1st visible item - */ - start: number; - /** - * Current scroll offset - */ - top: number; - /** - * - */ - onscroll: Fn2; - /** - * Max number of items visible - * Container height will be computed as: `numVisible * itemHeight` - */ - numVisible: number; - /** - * Total number of items. - */ - numItems: number; - /** - * Height (in pixels) of single item. - */ - itemHeight: number; - /** - * Item components. - */ - items: Iterable; + /** + * Current index of 1st visible item + */ + start: number; + /** + * Current scroll offset + */ + top: number; + /** + * + */ + onscroll: Fn2; + /** + * Max number of items visible + * Container height will be computed as: `numVisible * itemHeight` + */ + numVisible: number; + /** + * Total number of items. + */ + numItems: number; + /** + * Height (in pixels) of single item. + */ + itemHeight: number; + /** + * Item components. + */ + items: Iterable; } export const virtualScroller = ({ - start, - top, - onscroll, - numVisible, - numItems, - itemHeight, - items, + start, + top, + onscroll, + numVisible, + numItems, + itemHeight, + items, }: VScrollOpts) => [ - "div.overflow-y-scroll", - { - onscroll: (e: Event) => { - const top = (e.target).scrollTop; - const offset = Math.min( - Math.floor(top / itemHeight), - numItems - numVisible - ); - onscroll(top, offset); - }, - scrollTop: top, - style: { - height: `${numVisible * itemHeight}px`, - }, - }, - [ - "div", - { - style: { - height: `${numItems * itemHeight}px`, - "padding-top": `${ - Math.min(start, numItems - numVisible) * itemHeight - }px`, - }, - }, - iterator(comp(drop(start), take(numVisible + 1)), items), - ], + "div.overflow-y-scroll", + { + onscroll: (e: Event) => { + const top = (e.target).scrollTop; + const offset = Math.min( + Math.floor(top / itemHeight), + numItems - numVisible + ); + onscroll(top, offset); + }, + scrollTop: top, + style: { + height: `${numVisible * itemHeight}px`, + }, + }, + [ + "div", + { + style: { + height: `${numItems * itemHeight}px`, + "padding-top": `${ + Math.min(start, numItems - numVisible) * itemHeight + }px`, + }, + }, + iterator(comp(drop(start), take(numVisible + 1)), items), + ], ]; diff --git a/examples/hdom-vscroller/tsconfig.json b/examples/hdom-vscroller/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hdom-vscroller/tsconfig.json +++ b/examples/hdom-vscroller/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hiccup-canvas-arcs/package.json b/examples/hiccup-canvas-arcs/package.json index 02c0469711..3d57241cb0 100644 --- a/examples/hiccup-canvas-arcs/package.json +++ b/examples/hiccup-canvas-arcs/package.json @@ -1,40 +1,40 @@ { - "name": "@example/hiccup-canvas-arcs", - "private": true, - "version": "0.0.1", - "description": "Animated arcs & drawing using hiccup-canvas", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/color": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/hiccup-canvas": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/random": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom", - "hiccup-canvas" - ], - "screenshot": "examples/hiccup-canvas-arcs.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hiccup-canvas-arcs", + "private": true, + "version": "0.0.1", + "description": "Animated arcs & drawing using hiccup-canvas", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/color": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/hiccup-canvas": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/random": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom", + "hiccup-canvas" + ], + "screenshot": "examples/hiccup-canvas-arcs.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hiccup-canvas-arcs/src/index.ts b/examples/hiccup-canvas-arcs/src/index.ts index f898f8c9a1..61c649fccf 100644 --- a/examples/hiccup-canvas-arcs/src/index.ts +++ b/examples/hiccup-canvas-arcs/src/index.ts @@ -28,64 +28,64 @@ const ctx = canvas.getContext("2d")!; // generate random arc configurations const arcs = [ - ...map( - (i) => ({ - // radius - r: fit01(i, 50, W * 0.4), - // stroke width - w: SYSTEM.minmax(1, 5), - // randomized HSLA color - col: hsl([SYSTEM.norm(0.1), SYSTEM.minmax(0.5, 1), 0.5]), - // start angle - theta: SYSTEM.float(TAU), - // angle spread - spread: SYSTEM.float(TAU), - // rotation speed - speed: SYSTEM.norm(0.02), - }), - normRange(20) - ), + ...map( + (i) => ({ + // radius + r: fit01(i, 50, W * 0.4), + // stroke width + w: SYSTEM.minmax(1, 5), + // randomized HSLA color + col: hsl([SYSTEM.norm(0.1), SYSTEM.minmax(0.5, 1), 0.5]), + // start angle + theta: SYSTEM.float(TAU), + // angle spread + spread: SYSTEM.float(TAU), + // rotation speed + speed: SYSTEM.norm(0.02), + }), + normRange(20) + ), ]; // mouse position stream const mouse = fromDOMEvent(canvas, "mousemove").transform( - map((e) => { - const b = canvas.getBoundingClientRect(); - return [e.clientX - b.left, e.clientY - b.top]; - }) + map((e) => { + const b = canvas.getBoundingClientRect(); + return [e.clientX - b.left, e.clientY - b.top]; + }) ); // 60Hz update loop fromRAF().subscribe({ - next() { - // update rotations - arcs.forEach((a) => (a.theta += a.speed)); - // clear viewport - ctx.clearRect(0, 0, canvas.width, canvas.height); - // get mouse pos - const m = mouse.deref(); - draw( - ctx, - // group arcs and convert to hiccup tree required by `draw()` - // (see hiccup-canvas readme for details) - group( - {}, - arcs.map(({ r, w, col, theta, spread }) => { - // build (elliptic) arc from config - const a = arc(ORIGIN, r, 0, theta, theta + spread); - // convert to cubic path due to HTML Canvas API limitations - // (doesn't support elliptic arcs, so we need to convert them...) - // also perform shape picking by computing distance to - // closest point on arc to mouse pos. adjust color based on result - return pathFromCubics(asCubic(a), { - weight: w, - stroke: - m && dist(m, closestPoint(a, m)!) < PICK_DIST - ? PICK_COL - : col, - }); - }) - ) - ); - }, + next() { + // update rotations + arcs.forEach((a) => (a.theta += a.speed)); + // clear viewport + ctx.clearRect(0, 0, canvas.width, canvas.height); + // get mouse pos + const m = mouse.deref(); + draw( + ctx, + // group arcs and convert to hiccup tree required by `draw()` + // (see hiccup-canvas readme for details) + group( + {}, + arcs.map(({ r, w, col, theta, spread }) => { + // build (elliptic) arc from config + const a = arc(ORIGIN, r, 0, theta, theta + spread); + // convert to cubic path due to HTML Canvas API limitations + // (doesn't support elliptic arcs, so we need to convert them...) + // also perform shape picking by computing distance to + // closest point on arc to mouse pos. adjust color based on result + return pathFromCubics(asCubic(a), { + weight: w, + stroke: + m && dist(m, closestPoint(a, m)!) < PICK_DIST + ? PICK_COL + : col, + }); + }) + ) + ); + }, }); diff --git a/examples/hiccup-canvas-arcs/tsconfig.json b/examples/hiccup-canvas-arcs/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hiccup-canvas-arcs/tsconfig.json +++ b/examples/hiccup-canvas-arcs/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/hydrate-basics/package.json b/examples/hydrate-basics/package.json index 3ff7a2eca8..eb119a4549 100644 --- a/examples/hydrate-basics/package.json +++ b/examples/hydrate-basics/package.json @@ -1,37 +1,37 @@ { - "name": "@example/hydrate-basics", - "private": true, - "description": "Hiccup / hdom DOM hydration example", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/hiccup": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "hdom", - "hiccup" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/hydrate-basics", + "private": true, + "description": "Hiccup / hdom DOM hydration example", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/hiccup": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "hdom", + "hiccup" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/hydrate-basics/src/index.ts b/examples/hydrate-basics/src/index.ts index 5df76698b4..5b9b80c1ed 100644 --- a/examples/hydrate-basics/src/index.ts +++ b/examples/hydrate-basics/src/index.ts @@ -7,8 +7,8 @@ import { serialize } from "@thi.ng/hiccup/serialize"; // basic state container const state = defAtom({ - bg: "red", - freq: 0.01, + bg: "red", + freq: 0.01, }); // state updates @@ -17,90 +17,90 @@ const setFreq = (x: number) => state.resetIn(["freq"], x); // root component with different types of child components const app = () => { - // HOF canvas component w/ life cycle methods see for further - // reference: - // https://docs.thi.ng/umbrella/hdom-components/modules.html#canvas2D - // - // when serializing to html only the component's `render` method - // will be invoked. the component's `init` is invoked later when - // hydrating the DOM the `update` fn given here is canvas specific - const canvas = canvas2D({ - update: (el, ctx, _, time, __, ___, bg, freq) => { - const y = el.height / 2; - ctx.fillStyle = bg; - ctx.fillRect(0, 0, el.width, el.height); - ctx.strokeStyle = "white"; - ctx.lineWidth = 3; - ctx.beginPath(); - ctx.moveTo(0, y + y * Math.sin(time * freq)); - for (let x = 5; x < el.width; x += 5) { - ctx.lineTo(x, y + y * Math.sin((time + x) * freq)); - } - ctx.stroke(); - }, - }); - // when serializing to HTML all event attributes w/ function values - // will be excluded, however the event listeners will be attached - // during hydration (1st frame of hdom update loop) + // HOF canvas component w/ life cycle methods see for further + // reference: + // https://docs.thi.ng/umbrella/hdom-components/modules.html#canvas2D + // + // when serializing to html only the component's `render` method + // will be invoked. the component's `init` is invoked later when + // hydrating the DOM the `update` fn given here is canvas specific + const canvas = canvas2D({ + update: (el, ctx, _, time, __, ___, bg, freq) => { + const y = el.height / 2; + ctx.fillStyle = bg; + ctx.fillRect(0, 0, el.width, el.height); + ctx.strokeStyle = "white"; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.moveTo(0, y + y * Math.sin(time * freq)); + for (let x = 5; x < el.width; x += 5) { + ctx.lineTo(x, y + y * Math.sin((time + x) * freq)); + } + ctx.stroke(); + }, + }); + // when serializing to HTML all event attributes w/ function values + // will be excluded, however the event listeners will be attached + // during hydration (1st frame of hdom update loop) - // btw. the class names are for tachyons css - return (_state: IDeref) => { - const state = _state.deref(); - return [ - "div#root.w-50-ns.flex.ma2.sans-serif", - [ - "div.w-50-ns", - [canvas, { width: 200, height: 200 }, state.bg, state.freq], - ], - [ - "div.w-50-ns", - [ - "label.db.mb3", - { for: "#bg" }, - "Background color", - [ - dropdown, - { - id: "bg", - class: "w-100", - onchange: (e: Event) => - setBg((e.target).value), - }, - [ - ["", "Choose..."], - ["red", "Red"], - ["green", "Green"], - ["blue", "Blue"], - ], - state.bg, - ], - ], - [ - "label.db.mb3", - { for: "#freq" }, - "Frequency", - [ - "input", - { - id: "freq", - class: "w-100", - type: "range", - min: 0.001, - max: 0.02, - step: 0.001, - value: state.freq, - oninput: (e: Event) => - setFreq( - parseFloat( - (e.target).value - ) - ), - }, - ], - ], - ], - ]; - }; + // btw. the class names are for tachyons css + return (_state: IDeref) => { + const state = _state.deref(); + return [ + "div#root.w-50-ns.flex.ma2.sans-serif", + [ + "div.w-50-ns", + [canvas, { width: 200, height: 200 }, state.bg, state.freq], + ], + [ + "div.w-50-ns", + [ + "label.db.mb3", + { for: "#bg" }, + "Background color", + [ + dropdown, + { + id: "bg", + class: "w-100", + onchange: (e: Event) => + setBg((e.target).value), + }, + [ + ["", "Choose..."], + ["red", "Red"], + ["green", "Green"], + ["blue", "Blue"], + ], + state.bg, + ], + ], + [ + "label.db.mb3", + { for: "#freq" }, + "Frequency", + [ + "input", + { + id: "freq", + class: "w-100", + type: "range", + min: 0.001, + max: 0.02, + step: 0.001, + value: state.freq, + oninput: (e: Event) => + setFreq( + parseFloat( + (e.target).value + ) + ), + }, + ], + ], + ], + ]; + }; }; // emulate SSR by serializing to HTML diff --git a/examples/hydrate-basics/tsconfig.json b/examples/hydrate-basics/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/hydrate-basics/tsconfig.json +++ b/examples/hydrate-basics/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/imgui-basics/package.json b/examples/imgui-basics/package.json index 361c95abae..1ee9676b77 100644 --- a/examples/imgui-basics/package.json +++ b/examples/imgui-basics/package.json @@ -1,40 +1,40 @@ { - "name": "@example/imgui-basics", - "private": true, - "version": "0.0.1", - "description": "Minimal IMGUI usage example", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/imgui": "workspace:^", - "@thi.ng/layout": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom-canvas", - "imgui", - "layout", - "rstream-gestures" - ], - "screenshot": "examples/imgui-basics.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/imgui-basics", + "private": true, + "version": "0.0.1", + "description": "Minimal IMGUI usage example", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/imgui": "workspace:^", + "@thi.ng/layout": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom-canvas", + "imgui", + "layout", + "rstream-gestures" + ], + "screenshot": "examples/imgui-basics.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/imgui-basics/src/index.ts b/examples/imgui-basics/src/index.ts index 303901cf50..ea8b8591cd 100644 --- a/examples/imgui-basics/src/index.ts +++ b/examples/imgui-basics/src/index.ts @@ -12,56 +12,56 @@ import { tweenNumber } from "@thi.ng/rstream/tween"; // GUI initialization const gui = new IMGUI({ - theme: { - ...DEFAULT_THEME, - font: "16px 'IBM Plex Mono', monospace", - baseLine: 6, - focus: "#000", - }, + theme: { + ...DEFAULT_THEME, + font: "16px 'IBM Plex Mono', monospace", + baseLine: 6, + focus: "#000", + }, }); // hdom-canvas component specialization // use init lifecycle method to create/attach event listeners // and feed relevant details to IMGUI const _canvas = { - ...canvas, - init: (el: HTMLCanvasElement) => { - // unified mouse & touch event handling - gestureStream(el, {}).subscribe({ - next: (e) => gui.setMouse(e.pos, e.buttons), - }); - // key events are only required to make IMGUI components more accessible - // and keyboard controllable. - // Important: key events CANNOT ever be attached to a canvas itself... - fromDOMEvent(window, "keydown").subscribe({ - next(e) { - if (e.target !== document.body) return; - if ( - e.key === Key.TAB || - e.key === Key.SPACE || - e.key === Key.UP || - e.key === Key.DOWN - ) { - e.preventDefault(); - } - gui.setKey(e); - }, - }); - fromDOMEvent(window, "keyup").subscribe({ - next(e) { - gui.setKey(e); - }, - }); - }, + ...canvas, + init: (el: HTMLCanvasElement) => { + // unified mouse & touch event handling + gestureStream(el, {}).subscribe({ + next: (e) => gui.setMouse(e.pos, e.buttons), + }); + // key events are only required to make IMGUI components more accessible + // and keyboard controllable. + // Important: key events CANNOT ever be attached to a canvas itself... + fromDOMEvent(window, "keydown").subscribe({ + next(e) { + if (e.target !== document.body) return; + if ( + e.key === Key.TAB || + e.key === Key.SPACE || + e.key === Key.UP || + e.key === Key.DOWN + ) { + e.preventDefault(); + } + gui.setKey(e); + }, + }); + fromDOMEvent(window, "keyup").subscribe({ + next(e) { + gui.setKey(e); + }, + }); + }, }; // dummy app state details (using https://thi.ng/rstream) const PRESETS = [ - ["Mute", 0, 0], - ["Quiet", 1, 25], - ["Medium", 33, 50], - ["Party", 66, 75], + ["Mute", 0, 0], + ["Quiet", 1, 25], + ["Medium", 33, 50], + ["Party", 66, 75], ]; // initial volume state @@ -78,71 +78,71 @@ const smoothedVolume = tweenNumber(volume, 0, 0.2); // derived view for slider label const volumeLabel = smoothedVolume.map((x) => { - for (let i = PRESETS.length; i-- > 0; ) { - if (x >= PRESETS[i][1]) return `${Math.round(x)} (${PRESETS[i][0]})`; - } - return ""; + for (let i = PRESETS.length; i-- > 0; ) { + if (x >= PRESETS[i][1]) return `${Math.round(x)} (${PRESETS[i][0]})`; + } + return ""; }); // hdom update loop start(() => { - let res: any; - // create grid layout using https://thi.ng/layout - // position grid centered in window - const grid = gridLayout( - 16, - (window.innerHeight - 68) / 2, - window.innerWidth - 32, - 1, - 32, - 4 - ); + let res: any; + // create grid layout using https://thi.ng/layout + // position grid centered in window + const grid = gridLayout( + 16, + (window.innerHeight - 68) / 2, + window.innerWidth - 32, + 1, + 32, + 4 + ); - // prep GUI for next frame - gui.begin(); + // prep GUI for next frame + gui.begin(); - // volume slider component - // returns a number (new value) if user interacted w/ slider - res = sliderH( - gui, - grid, - "vol", - 0, - 100, - 1, - smoothedVolume.deref()!, - `Volume: ${volumeLabel.deref()!}`, - () => "" - ); - // update state if needed - res !== undefined && volume.next(res); + // volume slider component + // returns a number (new value) if user interacted w/ slider + res = sliderH( + gui, + grid, + "vol", + 0, + 100, + 1, + smoothedVolume.deref()!, + `Volume: ${volumeLabel.deref()!}`, + () => "" + ); + // update state if needed + res !== undefined && volume.next(res); - // create nested inner grid layout - let inner = grid.nest(PRESETS.length); - // create button for each volume preset - // and update state if a button was pressed - for (let preset of PRESETS) { - res = buttonH(gui, inner, `bt${preset[0]}`, preset[0]); - res && volume.next(preset[2]); - } + // create nested inner grid layout + let inner = grid.nest(PRESETS.length); + // create button for each volume preset + // and update state if a button was pressed + for (let preset of PRESETS) { + res = buttonH(gui, inner, `bt${preset[0]}`, preset[0]); + res && volume.next(preset[2]); + } - // end frame - gui.end(); + // end frame + gui.end(); - // return main/only component (see definition further above) - // the `gui` itself implements the `IToHiccup` interface and therefore - // can just be provided as canvas body - // (you can also provide other hiccup-canvas shapes/content here) - return [ - _canvas, - { - // disable hdom diffing for canvas children - __diff: false, - width: window.innerWidth, - height: window.innerHeight, - style: { background: gui.theme.globalBg, cursor: gui.cursor }, - ...gui.attribs, - }, - gui, - ]; + // return main/only component (see definition further above) + // the `gui` itself implements the `IToHiccup` interface and therefore + // can just be provided as canvas body + // (you can also provide other hiccup-canvas shapes/content here) + return [ + _canvas, + { + // disable hdom diffing for canvas children + __diff: false, + width: window.innerWidth, + height: window.innerHeight, + style: { background: gui.theme.globalBg, cursor: gui.cursor }, + ...gui.attribs, + }, + gui, + ]; }); diff --git a/examples/imgui-basics/tsconfig.json b/examples/imgui-basics/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/imgui-basics/tsconfig.json +++ b/examples/imgui-basics/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/imgui/package.json b/examples/imgui/package.json index 4da48ff60f..f28f466ba1 100644 --- a/examples/imgui/package.json +++ b/examples/imgui/package.json @@ -1,58 +1,58 @@ { - "name": "@example/imgui", - "private": true, - "version": "0.0.1", - "description": "Canvas based Immediate Mode GUI components", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/atom": "workspace:^", - "@thi.ng/bench": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/hiccup-carbon-icons": "workspace:^", - "@thi.ng/imgui": "workspace:^", - "@thi.ng/layout": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/paths": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^", - "@thi.ng/transducers-stats": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "geom", - "hdom", - "hdom-canvas", - "hiccup-carbon-icons", - "imgui", - "layout", - "rstream", - "rstream-gestures", - "transducers", - "transducers-hdom", - "vectors" - ], - "screenshot": "imgui/imgui-all.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/imgui", + "private": true, + "version": "0.0.1", + "description": "Canvas based Immediate Mode GUI components", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/atom": "workspace:^", + "@thi.ng/bench": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/hiccup-carbon-icons": "workspace:^", + "@thi.ng/imgui": "workspace:^", + "@thi.ng/layout": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/paths": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^", + "@thi.ng/transducers-stats": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "geom", + "hdom", + "hdom-canvas", + "hiccup-carbon-icons", + "imgui", + "layout", + "rstream", + "rstream-gestures", + "transducers", + "transducers-hdom", + "vectors" + ], + "screenshot": "imgui/imgui-all.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/imgui/src/index.ts b/examples/imgui/src/index.ts index 7c9ca9961f..f50628b2d5 100644 --- a/examples/imgui/src/index.ts +++ b/examples/imgui/src/index.ts @@ -51,23 +51,23 @@ import { vecOf } from "@thi.ng/vectors/vec-of"; // define theme colors in RGBA format for future compatibility with // WebGL backend const THEMES: Partial[] = [ - DEFAULT_THEME, - { - globalBg: "#ccc", - focus: [1, 0.66, 0, 1], - cursor: [0, 0, 0, 1], - bg: [1, 1, 1, 0.66], - bgDisabled: [1, 1, 1, 0.33], - bgHover: [1, 1, 1, 0.9], - fg: [0.8, 0, 0.8, 1], - fgDisabled: [0.8, 0, 0.8, 0.5], - fgHover: [1, 0, 1, 1], - text: [0.3, 0.3, 0.3, 1], - textDisabled: [0.3, 0.3, 0.3, 0.5], - textHover: [0.2, 0.2, 0.4, 1], - bgTooltip: [1, 1, 0.8, 0.85], - textTooltip: [0, 0, 0, 1], - }, + DEFAULT_THEME, + { + globalBg: "#ccc", + focus: [1, 0.66, 0, 1], + cursor: [0, 0, 0, 1], + bg: [1, 1, 1, 0.66], + bgDisabled: [1, 1, 1, 0.33], + bgHover: [1, 1, 1, 0.9], + fg: [0.8, 0, 0.8, 1], + fgDisabled: [0.8, 0, 0.8, 0.5], + fgHover: [1, 0, 1, 1], + text: [0.3, 0.3, 0.3, 1], + textDisabled: [0.3, 0.3, 0.3, 0.5], + textHover: [0.2, 0.2, 0.4, 1], + bgTooltip: [1, 1, 0.8, 0.85], + textTooltip: [0, 0, 0, 1], + }, ]; // float value formatters @@ -85,15 +85,15 @@ const THEME_IDS = ["Default", "Raspberry"]; // helper function to normalize hiccup icon paths // (transforms each path into one only consisting of cubic spline segments) const mkIcon = (icon: any[]) => [ - "g", - { stroke: "none", scale: 16 / 32 }, - ...iterator( - comp( - mapcat((p) => pathFromSvg(p[1].d)), - map(normalizedPath) - ), - icon.slice(2) - ), + "g", + { stroke: "none", scale: 16 / 32 }, + ...iterator( + comp( + mapcat((p) => pathFromSvg(p[1].d)), + map(normalizedPath) + ), + icon.slice(2) + ), ]; // icon definitions (from @thi.ng/hiccup-carbon-icons) @@ -101,155 +101,155 @@ const ICON1 = mkIcon(DOWNLOAD); const ICON2 = mkIcon(RESTART); interface AppState { - uiVisible: boolean; - uiMode: number; - theme: number; - radius: number; - gridW: number; - rgb: number[]; - pos: Vec; - txt: string; - toggles: boolean[]; - flags: boolean[]; - radio: number; + uiVisible: boolean; + uiMode: number; + theme: number; + radius: number; + gridW: number; + rgb: number[]; + pos: Vec; + txt: string; + toggles: boolean[]; + flags: boolean[]; + radio: number; } // main immutable app state wrapper (with time travel) const DB = new History( - new Atom({ - uiVisible: true, - uiMode: 0, - theme: 0, - radius: 10, - gridW: 15, - rgb: [0.9, 0.45, 0.5], - pos: [400, 140], - txt: "Hello there! This is a test, do not panic!", - toggles: new Array(12).fill(false), - flags: [true, false], - radio: 0, - }), - // max. 500 undo steps - 500 + new Atom({ + uiVisible: true, + uiMode: 0, + theme: 0, + radius: 10, + gridW: 15, + rgb: [0.9, 0.45, 0.5], + pos: [400, 140], + txt: "Hello there! This is a test, do not panic!", + toggles: new Array(12).fill(false), + flags: [true, false], + radio: 0, + }), + // max. 500 undo steps + 500 ); // theme merging helper const themeForID = (theme: number): Partial => ({ - ...THEMES[theme % THEMES.length], - font: FONT, - cursorBlink: 0, + ...THEMES[theme % THEMES.length], + font: FONT, + cursorBlink: 0, }); // state update handler for `rgb` value // if Alt key is pressed when this handler executes, // then all values will be set uniformly... const setRGB = (gui: IMGUI, res: number[]) => - res !== undefined && - (gui.isAltDown() - ? DB.resetIn(["rgb"], vecOf(3, res[1])) - : DB.resetIn(["rgb", res[0]], res[1])); + res !== undefined && + (gui.isAltDown() + ? DB.resetIn(["rgb"], vecOf(3, res[1])) + : DB.resetIn(["rgb", res[0]], res[1])); // main application const app = () => { - let maxW = 240; - let size = [window.innerWidth, window.innerHeight]; - let radialPos = [0, 0]; - let radialActive = false; - - // GUI instance - const gui = new IMGUI({ theme: themeForID(DB.deref().theme) }); - - // GUI benchmark (moving average) transducer - const bench = step(sma(50)); - - // augment hdom-canvas component with init lifecycle method to - // attach event streams once canvas has been mounted - const _canvas = { - ...canvas, - init(canv: HTMLCanvasElement) { - // add event streams to main stream combinator - // in order to trigger GUI updates... - main.add( - // merge all event streams into a single input to `main` - // (we don't actually care about their actual values and merely - // use them as mechanism to trigger updates) - merge({ - src: [ - // mouse & touch events - gestureStream(canv, {}).subscribe({ - next(e) { - gui.setMouse(e.pos, e.buttons); - }, - }), - // keydown & undo/redo handler: - // Ctrl/Command + Z = undo - // Shift + Ctrl/Command + Z = redo - fromDOMEvent(window, "keydown").subscribe({ - next(e) { - if (e.key === Key.TAB) { - e.preventDefault(); - } - if ( - (e.metaKey || e.ctrlKey) && - e.key.toLowerCase() === "z" - ) { - e.shiftKey ? DB.redo() : DB.undo(); - } else { - gui.setKey(e); - } - }, - }), - fromDOMEvent(window, "keyup").subscribe({ - next(e) { - gui.setKey(e); - }, - }), - fromDOMEvent(window, "resize").subscribe({ - next() { - maxW = Math.min(maxW, window.innerWidth - 16); - setC2( - size, - window.innerWidth, - window.innerHeight - ); - DB.swapIn(["pos"], (pos: Vec) => - min2([], pos, size) - ); - }, - }), - ], - }) - ); - }, - }; - - // main GUI update function - const updateGUI = (draw: boolean) => { - // obtain atom value - const state = DB.deref(); - // setup initial layout (single column) - const grid = gridLayout(10, 10, maxW - 20, 1, 16, 4); - - gui.setTheme(themeForID(state.theme)); - - // start frame - gui.begin(draw); - - // disable all GUI components if radial menu is active - gui.beginDisabled(radialActive); - - // button components return true if clicked - if ( - buttonH(gui, grid, "show", state.uiVisible ? "Hide UI" : "Show UI") - ) { - DB.resetIn(["uiVisible"], !state.uiVisible); - } - if (state.uiVisible) { - let inner: GridLayout; - let inner2: GridLayout; - let res: any; - // prettier-ignore - switch(state.uiMode) { + let maxW = 240; + let size = [window.innerWidth, window.innerHeight]; + let radialPos = [0, 0]; + let radialActive = false; + + // GUI instance + const gui = new IMGUI({ theme: themeForID(DB.deref().theme) }); + + // GUI benchmark (moving average) transducer + const bench = step(sma(50)); + + // augment hdom-canvas component with init lifecycle method to + // attach event streams once canvas has been mounted + const _canvas = { + ...canvas, + init(canv: HTMLCanvasElement) { + // add event streams to main stream combinator + // in order to trigger GUI updates... + main.add( + // merge all event streams into a single input to `main` + // (we don't actually care about their actual values and merely + // use them as mechanism to trigger updates) + merge({ + src: [ + // mouse & touch events + gestureStream(canv, {}).subscribe({ + next(e) { + gui.setMouse(e.pos, e.buttons); + }, + }), + // keydown & undo/redo handler: + // Ctrl/Command + Z = undo + // Shift + Ctrl/Command + Z = redo + fromDOMEvent(window, "keydown").subscribe({ + next(e) { + if (e.key === Key.TAB) { + e.preventDefault(); + } + if ( + (e.metaKey || e.ctrlKey) && + e.key.toLowerCase() === "z" + ) { + e.shiftKey ? DB.redo() : DB.undo(); + } else { + gui.setKey(e); + } + }, + }), + fromDOMEvent(window, "keyup").subscribe({ + next(e) { + gui.setKey(e); + }, + }), + fromDOMEvent(window, "resize").subscribe({ + next() { + maxW = Math.min(maxW, window.innerWidth - 16); + setC2( + size, + window.innerWidth, + window.innerHeight + ); + DB.swapIn(["pos"], (pos: Vec) => + min2([], pos, size) + ); + }, + }), + ], + }) + ); + }, + }; + + // main GUI update function + const updateGUI = (draw: boolean) => { + // obtain atom value + const state = DB.deref(); + // setup initial layout (single column) + const grid = gridLayout(10, 10, maxW - 20, 1, 16, 4); + + gui.setTheme(themeForID(state.theme)); + + // start frame + gui.begin(draw); + + // disable all GUI components if radial menu is active + gui.beginDisabled(radialActive); + + // button components return true if clicked + if ( + buttonH(gui, grid, "show", state.uiVisible ? "Hide UI" : "Show UI") + ) { + DB.resetIn(["uiVisible"], !state.uiVisible); + } + if (state.uiVisible) { + let inner: GridLayout; + let inner2: GridLayout; + let res: any; + // prettier-ignore + switch(state.uiMode) { case 0: // create empty row grid.next(); @@ -396,152 +396,152 @@ const app = () => { default: } - } - // remove disabled flag from stack - gui.endDisabled(); - - // radial menu - if (gui.isControlDown()) { - if (!radialActive) { - radialPos = [...gui.mouse]; - } - // menu backdrop - gui.add( - gui.resource("radial", hash(radialPos) + 1, () => [ - "g", - {}, - [ - "radialGradient", - { - id: "shadow", - from: radialPos, - to: radialPos, - r1: 5, - r2: 300, - }, - [ - [0, [1, 1, 1, 0.8]], - [0.5, [1, 1, 1, 0.66]], - [1, [1, 1, 1, 0]], - ], - ], - ["circle", { fill: "$shadow" }, radialPos, 300], - ]) - ); - let res: number | undefined; - if ( - (res = radialMenu( - gui, - "radial", - radialPos[0], - radialPos[1], - 100, - RADIAL_LABELS, - [] - )) !== undefined - ) { - DB.swap((db) => - setInManyUnsafe(db, ["uiMode"], res, ["uiVisible"], true) - ); - } - gui.add( - textLabelRaw( - add2([], radialPos, [0, 120]), - { fill: "#000", align: "center" }, - "Use cursor keys to navigate" - ), - textLabelRaw( - add2([], radialPos, [0, 134]), - { fill: "#000", align: "center" }, - "Click or Enter to switch UI" - ) - ); - if (!radialActive) { - gui.focusID = gui.hotID; - } - radialActive = true; - } else { - radialActive = false; - } - // resize - const [w, h] = size; - if ( - gui.activeID === NONE && - gui.isMouseDown() && - Math.abs(gui.mouse[0] - maxW) < 80 - ) { - maxW = clamp(gui.mouse[0], 240, w - 16); - } - - const { key, hotID, activeID, focusID, lastID } = gui; - const statLayout = gridLayout(10, h - 10 - 3 * 14, w, 1, 14, 0); - textLabel(gui, statLayout, `Key: ${key}`); - textLabel(gui, statLayout, `Focus: ${focusID} / ${lastID}`); - textLabel( - gui, - statLayout, - `IDs: ${hotID || "none"} / ${activeID || "none"}` - ); - - gui.end(); - }; - - // main component function - return () => { - const width = window.innerWidth; - const height = window.innerHeight; - - // this is only needed because we're NOT using a RAF update loop: - // call updateGUI twice to compensate for lack of regular 60fps update - // Note: Unless your GUI is super complex, this cost is pretty neglible - // and no actual drawing takes place here ... - - // the `timedResult` function measures execution time and returns tuple - // of [result, time]. We then pass the time taken to our SMA transducer - // to update and return a moving average. - const t = bench( - timedResult(() => { - updateGUI(false); - updateGUI(true); - })[1] - ); - // since the MA will only be available after the configured period, - // we will only display stats when they're ready... - t != null && - gui.add( - textLabelRaw( - [10, height - 10 - 4 * 14], - "#ff0", - `GUI time: ${F2(t)}ms` - ) - ); - // return hdom-canvas component with embedded GUI - return [ - _canvas, - { - width, - height, - style: { background: gui.theme.globalBg, cursor: gui.cursor }, - oncontextmenu: (e: Event) => e.preventDefault(), - ...gui.attribs, - }, - // GUI resize border line - line([maxW, 0], [maxW, height], { stroke: "#000" }), - [ - "text", - { - transform: [0, -1, 1, 0, maxW + 12, height / 2], - fill: "#000", - font: FONT, - align: "center", - }, - [0, 0], - "DRAG TO RESIZE", - ], - // IMGUI implements IToHiccup interface so just supply as is - gui, - ]; - }; + } + // remove disabled flag from stack + gui.endDisabled(); + + // radial menu + if (gui.isControlDown()) { + if (!radialActive) { + radialPos = [...gui.mouse]; + } + // menu backdrop + gui.add( + gui.resource("radial", hash(radialPos) + 1, () => [ + "g", + {}, + [ + "radialGradient", + { + id: "shadow", + from: radialPos, + to: radialPos, + r1: 5, + r2: 300, + }, + [ + [0, [1, 1, 1, 0.8]], + [0.5, [1, 1, 1, 0.66]], + [1, [1, 1, 1, 0]], + ], + ], + ["circle", { fill: "$shadow" }, radialPos, 300], + ]) + ); + let res: number | undefined; + if ( + (res = radialMenu( + gui, + "radial", + radialPos[0], + radialPos[1], + 100, + RADIAL_LABELS, + [] + )) !== undefined + ) { + DB.swap((db) => + setInManyUnsafe(db, ["uiMode"], res, ["uiVisible"], true) + ); + } + gui.add( + textLabelRaw( + add2([], radialPos, [0, 120]), + { fill: "#000", align: "center" }, + "Use cursor keys to navigate" + ), + textLabelRaw( + add2([], radialPos, [0, 134]), + { fill: "#000", align: "center" }, + "Click or Enter to switch UI" + ) + ); + if (!radialActive) { + gui.focusID = gui.hotID; + } + radialActive = true; + } else { + radialActive = false; + } + // resize + const [w, h] = size; + if ( + gui.activeID === NONE && + gui.isMouseDown() && + Math.abs(gui.mouse[0] - maxW) < 80 + ) { + maxW = clamp(gui.mouse[0], 240, w - 16); + } + + const { key, hotID, activeID, focusID, lastID } = gui; + const statLayout = gridLayout(10, h - 10 - 3 * 14, w, 1, 14, 0); + textLabel(gui, statLayout, `Key: ${key}`); + textLabel(gui, statLayout, `Focus: ${focusID} / ${lastID}`); + textLabel( + gui, + statLayout, + `IDs: ${hotID || "none"} / ${activeID || "none"}` + ); + + gui.end(); + }; + + // main component function + return () => { + const width = window.innerWidth; + const height = window.innerHeight; + + // this is only needed because we're NOT using a RAF update loop: + // call updateGUI twice to compensate for lack of regular 60fps update + // Note: Unless your GUI is super complex, this cost is pretty neglible + // and no actual drawing takes place here ... + + // the `timedResult` function measures execution time and returns tuple + // of [result, time]. We then pass the time taken to our SMA transducer + // to update and return a moving average. + const t = bench( + timedResult(() => { + updateGUI(false); + updateGUI(true); + })[1] + ); + // since the MA will only be available after the configured period, + // we will only display stats when they're ready... + t != null && + gui.add( + textLabelRaw( + [10, height - 10 - 4 * 14], + "#ff0", + `GUI time: ${F2(t)}ms` + ) + ); + // return hdom-canvas component with embedded GUI + return [ + _canvas, + { + width, + height, + style: { background: gui.theme.globalBg, cursor: gui.cursor }, + oncontextmenu: (e: Event) => e.preventDefault(), + ...gui.attribs, + }, + // GUI resize border line + line([maxW, 0], [maxW, height], { stroke: "#000" }), + [ + "text", + { + transform: [0, -1, 1, 0, maxW + 12, height / 2], + fill: "#000", + font: FONT, + align: "center", + }, + [0, 0], + "DRAG TO RESIZE", + ], + // IMGUI implements IToHiccup interface so just supply as is + gui, + ]; + }; }; // main stream combinator @@ -550,9 +550,9 @@ const app = () => { // event streams to this stream sync, which are then used to trigger future // updates on demand... const main = sync({ - src: { - state: fromAtom(DB), - }, + src: { + state: fromAtom(DB), + }, }); // subscription & transformation of app state stream. uses a RAF diff --git a/examples/imgui/tsconfig.json b/examples/imgui/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/imgui/tsconfig.json +++ b/examples/imgui/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/interceptor-basics/package.json b/examples/interceptor-basics/package.json index 630977a24b..633af29880 100644 --- a/examples/interceptor-basics/package.json +++ b/examples/interceptor-basics/package.json @@ -1,34 +1,34 @@ { - "name": "@example/interceptor-basics", - "private": true, - "description": "Event handling w/ interceptors and side effects", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/atom": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/interceptors": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "interceptors" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/interceptor-basics", + "private": true, + "description": "Event handling w/ interceptors and side effects", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/atom": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/interceptors": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "interceptors" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/interceptor-basics/src/index.ts b/examples/interceptor-basics/src/index.ts index cb169497d4..62bc92d507 100644 --- a/examples/interceptor-basics/src/index.ts +++ b/examples/interceptor-basics/src/index.ts @@ -1,10 +1,10 @@ import { defAtom } from "@thi.ng/atom/atom"; import { start } from "@thi.ng/hdom/start"; import { - dispatchNow, - EventBus, - FX_STATE, - valueUpdater, + dispatchNow, + EventBus, + FX_STATE, + valueUpdater, } from "@thi.ng/interceptors"; import { choices } from "@thi.ng/transducers/choices"; @@ -17,37 +17,37 @@ const db = defAtom({}); // event bus w/ handlers // see @thi.ng/interceptors for more details const bus = new EventBus(db, { - init: () => ({ - [FX_STATE]: { clicks: 0, color: "grey" }, - }), - "inc-counter": [ - valueUpdater("clicks", (x: number) => x + 1), - dispatchNow(["randomize-color"]), - ], - "randomize-color": valueUpdater("color", () => colors.next().value), + init: () => ({ + [FX_STATE]: { clicks: 0, color: "grey" }, + }), + "inc-counter": [ + valueUpdater("clicks", (x: number) => x + 1), + dispatchNow(["randomize-color"]), + ], + "randomize-color": valueUpdater("color", () => colors.next().value), }); start( - // this root component function will be executed via RAF. - // it first processes events and then only returns an updated - // component if there was a state update... - (ctx: any) => - ctx.bus.processQueue() - ? [ - "button", - { - style: { - padding: "1rem", - background: ctx.db.value.color, - }, - onclick: () => ctx.bus.dispatch(["inc-counter"]), - }, - `clicks: ${ctx.db.value.clicks}`, - ] - : null, - // hdom options incl. - // arbitrary user context object passed to all components - { ctx: { db, bus } } + // this root component function will be executed via RAF. + // it first processes events and then only returns an updated + // component if there was a state update... + (ctx: any) => + ctx.bus.processQueue() + ? [ + "button", + { + style: { + padding: "1rem", + background: ctx.db.value.color, + }, + onclick: () => ctx.bus.dispatch(["inc-counter"]), + }, + `clicks: ${ctx.db.value.clicks}`, + ] + : null, + // hdom options incl. + // arbitrary user context object passed to all components + { ctx: { db, bus } } ); // kick off diff --git a/examples/interceptor-basics/tsconfig.json b/examples/interceptor-basics/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/interceptor-basics/tsconfig.json +++ b/examples/interceptor-basics/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/interceptor-basics2/package.json b/examples/interceptor-basics2/package.json index 9f58959e6a..35ddcb6178 100644 --- a/examples/interceptor-basics2/package.json +++ b/examples/interceptor-basics2/package.json @@ -1,36 +1,36 @@ { - "name": "@example/interceptor-basics2", - "private": true, - "description": "Event handling w/ interceptors and side effects", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/interceptors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom", - "interceptors", - "paths" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/interceptor-basics2", + "private": true, + "description": "Event handling w/ interceptors and side effects", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/interceptors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom", + "interceptors", + "paths" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/interceptor-basics2/src/index.ts b/examples/interceptor-basics2/src/index.ts index b62c416cb5..55812a7f82 100644 --- a/examples/interceptor-basics2/src/index.ts +++ b/examples/interceptor-basics2/src/index.ts @@ -2,10 +2,17 @@ import type { IObjectOf, Path } from "@thi.ng/api"; import { defView, defViewUnsafe } from "@thi.ng/atom/view"; import { start } from "@thi.ng/hdom/start"; import { - ensureStateGreaterThan, - ensureStateLessThan, EventBus, EV_SET_VALUE, - EV_UPDATE_VALUE, - FX_DISPATCH_NOW, trace, type EffectDef, type Event, type EventDef, type IDispatch + ensureStateGreaterThan, + ensureStateLessThan, + EventBus, + EV_SET_VALUE, + EV_UPDATE_VALUE, + FX_DISPATCH_NOW, + trace, + type EffectDef, + type Event, + type EventDef, + type IDispatch, } from "@thi.ng/interceptors"; /////////////////////////////////////////////////////////////////////// @@ -23,48 +30,48 @@ const EV_ADD_VALUE = "add-value"; const FX_ADD_COUNTER = "add-counter"; const events: IObjectOf = { - // these event handlers delegate to "updateVal" in same - // processing frame (using FX_DISPATCH_NOW) - // note how we also inject the predicate interceptors here to ensure - // counter values will be always be in the range between 0 .. 100 - [EV_INC]: [ - ensureStateLessThan(100, undefined, () => - console.warn("eek, reached max") - ), - (_, [__, path]) => ({ [FX_DISPATCH_NOW]: [EV_ADD_VALUE, [path, 1]] }), - ], - [EV_DEC]: [ - ensureStateGreaterThan(0, undefined, () => - console.warn("eek, reached min") - ), - (_, [__, path]) => ({ [FX_DISPATCH_NOW]: [EV_ADD_VALUE, [path, -1]] }), - ], - - // similar to the EV_INIT handler above, here we just delegate to the - // the built-in EV_UPDATE_VALUE handler to update a given path value. - // Additionally, we inject the `trace` interceptor to log the event - // each time it's triggered - [EV_ADD_VALUE]: [ - trace, - (_, [__, [path, y]]) => ({ - [FX_DISPATCH_NOW]: [EV_UPDATE_VALUE, [path, (x: number) => x + y]], - }), - ], - - // this handler increments the `nextID` state value and - // triggers FX_ADD_COUNTER side effect with config options for the new counter - [EV_ADD_COUNTER]: (state) => ({ - [FX_DISPATCH_NOW]: [EV_ADD_VALUE, ["nextID", 1]], - // the FX_ADD_COUNTER side effect is defined further below - // here we simply prepare some configuration data for the new counter - [FX_ADD_COUNTER]: { - id: state.nextID, - start: ~~(Math.random() * 100), - color: ["gold", "orange", "springgreen", "yellow", "cyan"][ - ~~(Math.random() * 5) - ], - }, - }), + // these event handlers delegate to "updateVal" in same + // processing frame (using FX_DISPATCH_NOW) + // note how we also inject the predicate interceptors here to ensure + // counter values will be always be in the range between 0 .. 100 + [EV_INC]: [ + ensureStateLessThan(100, undefined, () => + console.warn("eek, reached max") + ), + (_, [__, path]) => ({ [FX_DISPATCH_NOW]: [EV_ADD_VALUE, [path, 1]] }), + ], + [EV_DEC]: [ + ensureStateGreaterThan(0, undefined, () => + console.warn("eek, reached min") + ), + (_, [__, path]) => ({ [FX_DISPATCH_NOW]: [EV_ADD_VALUE, [path, -1]] }), + ], + + // similar to the EV_INIT handler above, here we just delegate to the + // the built-in EV_UPDATE_VALUE handler to update a given path value. + // Additionally, we inject the `trace` interceptor to log the event + // each time it's triggered + [EV_ADD_VALUE]: [ + trace, + (_, [__, [path, y]]) => ({ + [FX_DISPATCH_NOW]: [EV_UPDATE_VALUE, [path, (x: number) => x + y]], + }), + ], + + // this handler increments the `nextID` state value and + // triggers FX_ADD_COUNTER side effect with config options for the new counter + [EV_ADD_COUNTER]: (state) => ({ + [FX_DISPATCH_NOW]: [EV_ADD_VALUE, ["nextID", 1]], + // the FX_ADD_COUNTER side effect is defined further below + // here we simply prepare some configuration data for the new counter + [FX_ADD_COUNTER]: { + id: state.nextID, + start: ~~(Math.random() * 100), + color: ["gold", "orange", "springgreen", "yellow", "cyan"][ + ~~(Math.random() * 5) + ], + }, + }), }; /////////////////////////////////////////////////////////////////////// @@ -76,78 +83,78 @@ const effects: IObjectOf = {}; // components const button = (bus: IDispatch, event: Event, label: string, id?: string) => [ - "button", - { id, onclick: () => bus.dispatch(event) }, - label, + "button", + { id, onclick: () => bus.dispatch(event) }, + label, ]; // counter component function // calls to this function will be triggered via the "addCounter" event and its side effect // (see further below) const counter = (bus: IDispatch, path: Path, start = 0, color: string) => { - const view = defViewUnsafe(bus.state, path); - bus.dispatch([EV_SET_VALUE, [path, start]]); - return [ - "div.counter", - { style: { background: color } }, - () => view.deref() || 0, - [ - "div", - button(bus, [EV_DEC, view.path], "-"), - button(bus, [EV_INC, view.path], "+"), - ], - ]; + const view = defViewUnsafe(bus.state, path); + bus.dispatch([EV_SET_VALUE, [path, start]]); + return [ + "div.counter", + { style: { background: color } }, + () => view.deref() || 0, + [ + "div", + button(bus, [EV_DEC, view.path], "-"), + button(bus, [EV_INC, view.path], "+"), + ], + ]; }; // main app const app = () => { - // an array to store counter component instances - // (only using component local state for KISS reasons) - const counters: any[] = []; - - // create event bus with app state atom and configure with above handlers/effects - const bus = new EventBus(null, events, effects); - - // in addition to externally defined event handlers & side effects - // each type can also be added & remove dynamically - // here we define the FX_ADD_COUNTER side effect, responsible for - // creating a new `counter()` component - bus.addEffect(FX_ADD_COUNTER, ({ id, color, start }, bus) => - counters.push(counter(bus, ["counters", id], start, color)) - ); - - // add derived view subscription for updating JSON state trace - // (only executed when state changes) - const json = defView(bus.state, [], (state) => - JSON.stringify(state, null, 2) - ); - - // this not just initializes the given state value - // the changed state will also trigger the 1st DOM rendering (see returned function below) - bus.dispatch([EV_SET_VALUE, ["nextID", 0]]); - - // our actual root component function passed to hdom - const root = () => [ - "div", - button(bus, [EV_ADD_COUNTER], "add counter", "addcounter"), - ["div", ...counters], - ["pre", json], - ]; - - return () => { - // here we do an optional fail fast check, a useful & energy saving - // approach for apps, which purely rely on the central app state. - // this example app is such a case and we can check if there were - // any events processed which caused a state change and only - // return the root component then (i.e. `processQueue()` returned `true`) - - // if there were no changes, we simply return nothing. - // hdom interprets this as a skipped frame (and does nothing until this - // function is called again during the next frame update cycle...) - if (bus.processQueue()) { - return root; - } - }; + // an array to store counter component instances + // (only using component local state for KISS reasons) + const counters: any[] = []; + + // create event bus with app state atom and configure with above handlers/effects + const bus = new EventBus(null, events, effects); + + // in addition to externally defined event handlers & side effects + // each type can also be added & remove dynamically + // here we define the FX_ADD_COUNTER side effect, responsible for + // creating a new `counter()` component + bus.addEffect(FX_ADD_COUNTER, ({ id, color, start }, bus) => + counters.push(counter(bus, ["counters", id], start, color)) + ); + + // add derived view subscription for updating JSON state trace + // (only executed when state changes) + const json = defView(bus.state, [], (state) => + JSON.stringify(state, null, 2) + ); + + // this not just initializes the given state value + // the changed state will also trigger the 1st DOM rendering (see returned function below) + bus.dispatch([EV_SET_VALUE, ["nextID", 0]]); + + // our actual root component function passed to hdom + const root = () => [ + "div", + button(bus, [EV_ADD_COUNTER], "add counter", "addcounter"), + ["div", ...counters], + ["pre", json], + ]; + + return () => { + // here we do an optional fail fast check, a useful & energy saving + // approach for apps, which purely rely on the central app state. + // this example app is such a case and we can check if there were + // any events processed which caused a state change and only + // return the root component then (i.e. `processQueue()` returned `true`) + + // if there were no changes, we simply return nothing. + // hdom interprets this as a skipped frame (and does nothing until this + // function is called again during the next frame update cycle...) + if (bus.processQueue()) { + return root; + } + }; }; // kick off hdom render loop diff --git a/examples/interceptor-basics2/tsconfig.json b/examples/interceptor-basics2/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/interceptor-basics2/tsconfig.json +++ b/examples/interceptor-basics2/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/iso-plasma/package.json b/examples/iso-plasma/package.json index 3af19f18cd..96551d0575 100644 --- a/examples/iso-plasma/package.json +++ b/examples/iso-plasma/package.json @@ -1,45 +1,45 @@ { - "name": "@example/iso-plasma", - "private": true, - "version": "0.0.1", - "description": "Animated sine plasma effect visualized using contour lines", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/geom-isoline": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom", - "geom-isoline", - "hdom", - "hdom-canvas", - "math", - "transducers", - "vectors" - ], - "screenshot": "geom/geom-isoline.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/iso-plasma", + "private": true, + "version": "0.0.1", + "description": "Animated sine plasma effect visualized using contour lines", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/geom-isoline": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom", + "geom-isoline", + "hdom", + "hdom-canvas", + "math", + "transducers", + "vectors" + ], + "screenshot": "geom/geom-isoline.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/iso-plasma/src/index.ts b/examples/iso-plasma/src/index.ts index b1718b70ca..3dd511e79a 100644 --- a/examples/iso-plasma/src/index.ts +++ b/examples/iso-plasma/src/index.ts @@ -19,42 +19,42 @@ const t0 = Date.now(); // pattern function const plasma = (n: number, t: number) => { - t = Math.sin(t * 0.005) * 100; - const tmod = 123 + 4.5 * Math.sin(t * 0.02); - return ([x, y]: number[]) => { - x *= 0.1; - y *= 0.1; - let acc = 0; - for (let i = 0; i < n; i++) { - const p = (i * (TAU / n) * tmod) / 2; - acc += Math.cos(TAU * (y * Math.cos(p) + x * Math.sin(p) + t)); - } - return acc / 3; - }; + t = Math.sin(t * 0.005) * 100; + const tmod = 123 + 4.5 * Math.sin(t * 0.02); + return ([x, y]: number[]) => { + x *= 0.1; + y *= 0.1; + let acc = 0; + for (let i = 0; i < n; i++) { + const p = (i * (TAU / n) * tmod) / 2; + acc += Math.cos(TAU * (y * Math.cos(p) + x * Math.sin(p) + t)); + } + return acc / 3; + }; }; // compute full pattern via given fn const makeField = (fn: Fn, width: number, height: number) => - setBorder([...map(fn, range2d(width, height))], width, height, 1000); + setBorder([...map(fn, range2d(width, height))], width, height, 1000); // hdom root component const app = () => { - const src = makeField(plasma(6, (Date.now() - t0) * 0.001), W, W); - const contours = iterator( - comp( - mapIndexed((i, x) => <[number, Vec]>[x, [i / 20, 0, 1 - i / 20]]), - mapcat(([i, col]) => - map((pts) => <[Vec[], Vec]>[pts, col], isolines(src, W, W, i)) - ), - map(([pts, col]) => polygon(pts, { stroke: col })) - ), - range(-1, 1, 0.1) - ); - return [ - canvas, - { width: 600, height: 600 }, - ["g", { scale: 600 / W, weight: 0.05, __diff: false }, contours], - ]; + const src = makeField(plasma(6, (Date.now() - t0) * 0.001), W, W); + const contours = iterator( + comp( + mapIndexed((i, x) => <[number, Vec]>[x, [i / 20, 0, 1 - i / 20]]), + mapcat(([i, col]) => + map((pts) => <[Vec[], Vec]>[pts, col], isolines(src, W, W, i)) + ), + map(([pts, col]) => polygon(pts, { stroke: col })) + ), + range(-1, 1, 0.1) + ); + return [ + canvas, + { width: 600, height: 600 }, + ["g", { scale: 600 / W, weight: 0.05, __diff: false }, contours], + ]; }; // kick off diff --git a/examples/iso-plasma/tsconfig.json b/examples/iso-plasma/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/iso-plasma/tsconfig.json +++ b/examples/iso-plasma/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/json-components/package.json b/examples/json-components/package.json index 9e03dc7c7c..c98c1a16b0 100644 --- a/examples/json-components/package.json +++ b/examples/json-components/package.json @@ -1,31 +1,31 @@ { - "name": "@example/json-components", - "private": true, - "description": "Transforming JSON into UI components", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true, - "screenshot": "examples/json-components.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/json-components", + "private": true, + "description": "Transforming JSON into UI components", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true, + "screenshot": "examples/json-components.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/json-components/src/index.ts b/examples/json-components/src/index.ts index 67e660ebd1..e4e405ae8c 100644 --- a/examples/json-components/src/index.ts +++ b/examples/json-components/src/index.ts @@ -6,32 +6,32 @@ import "./style.css"; // some dummy JSON records let db = [ - { - meta: { - author: { - name: "Alice Bobbera", - email: "a@b.it", - }, - created: "2018-02-03T12:13:14Z", - tags: ["drama", "queen"], - }, - title: "UI components for Dummies", - content: - "Sed doloribus molestias voluptatem ut delectus vitae quo eum. Ut praesentium sed omnis sequi rerum praesentium aperiam modi. Occaecati voluptatum quis vel facere quis quisquam.", - }, - { - meta: { - author: { - name: "Charlie Doran", - email: "c@d.es", - }, - created: "2018-02-02T01:23:45Z", - tags: ["simplicity", "rules"], - }, - title: "Look ma, so simple", - content: - "Ratione necessitatibus doloremque itaque. Nihil hic alias cumque beatae esse sapiente incidunt. Illum vel eveniet officia.", - }, + { + meta: { + author: { + name: "Alice Bobbera", + email: "a@b.it", + }, + created: "2018-02-03T12:13:14Z", + tags: ["drama", "queen"], + }, + title: "UI components for Dummies", + content: + "Sed doloribus molestias voluptatem ut delectus vitae quo eum. Ut praesentium sed omnis sequi rerum praesentium aperiam modi. Occaecati voluptatum quis vel facere quis quisquam.", + }, + { + meta: { + author: { + name: "Charlie Doran", + email: "c@d.es", + }, + created: "2018-02-02T01:23:45Z", + tags: ["simplicity", "rules"], + }, + title: "Look ma, so simple", + content: + "Ratione necessitatibus doloremque itaque. Nihil hic alias cumque beatae esse sapiente incidunt. Illum vel eveniet officia.", + }, ]; // component functions for individual keys in the JSON objects @@ -39,17 +39,17 @@ let db = [ // it's a higher-order function, since we will create different // instances for theming purposes... see below const item = (theme: any) => (item: any) => - [`div.item.${theme}`, item.title, item.meta, item.content]; + [`div.item.${theme}`, item.title, item.meta, item.content]; const meta = (meta: any) => ["div.meta", meta.author, meta.created, meta.tags]; const author = (author: any) => [ - "div", - ["strong", "author: "], - link(`mailto:${author.email}`, author.name), + "div", + ["strong", "author: "], + link(`mailto:${author.email}`, author.name), ]; const date = (iso: string) => [ - "div", - ["strong", "date: "], - new Date(Date.parse(iso)).toLocaleString(), + "div", + ["strong", "date: "], + new Date(Date.parse(iso)).toLocaleString(), ]; const link = (href: string, body: any) => ["a", { href }, body]; const tag = (tag: string) => ["li", link("#", tag)]; @@ -68,16 +68,16 @@ const content = (body: any) => ["div", body]; // giving component functions the same name as their object keys // makes this format very succinct const itemSpec: TransformSubSpec = { - meta: [ - meta, - { - author, - tags, - created: date, - }, - ], - title, - content, + meta: [ + meta, + { + author, + tags, + created: date, + }, + ], + title, + content, }; // build themed component instances using @thi.ng/tranducers' deepTransform() @@ -92,27 +92,27 @@ const itemDark = deepTransform([item("dark"), itemSpec]); // any change to the input should be immediately // reflected in the rendering const editor = (() => { - let body = JSON.stringify(db, null, 2); - return [ - "textarea", - { - oninput: (e: Event) => { - try { - db = JSON.parse((e.target).value); - } catch (_) {} - }, - }, - body, - ]; + let body = JSON.stringify(db, null, 2); + return [ + "textarea", + { + oninput: (e: Event) => { + try { + db = JSON.parse((e.target).value); + } catch (_) {} + }, + }, + body, + ]; })(); // start UI start(() => [ - "div#container", - ["div", editor], - [ - "div", - ["div", ["h2", "Light theme"], ...db.map(itemLight)], - ["div", ["h2", "Dark theme"], ...db.map(itemDark)], - ], + "div#container", + ["div", editor], + [ + "div", + ["div", ["h2", "Light theme"], ...db.map(itemLight)], + ["div", ["h2", "Dark theme"], ...db.map(itemDark)], + ], ]); diff --git a/examples/json-components/tsconfig.json b/examples/json-components/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/json-components/tsconfig.json +++ b/examples/json-components/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/login-form/package.json b/examples/login-form/package.json index 689fe4f1b3..6aa551b97d 100644 --- a/examples/login-form/package.json +++ b/examples/login-form/package.json @@ -1,37 +1,37 @@ { - "name": "@example/login-form", - "private": true, - "description": "Basic SPA example with atom-based UI router", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/expose": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/strings": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "hdom", - "strings" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/login-form", + "private": true, + "description": "Basic SPA example with atom-based UI router", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/expose": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/strings": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "hdom", + "strings" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/login-form/src/index.ts b/examples/login-form/src/index.ts index 235b1d77e0..97310a2b19 100644 --- a/examples/login-form/src/index.ts +++ b/examples/login-form/src/index.ts @@ -6,11 +6,11 @@ import { start } from "@thi.ng/hdom/start"; import { capitalize } from "@thi.ng/strings/case"; interface State { - state: string; - error?: string; - user: { - name?: string; - }; + state: string; + error?: string; + user: { + name?: string; + }; } // central immutable app state @@ -21,12 +21,12 @@ const appState = defView(db, ["state"]); // the error view converts the state value into a UI component array const error = defView(db, ["error"], (error) => - error ? ["div.error", error] : null + error ? ["div.error", error] : null ); // view transformer for the username value const user = defView(db, ["user", "name"], (name) => - name ? capitalize(name) : null + name ? capitalize(name) : null ); // state update functions @@ -41,61 +41,61 @@ const setUser = (e: Event) => setValue(user.path, (e.target).value); const setValue = (path: Path, val: any) => db.resetInUnsafe(path, val); const loginUser = () => { - if (user.deref() === "Admin") { - setError(null); - setState("main"); - } else { - setError("sorry, wrong username (try 'admin')"); - } + if (user.deref() === "Admin") { + setError(null); + setState("main"); + } else { + setError("sorry, wrong username (try 'admin')"); + } }; const logoutUser = () => { - setValue(user.path, null); - setState("logout"); + setValue(user.path, null); + setState("logout"); }; // UI components for different app states // note how the value views are used here const uiViews: any = { - // dummy login form - login: () => [ - "div#login", - ["h1", "Login"], - // embedded error view (will auto-deref) - error.deref(), - ["input", { type: "text", onchange: setUser }], - ["button", { onclick: loginUser }, "Login"], - ], - logout: () => [ - "div#logout", - ["h1", "Good bye"], - "You've been logged out. ", - ["a", { href: "#", onclick: () => setState("login") }, "Log back in?"], - ], - main: () => [ - "div#main", - ["h1", `Welcome, ${user.deref()}!`], - ["div", "Current app state:"], - [ - "div", - [ - "textarea", - { cols: 40, rows: 10 }, - JSON.stringify(db.deref(), null, 2), - ], - ], - ["button", { onclick: logoutUser }, "Logout"], - ], + // dummy login form + login: () => [ + "div#login", + ["h1", "Login"], + // embedded error view (will auto-deref) + error.deref(), + ["input", { type: "text", onchange: setUser }], + ["button", { onclick: loginUser }, "Login"], + ], + logout: () => [ + "div#logout", + ["h1", "Good bye"], + "You've been logged out. ", + ["a", { href: "#", onclick: () => setState("login") }, "Log back in?"], + ], + main: () => [ + "div#main", + ["h1", `Welcome, ${user.deref()}!`], + ["div", "Current app state:"], + [ + "div", + [ + "textarea", + { cols: 40, rows: 10 }, + JSON.stringify(db.deref(), null, 2), + ], + ], + ["button", { onclick: logoutUser }, "Logout"], + ], }; // finally define another derived view for the app state value // including a transformer, which maps the current app state value // to its correct UI component (incl. a fallback for illegal app states) const currView = defView( - db, - ["state"], - (state) => - uiViews[state] || ["div", ["h1", `No component for state: ${state}`]] + db, + ["state"], + (state) => + uiViews[state] || ["div", ["h1", `No component for state: ${state}`]] ); // app root component diff --git a/examples/login-form/tsconfig.json b/examples/login-form/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/login-form/tsconfig.json +++ b/examples/login-form/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/mandelbrot/package.json b/examples/mandelbrot/package.json index 29561e5cb2..096671c308 100644 --- a/examples/mandelbrot/package.json +++ b/examples/mandelbrot/package.json @@ -1,47 +1,47 @@ { - "name": "@example/mandelbrot", - "private": true, - "version": "0.0.1", - "description": "Worker based, interactive Mandelbrot visualization", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/compose": "workspace:^", - "@thi.ng/dl-asset": "workspace:^", - "@thi.ng/equiv": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "dl-asset", - "hdom-components", - "math", - "rstream", - "rstream-gestures", - "transducers", - "transducers-hdom" - ], - "screenshot": "examples/mandelbrot.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/mandelbrot", + "private": true, + "version": "0.0.1", + "description": "Worker based, interactive Mandelbrot visualization", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/compose": "workspace:^", + "@thi.ng/dl-asset": "workspace:^", + "@thi.ng/equiv": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "dl-asset", + "hdom-components", + "math", + "rstream", + "rstream-gestures", + "transducers", + "transducers-hdom" + ], + "screenshot": "examples/mandelbrot.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/mandelbrot/src/gradient.ts b/examples/mandelbrot/src/gradient.ts index f0db2c977d..7225d84217 100644 --- a/examples/mandelbrot/src/gradient.ts +++ b/examples/mandelbrot/src/gradient.ts @@ -11,67 +11,67 @@ import { zip } from "@thi.ng/transducers/zip"; // see http://dev.thi.ng/gradients/ const cosColor = ( - dc: number[], - amp: number[], - fmod: number[], - phase: number[], - t: number + dc: number[], + amp: number[], + fmod: number[], + phase: number[], + t: number ) => - transduce( - map(([a, b, c, d]) => - clamp01(a + b * Math.cos(TAU * (c * t + d))) - ), - push(), - zip(dc, amp, fmod, phase) - ); + transduce( + map(([a, b, c, d]) => + clamp01(a + b * Math.cos(TAU * (c * t + d))) + ), + push(), + zip(dc, amp, fmod, phase) + ); export const cosineGradient = (n: number, spec: number[][]): number[] => { - const [dc, amp, fmod, phase] = spec; - return transduce( - comp( - map((t: number) => cosColor(dc, amp, fmod, phase, t)), - map( - ([r, g, b]) => - ((b * 255) << 16) | - ((g * 255) << 8) | - (r * 255) | - 0xff000000 - ) - ), - push(), - normRange(n - 1) - ); + const [dc, amp, fmod, phase] = spec; + return transduce( + comp( + map((t: number) => cosColor(dc, amp, fmod, phase, t)), + map( + ([r, g, b]) => + ((b * 255) << 16) | + ((g * 255) << 8) | + (r * 255) | + 0xff000000 + ) + ), + push(), + normRange(n - 1) + ); }; export const GRADIENTS = [ - [ - [0.5, 0.5, 0.5], - [0.5, 0.5, 0.5], - [-1.0, -1.0, -1.0], - [0.0, 0.1, 0.2], - ], - [ - [0.5, 0.5, 0.5], - [0.5, 0.5, 0.5], - [0.5, 0.618, 0.5], - [-1.0, 0.828, -0.152], - ], - [ - [0.402, 0.654, 0.247], - [0.835, 0.668, 0.42], - [1.226, 1.553, 1.445], - [2.684, 6.256, 4.065], - ], - [ - [0.5, 0.5, 0.5], - [0.5, 0.5, 0.5], - [0.5, 0.5, 0.5], - [0.5, 0.5, 0.5], - ], - [ - [0.5, 0.5, 0.5], - [1.0, 1.0, 1.0], - [10.0, 10.0, 10.0], - [0.0, 0.0, 0.0], - ], + [ + [0.5, 0.5, 0.5], + [0.5, 0.5, 0.5], + [-1.0, -1.0, -1.0], + [0.0, 0.1, 0.2], + ], + [ + [0.5, 0.5, 0.5], + [0.5, 0.5, 0.5], + [0.5, 0.618, 0.5], + [-1.0, 0.828, -0.152], + ], + [ + [0.402, 0.654, 0.247], + [0.835, 0.668, 0.42], + [1.226, 1.553, 1.445], + [2.684, 6.256, 4.065], + ], + [ + [0.5, 0.5, 0.5], + [0.5, 0.5, 0.5], + [0.5, 0.5, 0.5], + [0.5, 0.5, 0.5], + ], + [ + [0.5, 0.5, 0.5], + [1.0, 1.0, 1.0], + [10.0, 10.0, 10.0], + [0.0, 0.0, 0.0], + ], ].map(partial(cosineGradient, 256)); diff --git a/examples/mandelbrot/src/index.ts b/examples/mandelbrot/src/index.ts index ed8befe4a1..45b7fbf75d 100644 --- a/examples/mandelbrot/src/index.ts +++ b/examples/mandelbrot/src/index.ts @@ -38,48 +38,48 @@ const sel2 = stream(); // main stream combinator const main = sync({ - src: { x1, y1, x2, y2, iter, gradient, sel1, sel2 }, + src: { x1, y1, x2, y2, iter, gradient, sel1, sel2 }, }); // URL hash updater main.subscribe({ - next: ({ x1, y1, x2, y2, iter, gradient }) => - (location.hash = `${ff(x1)};${ff(y1)};${ff(x2)};${ff( - y2 - )};${iter};${gradient}`), + next: ({ x1, y1, x2, y2, iter, gradient }) => + (location.hash = `${ff(x1)};${ff(y1)};${ff(x2)};${ff( + y2 + )};${iter};${gradient}`), }); // update param streams to trigger new render const newRender = ( - a: number, - b: number, - c: number, - d: number, - i?: number, - g?: number + a: number, + b: number, + c: number, + d: number, + i?: number, + g?: number ) => { - x1.next(a); - y1.next(b); - x2.next(c); - y2.next(d); - i !== undefined && iter.next(i); - g !== undefined && gradient.next(g); - // clear selection - sel1.next(null); - sel2.next(null); + x1.next(a); + y1.next(b); + x2.next(c); + y2.next(d); + i !== undefined && iter.next(i); + g !== undefined && gradient.next(g); + // clear selection + sel1.next(null); + sel2.next(null); }; const updateZoom = (zoom: number) => { - let _x1 = x1.deref()!; - let _y1 = y1.deref()!; - let _x2 = x2.deref()!; - let _y2 = y2.deref()!; - newRender( - mix(_x1, _x2, zoom), - mix(_y1, _y2, zoom), - mix(_x2, _x1, zoom), - mix(_y2, _y1, zoom) - ); + let _x1 = x1.deref()!; + let _y1 = y1.deref()!; + let _x2 = x2.deref()!; + let _y2 = y2.deref()!; + newRender( + mix(_x1, _x2, zoom), + mix(_y1, _y2, zoom), + mix(_x2, _x1, zoom), + mix(_y2, _y1, zoom) + ); }; // formatting helper (for URL hash) @@ -87,204 +87,204 @@ const ff = (x: number) => x.toExponential(10); // root component HOF const app = () => { - let img: ImageData; - // canvas HOF component - const canvas = canvas2D({ - // canvas init lifecycle method - init: (el, ctx) => { - // obtain canvas pixel buffer - img = ctx.getImageData(0, 0, el.width, el.height); - // setup render task stream pipeline - // first we combine the various parameter streams - // augment with canvas size - // then submit tuple to worker and copy resulting pixels to canvas + let img: ImageData; + // canvas HOF component + const canvas = canvas2D({ + // canvas init lifecycle method + init: (el, ctx) => { + // obtain canvas pixel buffer + img = ctx.getImageData(0, 0, el.width, el.height); + // setup render task stream pipeline + // first we combine the various parameter streams + // augment with canvas size + // then submit tuple to worker and copy resulting pixels to canvas - // any currently active worker will be terminated and - // restarted with each param change. this is achieved via - // the `interrupt` option and ensures only the most recent - // configuration is being fully executed without having to - // wait for older render tasks to complete... - sync({ src: { x1, y1, x2, y2, iter, gradient } }) - .map((obj) => ({ ...obj, w: el.width, h: el.height })) - .subscribe( - tunnel({ - src: () => new WORKER(), - interrupt: true, - }) - ) - .subscribe({ - next(pix: any) { - img.data.set(new Uint8Array(pix)); - ctx.putImageData(img, 0, 0); - // frame export & auto zoom out - if (AUTO_ZOOM) { - downloadCanvas(el, `frame-${Z4(frame++)}`); - setTimeout(() => updateZoom(-0.02), 100); - } - }, - }); - // also initialize gesture stream for allowing users to draw - // target zoom rectangle - gestureStream(el, { - scale: true, - absZoom: false, - smooth: 1e-3, - }).subscribe({ - next(e) { - const _x1 = x1.deref()!; - const _y1 = y1.deref()!; - const _x2 = x2.deref()!; - const _y2 = y2.deref()!; - switch (e.type) { - case "start": - sel1.next(e.pos); - break; - case "drag": - sel2.next(e.pos); - break; - case "end": { - const p = sel1.deref(); - if (!p || equiv(p, e.pos)) return; - // compute target coord based on current zoom region - let ax = fit(p[0], 0, el.width, _x1, _x2); - let ay = fit(p[1], 0, el.height, _y1, _y2); - let bx = fit(e.pos[0], 0, el.width, _x1, _x2); - let by = fit(e.pos[1], 0, el.height, _y1, _y2); - if (ax > bx) { - const t = ax; - ax = bx; - bx = t; - } - if (ay > by) { - const t = ay; - ay = by; - by = t; - } - const aspect = (bx - ax) / (by - ay); - // adjust aspect ratio of target region - if (aspect > 1) { - by = ay + (bx - ax); - } else if (aspect < 1) { - bx = ax + (by - ay); - } - newRender(ax, ay, bx, by); - break; - } - case "zoom": - updateZoom(e.zoom); - break; - default: - } - }, - }); - // key controls fine tuning region - window.addEventListener("keydown", (e) => { - let _x1 = x1.deref()!; - let _y1 = y1.deref()!; - let _x2 = x2.deref()!; - let _y2 = y2.deref()!; - const amp = e.shiftKey ? 0.1 : 0.01; - const deltaX = (_x2 - _x1) * amp; - const deltaY = (_y2 - _y1) * amp; - switch (e.code) { - case "ArrowDown": - _y1 += deltaY; - _y2 += deltaY; - newRender(_x1, _y1, _x2, _y2); - break; - case "ArrowUp": - _y1 -= deltaY; - _y2 -= deltaY; - newRender(_x1, _y1, _x2, _y2); - break; - case "ArrowLeft": - _x1 -= deltaX; - _x2 -= deltaX; - newRender(_x1, _y1, _x2, _y2); - break; - case "ArrowRight": - _x1 += deltaX; - _x2 += deltaX; - newRender(_x1, _y1, _x2, _y2); - break; - } - }); - }, - // canvas update handler - update: (_, ctx, ...args) => { - const a = args[4]; - const b = args[5]; - ctx.putImageData(img, 0, 0); - // if given, draw zoom rectangle - if (a && b) { - ctx.strokeStyle = "red"; - ctx.strokeRect(a[0], a[1], b[0] - a[0], b[1] - a[1]); - } - }, - }); - // return actual root component function - return ({ sel1, sel2 }: any) => { - return [ - "div.flex-l.sans-serif.f7", - [canvas, { id: "main", width: SIZE, height: SIZE }, sel1, sel2], - [ - "div.pa2.lh-copy", - ["h1.ma0", "Mandelbrot explorer"], - [slider, x1, -2.5, 1, 1e-8, "x1"], - [slider, y1, -1, 1, 1e-8, "y1"], - [slider, x2, -2.5, 1, 1e-8, "x2"], - [slider, y2, -1, 1, 1e-8, "y2"], - [slider, iter, 1, 1000, 1, "iter"], - [slider, gradient, 0, 4, 0, "gradient"], - [ - "button", - { - onclick: () => - newRender.apply(null, DEFAULT_CONFIG), - }, - "reset", - ], - [ - "div", - [ - "ul", - ["li", "Click & drag to draw target zoom rectangle"], - ["li", "Mouse wheel to zoom in / out"], - [ - "li", - "Cursor keys to fine tune region (+ Shift for bigger steps)", - ], - ], - ], - ], - ]; - }; + // any currently active worker will be terminated and + // restarted with each param change. this is achieved via + // the `interrupt` option and ensures only the most recent + // configuration is being fully executed without having to + // wait for older render tasks to complete... + sync({ src: { x1, y1, x2, y2, iter, gradient } }) + .map((obj) => ({ ...obj, w: el.width, h: el.height })) + .subscribe( + tunnel({ + src: () => new WORKER(), + interrupt: true, + }) + ) + .subscribe({ + next(pix: any) { + img.data.set(new Uint8Array(pix)); + ctx.putImageData(img, 0, 0); + // frame export & auto zoom out + if (AUTO_ZOOM) { + downloadCanvas(el, `frame-${Z4(frame++)}`); + setTimeout(() => updateZoom(-0.02), 100); + } + }, + }); + // also initialize gesture stream for allowing users to draw + // target zoom rectangle + gestureStream(el, { + scale: true, + absZoom: false, + smooth: 1e-3, + }).subscribe({ + next(e) { + const _x1 = x1.deref()!; + const _y1 = y1.deref()!; + const _x2 = x2.deref()!; + const _y2 = y2.deref()!; + switch (e.type) { + case "start": + sel1.next(e.pos); + break; + case "drag": + sel2.next(e.pos); + break; + case "end": { + const p = sel1.deref(); + if (!p || equiv(p, e.pos)) return; + // compute target coord based on current zoom region + let ax = fit(p[0], 0, el.width, _x1, _x2); + let ay = fit(p[1], 0, el.height, _y1, _y2); + let bx = fit(e.pos[0], 0, el.width, _x1, _x2); + let by = fit(e.pos[1], 0, el.height, _y1, _y2); + if (ax > bx) { + const t = ax; + ax = bx; + bx = t; + } + if (ay > by) { + const t = ay; + ay = by; + by = t; + } + const aspect = (bx - ax) / (by - ay); + // adjust aspect ratio of target region + if (aspect > 1) { + by = ay + (bx - ax); + } else if (aspect < 1) { + bx = ax + (by - ay); + } + newRender(ax, ay, bx, by); + break; + } + case "zoom": + updateZoom(e.zoom); + break; + default: + } + }, + }); + // key controls fine tuning region + window.addEventListener("keydown", (e) => { + let _x1 = x1.deref()!; + let _y1 = y1.deref()!; + let _x2 = x2.deref()!; + let _y2 = y2.deref()!; + const amp = e.shiftKey ? 0.1 : 0.01; + const deltaX = (_x2 - _x1) * amp; + const deltaY = (_y2 - _y1) * amp; + switch (e.code) { + case "ArrowDown": + _y1 += deltaY; + _y2 += deltaY; + newRender(_x1, _y1, _x2, _y2); + break; + case "ArrowUp": + _y1 -= deltaY; + _y2 -= deltaY; + newRender(_x1, _y1, _x2, _y2); + break; + case "ArrowLeft": + _x1 -= deltaX; + _x2 -= deltaX; + newRender(_x1, _y1, _x2, _y2); + break; + case "ArrowRight": + _x1 += deltaX; + _x2 += deltaX; + newRender(_x1, _y1, _x2, _y2); + break; + } + }); + }, + // canvas update handler + update: (_, ctx, ...args) => { + const a = args[4]; + const b = args[5]; + ctx.putImageData(img, 0, 0); + // if given, draw zoom rectangle + if (a && b) { + ctx.strokeStyle = "red"; + ctx.strokeRect(a[0], a[1], b[0] - a[0], b[1] - a[1]); + } + }, + }); + // return actual root component function + return ({ sel1, sel2 }: any) => { + return [ + "div.flex-l.sans-serif.f7", + [canvas, { id: "main", width: SIZE, height: SIZE }, sel1, sel2], + [ + "div.pa2.lh-copy", + ["h1.ma0", "Mandelbrot explorer"], + [slider, x1, -2.5, 1, 1e-8, "x1"], + [slider, y1, -1, 1, 1e-8, "y1"], + [slider, x2, -2.5, 1, 1e-8, "x2"], + [slider, y2, -1, 1, 1e-8, "y2"], + [slider, iter, 1, 1000, 1, "iter"], + [slider, gradient, 0, 4, 0, "gradient"], + [ + "button", + { + onclick: () => + newRender.apply(null, DEFAULT_CONFIG), + }, + "reset", + ], + [ + "div", + [ + "ul", + ["li", "Click & drag to draw target zoom rectangle"], + ["li", "Mouse wheel to zoom in / out"], + [ + "li", + "Cursor keys to fine tune region (+ Shift for bigger steps)", + ], + ], + ], + ], + ]; + }; }; // slider component which emits value changes on given stream const slider = ( - _: any, - stream: Stream, - min: number, - max: number, - step: number, - label: string + _: any, + stream: Stream, + min: number, + max: number, + step: number, + label: string ) => [ - "div", - ["div", ["strong", `${label}: `], stream.deref()], - [ - "input", - { - type: "range", - style: { width: "50vw" }, - min, - max, - step, - value: stream.deref(), - oninput: (e: Event) => - stream.next(parseFloat((e.target).value)), - }, - ], + "div", + ["div", ["strong", `${label}: `], stream.deref()], + [ + "input", + { + type: "range", + style: { width: "50vw" }, + min, + max, + step, + value: stream.deref(), + oninput: (e: Event) => + stream.next(parseFloat((e.target).value)), + }, + ], ]; // attach root component & DOM update to main stream @@ -292,10 +292,10 @@ main.transform(map(app()), updateDOM()); // init parameter streams, if possible from location.hash newRender.apply( - null, - ( - (location.hash.length > 1 - ? location.hash.substring(1).split(";").map(parseFloat) - : DEFAULT_CONFIG) - ) + null, + ( + (location.hash.length > 1 + ? location.hash.substring(1).split(";").map(parseFloat) + : DEFAULT_CONFIG) + ) ); diff --git a/examples/mandelbrot/src/worker.ts b/examples/mandelbrot/src/worker.ts index 0a2463bb45..bca5c67d66 100644 --- a/examples/mandelbrot/src/worker.ts +++ b/examples/mandelbrot/src/worker.ts @@ -4,38 +4,38 @@ import { GRADIENTS } from "./gradient"; // host message listener & responder const $self: any = self; self.addEventListener("message", (e) => { - console.log(e.data); - const pix = render(e.data); - $self.postMessage(pix.buffer, [pix.buffer]); + console.log(e.data); + const pix = render(e.data); + $self.postMessage(pix.buffer, [pix.buffer]); }); // single pixel fractal evaluation // see: https://en.wikipedia.org/wiki/Mandelbrot_set const mandelbrot = (x0: number, y0: number, n: number) => { - let x = 0; - let y = 0; - let i = 0; - while (i < n && x * x + y * y < 4) { - const t = x * x - y * y + x0; - y = 2 * x * y + y0; - x = t; - i++; - } - return ((i / n) * 255) | 0; + let x = 0; + let y = 0; + let i = 0; + while (i < n && x * x + y * y < 4) { + const t = x * x - y * y + x0; + y = 2 * x * y + y0; + x = t; + i++; + } + return ((i / n) * 255) | 0; }; // generates new fractal image based on given config tuple const render = ({ x1, y1, x2, y2, iter, w, h, gradient }: any) => { - const grad = GRADIENTS[gradient]; - const pix = new Uint32Array(w * h); - for (let y = 0, i = 0; y < h; y++) { - for (let x = 0; x < w; x++) { - // pix[i++] = splat8_24(mandelbrot(fit01(x / w, x1, x2), fit01(y / w, y1, y2), iter, k * x / w)) | 0xff000000; - pix[i++] = - grad[ - mandelbrot(fit01(x / w, x1, x2), fit01(y / w, y1, y2), iter) - ]; - } - } - return pix; + const grad = GRADIENTS[gradient]; + const pix = new Uint32Array(w * h); + for (let y = 0, i = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + // pix[i++] = splat8_24(mandelbrot(fit01(x / w, x1, x2), fit01(y / w, y1, y2), iter, k * x / w)) | 0xff000000; + pix[i++] = + grad[ + mandelbrot(fit01(x / w, x1, x2), fit01(y / w, y1, y2), iter) + ]; + } + } + return pix; }; diff --git a/examples/mandelbrot/tsconfig.json b/examples/mandelbrot/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/mandelbrot/tsconfig.json +++ b/examples/mandelbrot/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/markdown/package.json b/examples/markdown/package.json index 616c946a01..4a63e6eb8e 100644 --- a/examples/markdown/package.json +++ b/examples/markdown/package.json @@ -1,40 +1,40 @@ { - "name": "@example/markdown", - "private": true, - "version": "0.0.1", - "description": "Minimal Markdown to Hiccup to HTML parser / transformer", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/bench": "workspace:^", - "@thi.ng/hiccup-markdown": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "bench", - "fsm", - "hiccup-markdown", - "rstream", - "transducers-hdom" - ], - "screenshot": "examples/markdown-parser.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/markdown", + "private": true, + "version": "0.0.1", + "description": "Minimal Markdown to Hiccup to HTML parser / transformer", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/bench": "workspace:^", + "@thi.ng/hiccup-markdown": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "bench", + "fsm", + "hiccup-markdown", + "rstream", + "transducers-hdom" + ], + "screenshot": "examples/markdown-parser.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/markdown/src/index.ts b/examples/markdown/src/index.ts index b2b5be6b1e..a43140b09b 100644 --- a/examples/markdown/src/index.ts +++ b/examples/markdown/src/index.ts @@ -14,50 +14,50 @@ import readme from "./README.md?url"; // custom tag factories (passed to parser) // uses Tachyons CSS classes for styling const CUSTOM_TAGS: Partial = { - blockquote: (xs) => ["blockquote.pl3.bl.bw2.i.f4.gray", ...xs], - code: (body) => ["code.bg-light-gray.ph1", body], - codeblock: (lang, body) => [ - "pre.bg-washed-yellow.pa3.f7.overflow-x-scroll", - { lang: lang || "code" }, - ["code", body], - ], - link: (href, body) => [ - "a.link.dark-blue.hover-white.hover-bg-dark-blue.b", - { href }, - body, - ], - strike: (body) => ["del.bg-washed-red", body], - table: (xs) => ["table.w-100.collapse.ba.b--black-10", ["tbody", ...xs]], - tr: (_, xs) => ["tr.striped--near-white", ...xs], - td: (i, xs) => [i < 1 ? "th.pa2.ttu.tl" : "td.pa2", ...xs], + blockquote: (xs) => ["blockquote.pl3.bl.bw2.i.f4.gray", ...xs], + code: (body) => ["code.bg-light-gray.ph1", body], + codeblock: (lang, body) => [ + "pre.bg-washed-yellow.pa3.f7.overflow-x-scroll", + { lang: lang || "code" }, + ["code", body], + ], + link: (href, body) => [ + "a.link.dark-blue.hover-white.hover-bg-dark-blue.b", + { href }, + body, + ], + strike: (body) => ["del.bg-washed-red", body], + table: (xs) => ["table.w-100.collapse.ba.b--black-10", ["tbody", ...xs]], + tr: (_, xs) => ["tr.striped--near-white", ...xs], + td: (i, xs) => [i < 1 ? "th.pa2.ttu.tl" : "td.pa2", ...xs], }; // UI root component const app = - (input: Stream) => - ({ src, parsed: [hiccup, time] }: any) => - [ - "div.flex.vh-100.sans-serif.flex-column.flex-row-l", - [ - "div.w-100.h-50.w-50-l.h-100-l", - [ - "textarea.w-100.vh-50.vh-100-l.bg-washed-blue.navy.pa3.f7.code.lh-copy", - { - value: src, - oninput: (e: Event) => - input.next((e.target).value), - }, - ], - ], - [ - "div.w-100.h-50.w-50-l.vh-100-l.overflow-y-scroll.pa3.lh-copy", - [ - "div.pa2.bg-yellow.purple.f7", - `Parsed ${src.length} chars in ${time | 0}ms`, - ], - ...hiccup, - ], - ]; + (input: Stream) => + ({ src, parsed: [hiccup, time] }: any) => + [ + "div.flex.vh-100.sans-serif.flex-column.flex-row-l", + [ + "div.w-100.h-50.w-50-l.h-100-l", + [ + "textarea.w-100.vh-50.vh-100-l.bg-washed-blue.navy.pa3.f7.code.lh-copy", + { + value: src, + oninput: (e: Event) => + input.next((e.target).value), + }, + ], + ], + [ + "div.w-100.h-50.w-50-l.vh-100-l.overflow-y-scroll.pa3.lh-copy", + [ + "div.pa2.bg-yellow.purple.f7", + `Parsed ${src.length} chars in ${time | 0}ms`, + ], + ...hiccup, + ], + ]; // markdown input stream // seed w/ temp input @@ -65,22 +65,22 @@ const src = reactive("# Loading readme..."); // stream transformer & UI update src.transform( - map((src) => ({ - src, - // append exta newline to force last paragraph (see readme) - parsed: timedResult(() => [ - ...iterator(parse(CUSTOM_TAGS), src + "\n"), - ]), - })), - map(app(src)), - updateDOM() + map((src) => ({ + src, + // append exta newline to force last paragraph (see readme) + parsed: timedResult(() => [ + ...iterator(parse(CUSTOM_TAGS), src + "\n"), + ]), + })), + map(app(src)), + updateDOM() ); // load markdown & seed input fetch(readme) - .then((res) => res.text()) - .then((txt) => src.next(txt)) - .catch((e) => src.next(`# Error loading file: ${e}`)); + .then((res) => res.text()) + .then((txt) => src.next(txt)) + .catch((e) => src.next(`# Error loading file: ${e}`)); // // HMR handling // if (process.env.NODE_ENV !== "production") { diff --git a/examples/markdown/tsconfig.json b/examples/markdown/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/markdown/tsconfig.json +++ b/examples/markdown/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/multitouch/package.json b/examples/multitouch/package.json index 5656277390..c333057a9d 100644 --- a/examples/multitouch/package.json +++ b/examples/multitouch/package.json @@ -1,40 +1,40 @@ { - "name": "@example/multitouch", - "private": true, - "version": "0.0.1", - "description": "Basic rstream-gestures multi-touch demo", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/memoize": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom-canvas", - "memoize", - "rstream", - "rstream-gestures", - "transducers-hdom" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/multitouch", + "private": true, + "version": "0.0.1", + "description": "Basic rstream-gestures multi-touch demo", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/memoize": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom-canvas", + "memoize", + "rstream", + "rstream-gestures", + "transducers-hdom" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/multitouch/src/index.ts b/examples/multitouch/src/index.ts index cd4d22c7ed..593848dabd 100644 --- a/examples/multitouch/src/index.ts +++ b/examples/multitouch/src/index.ts @@ -1,9 +1,9 @@ import { canvas } from "@thi.ng/hdom-canvas"; import { memoize1 } from "@thi.ng/memoize/memoize1"; import { - type GestureEvent, - type GestureInfo, - gestureStream, + type GestureEvent, + type GestureInfo, + gestureStream, } from "@thi.ng/rstream-gestures"; import { CloseMode } from "@thi.ng/rstream/api"; import { sync } from "@thi.ng/rstream/sync"; @@ -16,56 +16,56 @@ import { mapcat } from "@thi.ng/transducers/mapcat"; // uses init lifecycle method to attach gesture stream // to `main` stream... const MTCanvas = memoize1((id: string) => { - const _canvas = { - ...canvas, - init(el: HTMLElement) { - main.add(gestureStream(el), el.id); - }, - }; - return (attribs: any, gesture: GestureEvent = {}) => [ - _canvas, - { id, class: "bg-washed-yellow", ...attribs }, - [ - "g", - { fill: "red" }, - // visualize active gestures - ...mapcat( - (i: GestureInfo) => [ - ["circle", { stroke: "#333", fill: "none" }, i.start, 20], - ["circle", {}, i.pos, 20 * (gesture.zoom || 1)], - i.start - ? ["line", { stroke: "#333" }, i.start, i.pos] - : null, - ], - gesture.active || [] - ), - ], - ]; + const _canvas = { + ...canvas, + init(el: HTMLElement) { + main.add(gestureStream(el), el.id); + }, + }; + return (attribs: any, gesture: GestureEvent = {}) => [ + _canvas, + { id, class: "bg-washed-yellow", ...attribs }, + [ + "g", + { fill: "red" }, + // visualize active gestures + ...mapcat( + (i: GestureInfo) => [ + ["circle", { stroke: "#333", fill: "none" }, i.start, 20], + ["circle", {}, i.pos, 20 * (gesture.zoom || 1)], + i.start + ? ["line", { stroke: "#333" }, i.start, i.pos] + : null, + ], + gesture.active || [] + ), + ], + ]; }); // main stream w/ initial trigger input const main = sync({ - src: { temp: trigger() }, - closeIn: CloseMode.NEVER, + src: { temp: trigger() }, + closeIn: CloseMode.NEVER, }); main.transform( - map(({ main }) => [ - "div", - ["h1", "Multitouch"], - [ - "p", - "Click/touch & drag in the yellow area below. Multiple cursors only supported via touch.", - ], - MTCanvas("main")({ width: 480, height: 360 }, main || {}), - [ - "div", - [ - "textarea.code.f7", - { cols: 60, rows: 25 }, - JSON.stringify(main, null, 2), - ], - ], - ]), - updateDOM() + map(({ main }) => [ + "div", + ["h1", "Multitouch"], + [ + "p", + "Click/touch & drag in the yellow area below. Multiple cursors only supported via touch.", + ], + MTCanvas("main")({ width: 480, height: 360 }, main || {}), + [ + "div", + [ + "textarea.code.f7", + { cols: 60, rows: 25 }, + JSON.stringify(main, null, 2), + ], + ], + ]), + updateDOM() ); diff --git a/examples/multitouch/tsconfig.json b/examples/multitouch/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/multitouch/tsconfig.json +++ b/examples/multitouch/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/package-stats/package.json b/examples/package-stats/package.json index 93ceac0b21..808446490e 100644 --- a/examples/package-stats/package.json +++ b/examples/package-stats/package.json @@ -1,43 +1,43 @@ { - "name": "@example/package-stats", - "private": true, - "description": "CLI util to visualize umbrella pkg stats", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "type": "module", - "scripts": { - "build": "tools:node-esm src/index.ts" - }, - "devDependencies": { - "tools": "workspace:^", - "typescript": "^4.7.4" - }, - "dependencies": { - "@thi.ng/checks": "workspace:^", - "@thi.ng/dgraph": "workspace:^", - "@thi.ng/hiccup": "workspace:^", - "@thi.ng/hiccup-svg": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/paths": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "online": false, - "readme": [ - "dgraph", - "hiccup", - "hiccup-svg", - "transducers", - "vectors" - ], - "screenshot": "examples/package-stats.png" - } + "name": "@example/package-stats", + "private": true, + "description": "CLI util to visualize umbrella pkg stats", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "type": "module", + "scripts": { + "build": "tools:node-esm src/index.ts" + }, + "devDependencies": { + "tools": "workspace:^", + "typescript": "^4.7.4" + }, + "dependencies": { + "@thi.ng/checks": "workspace:^", + "@thi.ng/dgraph": "workspace:^", + "@thi.ng/hiccup": "workspace:^", + "@thi.ng/hiccup-svg": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/paths": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "online": false, + "readme": [ + "dgraph", + "hiccup", + "hiccup-svg", + "transducers", + "vectors" + ], + "screenshot": "examples/package-stats.png" + } } diff --git a/examples/package-stats/src/dep-chart.ts b/examples/package-stats/src/dep-chart.ts index 038372daf8..f7db67e4af 100644 --- a/examples/package-stats/src/dep-chart.ts +++ b/examples/package-stats/src/dep-chart.ts @@ -3,18 +3,18 @@ import { DGraph } from "@thi.ng/dgraph"; import { serialize } from "@thi.ng/hiccup"; import { group, text } from "@thi.ng/hiccup-svg"; import { - comp, - filter, - map, - mapcat, - mapIndexed, - max, - pluck, - push, - reducer, - repeat, - transduce, - zip, + comp, + filter, + map, + mapcat, + mapIndexed, + max, + pluck, + push, + reducer, + repeat, + transduce, + zip, } from "@thi.ng/transducers"; import * as fs from "fs"; import { barChart, labeledTickX, labeledTickY } from "./viz.js"; @@ -22,75 +22,75 @@ import { barChart, labeledTickX, labeledTickY } from "./viz.js"; const BASE_DIR = "../../packages/"; const packages: { id: string; v: string; deps: string[] }[] = transduce( - comp( - map((f) => BASE_DIR + f), - filter((f) => fs.statSync(f).isDirectory()), - map((f) => { - try { - return fs.readFileSync(f + "/package.json"); - } catch (_) {} - }), - filter(exists), - map((p) => JSON.parse(p!.toString())), - map((p) => ({ - id: p.name, - v: p.version, - deps: p.dependencies ? Object.keys(p.dependencies) : [], - })) - ), - push(), - fs.readdirSync(BASE_DIR) + comp( + map((f) => BASE_DIR + f), + filter((f) => fs.statSync(f).isDirectory()), + map((f) => { + try { + return fs.readFileSync(f + "/package.json"); + } catch (_) {} + }), + filter(exists), + map((p) => JSON.parse(p!.toString())), + map((p) => ({ + id: p.name, + v: p.version, + deps: p.dependencies ? Object.keys(p.dependencies) : [], + })) + ), + push(), + fs.readdirSync(BASE_DIR) ); const graph = transduce( - mapcat((p: any) => zip(repeat(p.id), p.deps)), - reducer( - () => new DGraph(), - (g, [p, d]: any) => g.addDependency(p, d) - ), - packages + mapcat((p: any) => zip(repeat(p.id), p.deps)), + reducer( + () => new DGraph(), + (g, [p, d]: any) => g.addDependency(p, d) + ), + packages ); const packageDeps = packages - .map((p: any) => [p.id, graph.transitiveDependents(p.id).size]) - .sort((a, b) => b[1] - a[1]); + .map((p: any) => [p.id, graph.transitiveDependents(p.id).size]) + .sort((a, b) => b[1] - a[1]); const maxDeps = transduce(pluck(1), max(), packageDeps); const width = packages.length * 16; fs.writeFileSync( - `package-deps.svg`, - serialize([ - barChart, - { - attribs: { - width: width, - height: 260, - "font-size": "10px", - "font-family": "Iosevka-Term-Light, Menlo, sans-serif", - }, - x: { - axis: [50, width - 15, 170], - domain: [0, packageDeps.length, 1], - range: [50, width - 5], - ticks: [...map((x) => x[0].substring(8), packageDeps)], - label: labeledTickX, - }, - y: { - axis: [170, 10, 35], - domain: [0, maxDeps, 10], - range: [160, 20], - label: labeledTickY(width - 15), - }, - axis: "#666", - fill: "#0cc", - }, - mapIndexed((i, m) => [i, m[1]], packageDeps), - group( - { "font-size": "20px", "text-anchor": "middle" }, - text([width / 2 + 25, 28], "@thi.ng/umbrella internal re-use"), - text([width / 2 + 25, 56], "(transitive dependents)") - ), - ]) + `package-deps.svg`, + serialize([ + barChart, + { + attribs: { + width: width, + height: 260, + "font-size": "10px", + "font-family": "Iosevka-Term-Light, Menlo, sans-serif", + }, + x: { + axis: [50, width - 15, 170], + domain: [0, packageDeps.length, 1], + range: [50, width - 5], + ticks: [...map((x) => x[0].substring(8), packageDeps)], + label: labeledTickX, + }, + y: { + axis: [170, 10, 35], + domain: [0, maxDeps, 10], + range: [160, 20], + label: labeledTickY(width - 15), + }, + axis: "#666", + fill: "#0cc", + }, + mapIndexed((i, m) => [i, m[1]], packageDeps), + group( + { "font-size": "20px", "text-anchor": "middle" }, + text([width / 2 + 25, 28], "@thi.ng/umbrella internal re-use"), + text([width / 2 + 25, 56], "(transitive dependents)") + ), + ]) ); diff --git a/examples/package-stats/src/size-chart.ts b/examples/package-stats/src/size-chart.ts index bb351dad30..74d7d806a5 100644 --- a/examples/package-stats/src/size-chart.ts +++ b/examples/package-stats/src/size-chart.ts @@ -3,13 +3,13 @@ import { group, text } from "@thi.ng/hiccup-svg"; import { defGetterUnsafe } from "@thi.ng/paths"; import { bytes } from "@thi.ng/strings"; import { - comp, - filter, - map, - mapIndexed, - max, - push, - transduce, + comp, + filter, + map, + mapIndexed, + max, + push, + transduce, } from "@thi.ng/transducers"; import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs"; import { barChart, labeledTickX, labeledTickY } from "./viz.js"; @@ -19,72 +19,72 @@ const BASE_DIR = "../../packages/"; const IGNORE_PACKAGES = new Set(["hiccup-carbon-icons"]); const meta = transduce( - comp( - filter((x) => !IGNORE_PACKAGES.has(x)), - map((m: string) => [m, BASE_DIR + m + "/.meta/size.json"]), - filter(([_, path]) => existsSync(path)), - map(([m, path]) => [m, JSON.parse(readFileSync(path).toString())]) - ), - push(), - readdirSync(BASE_DIR) + comp( + filter((x) => !IGNORE_PACKAGES.has(x)), + map((m: string) => [m, BASE_DIR + m + "/.meta/size.json"]), + filter(([_, path]) => existsSync(path)), + map(([m, path]) => [m, JSON.parse(readFileSync(path).toString())]) + ), + push(), + readdirSync(BASE_DIR) ); writeFileSync( - `package-sizes-${new Date().toISOString().substring(0, 10)}.json`, - JSON.stringify(meta, null, 4) + `package-sizes-${new Date().toISOString().substring(0, 10)}.json`, + JSON.stringify(meta, null, 4) ); console.log(meta.length); const fileSizeChart = (stats: any, modType: string, type: string) => { - const get = defGetterUnsafe([1, modType, type]); - stats = [...stats].sort((a, b) => get(b) - get(a)); + const get = defGetterUnsafe([1, modType, type]); + stats = [...stats].sort((a, b) => get(b) - get(a)); - const width = stats.length * 16; + const width = stats.length * 16; - const maxSize = transduce( - map(([_, m]) => m.esm[type]), - max(), - stats - ); + const maxSize = transduce( + map(([_, m]) => m.esm[type]), + max(), + stats + ); - writeFileSync( - `package-sizes-${modType}.svg`, - serialize([ - barChart, - { - attribs: { - width: width, - height: 260, - "font-size": "10px", - "font-family": "Iosevka-Term-Light, Menlo, sans-serif", - }, - x: { - axis: [80, width - 15, 170], - domain: [0, stats.length, 1], - range: [80, width - 5], - ticks: [...map((x: any) => x[0], stats)], - label: labeledTickX, - }, - y: { - axis: [170, 10, 65], - domain: [0, maxSize, 5 * 1024], - range: [160, 20], - label: labeledTickY(width - 15, bytes), - }, - axis: "#666", - fill: "#0cc", - }, - mapIndexed((i, m) => [i, get(m)], stats), - group( - { "font-size": "20px", "text-anchor": "middle" }, - text( - [width / 2 + 40, 28], - `@thi.ng/umbrella package sizes (${modType.toUpperCase()})` - ), - text([width / 2 + 40, 56], `(minified + gzipped)`) - ), - ]) - ); + writeFileSync( + `package-sizes-${modType}.svg`, + serialize([ + barChart, + { + attribs: { + width: width, + height: 260, + "font-size": "10px", + "font-family": "Iosevka-Term-Light, Menlo, sans-serif", + }, + x: { + axis: [80, width - 15, 170], + domain: [0, stats.length, 1], + range: [80, width - 5], + ticks: [...map((x: any) => x[0], stats)], + label: labeledTickX, + }, + y: { + axis: [170, 10, 65], + domain: [0, maxSize, 5 * 1024], + range: [160, 20], + label: labeledTickY(width - 15, bytes), + }, + axis: "#666", + fill: "#0cc", + }, + mapIndexed((i, m) => [i, get(m)], stats), + group( + { "font-size": "20px", "text-anchor": "middle" }, + text( + [width / 2 + 40, 28], + `@thi.ng/umbrella package sizes (${modType.toUpperCase()})` + ), + text([width / 2 + 40, 56], `(minified + gzipped)`) + ), + ]) + ); }; fileSizeChart(meta, "esm", "gzip"); diff --git a/examples/package-stats/src/viz.ts b/examples/package-stats/src/viz.ts index 7fb4fe2ac9..3c9c6e8d7a 100644 --- a/examples/package-stats/src/viz.ts +++ b/examples/package-stats/src/viz.ts @@ -4,95 +4,109 @@ import { map, mapcat, mapIndexed, range } from "@thi.ng/transducers"; // iterator of range mapped tuples: `[mapped, orig]` const mappedRange = ( - from: number, - to: number, - step: number, - start: number, - end: number + from: number, + to: number, + step: number, + start: number, + end: number ) => map((n) => [fit(n, from, to, start, end), n], range(from, to, step)); const mappedTicks = (start: number, end: number, ticks: any[]) => - mapIndexed((i, x) => [fit01(i / ticks.length, start, end), x], 0, ticks); + mapIndexed((i, x) => [fit01(i / ticks.length, start, end), x], 0, ticks); // reusuable axis tick & label combo export const tick = ( - x1: number, - y1: number, - x2: number, - y2: number, - tx: number, - ty: number, - label: any + x1: number, + y1: number, + x2: number, + y2: number, + tx: number, + ty: number, + label: any ) => [line([x1, y1], [x2, y2]), text([tx, ty], label, { stroke: "none" })]; // mapping fn for x-axis ticks -const tickX = (y: number) => ([x, n]: [number, any]) => - tick(x, y, x, y + 10, x, y + 20, n); +const tickX = + (y: number) => + ([x, n]: [number, any]) => + tick(x, y, x, y + 10, x, y + 20, n); // mapping fn for y-axis ticks -const tickY = (x: number) => ([y, n]: [number, any]) => - tick(x - 10, y, x, y, x - 15, y, n); +const tickY = + (x: number) => + ([y, n]: [number, any]) => + tick(x - 10, y, x, y, x - 15, y, n); -export const labeledTickX = (y: number) => ([x, n]: any[]) => [ - line([x, y], [x, y + 5]), - text([x, y + 15], n, { - stroke: "none", - "text-anchor": "end", - transform: `rotate(-45 ${x} ${y + 15})`, - }), -]; +export const labeledTickX = + (y: number) => + ([x, n]: any[]) => + [ + line([x, y], [x, y + 5]), + text([x, y + 15], n, { + stroke: "none", + "text-anchor": "end", + transform: `rotate(-45 ${x} ${y + 15})`, + }), + ]; -export const labeledTickY = (width: number, fmt = (x: number) => String(x)) => ( - x: number -) => ([y, n]: [number, any]) => [ - ...tick(x - 5, y, x, y, x - 10, y + 4, n > 0 ? fmt(n) : 0), - n > 0 ? line([x + 20, y], [width, y], { "stroke-dasharray": "1 3" }) : null, -]; +export const labeledTickY = + (width: number, fmt = (x: number) => String(x)) => + (x: number) => + ([y, n]: [number, any]) => + [ + ...tick(x - 5, y, x, y, x - 10, y + 4, n > 0 ? fmt(n) : 0), + n > 0 + ? line([x + 20, y], [width, y], { "stroke-dasharray": "1 3" }) + : null, + ]; // x-axis with ticks as SVG group export const axisX = ({ axis: a, domain: d, range: r, label, ticks }: any) => [ - "g", - { "text-anchor": "middle" }, - line([a[0], a[2]], [a[1], a[2]]), - mapcat( - (label || tickX)(a[2]), - ticks - ? mappedTicks(r[0], r[1], ticks) - : mappedRange(d[0], d[1], d[2], r[0], r[1]) - ), + "g", + { "text-anchor": "middle" }, + line([a[0], a[2]], [a[1], a[2]]), + mapcat( + (label || tickX)(a[2]), + ticks + ? mappedTicks(r[0], r[1], ticks) + : mappedRange(d[0], d[1], d[2], r[0], r[1]) + ), ]; // y-axis with ticks as SVG group export const axisY = ({ axis: a, domain: d, range: r, label, ticks }: any) => [ - "g", - { "text-anchor": "end" }, - line([a[2], a[0]], [a[2], a[1]]), - mapcat( - (label || tickY)(a[2]), - ticks - ? mappedTicks(r[0], r[1], ticks) - : mappedRange(d[0], d[1], d[2], r[0], r[1]) - ), + "g", + { "text-anchor": "end" }, + line([a[2], a[0]], [a[2], a[1]]), + mapcat( + (label || tickY)(a[2]), + ticks + ? mappedTicks(r[0], r[1], ticks) + : mappedRange(d[0], d[1], d[2], r[0], r[1]) + ), ]; // mapping fn to create a single bar from `[domainPos, value]` -const bar = ( - { domain: xd, range: xr }: any, - { domain: yd, range: yr }: any -) => ([xx, yy]: any) => { - const y = fit(yy, yd[0], yd[1], yr[0], yr[1]); - return rect([fit(xx, xd[0], xd[1], xr[0], xr[1]) - 5, y], 10, yr[0] - y); -}; +const bar = + ({ domain: xd, range: xr }: any, { domain: yd, range: yr }: any) => + ([xx, yy]: any) => { + const y = fit(yy, yd[0], yd[1], yr[0], yr[1]); + return rect( + [fit(xx, xd[0], xd[1], xr[0], xr[1]) - 5, y], + 10, + yr[0] - y + ); + }; // complete bar chart component export const barChart = (_: any, opts: any, values: any, ...xs: any) => - svg( - opts.attribs, - group( - { stroke: opts.axis, fill: opts.axis }, - axisX(opts.x), - axisY(opts.y) - ), - group({ fill: opts.fill }, map(bar(opts.x, opts.y), values)), - ...xs - ); + svg( + opts.attribs, + group( + { stroke: opts.axis, fill: opts.axis }, + axisX(opts.x), + axisY(opts.y) + ), + group({ fill: opts.fill }, map(bar(opts.x, opts.y), values)), + ...xs + ); diff --git a/examples/package-stats/tsconfig.json b/examples/package-stats/tsconfig.json index 8e6b5ba329..ab66b8b7eb 100644 --- a/examples/package-stats/tsconfig.json +++ b/examples/package-stats/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": ".", - "noUnusedLocals": false, - "noUnusedParameters": false - }, - "include": ["src/**/*"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "noUnusedLocals": false, + "noUnusedParameters": false + }, + "include": ["src/**/*"] } diff --git a/examples/parse-playground/package.json b/examples/parse-playground/package.json index 5fedce8c82..fa261486b2 100644 --- a/examples/parse-playground/package.json +++ b/examples/parse-playground/package.json @@ -1,53 +1,53 @@ { - "name": "@example/parse-playground", - "private": true, - "version": "0.0.1", - "description": "Parser grammar livecoding editor/playground & codegen", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/bench": "workspace:^", - "@thi.ng/dl-asset": "workspace:^", - "@thi.ng/hiccup-carbon-icons": "workspace:^", - "@thi.ng/hiccup-html": "workspace:^", - "@thi.ng/parse": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/rdom-components": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-binary": "workspace:^", - "@ygoe/msgpack": "^1.0.3" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "bench", - "dl-asset", - "hiccup-carbon-icons", - "hiccup-html", - "parse", - "rdom", - "rdom-components", - "rstream", - "transducers", - "transducers-binary" - ], - "screenshot": "examples/parse-playground.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/parse-playground", + "private": true, + "version": "0.0.1", + "description": "Parser grammar livecoding editor/playground & codegen", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/bench": "workspace:^", + "@thi.ng/dl-asset": "workspace:^", + "@thi.ng/hiccup-carbon-icons": "workspace:^", + "@thi.ng/hiccup-html": "workspace:^", + "@thi.ng/parse": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/rdom-components": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-binary": "workspace:^", + "@ygoe/msgpack": "^1.0.3" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "bench", + "dl-asset", + "hiccup-carbon-icons", + "hiccup-html", + "parse", + "rdom", + "rdom-components", + "rstream", + "transducers", + "transducers-binary" + ], + "screenshot": "examples/parse-playground.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/parse-playground/src/api.ts b/examples/parse-playground/src/api.ts index 9bb083b6ff..85152f01e4 100644 --- a/examples/parse-playground/src/api.ts +++ b/examples/parse-playground/src/api.ts @@ -1,19 +1,19 @@ export type Status = "ok" | "partial" | "fail" | "err"; export interface State { - grammar: string; - input: string; - rule: string; + grammar: string; + input: string; + rule: string; } export interface ParseResult { - status: Status; - body: string; - time?: number; + status: Status; + body: string; + time?: number; } export interface CodeTemplate { - name: string; - ext: string; - code: string; + name: string; + ext: string; + code: string; } diff --git a/examples/parse-playground/src/config.ts b/examples/parse-playground/src/config.ts index 5269ac1280..fe6a79227d 100644 --- a/examples/parse-playground/src/config.ts +++ b/examples/parse-playground/src/config.ts @@ -7,10 +7,10 @@ export const DOC_URL = REPO_URL + "packages/parse#readme"; export const SRC_URL = REPO_URL + "examples/parse-playground"; export const CODE_TEMPLATES: IObjectOf = { - js: { - name: "JavaScript (ESM)", - ext: "js", - code: `// Downloaded @ {1} + js: { + name: "JavaScript (ESM)", + ext: "js", + code: `// Downloaded @ {1} // Source: {0} import { defContext } from "@thi.ng/parse/context"; @@ -22,12 +22,12 @@ export const parse = (src, opts) => { const ctx = defContext(src, opts); return { result: lang.rules.{3}(ctx), ctx }; };`, - }, + }, - ts: { - name: "TypeScript", - ext: "ts", - code: `// Downloaded @ {1} + ts: { + name: "TypeScript", + ext: "ts", + code: `// Downloaded @ {1} // Source: {0} import type { ContextOpts } from "@thi.ng/parse"; @@ -40,7 +40,7 @@ export const parse = (src: string, opts?: Partial) => { const ctx = defContext(src, opts); return { result: lang!.rules.{3}(ctx), ctx }; };`, - }, + }, }; export const DEFAULT_GRAMMAR = `# grammar rules... @@ -53,36 +53,36 @@ main: => hoist ; export const DEFAULT_RULE = "main"; export const DEFAULT_INPUTS = [ - `(def hello (x) (str "hello, " x)) + `(def hello (x) (str "hello, " x)) (print (hello -12.3))`, - "(hello world)", - "intentionally left blank", + "(hello world)", + "intentionally left blank", ]; export const LINK_CLASSES = "link blue"; export const BUTTON_CLASSES = - "pa2 db w-100 bg-blue hover-bg-dark-blue bg-animate white bn pointer"; + "pa2 db w-100 bg-blue hover-bg-dark-blue bg-animate white bn pointer"; export const PANEL_CLASSES = "w-100 pa2 code f7 lh-copy bg-animate"; export const EDITOR_OPTS: Partial = { - wrapper: { class: "relative" }, - editor: { attribs: { class: PANEL_CLASSES + " editor", rows: 16 } }, - cursor: { - attribs: { - class: "absolute top-0 right-0 z1 pa2 br3 br--left br--bottom bg-light-gray gray tr f7", - }, - }, + wrapper: { class: "relative" }, + editor: { attribs: { class: PANEL_CLASSES + " editor", rows: 16 } }, + cursor: { + attribs: { + class: "absolute top-0 right-0 z1 pa2 br3 br--left br--bottom bg-light-gray gray tr f7", + }, + }, }; export const DROPDOWN_ATTRIBS = { class: "db w-100 mb2 pa2" }; export const BG_COLS: Record = { - ok: "bg-washed-green dark-green", - partial: "bg-washed-yellow orange", - fail: "bg-washed-red dark-red", - err: "bg-light-red white", + ok: "bg-washed-green dark-green", + partial: "bg-washed-yellow orange", + fail: "bg-washed-red dark-red", + err: "bg-light-red white", }; export const TAB_CLASSES = ".dib.w3.pa2.mr2.br3.br--top.pointer"; diff --git a/examples/parse-playground/src/index.ts b/examples/parse-playground/src/index.ts index 43b4e1a5fe..890abe98ba 100644 --- a/examples/parse-playground/src/index.ts +++ b/examples/parse-playground/src/index.ts @@ -12,8 +12,8 @@ import { defContext } from "@thi.ng/parse/context"; import { defGrammar } from "@thi.ng/parse/grammar"; import { print } from "@thi.ng/parse/xform/print"; import { - dynamicDropdown, - staticDropdown, + dynamicDropdown, + staticDropdown, } from "@thi.ng/rdom-components/dropdown"; import { editor } from "@thi.ng/rdom-components/editor"; import { iconButton } from "@thi.ng/rdom-components/icon-button"; @@ -33,27 +33,27 @@ import { range } from "@thi.ng/transducers/range"; import { deserialize, serialize } from "@ygoe/msgpack"; import type { ParseResult, Status } from "./api"; import { - BG_COLS, - BUTTON_CLASSES, - CODE_TEMPLATES, - DEFAULT_GRAMMAR, - DEFAULT_INPUTS, - DEFAULT_RULE, - DOC_URL, - DROPDOWN_ATTRIBS, - EDITOR_OPTS, - LINK_CLASSES, - PANEL_CLASSES, - SRC_URL, - TAB_CLASSES, + BG_COLS, + BUTTON_CLASSES, + CODE_TEMPLATES, + DEFAULT_GRAMMAR, + DEFAULT_INPUTS, + DEFAULT_RULE, + DOC_URL, + DROPDOWN_ATTRIBS, + EDITOR_OPTS, + LINK_CLASSES, + PANEL_CLASSES, + SRC_URL, + TAB_CLASSES, } from "./config"; // attempt to restore app state from URI hash fragment // this uses a base64 & msgpack encoded version of the two editors const parseState = ((): Nullable => { - try { - return deserialize(base64Decode(location.hash.substring(1))); - } catch (e) {} + try { + return deserialize(base64Decode(location.hash.substring(1))); + } catch (e) {} })() || [DEFAULT_GRAMMAR, DEFAULT_RULE, ...DEFAULT_INPUTS]; // init reactive stream values from parsed state (or defaults) the @@ -61,7 +61,7 @@ const parseState = ((): Nullable => { // streams to remain active, even if there're no current subscribers... // (usually a stream terminates when its last subscriber has unsubscribed) const [srcGrammar, activeRule, ...srcInputs] = parseState.map((src) => - reactive(src, { closeOut: CloseMode.NEVER }) + reactive(src, { closeOut: CloseMode.NEVER }) ); console.log(srcGrammar.deref()); console.log(srcInputs[0].deref()); @@ -77,216 +77,216 @@ const activeInput = inputID.subscribe(metaStream((id) => srcInputs[id])); // stream transform attempting to compile grammar const lang = srcGrammar.map( - (src): Partial<{ lang: Language; error: Error }> => { - try { - return { lang: defGrammar(src) }; - } catch (e) { - return { error: e }; - } - } + (src): Partial<{ lang: Language; error: Error }> => { + try { + return { lang: defGrammar(src) }; + } catch (e) { + return { error: e }; + } + } ); // stream transform to extract parser rule IDs const ruleIDs = lang.transform( - filter((l) => !!l.lang), - map((l: any) => [ - "Choose parser...", - ...Object.keys(l.lang.rules).filter((x) => /^[a-z0-9._$-]+$/.test(x)), - ]) + filter((l) => !!l.lang), + map((l: any) => [ + "Choose parser...", + ...Object.keys(l.lang.rules).filter((x) => /^[a-z0-9._$-]+$/.test(x)), + ]) ); const $result = (status: Status, body: string, time?: number): ParseResult => ({ - status, - body, - time, + status, + body, + time, }); // stream combinator attempting to parse test input and if successful // traverse & prettyprint result AST const result = sync({ - src: { - lang, - src: activeInput, - rule: activeRule, - }, + src: { + lang, + src: activeInput, + rule: activeRule, + }, }).map(({ lang, src, rule }): ParseResult => { - // error if no valid grammar - if (!lang.lang) return $result("err", lang.error!.message); - const parser = lang.lang.rules[rule]; - if (!parser) return $result("err", `invalid or missing parser: ${rule}`); - try { - const ast: string[] = []; - const ctx = defContext(src, { retain: true }); - // measure execution time of the parsing process - const [res, time] = timedResult(() => - print(parser, (x) => ast.push(x))(ctx) - ); - const body = ast.join("\n"); - return res - ? ctx.done - ? $result("ok", body, time) - : $result( - "partial", - `partial match only (stopped @ ${ctx.state.l}:${ctx.state.c})...\n\n${body}`, - time - ) - : $result( - "fail", - `input parse failure (no match)...\n\n${body}`, - time - ); - } catch (e) { - return $result("err", `Parse error: ${e}`); - } + // error if no valid grammar + if (!lang.lang) return $result("err", lang.error!.message); + const parser = lang.lang.rules[rule]; + if (!parser) return $result("err", `invalid or missing parser: ${rule}`); + try { + const ast: string[] = []; + const ctx = defContext(src, { retain: true }); + // measure execution time of the parsing process + const [res, time] = timedResult(() => + print(parser, (x) => ast.push(x))(ctx) + ); + const body = ast.join("\n"); + return res + ? ctx.done + ? $result("ok", body, time) + : $result( + "partial", + `partial match only (stopped @ ${ctx.state.l}:${ctx.state.c})...\n\n${body}`, + time + ) + : $result( + "fail", + `input parse failure (no match)...\n\n${body}`, + time + ); + } catch (e) { + return $result("err", `Parse error: ${e}`); + } }); // update URL hash fragment with the msgpack & base64 encoded version of // the selected parser rule and contents of all editors sync({ - src: { grammar: srcGrammar, rule: activeRule, _: activeInput }, + src: { grammar: srcGrammar, rule: activeRule, _: activeInput }, }).subscribe({ - next({ grammar, rule }) { - const hash = base64Encode( - { safe: true }, - serialize([grammar, rule, ...srcInputs.map((i) => i.deref())]) - ).replace(/=/g, ""); - location.hash = - hash.length < 0x10000 ? hash : "content-too-large-for-uri-hash"; - }, + next({ grammar, rule }) { + const hash = base64Encode( + { safe: true }, + serialize([grammar, rule, ...srcInputs.map((i) => i.deref())]) + ).replace(/=/g, ""); + location.hash = + hash.length < 0x10000 ? hash : "content-too-large-for-uri-hash"; + }, }); // derives CSS classes from parse result type const formatStatus = (res: ParseResult) => - PANEL_CLASSES + " " + BG_COLS[res.status]; + PANEL_CLASSES + " " + BG_COLS[res.status]; // formats parse duration value const formatTime = (res: ParseResult) => - `parsed in: ${res.time != null ? ~~res.time + "ms" : "n/a"}`; + `parsed in: ${res.time != null ? ~~res.time + "ms" : "n/a"}`; // takes a template ID and grammar src, generates source code and // triggers file download const downloadParser = (tplID: string, src: string) => { - const { ext, code } = CODE_TEMPLATES[tplID]; - downloadWithMime( - `parser.${ext}`, - interpolate( - code, - location.href, - new Date().toISOString(), - src.trim().replace(/\\/g, "\\\\").replace(/`/g, "\\`"), - activeRule.deref()! - ), - { mime: "text/plain", utf8: true } - ); + const { ext, code } = CODE_TEMPLATES[tplID]; + downloadWithMime( + `parser.${ext}`, + interpolate( + code, + location.href, + new Date().toISOString(), + src.trim().replace(/\\/g, "\\\\").replace(/`/g, "\\`"), + activeRule.deref()! + ), + { mime: "text/plain", utf8: true } + ); }; // simple styled link component const link = (href: string, label: string) => - anchor({ class: LINK_CLASSES, target: "_blank", href }, label); + anchor({ class: LINK_CLASSES, target: "_blank", href }, label); // compile entire UI and mount in DOM $compile( - div( - {}, - h1(".ma0.fw2", {}, "Let's make a parser... "), - div( - ".mb2", - {}, - link(DOC_URL, "Documentation"), - " / ", - link(SRC_URL, "Source code") - ), - main( - {}, - // grammar editor - div( - {}, - editor(srcGrammar, { - ...EDITOR_OPTS, - // override wrapper style for this editor - wrapper: { class: "relative mt4-l" }, - }) - ), - // test input editor - div( - {}, - // tabbed content component (here to wrap multiple editors for - // test inputs) - tabs(inputID, { - // facctory function for single tab headings - head: (_, title, id, selected) => - div( - TAB_CLASSES, - { - // the class attrib is defined as object of - // booleans here, where each key's value - // indicates if that class should be used or not - // the classes listed here will be merged with - // the `TAB_CLASSES` given above - class: { - "bg-white black": selected, - "bg-moon-gray gray": !selected, - }, - // all tab headers should have an onclick - // handler (unless you want to disable selecting - // tabs in some cases) - onclick: () => inputID.next(id), - }, - title - ), - // array of tab specs - // the `content` fn should return a `ComponentLike` data structure - // it's an async fn to support lazy & dynamic import() of tab contents - sections: [ - ...map( - (i) => ({ - title: `#${i + 1}`, - content: async () => - editor(srcInputs[i], EDITOR_OPTS), - }), - range(DEFAULT_INPUTS.length) - ), - ], - }) - ), - // AST output - div( - EDITOR_OPTS.wrapper, - textArea({ - class: result.map(formatStatus), - value: result.transform(pluck("body")), - disabled: true, - rows: 16, - }), - div(EDITOR_OPTS.cursor!.attribs, result.map(formatTime)) - ), - // user controls - div( - ".w-100", - {}, - // reactive dropdown of user defined parser rules - dynamicDropdown(ruleIDs, activeRule, { - attribs: DROPDOWN_ATTRIBS, - }), - // static dropdown of code generator templates - staticDropdown(Object.keys(CODE_TEMPLATES), activeTpl, { - attribs: DROPDOWN_ATTRIBS, - label: (id) => CODE_TEMPLATES[id].name, - }), - // download button - iconButton({ - attribs: { - class: BUTTON_CLASSES, - onclick: () => - downloadParser( - activeTpl.deref()!, - srcGrammar.deref()! - ), - }, - icon: withSize(DOWNLOAD, "12px"), - label: "Download parser", - }) - ) - ) - ) + div( + {}, + h1(".ma0.fw2", {}, "Let's make a parser... "), + div( + ".mb2", + {}, + link(DOC_URL, "Documentation"), + " / ", + link(SRC_URL, "Source code") + ), + main( + {}, + // grammar editor + div( + {}, + editor(srcGrammar, { + ...EDITOR_OPTS, + // override wrapper style for this editor + wrapper: { class: "relative mt4-l" }, + }) + ), + // test input editor + div( + {}, + // tabbed content component (here to wrap multiple editors for + // test inputs) + tabs(inputID, { + // facctory function for single tab headings + head: (_, title, id, selected) => + div( + TAB_CLASSES, + { + // the class attrib is defined as object of + // booleans here, where each key's value + // indicates if that class should be used or not + // the classes listed here will be merged with + // the `TAB_CLASSES` given above + class: { + "bg-white black": selected, + "bg-moon-gray gray": !selected, + }, + // all tab headers should have an onclick + // handler (unless you want to disable selecting + // tabs in some cases) + onclick: () => inputID.next(id), + }, + title + ), + // array of tab specs + // the `content` fn should return a `ComponentLike` data structure + // it's an async fn to support lazy & dynamic import() of tab contents + sections: [ + ...map( + (i) => ({ + title: `#${i + 1}`, + content: async () => + editor(srcInputs[i], EDITOR_OPTS), + }), + range(DEFAULT_INPUTS.length) + ), + ], + }) + ), + // AST output + div( + EDITOR_OPTS.wrapper, + textArea({ + class: result.map(formatStatus), + value: result.transform(pluck("body")), + disabled: true, + rows: 16, + }), + div(EDITOR_OPTS.cursor!.attribs, result.map(formatTime)) + ), + // user controls + div( + ".w-100", + {}, + // reactive dropdown of user defined parser rules + dynamicDropdown(ruleIDs, activeRule, { + attribs: DROPDOWN_ATTRIBS, + }), + // static dropdown of code generator templates + staticDropdown(Object.keys(CODE_TEMPLATES), activeTpl, { + attribs: DROPDOWN_ATTRIBS, + label: (id) => CODE_TEMPLATES[id].name, + }), + // download button + iconButton({ + attribs: { + class: BUTTON_CLASSES, + onclick: () => + downloadParser( + activeTpl.deref()!, + srcGrammar.deref()! + ), + }, + icon: withSize(DOWNLOAD, "12px"), + label: "Download parser", + }) + ) + ) + ) ).mount(document.body); diff --git a/examples/parse-playground/tsconfig.json b/examples/parse-playground/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/parse-playground/tsconfig.json +++ b/examples/parse-playground/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/pixel-basics/package.json b/examples/pixel-basics/package.json index 0dc596ed61..90615b6e5f 100644 --- a/examples/pixel-basics/package.json +++ b/examples/pixel-basics/package.json @@ -1,31 +1,31 @@ { - "name": "@example/pixel-basics", - "private": true, - "description": "Pixel buffer manipulations", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/pixel": "workspace:^", - "@thi.ng/porter-duff": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true, - "screenshot": "pixel/pixel-basics.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/pixel-basics", + "private": true, + "description": "Pixel buffer manipulations", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/pixel": "workspace:^", + "@thi.ng/porter-duff": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true, + "screenshot": "pixel/pixel-basics.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/pixel-basics/src/index.ts b/examples/pixel-basics/src/index.ts index 5f36a36d5e..d24aac9000 100644 --- a/examples/pixel-basics/src/index.ts +++ b/examples/pixel-basics/src/index.ts @@ -8,46 +8,46 @@ import IMG from "./haystack.jpg"; import LOGO from "./logo-64.png"; Promise.all([IMG, LOGO].map((x) => imagePromise(x))).then(([img, logo]) => { - // init 16bit int RGB pixel buffer from image (resized to 256x256) - const buf = intBufferFromImage(img, RGB565, 256, 256); - - // create grayscale buffer for logo and use Porter-Duff operator to - // composite with main image. Since the logo has transparency, we - // need to premultiply alpha first... - intBufferFromImage(logo, GRAY_ALPHA8).premultiply().blend(SRC_OVER_I, buf, { - dx: 10, - dy: 10, - }); - - // extract sub-image - const region = buf.getRegion(32, 96, 128, 64); - // copy region back at new position - region.blit(buf, { dx: 96, dy: 32 }); - - // or alternatively blit buf into itself - // buf.blit(buf, { dx: 96, dy: 32, sx: 32, sy: 96, w: 128, h: 64 }); - - // create html canvas - // (returns obj of canvas & 2d context) - const ctx = canvas2d(buf.width, buf.height * 3); - - // write pixel buffer to canvas - buf.blitCanvas(ctx.canvas); - - // manipulate single color channel - const id = 0; - const ch = buf.getChannel(id).invert(); - for (let y = 0; y < ch.height; y += 2) { - for (let x = (y >> 1) & 1; x < ch.width; x += 2) { - ch.setAt(x, y, 0xff); - } - } - // replace original channel - buf.setChannel(id, ch); - // write pixel buffer to new position - buf.blitCanvas(ctx.canvas, 0, buf.height); - // create & write grayscale version - buf.as(GRAY8).blitCanvas(ctx.canvas, 0, buf.height * 2); - - document.body.appendChild(ctx.canvas); + // init 16bit int RGB pixel buffer from image (resized to 256x256) + const buf = intBufferFromImage(img, RGB565, 256, 256); + + // create grayscale buffer for logo and use Porter-Duff operator to + // composite with main image. Since the logo has transparency, we + // need to premultiply alpha first... + intBufferFromImage(logo, GRAY_ALPHA8).premultiply().blend(SRC_OVER_I, buf, { + dx: 10, + dy: 10, + }); + + // extract sub-image + const region = buf.getRegion(32, 96, 128, 64); + // copy region back at new position + region.blit(buf, { dx: 96, dy: 32 }); + + // or alternatively blit buf into itself + // buf.blit(buf, { dx: 96, dy: 32, sx: 32, sy: 96, w: 128, h: 64 }); + + // create html canvas + // (returns obj of canvas & 2d context) + const ctx = canvas2d(buf.width, buf.height * 3); + + // write pixel buffer to canvas + buf.blitCanvas(ctx.canvas); + + // manipulate single color channel + const id = 0; + const ch = buf.getChannel(id).invert(); + for (let y = 0; y < ch.height; y += 2) { + for (let x = (y >> 1) & 1; x < ch.width; x += 2) { + ch.setAt(x, y, 0xff); + } + } + // replace original channel + buf.setChannel(id, ch); + // write pixel buffer to new position + buf.blitCanvas(ctx.canvas, 0, buf.height); + // create & write grayscale version + buf.as(GRAY8).blitCanvas(ctx.canvas, 0, buf.height * 2); + + document.body.appendChild(ctx.canvas); }); diff --git a/examples/pixel-basics/tsconfig.json b/examples/pixel-basics/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/pixel-basics/tsconfig.json +++ b/examples/pixel-basics/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/pixel-dither/package.json b/examples/pixel-dither/package.json index 001ff602c7..1739f171b9 100644 --- a/examples/pixel-dither/package.json +++ b/examples/pixel-dither/package.json @@ -1,35 +1,35 @@ { - "name": "@example/pixel-dither", - "private": true, - "version": "0.0.1", - "description": "Showcase of various dithering algorithms", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/pixel": "workspace:^", - "@thi.ng/pixel-dither": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "pixel", - "pixel-dither" - ], - "screenshot": "examples/pixel-dither.jpg" - } + "name": "@example/pixel-dither", + "private": true, + "version": "0.0.1", + "description": "Showcase of various dithering algorithms", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/pixel": "workspace:^", + "@thi.ng/pixel-dither": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "pixel", + "pixel-dither" + ], + "screenshot": "examples/pixel-dither.jpg" + } } diff --git a/examples/pixel-dither/src/index.ts b/examples/pixel-dither/src/index.ts index 0d3c11bcb7..e86ea94d86 100644 --- a/examples/pixel-dither/src/index.ts +++ b/examples/pixel-dither/src/index.ts @@ -1,17 +1,17 @@ import type { IObjectOf } from "@thi.ng/api"; import { GRAY8 } from "@thi.ng/pixel"; import { - ATKINSON, - BURKES, - DIFFUSION_2D, - DIFFUSION_COLUMN, - DIFFUSION_ROW, - ditherWith, - FLOYD_STEINBERG, - JARVIS_JUDICE_NINKE, - SIERRA2, - STUCKI, - THRESHOLD, + ATKINSON, + BURKES, + DIFFUSION_2D, + DIFFUSION_COLUMN, + DIFFUSION_ROW, + ditherWith, + FLOYD_STEINBERG, + JARVIS_JUDICE_NINKE, + SIERRA2, + STUCKI, + THRESHOLD, } from "@thi.ng/pixel-dither"; import type { DitherKernel } from "@thi.ng/pixel-dither/api"; import { canvas2d, imagePromise } from "@thi.ng/pixel/canvas"; @@ -19,38 +19,38 @@ import { IntBuffer, intBufferFromImage } from "@thi.ng/pixel/int"; import IMG from "./michelangelo.png"; (async () => { - const img = await imagePromise(IMG); + const img = await imagePromise(IMG); - const root = document.getElementById("app")!; - root.appendChild(img); + const root = document.getElementById("app")!; + root.appendChild(img); - const processImage = (buf: IntBuffer, id: string, kernel: DitherKernel) => { - const { canvas, ctx } = canvas2d(buf.width, buf.height, root); - ditherWith(kernel, buf.copy()).blitCanvas(canvas); - ctx.fillStyle = "white"; - ctx.fillRect(0, buf.height - 12, ctx.measureText(id).width + 8, 12); - ctx.fillStyle = "red"; - ctx.fillText(id, 4, buf.height - 2); - }; + const processImage = (buf: IntBuffer, id: string, kernel: DitherKernel) => { + const { canvas, ctx } = canvas2d(buf.width, buf.height, root); + ditherWith(kernel, buf.copy()).blitCanvas(canvas); + ctx.fillStyle = "white"; + ctx.fillRect(0, buf.height - 12, ctx.measureText(id).width + 8, 12); + ctx.fillStyle = "red"; + ctx.fillText(id, 4, buf.height - 2); + }; - const buf = intBufferFromImage(img, GRAY8); + const buf = intBufferFromImage(img, GRAY8); - Object.entries(>{ - ATKINSON: ATKINSON, - BURKES: BURKES, - DIFFUSION_ROW: DIFFUSION_ROW, - DIFFUSION_COLUMN: DIFFUSION_COLUMN, - DIFFUSION_2D: DIFFUSION_2D, - FLOYD_STEINBERG: FLOYD_STEINBERG, - JARVIS_JUDICE_NINKE: JARVIS_JUDICE_NINKE, - SIERRA2: SIERRA2, - STUCKI: STUCKI, - THRESHOLD: THRESHOLD, - CUSTOM: { - ox: [1], - oy: [1], - weights: [1], - shift: 1, - }, - }).forEach(([id, k]) => processImage(buf, id, k)); + Object.entries(>{ + ATKINSON: ATKINSON, + BURKES: BURKES, + DIFFUSION_ROW: DIFFUSION_ROW, + DIFFUSION_COLUMN: DIFFUSION_COLUMN, + DIFFUSION_2D: DIFFUSION_2D, + FLOYD_STEINBERG: FLOYD_STEINBERG, + JARVIS_JUDICE_NINKE: JARVIS_JUDICE_NINKE, + SIERRA2: SIERRA2, + STUCKI: STUCKI, + THRESHOLD: THRESHOLD, + CUSTOM: { + ox: [1], + oy: [1], + weights: [1], + shift: 1, + }, + }).forEach(([id, k]) => processImage(buf, id, k)); })(); diff --git a/examples/pixel-dither/tsconfig.json b/examples/pixel-dither/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/pixel-dither/tsconfig.json +++ b/examples/pixel-dither/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/pixel-indexed/package.json b/examples/pixel-indexed/package.json index 90e8d8163d..abce1f7aa4 100644 --- a/examples/pixel-indexed/package.json +++ b/examples/pixel-indexed/package.json @@ -1,39 +1,39 @@ { - "name": "@example/pixel-indexed", - "private": true, - "version": "0.0.1", - "description": "Image dithering and remapping using indexed palettes", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "dependencies": { - "@thi.ng/color": "workspace:^", - "@thi.ng/color-palettes": "workspace:^", - "@thi.ng/pixel": "workspace:^", - "@thi.ng/pixel-dither": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "color", - "color-palettes", - "k-means", - "pixel", - "pixel-dither" - ], - "screenshot": "examples/pixel-indexed.jpg" - } + "name": "@example/pixel-indexed", + "private": true, + "version": "0.0.1", + "description": "Image dithering and remapping using indexed palettes", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "dependencies": { + "@thi.ng/color": "workspace:^", + "@thi.ng/color-palettes": "workspace:^", + "@thi.ng/pixel": "workspace:^", + "@thi.ng/pixel-dither": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "color", + "color-palettes", + "k-means", + "pixel", + "pixel-dither" + ], + "screenshot": "examples/pixel-indexed.jpg" + } } diff --git a/examples/pixel-indexed/src/index.ts b/examples/pixel-indexed/src/index.ts index 9a101408d1..b56c82f1e5 100644 --- a/examples/pixel-indexed/src/index.ts +++ b/examples/pixel-indexed/src/index.ts @@ -12,29 +12,29 @@ import { IntBuffer, intBufferFromImage } from "@thi.ng/pixel/int"; import IMG from "./test.jpg"; (async () => { - const img = await imagePromise(IMG); + const img = await imagePromise(IMG); - const root = document.getElementById("app")!; - root.appendChild(img); + const root = document.getElementById("app")!; + root.appendChild(img); - const processImage = (buf: IntBuffer, palette: number[]) => - orderedDither(buf.copy(), 8, 3) - .as(defIndexed(palette)) - .blitCanvas(canvas2d(buf.width, buf.height, root).canvas); + const processImage = (buf: IntBuffer, palette: number[]) => + orderedDither(buf.copy(), 8, 3) + .as(defIndexed(palette)) + .blitCanvas(canvas2d(buf.width, buf.height, root).canvas); - // dither image and convert to indexed color using given palette - const buf = intBufferFromImage(img, ARGB8888); + // dither image and convert to indexed color using given palette + const buf = intBufferFromImage(img, ARGB8888); - // extract palette from image and use it - // to create indexed color version - processImage( - buf, - dominantColors(floatBuffer(buf.scale(1 / 4), FLOAT_RGB), 6).map((c) => - srgbIntArgb32(c.color) - ) - ); + // extract palette from image and use it + // to create indexed color version + processImage( + buf, + dominantColors(floatBuffer(buf.scale(1 / 4), FLOAT_RGB), 6).map((c) => + srgbIntArgb32(c.color) + ) + ); - // another version using a preset palette - // see https://github.com/thi-ng/umbrella/tree/develop/packages/color-palettes#available-palettes - processImage(buf, THEMES["00QMxescYuh8eYT39"].map(parseHex)); + // another version using a preset palette + // see https://github.com/thi-ng/umbrella/tree/develop/packages/color-palettes#available-palettes + processImage(buf, THEMES["00QMxescYuh8eYT39"].map(parseHex)); })(); diff --git a/examples/pixel-indexed/tsconfig.json b/examples/pixel-indexed/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/pixel-indexed/tsconfig.json +++ b/examples/pixel-indexed/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/pixel-sorting/package.json b/examples/pixel-sorting/package.json index 1d92591716..46b79a59f0 100644 --- a/examples/pixel-sorting/package.json +++ b/examples/pixel-sorting/package.json @@ -1,47 +1,47 @@ { - "name": "@example/pixel-sorting", - "private": true, - "version": "0.0.1", - "description": "Interactive pixel sorting tool using thi.ng/color & thi.ng/pixel", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "dependencies": { - "@thi.ng/bench": "workspace:^", - "@thi.ng/color": "workspace:^", - "@thi.ng/hiccup-html": "workspace:^", - "@thi.ng/intervals": "workspace:^", - "@thi.ng/pixel": "workspace:^", - "@thi.ng/random": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "bench", - "color", - "hiccup-html", - "intervals", - "pixel", - "random", - "rdom", - "rstream" - ], - "screenshot": "examples/pixel-sorting.png" - } + "name": "@example/pixel-sorting", + "private": true, + "version": "0.0.1", + "description": "Interactive pixel sorting tool using thi.ng/color & thi.ng/pixel", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "dependencies": { + "@thi.ng/bench": "workspace:^", + "@thi.ng/color": "workspace:^", + "@thi.ng/hiccup-html": "workspace:^", + "@thi.ng/intervals": "workspace:^", + "@thi.ng/pixel": "workspace:^", + "@thi.ng/random": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "bench", + "color", + "hiccup-html", + "intervals", + "pixel", + "random", + "rdom", + "rstream" + ], + "screenshot": "examples/pixel-sorting.png" + } } diff --git a/examples/pixel-sorting/src/index.ts b/examples/pixel-sorting/src/index.ts index 0bdb37a027..6589eb101d 100644 --- a/examples/pixel-sorting/src/index.ts +++ b/examples/pixel-sorting/src/index.ts @@ -4,12 +4,12 @@ import { luminanceAbgr32 } from "@thi.ng/color/luminance-rgb"; import { sortMapped } from "@thi.ng/color/sort"; import { div } from "@thi.ng/hiccup-html/blocks"; import { - checkbox, - inputFile, - inputNumber, - inputRange, - label, - type InputNumericAttribs + checkbox, + inputFile, + inputNumber, + inputRange, + label, + type InputNumericAttribs, } from "@thi.ng/hiccup-html/forms"; import { h1 } from "@thi.ng/hiccup-html/sections"; import { closedOpen, intersection } from "@thi.ng/intervals"; @@ -26,65 +26,65 @@ import { sync } from "@thi.ng/rstream/sync"; import { map } from "@thi.ng/transducers/map"; interface ProcessParams { - iter: number; - horizontal: boolean; - reverse: boolean; - min: number; - max: number; + iter: number; + horizontal: boolean; + reverse: boolean; + min: number; + max: number; } /** * Takes a ABGR pixel buffer and performs randomized pixel sorting based on * given config options. * - * @param buf - - * @param param1 - + * @param buf - + * @param param1 - */ const pixelSortBuffer = ( - buf: IntBuffer, - { iter, horizontal, reverse, min, max }: ProcessParams + buf: IntBuffer, + { iter, horizontal, reverse, min, max }: ProcessParams ) => { - const { pixels, width, height } = buf; - const row = closedOpen(0, width); - const column = closedOpen(0, height); - for (let i = iter; i-- > 0; ) { - const num = SYSTEM.minmax(min, max) | 0; - const n2 = num >> 1; - // random start/pixel position in image - const x = SYSTEM.minmax(horizontal ? -n2 : 0, width) | 0; - const y = SYSTEM.minmax(horizontal ? 0 : -n2, height) | 0; - // build & clamp intervals so that depending on process direction - // we're not reading beyond RHS of selected row or bottom of selected column - const ix = intersection(closedOpen(x, x + num), row)!; - const iy = intersection(closedOpen(y, y + num), column)!; - // skip if interval is empty - if (!(ix && iy && ix.size && iy.size)) continue; - // memory map selected pixels in either horizontal or vertical order - // `mapBuffer()` returns an array of sRGB views of the underlying pixel buffer. - // if vertical order, there will be `width` elements between each selected pixel - const strip = abgr32.mapBuffer( - // buffer to map - pixels, - // num pixels to map - (horizontal ? ix : iy).size, - // start index in pixel buffer - iy.l * width + ix.l, - // channel stride (ignored in our case) - 1, - // pixel stride - horizontal ? 1 : width - ); - // now we're sorting these selected pixels in place (i.e. directly - // within the pixel buffer) and by luminance - sortMapped(strip, luminanceAbgr32, reverse); - // mark sorted pixels - // strip.forEach((x) => ((x[0] += 0.05), x.clamp())); - } - return buf; + const { pixels, width, height } = buf; + const row = closedOpen(0, width); + const column = closedOpen(0, height); + for (let i = iter; i-- > 0; ) { + const num = SYSTEM.minmax(min, max) | 0; + const n2 = num >> 1; + // random start/pixel position in image + const x = SYSTEM.minmax(horizontal ? -n2 : 0, width) | 0; + const y = SYSTEM.minmax(horizontal ? 0 : -n2, height) | 0; + // build & clamp intervals so that depending on process direction + // we're not reading beyond RHS of selected row or bottom of selected column + const ix = intersection(closedOpen(x, x + num), row)!; + const iy = intersection(closedOpen(y, y + num), column)!; + // skip if interval is empty + if (!(ix && iy && ix.size && iy.size)) continue; + // memory map selected pixels in either horizontal or vertical order + // `mapBuffer()` returns an array of sRGB views of the underlying pixel buffer. + // if vertical order, there will be `width` elements between each selected pixel + const strip = abgr32.mapBuffer( + // buffer to map + pixels, + // num pixels to map + (horizontal ? ix : iy).size, + // start index in pixel buffer + iy.l * width + ix.l, + // channel stride (ignored in our case) + 1, + // pixel stride + horizontal ? 1 : width + ); + // now we're sorting these selected pixels in place (i.e. directly + // within the pixel buffer) and by luminance + sortMapped(strip, luminanceAbgr32, reverse); + // mark sorted pixels + // strip.forEach((x) => ((x[0] += 0.05), x.clamp())); + } + return buf; }; const processImage = (img: HTMLImageElement, opts: ProcessParams) => - timed(() => pixelSortBuffer(intBufferFromImage(img, ABGR8888), opts)); + timed(() => pixelSortBuffer(intBufferFromImage(img, ABGR8888), opts)); // stream of input files const file = stream(); @@ -105,135 +105,135 @@ const reverse = reactive(false); // stream combinator & image processor const result = sync({ - src: { - image, - iter, - horizontal, - reverse, - min, - max, - }, - closeOut: CloseMode.NEVER, - xform: map((params) => processImage(params.image, params)), + src: { + image, + iter, + horizontal, + reverse, + min, + max, + }, + closeOut: CloseMode.NEVER, + xform: map((params) => processImage(params.image, params)), }); // triggers reading file as an image // once ready, puts image into `image` stream for further processing file.subscribe({ - next(file) { - const url = URL.createObjectURL(file); - const img = new Image(); - img.onload = () => { - image.next(img); - URL.revokeObjectURL(url); // house keeping! - }; - img.src = url; - }, + next(file) { + const url = URL.createObjectURL(file); + const img = new Image(); + img.onload = () => { + image.next(img); + URL.revokeObjectURL(url); // house keeping! + }; + img.src = url; + }, }); // thi.ng/rdom UI component // creates a canvas element and blits given pixel buffer into it // when the component mounts class PixelCanvas extends Component { - constructor(protected buffer: IntBuffer) { - super(); - } + constructor(protected buffer: IntBuffer) { + super(); + } - async mount(parent: Element, index?: NumOrElement) { - const buf = this.buffer; - this.el = this.$el( - "canvas", - { width: buf.width, height: buf.height }, - null, - parent, - index - ); - buf.blitCanvas(this.el); - return this.el; - } + async mount(parent: Element, index?: NumOrElement) { + const buf = this.buffer; + this.el = this.$el( + "canvas", + { width: buf.width, height: buf.height }, + null, + parent, + index + ); + buf.blitCanvas(this.el); + return this.el; + } } // UI component, grouping a form field label & input slider // slider is linked w/ given value stream const labeledRange = ( - stream: Stream, - id: string, - labelBody: string, - opts: Partial + stream: Stream, + id: string, + labelBody: string, + opts: Partial ) => { - const onchange = (e: InputEvent) => - stream.next(parseInt((e.target).value)); - return div( - ".mb2", - {}, - label(".dib.w4", { for: id }, labelBody), - inputRange({ - ...opts, - id, - value: stream, - onchange, - }), - inputNumber(".ml3.w4.tr", { - ...opts, - value: stream, - onchange, - }) - ); + const onchange = (e: InputEvent) => + stream.next(parseInt((e.target).value)); + return div( + ".mb2", + {}, + label(".dib.w4", { for: id }, labelBody), + inputRange({ + ...opts, + id, + value: stream, + onchange, + }), + inputNumber(".ml3.w4.tr", { + ...opts, + value: stream, + onchange, + }) + ); }; // UI component, grouping a form field label & checkbox // checkbox is linked w/ given value stream const labeledCheckbox = ( - stream: Stream, - id: string, - labelBody: string + stream: Stream, + id: string, + labelBody: string ) => - div( - ".mb2", - {}, - label(".dib.w4", { for: id }, labelBody), - checkbox({ - id, - checked: stream, - onchange: (e) => - stream.next(!!(e.target).checked), - }) - ); + div( + ".mb2", + {}, + label(".dib.w4", { for: id }, labelBody), + checkbox({ + id, + checked: stream, + onchange: (e) => + stream.next(!!(e.target).checked), + }) + ); // compile & mount main UI $compile( - div( - {}, - h1({}, "Glitch my pic!"), - div( - ".mb2", - {}, - label(".dib.w4", { for: "file" }, "Image"), - inputFile({ - id: "file", - accept: ["image/jpg", "image/png", "image/gif"], - multiple: false, - onchange: (e) => - file.next((e.target).files![0]), - }) - ), - labeledRange(iter, "iter", "Iterations", { - min: 0, - max: 50000, - step: 1000, - }), - labeledRange(min, "min", "Min. scatter", { - min: 0, - max: 200, - step: 5, - }), - labeledRange(max, "max", "Max. scatter", { - min: 0, - max: 200, - step: 5, - }), - labeledCheckbox(horizontal, "horizontal", "Horizontal"), - labeledCheckbox(reverse, "order", "Reverse order"), - div({}, $replace(result.map((buf) => new PixelCanvas(buf)))) - ) + div( + {}, + h1({}, "Glitch my pic!"), + div( + ".mb2", + {}, + label(".dib.w4", { for: "file" }, "Image"), + inputFile({ + id: "file", + accept: ["image/jpg", "image/png", "image/gif"], + multiple: false, + onchange: (e) => + file.next((e.target).files![0]), + }) + ), + labeledRange(iter, "iter", "Iterations", { + min: 0, + max: 50000, + step: 1000, + }), + labeledRange(min, "min", "Min. scatter", { + min: 0, + max: 200, + step: 5, + }), + labeledRange(max, "max", "Max. scatter", { + min: 0, + max: 200, + step: 5, + }), + labeledCheckbox(horizontal, "horizontal", "Horizontal"), + labeledCheckbox(reverse, "order", "Reverse order"), + div({}, $replace(result.map((buf) => new PixelCanvas(buf)))) + ) ).mount(document.getElementById("app")!); diff --git a/examples/pixel-sorting/tsconfig.json b/examples/pixel-sorting/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/pixel-sorting/tsconfig.json +++ b/examples/pixel-sorting/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/pointfree-svg/package.json b/examples/pointfree-svg/package.json index c2066c5cfc..af9494482b 100644 --- a/examples/pointfree-svg/package.json +++ b/examples/pointfree-svg/package.json @@ -1,33 +1,33 @@ { - "name": "@example/pointfree-svg", - "private": true, - "description": "Generate SVG using pointfree DSL", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "type": "module", - "scripts": { - "build": "tools:node-esm src/index.ts" - }, - "dependencies": { - "@thi.ng/hiccup": "workspace:^", - "@thi.ng/hiccup-svg": "workspace:^", - "@thi.ng/pointfree": "workspace:^", - "@thi.ng/pointfree-lang": "workspace:^" - }, - "devDependencies": { - "tools": "workspace:^", - "typescript": "^4.7.4" - }, - "thi.ng": { - "online": false, - "readme": true, - "screenshot": "examples/pointfree-svg.png" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - } + "name": "@example/pointfree-svg", + "private": true, + "description": "Generate SVG using pointfree DSL", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "type": "module", + "scripts": { + "build": "tools:node-esm src/index.ts" + }, + "dependencies": { + "@thi.ng/hiccup": "workspace:^", + "@thi.ng/hiccup-svg": "workspace:^", + "@thi.ng/pointfree": "workspace:^", + "@thi.ng/pointfree-lang": "workspace:^" + }, + "devDependencies": { + "tools": "workspace:^", + "typescript": "^4.7.4" + }, + "thi.ng": { + "online": false, + "readme": true, + "screenshot": "examples/pointfree-svg.png" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + } } diff --git a/examples/pointfree-svg/src/index.ts b/examples/pointfree-svg/src/index.ts index 78745c8c7b..5a212d6cc3 100644 --- a/examples/pointfree-svg/src/index.ts +++ b/examples/pointfree-svg/src/index.ts @@ -73,26 +73,26 @@ serialize swap write-file // initialize environment and pre-compile library source const env = ffi( - // predefined variables - { - "svg.line": svg.line, - "svg.circle": svg.circle, - "svg.svg": svg.svg, - shapes: [], - }, - // foreign function interface (FFI) - // custom words usable by the DSL - { - // ( svgdom -- svgstring ) - serialize: maptos(serialize), - // ( body filename -- ) - "write-file": (ctx) => { - const stack = ctx[0]; - ensureStack(stack, 2); - writeFileSync(stack.pop(), stack.pop()); - return ctx; - }, - } + // predefined variables + { + "svg.line": svg.line, + "svg.circle": svg.circle, + "svg.svg": svg.svg, + shapes: [], + }, + // foreign function interface (FFI) + // custom words usable by the DSL + { + // ( svgdom -- svgstring ) + serialize: maptos(serialize), + // ( body filename -- ) + "write-file": (ctx) => { + const stack = ctx[0]; + ensureStack(stack, 2); + writeFileSync(stack.pop(), stack.pop()); + return ctx; + }, + } ); // compile lib (resulting words are stored in env) run(libsrc, env); diff --git a/examples/pointfree-svg/tsconfig.json b/examples/pointfree-svg/tsconfig.json index 4ee43ac513..b4bceb5b17 100644 --- a/examples/pointfree-svg/tsconfig.json +++ b/examples/pointfree-svg/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["src/**/*"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["src/**/*"] } diff --git a/examples/poisson-circles/package.json b/examples/poisson-circles/package.json index 2288df9c72..e2b30b2f64 100644 --- a/examples/poisson-circles/package.json +++ b/examples/poisson-circles/package.json @@ -1,38 +1,38 @@ { - "name": "@example/poisson-circles", - "private": true, - "version": "0.0.1", - "description": "2D Poisson-disc sampler with procedural gradient map", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/geom": "workspace:^", - "@thi.ng/geom-accel": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/poisson": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom", - "geom-accel", - "poisson" - ], - "screenshot": "poisson/poisson.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/poisson-circles", + "private": true, + "version": "0.0.1", + "description": "2D Poisson-disc sampler with procedural gradient map", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/geom": "workspace:^", + "@thi.ng/geom-accel": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/poisson": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom", + "geom-accel", + "poisson" + ], + "screenshot": "poisson/poisson.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/poisson-circles/src/index.ts b/examples/poisson-circles/src/index.ts index 6dbd96dcfa..105f6d16f0 100644 --- a/examples/poisson-circles/src/index.ts +++ b/examples/poisson-circles/src/index.ts @@ -9,20 +9,20 @@ import { randMinMax2 } from "@thi.ng/vectors/random"; const index = new KdTreeSet(2); const pts = samplePoisson({ - index, - points: () => randMinMax2(null, [0, 0], [500, 500]), - density: (p) => fit01(Math.pow(dist(p, [250, 250]) / 250, 2), 2, 10), - iter: 5, - max: 8000, - quality: 500, + index, + points: () => randMinMax2(null, [0, 0], [500, 500]), + density: (p) => fit01(Math.pow(dist(p, [250, 250]) / 250, 2), 2, 10), + iter: 5, + max: 8000, + quality: 500, }); // use thi.ng/geom to visualize results // each circle's radius is set to distance to its nearest neighbor const circles = pts.map((p) => - circle(p, dist(p, index.queryKeys(p, 40, 2)[1]) / 2) + circle(p, dist(p, index.queryKeys(p, 40, 2)[1]) / 2) ); document.body.innerHTML = asSvg( - svgDoc({ fill: "none", stroke: "blue" }, ...circles) + svgDoc({ fill: "none", stroke: "blue" }, ...circles) ); diff --git a/examples/poisson-circles/tsconfig.json b/examples/poisson-circles/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/poisson-circles/tsconfig.json +++ b/examples/poisson-circles/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/poly-spline/package.json b/examples/poly-spline/package.json index ce475fe94b..e8dce43218 100644 --- a/examples/poly-spline/package.json +++ b/examples/poly-spline/package.json @@ -1,41 +1,41 @@ { - "name": "@example/poly-spline", - "private": true, - "description": "Polygon to cubic curve conversion & visualization", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/dsp": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "dsp", - "geom", - "geom-splines", - "hiccup-svg", - "transducers", - "transducers-hdom" - ], - "screenshot": "examples/poly-spline.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/poly-spline", + "private": true, + "description": "Polygon to cubic curve conversion & visualization", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/dsp": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "dsp", + "geom", + "geom-splines", + "hiccup-svg", + "transducers", + "transducers-hdom" + ], + "screenshot": "examples/poly-spline.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/poly-spline/src/index.ts b/examples/poly-spline/src/index.ts index fe5212e50e..f1aaf0fbee 100644 --- a/examples/poly-spline/src/index.ts +++ b/examples/poly-spline/src/index.ts @@ -20,8 +20,8 @@ import { reducer } from "@thi.ng/transducers/reduce"; import { scan } from "@thi.ng/transducers/scan"; const BUTTONS = { - blue: "bg-blue white hover-bg-light-blue hover-navy", - green: "bg-green white hover-bg-light-green hover-dark-green", + blue: "bg-blue white hover-bg-light-blue hover-navy", + green: "bg-green white hover-bg-light-green hover-dark-green", }; // HOF event listener to emit a value on given stream @@ -29,138 +29,138 @@ const emitOnStream = (stream: Stream, val: any) => () => stream.next(val); // button UI component const button = ( - _: any, - clazz: string, - onclick: EventListener, - label: string + _: any, + clazz: string, + onclick: EventListener, + label: string ) => [ - "a", - { - href: "#", - onclick, - class: "dib w4 mr2 pa2 link " + clazz, - }, - label, + "a", + { + href: "#", + onclick, + class: "dib w4 mr2 pa2 link " + clazz, + }, + label, ]; // slider UI component const slider = ( - _: any, - attribs: any, - stream: Stream, - label: string + _: any, + attribs: any, + stream: Stream, + label: string ) => [ - "div.mv3", - ["span.dib.w4.mr2", label], - [ - "input.mr3", - { - type: "range", - value: stream.deref(), - oninput: (e: any) => stream.next(parseFloat(e.target.value)), - ...attribs, - }, - ], - stream.deref()!.toFixed(1), + "div.mv3", + ["span.dib.w4.mr2", label], + [ + "input.mr3", + { + type: "range", + value: stream.deref(), + oninput: (e: any) => stream.next(parseFloat(e.target.value)), + ...attribs, + }, + ], + stream.deref()!.toFixed(1), ]; // main app component / stream transformer // attached to `main` stream sync and responsible to build full UI // from current stream values const app = - ( - _mode: Stream, - _uniform: Stream, - _scale: Stream, - _uniScale: Stream - ) => - ({ poly, mode, uniform, scale, uniScale }: any) => { - // reconstruct poly as cubic curve segments - // reference: https://github.com/thi-ng/umbrella/tree/develop/packages/geom-splines#polygon-to-cubic-curve-conversion - const cubics = asCubic(poly, { - breakPoints: mode, - scale: scale * (uniform ? uniScale : 1), - uniform, - }); - // visualize control points as circles - const controlPoints = iterator( - comp( - mapcat((x) => x.points), - map((p) => circle(p, 0.75)) - ), - cubics - ); - // visualize control point handles - const handles = iterator( - comp( - mapcat((x) => x.points), - partition(2), - map(line) - ), - cubics - ); - return [ - "div.sans-serif.ma3", - // user controls - [ - "div", - [ - button, - BUTTONS.blue, - emitOnStream(_mode, true), - mode ? "break points" : "control points", - ], - [ - button, - BUTTONS.green, - emitOnStream(_uniform, true), - uniform ? "uniform" : "non-uniform", - ], - [ - slider, - { min: -1.3, max: 1.3, step: 0.1 }, - _scale, - "tangent scale", - ], - [ - slider, - { min: 0, max: 100, step: 1, disabled: !uniform }, - _uniScale, - "uniform scale", - ], - ], - [ - "div", - // all @thi.ng/geom shapes implement the `IToHiccup` interface - // and so can be used directly in @thi.ng/hdom-canvas - // visualizations. However, here we're using SVG and hence will - // need to provide a `convert` attribute to transform the hiccup - // format into a hiccup-svg compatible format see: - // https://docs.thi.ng/umbrella/hiccup-svg/modules.html#convertTree - svgDoc( - { - convert: true, - width: 480, - height: 480, - viewBox: "-150 -150 300 300", - fill: "none", - stroke: "#ccc", - "stroke-width": 0.25, - }, - poly, - withAttribs(pathFromCubics(cubics), { - stroke: mode ? "blue" : "red", - "stroke-width": 1, - }), - group({ stroke: "#333" }, [...controlPoints, ...handles]) - ), - ], - ]; - }; + ( + _mode: Stream, + _uniform: Stream, + _scale: Stream, + _uniScale: Stream + ) => + ({ poly, mode, uniform, scale, uniScale }: any) => { + // reconstruct poly as cubic curve segments + // reference: https://github.com/thi-ng/umbrella/tree/develop/packages/geom-splines#polygon-to-cubic-curve-conversion + const cubics = asCubic(poly, { + breakPoints: mode, + scale: scale * (uniform ? uniScale : 1), + uniform, + }); + // visualize control points as circles + const controlPoints = iterator( + comp( + mapcat((x) => x.points), + map((p) => circle(p, 0.75)) + ), + cubics + ); + // visualize control point handles + const handles = iterator( + comp( + mapcat((x) => x.points), + partition(2), + map(line) + ), + cubics + ); + return [ + "div.sans-serif.ma3", + // user controls + [ + "div", + [ + button, + BUTTONS.blue, + emitOnStream(_mode, true), + mode ? "break points" : "control points", + ], + [ + button, + BUTTONS.green, + emitOnStream(_uniform, true), + uniform ? "uniform" : "non-uniform", + ], + [ + slider, + { min: -1.3, max: 1.3, step: 0.1 }, + _scale, + "tangent scale", + ], + [ + slider, + { min: 0, max: 100, step: 1, disabled: !uniform }, + _uniScale, + "uniform scale", + ], + ], + [ + "div", + // all @thi.ng/geom shapes implement the `IToHiccup` interface + // and so can be used directly in @thi.ng/hdom-canvas + // visualizations. However, here we're using SVG and hence will + // need to provide a `convert` attribute to transform the hiccup + // format into a hiccup-svg compatible format see: + // https://docs.thi.ng/umbrella/hiccup-svg/modules.html#convertTree + svgDoc( + { + convert: true, + width: 480, + height: 480, + viewBox: "-150 -150 300 300", + fill: "none", + stroke: "#ccc", + "stroke-width": 0.25, + }, + poly, + withAttribs(pathFromCubics(cubics), { + stroke: mode ? "blue" : "red", + "stroke-width": 1, + }), + group({ stroke: "#333" }, [...controlPoints, ...handles]) + ), + ], + ]; + }; // stream of animated polygons const poly = fromRAF().transform( - map((t) => star(100, 6, [sin(t, 0.01, 0.5, 0.8), 1])) + map((t) => star(100, 6, [sin(t, 0.01, 0.5, 0.8), 1])) ); // poly spline mode flag (control points vs break points) @@ -174,21 +174,21 @@ const uniScale = reactive(25); // re-usable transducer implementing a toggle switch const toggle = scan( - reducer( - () => true, - (acc) => !acc - ) + reducer( + () => true, + (acc) => !acc + ) ); // main stream combinator const main = sync({ - src: { - poly, - mode: mode.transform(toggle), - uniform: uniform.transform(toggle), - scale, - uniScale, - }, + src: { + poly, + mode: mode.transform(toggle), + uniform: uniform.transform(toggle), + scale, + uniScale, + }, }); // transform to create & apply UI diff --git a/examples/poly-spline/tsconfig.json b/examples/poly-spline/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/poly-spline/tsconfig.json +++ b/examples/poly-spline/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/porter-duff/package.json b/examples/porter-duff/package.json index 5fe5d486e0..d950a0a774 100644 --- a/examples/porter-duff/package.json +++ b/examples/porter-duff/package.json @@ -1,31 +1,31 @@ { - "name": "@example/porter-duff", - "private": true, - "description": "Port-Duff image compositing / alpha blending", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/pixel": "workspace:^", - "@thi.ng/porter-duff": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true, - "screenshot": "porter-duff/porter-duff2.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/porter-duff", + "private": true, + "description": "Port-Duff image compositing / alpha blending", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/pixel": "workspace:^", + "@thi.ng/porter-duff": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true, + "screenshot": "porter-duff/porter-duff2.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/porter-duff/src/index.ts b/examples/porter-duff/src/index.ts index 3543e6192d..88af0e0cc5 100644 --- a/examples/porter-duff/src/index.ts +++ b/examples/porter-duff/src/index.ts @@ -2,73 +2,73 @@ import { canvas2d, imagePromise } from "@thi.ng/pixel/canvas"; import { ABGR8888 } from "@thi.ng/pixel/format/abgr8888"; import { intBufferFromCanvas, intBufferFromImage } from "@thi.ng/pixel/int"; import { - DEST_ATOP_I, - DEST_I, - DEST_IN_I, - DEST_OUT_I, - DEST_OVER_I, - PLUS_I, - SRC_ATOP_I, - SRC_I, - SRC_IN_I, - SRC_OUT_I, - SRC_OVER_I, - XOR_I, + DEST_ATOP_I, + DEST_I, + DEST_IN_I, + DEST_OUT_I, + DEST_OVER_I, + PLUS_I, + SRC_ATOP_I, + SRC_I, + SRC_IN_I, + SRC_OUT_I, + SRC_OVER_I, + XOR_I, } from "@thi.ng/porter-duff/porter-duff"; import IMG2 from "./plus.png"; import IMG from "./ring.png"; const MODES: any = { - SRC: SRC_I, - DEST: DEST_I, - SRC_OVER: SRC_OVER_I, - DEST_OVER: DEST_OVER_I, - SRC_IN: SRC_IN_I, - DEST_IN: DEST_IN_I, - SRC_OUT: SRC_OUT_I, - DEST_OUT: DEST_OUT_I, - SRC_ATOP: SRC_ATOP_I, - DEST_ATOP: DEST_ATOP_I, - XOR: XOR_I, - PLUS: PLUS_I, + SRC: SRC_I, + DEST: DEST_I, + SRC_OVER: SRC_OVER_I, + DEST_OVER: DEST_OVER_I, + SRC_IN: SRC_IN_I, + DEST_IN: DEST_IN_I, + SRC_OUT: SRC_OUT_I, + DEST_OUT: DEST_OUT_I, + SRC_ATOP: SRC_ATOP_I, + DEST_ATOP: DEST_ATOP_I, + XOR: XOR_I, + PLUS: PLUS_I, }; const IDS = Object.keys(MODES); Promise.all([IMG, IMG2].map((x) => imagePromise(x))) - .then(([circle, plus]) => { - const srcBuf = intBufferFromImage(circle, ABGR8888).premultiply(); - const destBuf = intBufferFromImage(plus, ABGR8888).premultiply(); + .then(([circle, plus]) => { + const srcBuf = intBufferFromImage(circle, ABGR8888).premultiply(); + const destBuf = intBufferFromImage(plus, ABGR8888).premultiply(); - const ctx = canvas2d(destBuf.width * 4, (destBuf.height + 20) * 3); - document.getElementById("app")!.appendChild(ctx.canvas); + const ctx = canvas2d(destBuf.width * 4, (destBuf.height + 20) * 3); + document.getElementById("app")!.appendChild(ctx.canvas); - const res = intBufferFromCanvas(ctx.canvas); + const res = intBufferFromCanvas(ctx.canvas); - for (let y = 0, i = 0; y < 3; y++) { - for (let x = 0; x < 4; x++, i++) { - const dx = x * destBuf.width; - const dy = y * (destBuf.height + 20); - destBuf.blit(res, { dx, dy }); - srcBuf.blend(MODES[IDS[i]], res, { dx, dy }); - } - } + for (let y = 0, i = 0; y < 3; y++) { + for (let x = 0; x < 4; x++, i++) { + const dx = x * destBuf.width; + const dy = y * (destBuf.height + 20); + destBuf.blit(res, { dx, dy }); + srcBuf.blend(MODES[IDS[i]], res, { dx, dy }); + } + } - res.postmultiply(); - res.blitCanvas(ctx.canvas); + res.postmultiply(); + res.blitCanvas(ctx.canvas); - ctx.ctx.fillStyle = "black"; - ctx.ctx.font = "12px Arial"; - ctx.ctx.textAlign = "center"; + ctx.ctx.fillStyle = "black"; + ctx.ctx.font = "12px Arial"; + ctx.ctx.textAlign = "center"; - for (let y = 0, i = 0; y < 3; y++) { - for (let x = 0; x < 4; x++, i++) { - ctx.ctx.fillText( - IDS[i], - (x + 0.5) * destBuf.width, - (y + 1) * (destBuf.height + 20) - 6 - ); - } - } - }) - .catch((e) => console.log(e)); + for (let y = 0, i = 0; y < 3; y++) { + for (let x = 0; x < 4; x++, i++) { + ctx.ctx.fillText( + IDS[i], + (x + 0.5) * destBuf.width, + (y + 1) * (destBuf.height + 20) - 6 + ); + } + } + }) + .catch((e) => console.log(e)); diff --git a/examples/porter-duff/tsconfig.json b/examples/porter-duff/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/porter-duff/tsconfig.json +++ b/examples/porter-duff/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/ramp-synth/package.json b/examples/ramp-synth/package.json index 55c9cca732..6e1c7da366 100644 --- a/examples/ramp-synth/package.json +++ b/examples/ramp-synth/package.json @@ -1,43 +1,43 @@ { - "name": "@example/ramp-synth", - "private": true, - "version": "0.0.1", - "description": "Unison wavetable synth with waveform editor", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/arrays": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/ramp": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom", - "hdom-canvas", - "math", - "ramp", - "transducers", - "vectors" - ], - "screenshot": "examples/ramp-synth.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/ramp-synth", + "private": true, + "version": "0.0.1", + "description": "Unison wavetable synth with waveform editor", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/arrays": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/ramp": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom", + "hdom-canvas", + "math", + "ramp", + "transducers", + "vectors" + ], + "screenshot": "examples/ramp-synth.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/ramp-synth/src/api.ts b/examples/ramp-synth/src/api.ts index 5191f3a52a..0ed953b68e 100644 --- a/examples/ramp-synth/src/api.ts +++ b/examples/ramp-synth/src/api.ts @@ -11,17 +11,17 @@ export const SNAP = 5 / WIDTH; export const BASE_FREQ = 60; export const PRESETS: Vec[][] = [ - [ - [0, 0], - [0.98, 1], - [1, 0], - ], - [ - [0.25, 0.5], - [0.5, 1], - [0.625, 0.1], - [0.75, 0.5], - ], - [...map((i) => [i / 20, i & 1 ? i / 20 : 0], range(21))], - [...map((i) => [i / 12, i % 3 ? 1 : 0.25], range(13))], + [ + [0, 0], + [0.98, 1], + [1, 0], + ], + [ + [0.25, 0.5], + [0.5, 1], + [0.625, 0.1], + [0.75, 0.5], + ], + [...map((i) => [i / 20, i & 1 ? i / 20 : 0], range(21))], + [...map((i) => [i / 12, i % 3 ? 1 : 0.25], range(13))], ]; diff --git a/examples/ramp-synth/src/audio.ts b/examples/ramp-synth/src/audio.ts index 4ff6df0106..9350f97c53 100644 --- a/examples/ramp-synth/src/audio.ts +++ b/examples/ramp-synth/src/audio.ts @@ -8,38 +8,38 @@ let src: AudioBufferSourceNode; export const isAudioActive = () => !!actx; export const initAudio = (freq: number) => { - if (actx) return; - actx = new AudioContext(); - buf = actx.createBuffer(2, actx.sampleRate / freq, actx.sampleRate); - src = actx.createBufferSource(); - src.buffer = buf; - src.loop = true; - src.connect(actx.destination); - src.start(); + if (actx) return; + actx = new AudioContext(); + buf = actx.createBuffer(2, actx.sampleRate / freq, actx.sampleRate); + src = actx.createBufferSource(); + src.buffer = buf; + src.loop = true; + src.connect(actx.destination); + src.start(); }; export const stopAudio = () => { - src.stop(); - actx!.suspend(); - actx = undefined; + src.stop(); + actx!.suspend(); + actx = undefined; }; export const updateAudio = (ramp: IRamp, detune = 0.01) => { - if (!actx) return; - const left = buf.getChannelData(0); - const right = buf.getChannelData(1); - const f4 = 1 + detune; - const f5 = 2 + detune; - const f6 = 4 + detune; - for (let i = 0, n = left.length; i < n; i++) { - let t = i / n; - const y1 = ramp.at(t); - const y2 = ramp.at(t * 2); - const y3 = ramp.at(t * 4); - const y4 = ramp.at(fract(t * f4)); - const y5 = ramp.at(fract(t * f5)); - const y6 = ramp.at(fract(t * f6)); - left[i] = y1 * 0.5 + y5 * 0.2 + y3 * 0.3 - 1; - right[i] = y4 * 0.5 + y2 * 0.3 + y6 * 0.2 - 1; - } + if (!actx) return; + const left = buf.getChannelData(0); + const right = buf.getChannelData(1); + const f4 = 1 + detune; + const f5 = 2 + detune; + const f6 = 4 + detune; + for (let i = 0, n = left.length; i < n; i++) { + let t = i / n; + const y1 = ramp.at(t); + const y2 = ramp.at(t * 2); + const y3 = ramp.at(t * 4); + const y4 = ramp.at(fract(t * f4)); + const y5 = ramp.at(fract(t * f5)); + const y6 = ramp.at(fract(t * f6)); + left[i] = y1 * 0.5 + y5 * 0.2 + y3 * 0.3 - 1; + right[i] = y4 * 0.5 + y2 * 0.3 + y6 * 0.2 - 1; + } }; diff --git a/examples/ramp-synth/src/components.ts b/examples/ramp-synth/src/components.ts index a4146202a6..3c86b8a5a3 100644 --- a/examples/ramp-synth/src/components.ts +++ b/examples/ramp-synth/src/components.ts @@ -3,37 +3,37 @@ import type { IRamp } from "@thi.ng/ramp"; import { map } from "@thi.ng/transducers/map"; const tick = (x: number) => [ - "polygon", - { translate: [x, 0], stroke: "black", fill: "#666" }, - [ - [-4, 3], - [0, -2], - [4, 3], - [4, 9], - [-4, 9], - ], + "polygon", + { translate: [x, 0], stroke: "black", fill: "#666" }, + [ + [-4, 3], + [0, -2], + [4, 3], + [4, 9], + [-4, 9], + ], ]; export const rampViz = (ramp: IRamp, width: number, height: number) => { - const cp = ramp.stops; - return [ - "g", - {}, - [ - "polygon", - { fill: "$ramp", scale: [width, height] }, - [ - [0, 1], - [0, 1 - cp[0][1]], - ...map((p) => [p[0], 1 - p[1]], ramp.interpolatedPoints()), - [1, 1 - peek(cp)[1]], - [1, 1], - ], - ], - [ - "g", - { translate: [0, height] }, - ...ramp.stops.map((p) => tick(width * p[0])), - ], - ]; + const cp = ramp.stops; + return [ + "g", + {}, + [ + "polygon", + { fill: "$ramp", scale: [width, height] }, + [ + [0, 1], + [0, 1 - cp[0][1]], + ...map((p) => [p[0], 1 - p[1]], ramp.interpolatedPoints()), + [1, 1 - peek(cp)[1]], + [1, 1], + ], + ], + [ + "g", + { translate: [0, height] }, + ...ramp.stops.map((p) => tick(width * p[0])), + ], + ]; }; diff --git a/examples/ramp-synth/src/index.ts b/examples/ramp-synth/src/index.ts index c6ca65b4b1..705cd5e90e 100644 --- a/examples/ramp-synth/src/index.ts +++ b/examples/ramp-synth/src/index.ts @@ -9,14 +9,14 @@ import { repeatedly } from "@thi.ng/transducers/repeatedly"; import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; import { setC2 } from "@thi.ng/vectors/setc"; import { - BASE_FREQ, - CHEIGHT, - CWIDTH, - HEIGHT, - PAD, - PRESETS, - SNAP, - WIDTH, + BASE_FREQ, + CHEIGHT, + CWIDTH, + HEIGHT, + PAD, + PRESETS, + SNAP, + WIDTH, } from "./api"; import { initAudio, isAudioActive, stopAudio, updateAudio } from "./audio"; import { rampViz } from "./components"; @@ -26,34 +26,34 @@ const timeForPos = (x: number) => fitClamped(x, PAD, WIDTH - PAD, 0, 1); const valueForPos = (y: number) => fitClamped(y, PAD, HEIGHT - PAD, 1, 0); const editRamp = (mpos: ReadonlyVec, currID: number) => { - const y = valueForPos(mpos[1]); - let t = timeForPos(mpos[0]); - let i = currID !== -1 ? currID : ramp.closestIndex(t, SNAP); - if (i !== -1) { - t = ramp.clampedIndexTime(i, t, SNAP); - ramp.stops[i] = - mpos[1] <= HEIGHT - PAD ? [t, y] : [t, ramp.stops[i][1]]; - } else if (currID === -1) { - ramp.stops.push([t, y]); - ramp.sort(); - i = ramp.closestIndex(t, SNAP); - } - updateAudio(ramp); - return i; + const y = valueForPos(mpos[1]); + let t = timeForPos(mpos[0]); + let i = currID !== -1 ? currID : ramp.closestIndex(t, SNAP); + if (i !== -1) { + t = ramp.clampedIndexTime(i, t, SNAP); + ramp.stops[i] = + mpos[1] <= HEIGHT - PAD ? [t, y] : [t, ramp.stops[i][1]]; + } else if (currID === -1) { + ramp.stops.push([t, y]); + ramp.sort(); + i = ramp.closestIndex(t, SNAP); + } + updateAudio(ramp); + return i; }; const updateMarker = (id: number, mpos: ReadonlyVec) => { - ramp.stops[id][0] = ramp.clampedIndexTime(id, timeForPos(mpos[0]), SNAP); - ramp.sort(); - updateAudio(ramp); + ramp.stops[id][0] = ramp.clampedIndexTime(id, timeForPos(mpos[0]), SNAP); + ramp.sort(); + updateAudio(ramp); }; const toggleRampMode = () => { - ramp = - ramp instanceof LinearRamp - ? new HermiteRamp(ramp.stops) - : new LinearRamp(ramp.stops); - updateAudio(ramp); + ramp = + ramp instanceof LinearRamp + ? new HermiteRamp(ramp.stops) + : new LinearRamp(ramp.stops); + updateAudio(ramp); }; let ramp: IRamp = new HermiteRamp(PRESETS[0].map((p) => (p).slice())); @@ -67,138 +67,138 @@ let currT = 0; let autoToggle = false; const gradient = [ - "defs", - {}, - [ - "linearGradient", - { - id: "ramp", - from: [0, 0], - to: [0, 1], - }, - [ - [0, "#99b"], - [1, "#111"], - ], - ], + "defs", + {}, + [ + "linearGradient", + { + id: "ramp", + from: [0, 0], + to: [0, 1], + }, + [ + [0, "#99b"], + [1, "#111"], + ], + ], ]; start(() => { - currT = fract(currT + 0.01); - if (autoToggle && fract(currT * 2) < 0.01) { - toggleRampMode(); - } - return [ - canvas, - { - width: WIDTH, - height: HEIGHT, - onmousedown: () => { - isDown = true; - selID = - mpos[1] >= HEIGHT - PAD - ? ramp.closestIndex(timeForPos(mpos[0]), SNAP) - : -1; - currID = -1; - }, - onmouseup: () => { - isDown = false; - selID = -1; - currID = -1; - }, - onmousemove: (e: MouseEvent) => { - const b = (e.target).getBoundingClientRect(); - setC2(mpos, e.clientX - b.left, e.clientY - b.top); - if (isDown) { - if (selID !== -1) { - updateMarker(selID, mpos); - } else { - currID = editRamp(mpos, currID); - } - } - }, - }, - gradient, - [ - "g", - { - translate: [PAD, PAD], - __diff: false, - }, - rampViz(ramp, CWIDTH, CHEIGHT), - [ - "g", - { translate: [currT * CWIDTH, 0] }, - ["line", { stroke: "red" }, [0, 0], [0, CHEIGHT]], - [ - "circle", - { fill: "red" }, - [0, (1 - ramp.at(currT)) * CHEIGHT], - 3, - ], - ], - ], - selID !== -1 || - currID !== -1 || - ramp.closestIndex(timeForPos(mpos[0]), SNAP) !== -1 - ? [ - "g", - { stroke: "#777", dash: [1, 2] }, - ["line", {}, [mpos[0], PAD], [mpos[0], PAD + CHEIGHT]], - mpos[1] < HEIGHT - PAD - ? ["line", {}, [PAD, mpos[1]], [PAD + CWIDTH, mpos[1]]] - : null, - ] - : null, - ]; + currT = fract(currT + 0.01); + if (autoToggle && fract(currT * 2) < 0.01) { + toggleRampMode(); + } + return [ + canvas, + { + width: WIDTH, + height: HEIGHT, + onmousedown: () => { + isDown = true; + selID = + mpos[1] >= HEIGHT - PAD + ? ramp.closestIndex(timeForPos(mpos[0]), SNAP) + : -1; + currID = -1; + }, + onmouseup: () => { + isDown = false; + selID = -1; + currID = -1; + }, + onmousemove: (e: MouseEvent) => { + const b = (e.target).getBoundingClientRect(); + setC2(mpos, e.clientX - b.left, e.clientY - b.top); + if (isDown) { + if (selID !== -1) { + updateMarker(selID, mpos); + } else { + currID = editRamp(mpos, currID); + } + } + }, + }, + gradient, + [ + "g", + { + translate: [PAD, PAD], + __diff: false, + }, + rampViz(ramp, CWIDTH, CHEIGHT), + [ + "g", + { translate: [currT * CWIDTH, 0] }, + ["line", { stroke: "red" }, [0, 0], [0, CHEIGHT]], + [ + "circle", + { fill: "red" }, + [0, (1 - ramp.at(currT)) * CHEIGHT], + 3, + ], + ], + ], + selID !== -1 || + currID !== -1 || + ramp.closestIndex(timeForPos(mpos[0]), SNAP) !== -1 + ? [ + "g", + { stroke: "#777", dash: [1, 2] }, + ["line", {}, [mpos[0], PAD], [mpos[0], PAD + CHEIGHT]], + mpos[1] < HEIGHT - PAD + ? ["line", {}, [PAD, mpos[1]], [PAD + CWIDTH, mpos[1]]] + : null, + ] + : null, + ]; }); window.addEventListener("keydown", (e) => { - const t = timeForPos(mpos[0]); - switch (e.key) { - case "l": - toggleRampMode(); - break; - case "t": - autoToggle = !autoToggle; - break; - case "u": - ramp.uniform(); - updateAudio(ramp); - break; - case "x": - if (ramp.removeStopAt(t, SNAP)) { - updateAudio(ramp); - } - break; - case "s": - PRESETS[4] = ramp.stops.slice(); - break; - case "r": - ramp.stops = [ - ...repeatedly( - () => [Math.random(), Math.random()], - Math.random() * 37 + 3 - ), - ]; - ramp.sort(); - updateAudio(ramp); - break; - case "1": - case "2": - case "3": - case "4": - case "5": - ramp.stops = PRESETS[e.keyCode - 49].map((p) => (p).slice()); - updateAudio(ramp); - break; - case "a": - if (!isAudioActive()) { - initAudio(BASE_FREQ); - updateAudio(ramp); - } else { - stopAudio(); - } - break; - } + const t = timeForPos(mpos[0]); + switch (e.key) { + case "l": + toggleRampMode(); + break; + case "t": + autoToggle = !autoToggle; + break; + case "u": + ramp.uniform(); + updateAudio(ramp); + break; + case "x": + if (ramp.removeStopAt(t, SNAP)) { + updateAudio(ramp); + } + break; + case "s": + PRESETS[4] = ramp.stops.slice(); + break; + case "r": + ramp.stops = [ + ...repeatedly( + () => [Math.random(), Math.random()], + Math.random() * 37 + 3 + ), + ]; + ramp.sort(); + updateAudio(ramp); + break; + case "1": + case "2": + case "3": + case "4": + case "5": + ramp.stops = PRESETS[e.keyCode - 49].map((p) => (p).slice()); + updateAudio(ramp); + break; + case "a": + if (!isAudioActive()) { + initAudio(BASE_FREQ); + updateAudio(ramp); + } else { + stopAudio(); + } + break; + } }); diff --git a/examples/ramp-synth/tsconfig.json b/examples/ramp-synth/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/ramp-synth/tsconfig.json +++ b/examples/ramp-synth/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rdom-basics/package.json b/examples/rdom-basics/package.json index daa4a7105c..ae1c8ee287 100644 --- a/examples/rdom-basics/package.json +++ b/examples/rdom-basics/package.json @@ -1,37 +1,37 @@ { - "name": "@example/rdom-basics", - "private": true, - "version": "0.0.1", - "description": "Demonstates various rdom usage patterns", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/checks": "workspace:^", - "@thi.ng/compose": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "rdom", - "rstream", - "transducers" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/rdom-basics", + "private": true, + "version": "0.0.1", + "description": "Demonstates various rdom usage patterns", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/checks": "workspace:^", + "@thi.ng/compose": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "rdom", + "rstream", + "transducers" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/rdom-basics/src/index.ts b/examples/rdom-basics/src/index.ts index a16eab6bcc..69e2a4d7b1 100644 --- a/examples/rdom-basics/src/index.ts +++ b/examples/rdom-basics/src/index.ts @@ -20,88 +20,88 @@ const date = fromInterval(1000).transform(map(() => new Date())); const items = stream(); const typewriter = (min: number, max: number) => (src: string) => - stream((s) => { - let active = true; - (async () => { - for (let i = 1; active && i <= src.length; i++) { - s.next(src.substring(0, i)); - await delayed(0, Math.random() * (max - min) + min); - } - s.closeIn !== CloseMode.NEVER && s.done(); - })(); - return () => (active = false); - }); + stream((s) => { + let active = true; + (async () => { + for (let i = 1; active && i <= src.length; i++) { + s.next(src.substring(0, i)); + await delayed(0, Math.random() * (max - min) + min); + } + s.closeIn !== CloseMode.NEVER && s.done(); + })(); + return () => (active = false); + }); const names = ["TypeScript", "@thi.ng/rdom", "toxi", "Discord"]; const typing = fromIterable(choices(names), { delay: 2000 }).subscribe( - metaStream(typewriter(16, 100), { closeOut: CloseMode.NEVER }) + metaStream(typewriter(16, 100), { closeOut: CloseMode.NEVER }) ); const itemChoices = [ - date.transform(map((d) => d.toISOString())), - body, - typing, - ...names, + date.transform(map((d) => d.toISOString())), + body, + typing, + ...names, ]; const randomizeBody = () => body.next(names[~~(Math.random() * names.length)]); const randomizeList = () => - items.next([...take(Math.random() * 400 + 100, choices(itemChoices))]); + items.next([...take(Math.random() * 400 + 100, choices(itemChoices))]); const button = (onclick: EventListener, label: string) => [ - "button.mr2", - { onclick }, - label, + "button.mr2", + { onclick }, + label, ]; const mpos = fromDOMEvent(window, "mousemove").transform( - map((e) => [e.pageX, e.pageY]) + map((e) => [e.pageX, e.pageY]) ); randomizeBody(); randomizeList(); const root = $compile([ - "div.ma3.bg-dark-gray.white", - {}, - [ - "h1.absolute.white.z1", - { - class: { blur }, - style: mpos.transform( - map(([x, y]) => ({ left: x + "px", top: y + "px" })) - ), - }, - $replace( - sync({ - src: { body, mpos }, - xform: map((x) => [ - "span", - {}, - x.body, - ["span.ml2.light-green", {}, `[${x.mpos}]`], - ]), - }) - ), - ], - [ - "div.mv3", - {}, - button(() => blur.next(!blur.deref()), "toggle blur"), - button(randomizeBody, "randomize title"), - button(randomizeList, "randomize list"), - ], - ["div.hot-pink", {}, date], - ["div.pink", {}, items.transform(map((x) => `${x.length} items`))], - $list(items, "ul.f7", { style: { "column-count": 2 } }, (x) => [ - "li", - { - class: isString(x) ? "light-blue" : x === typing ? "red" : "yellow", - }, - x, - ]), + "div.ma3.bg-dark-gray.white", + {}, + [ + "h1.absolute.white.z1", + { + class: { blur }, + style: mpos.transform( + map(([x, y]) => ({ left: x + "px", top: y + "px" })) + ), + }, + $replace( + sync({ + src: { body, mpos }, + xform: map((x) => [ + "span", + {}, + x.body, + ["span.ml2.light-green", {}, `[${x.mpos}]`], + ]), + }) + ), + ], + [ + "div.mv3", + {}, + button(() => blur.next(!blur.deref()), "toggle blur"), + button(randomizeBody, "randomize title"), + button(randomizeList, "randomize list"), + ], + ["div.hot-pink", {}, date], + ["div.pink", {}, items.transform(map((x) => `${x.length} items`))], + $list(items, "ul.f7", { style: { "column-count": 2 } }, (x) => [ + "li", + { + class: isString(x) ? "light-blue" : x === typing ? "red" : "yellow", + }, + x, + ]), ]); root.mount(document.getElementById("app")!); diff --git a/examples/rdom-basics/tsconfig.json b/examples/rdom-basics/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rdom-basics/tsconfig.json +++ b/examples/rdom-basics/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rdom-delayed-update/package.json b/examples/rdom-delayed-update/package.json index b74e72c114..a4dc8a3abd 100644 --- a/examples/rdom-delayed-update/package.json +++ b/examples/rdom-delayed-update/package.json @@ -1,37 +1,37 @@ { - "name": "@example/rdom-delayed-update", - "private": true, - "version": "0.0.1", - "description": "Dynamically loaded images w/ preloader state", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "dependencies": { - "@thi.ng/compose": "workspace:^", - "@thi.ng/random": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "rdom", - "rstream" - ], - "screenshot": "examples/rdom-delayed-update.jpg" - } + "name": "@example/rdom-delayed-update", + "private": true, + "version": "0.0.1", + "description": "Dynamically loaded images w/ preloader state", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "dependencies": { + "@thi.ng/compose": "workspace:^", + "@thi.ng/random": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "rdom", + "rstream" + ], + "screenshot": "examples/rdom-delayed-update.jpg" + } } diff --git a/examples/rdom-delayed-update/src/index.ts b/examples/rdom-delayed-update/src/index.ts index e68deb9265..2cc32f3b85 100644 --- a/examples/rdom-delayed-update/src/index.ts +++ b/examples/rdom-delayed-update/src/index.ts @@ -11,18 +11,18 @@ import { reactive } from "@thi.ng/rstream/stream"; import { cycle } from "@thi.ng/transducers/cycle"; interface UserSummary { - id: number; - name: string; + id: number; + name: string; } const users = reactive([ - { id: 1, name: "Alice" }, - { id: 2, name: "Bob" }, - { id: 3, name: "Charlie" }, - { id: 4, name: "Dora" }, - { id: 5, name: "Emma" }, - { id: 6, name: "Fred" }, - { id: 7, name: "Gina" }, + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, + { id: 3, name: "Charlie" }, + { id: 4, name: "Dora" }, + { id: 5, name: "Emma" }, + { id: 6, name: "Fred" }, + { id: 7, name: "Gina" }, ]); const colors = cycle(["f00", "0ff", "f0f", "f90", "00f", "0f0"]); @@ -30,89 +30,89 @@ const colors = cycle(["f00", "0ff", "f0f", "f90", "00f", "0f0"]); /** * User thumbnail component. Image element w/ reactive `src` attrib. * - * @param srcUrl - + * @param srcUrl - */ const userThumb = (srcUrl: Promise) => [ - "img.db.w-100", - { - // src image attribute - src: merge({ - // stream sources - src: [ - // preloader image (should be local really) - reactive( - "https://via.placeholder.com/640x360.png?text=wait+for+it..." - ), - // final image - fromPromise(srcUrl), - ///.... - ], - }), - }, + "img.db.w-100", + { + // src image attribute + src: merge({ + // stream sources + src: [ + // preloader image (should be local really) + reactive( + "https://via.placeholder.com/640x360.png?text=wait+for+it..." + ), + // final image + fromPromise(srcUrl), + ///.... + ], + }), + }, ]; /** * Alternative project thumbnail with custom inner pre-loader component. * - * @param srcUrl - + * @param srcUrl - */ const userThumbAlt = (srcUrl: Promise) => [ - "div.aspect-ratio.aspect-ratio--16x9.tc", - {}, - $refresh( - fromPromise(srcUrl), - async (src) => ["img.w-100", { src }], - async (x) => ["img.w-100", { src: "broken.png" }], - async () => ["div", { style: { padding: "25% 0" } }, "loading..."] - ), + "div.aspect-ratio.aspect-ratio--16x9.tc", + {}, + $refresh( + fromPromise(srcUrl), + async (src) => ["img.w-100", { src }], + async (x) => ["img.w-100", { src: "broken.png" }], + async () => ["div", { style: { padding: "25% 0" } }, "loading..."] + ), ]; class UserComponent extends Component { - constructor(public user: UserSummary) { - super(); - } + constructor(public user: UserSummary) { + super(); + } - async mount(parent: Element, index?: NumOrElement): Promise { - this.el = await this.$tree( - this.$compile([ - "div.bg-black.white", - {}, - // also try out userThumbAlt... - userThumb( - // intentionally delay - delayed( - `https://via.placeholder.com/640x360.png/${ - colors.next().value - }/fff?text=${this.user.name}`, - SYSTEM.minmax(0.5, 1) * 2000 - ) - ), - [ - "h3.pa2.ma0.f6", - {}, - `User #${this.user.id}: ${this.user.name}`, - ], - ]), - parent, - index - ); - return this.el!; - } + async mount(parent: Element, index?: NumOrElement): Promise { + this.el = await this.$tree( + this.$compile([ + "div.bg-black.white", + {}, + // also try out userThumbAlt... + userThumb( + // intentionally delay + delayed( + `https://via.placeholder.com/640x360.png/${ + colors.next().value + }/fff?text=${this.user.name}`, + SYSTEM.minmax(0.5, 1) * 2000 + ) + ), + [ + "h3.pa2.ma0.f6", + {}, + `User #${this.user.id}: ${this.user.name}`, + ], + ]), + parent, + index + ); + return this.el!; + } } // main root component $compile( - $klist( - users, - "div", - { - style: { - display: "grid", - "grid-template-columns": "1fr 1fr 1fr", - gap: "0.25rem", - }, - }, - (user) => new UserComponent(user), - (user) => user.id - ) + $klist( + users, + "div", + { + style: { + display: "grid", + "grid-template-columns": "1fr 1fr 1fr", + gap: "0.25rem", + }, + }, + (user) => new UserComponent(user), + (user) => user.id + ) ).mount(document.getElementById("app")!); diff --git a/examples/rdom-delayed-update/tsconfig.json b/examples/rdom-delayed-update/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rdom-delayed-update/tsconfig.json +++ b/examples/rdom-delayed-update/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rdom-dnd/package.json b/examples/rdom-dnd/package.json index 5dbb0e32f5..28904cb6e2 100644 --- a/examples/rdom-dnd/package.json +++ b/examples/rdom-dnd/package.json @@ -1,38 +1,38 @@ { - "name": "@example/rdom-dnd", - "private": true, - "version": "0.0.1", - "description": "rdom drag & drop example", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/hiccup-carbon-icons": "workspace:^", - "@thi.ng/hiccup-html": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hiccup-carbon-icons", - "hiccup-html", - "rdom" - ], - "screenshot": "examples/rdom-dnd.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/rdom-dnd", + "private": true, + "version": "0.0.1", + "description": "rdom drag & drop example", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/hiccup-carbon-icons": "workspace:^", + "@thi.ng/hiccup-html": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hiccup-carbon-icons", + "hiccup-html", + "rdom" + ], + "screenshot": "examples/rdom-dnd.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/rdom-dnd/src/draggable.ts b/examples/rdom-dnd/src/draggable.ts index a20e423582..987da483ce 100644 --- a/examples/rdom-dnd/src/draggable.ts +++ b/examples/rdom-dnd/src/draggable.ts @@ -4,85 +4,85 @@ import type { ComponentLike, NumOrElement } from "@thi.ng/rdom"; import { Component } from "@thi.ng/rdom/component"; interface DraggableOpts { - scope?: string; - dropzone: string; - start: any; - hover: any; - end: any; - onstart?: Fn; - onend?: Fn; - ondrop?: Fn; + scope?: string; + dropzone: string; + start: any; + hover: any; + end: any; + onstart?: Fn; + onend?: Fn; + ondrop?: Fn; } export class Draggable extends Component { - protected opts: DraggableOpts; - protected active = false; + protected opts: DraggableOpts; + protected active = false; - constructor(protected inner: ComponentLike, opts?: Partial) { - super(); - this.opts = { - dropzone: "dropzone", - start: { opacity: 0.5 }, - end: { opacity: null }, - hover: { background: "yellow" }, - ...opts, - }; - } + constructor(protected inner: ComponentLike, opts?: Partial) { + super(); + this.opts = { + dropzone: "dropzone", + start: { opacity: 0.5 }, + end: { opacity: null }, + hover: { background: "yellow" }, + ...opts, + }; + } - async mount(parent: Element, index: NumOrElement) { - const opts = this.opts; - this.el = await this.$tree( - div( - { - draggable: true, - ondragstart: (e) => { - e.dataTransfer!.setData("text/plain", ""); - this.active = true; - this.$style(opts.start); - opts.onstart && opts.onstart(e); - }, - ondragend: (e) => { - this.active = false; - this.$style(opts.end); - opts.onend && opts.onend(e); - }, - }, - this.inner - ), - parent, - index - ); - const isDropZone = (e: DragEvent) => - this.active && - (e.target).dataset.dropzone === opts.dropzone; - const undoHover = objWithNull(opts.hover); - // add event handlers to configured DOM scope - this.$attribs( - { - ondragover: (e: DragEvent) => e.preventDefault(), - ondragenter: (e: DragEvent) => - isDropZone(e) && this.$style(opts.hover, e.target), - ondragleave: (e: DragEvent) => - isDropZone(e) && this.$style(undoHover, e.target), - ondrop: (e: DragEvent) => { - e.preventDefault(); - if (isDropZone(e)) { - this.$style(opts.end); - this.$style(undoHover, e.target); - this.$moveTo(e.target); - opts.ondrop && opts.ondrop(e); - } - this.active = false; - opts.onend && opts.onend(e); - }, - }, - this.opts.scope - ? document.getElementById(this.opts.scope)! - : document - ); - return this.el!; - } + async mount(parent: Element, index: NumOrElement) { + const opts = this.opts; + this.el = await this.$tree( + div( + { + draggable: true, + ondragstart: (e) => { + e.dataTransfer!.setData("text/plain", ""); + this.active = true; + this.$style(opts.start); + opts.onstart && opts.onstart(e); + }, + ondragend: (e) => { + this.active = false; + this.$style(opts.end); + opts.onend && opts.onend(e); + }, + }, + this.inner + ), + parent, + index + ); + const isDropZone = (e: DragEvent) => + this.active && + (e.target).dataset.dropzone === opts.dropzone; + const undoHover = objWithNull(opts.hover); + // add event handlers to configured DOM scope + this.$attribs( + { + ondragover: (e: DragEvent) => e.preventDefault(), + ondragenter: (e: DragEvent) => + isDropZone(e) && this.$style(opts.hover, e.target), + ondragleave: (e: DragEvent) => + isDropZone(e) && this.$style(undoHover, e.target), + ondrop: (e: DragEvent) => { + e.preventDefault(); + if (isDropZone(e)) { + this.$style(opts.end); + this.$style(undoHover, e.target); + this.$moveTo(e.target); + opts.ondrop && opts.ondrop(e); + } + this.active = false; + opts.onend && opts.onend(e); + }, + }, + this.opts.scope + ? document.getElementById(this.opts.scope)! + : document + ); + return this.el!; + } } const objWithNull = (obj: any) => - Object.keys(obj).reduce((acc: any, id) => ((acc[id] = null), acc), {}); + Object.keys(obj).reduce((acc: any, id) => ((acc[id] = null), acc), {}); diff --git a/examples/rdom-dnd/src/index.ts b/examples/rdom-dnd/src/index.ts index 170da19713..e1e307d610 100644 --- a/examples/rdom-dnd/src/index.ts +++ b/examples/rdom-dnd/src/index.ts @@ -8,48 +8,48 @@ import { Draggable } from "./draggable"; import { Notification, type NotifyOpts } from "./notification"; const messages = cycle([ - { type: "info", msg: "All systems working" }, - { type: "success", msg: "All your base are belong to us" }, - { type: "warn", msg: "All files deleted" }, + { type: "info", msg: "All systems working" }, + { type: "success", msg: "All your base are belong to us" }, + { type: "warn", msg: "All files deleted" }, ]); const notify = new Notification(); const dragBank = (id: string, col1: string, col2: string, icon: any) => - div( - { id }, - dropZone( - col1, - id, - new Draggable(dragButton(icon), { - scope: id, - dropzone: id, - hover: { background: col2 }, - ondrop: () => notify.update(messages.next().value), - }) - ), - dropZone(col1, id), - dropZone(col1, id) - ); + div( + { id }, + dropZone( + col1, + id, + new Draggable(dragButton(icon), { + scope: id, + dropzone: id, + hover: { background: col2 }, + ondrop: () => notify.update(messages.next().value), + }) + ), + dropZone(col1, id), + dropZone(col1, id) + ); const dropZone = (col: string, id: string, body?: any) => - div( - { class: `v-top dib mr2 w4 h4 pa4 bg-${col}`, data: { dropzone: id } }, - body - ); + div( + { class: `v-top dib mr2 w4 h4 pa4 bg-${col}`, data: { dropzone: id } }, + body + ); const dragButton = (icon: any) => - div( - { class: "w3 h3 bg-black white flex items-center justify-center" }, - withSize(icon, "24px") - ); + div( + { class: "w3 h3 bg-black white flex items-center justify-center" }, + withSize(icon, "24px") + ); $compile( - div( - null, - notify, - div(".mv2", null, "Drag & drop the icons..."), - dragBank("bank1", "red", "cyan", ADD_ALT), - dragBank("bank2", "blue", "orange", CLOSE_OUTLINE) - ) + div( + null, + notify, + div(".mv2", null, "Drag & drop the icons..."), + dragBank("bank1", "red", "cyan", ADD_ALT), + dragBank("bank2", "blue", "orange", CLOSE_OUTLINE) + ) ).mount(document.getElementById("app")!); diff --git a/examples/rdom-dnd/src/notification.ts b/examples/rdom-dnd/src/notification.ts index 6caf3d4645..13f3dbb94c 100644 --- a/examples/rdom-dnd/src/notification.ts +++ b/examples/rdom-dnd/src/notification.ts @@ -8,47 +8,47 @@ import type { NumOrElement } from "@thi.ng/rdom"; import { Component } from "@thi.ng/rdom/component"; const PRESETS = { - info: { class: "bg-lightest-blue blue", icon: INFORMATION_FILLED }, - success: { class: "bg-washed-green dark-green", icon: CHECKMARK_FILLED }, - warn: { class: "bg-washed-red dark-red", icon: WARNING_ALT_FILLED }, + info: { class: "bg-lightest-blue blue", icon: INFORMATION_FILLED }, + success: { class: "bg-washed-green dark-green", icon: CHECKMARK_FILLED }, + warn: { class: "bg-washed-red dark-red", icon: WARNING_ALT_FILLED }, }; export interface NotifyOpts { - type: keyof typeof PRESETS; - msg: string; + type: keyof typeof PRESETS; + msg: string; } export class Notification extends Component { - timeout!: number; + timeout!: number; - async mount(parent: Element, index?: NumOrElement) { - return (this.el = this.$el( - "div", - { hidden: true }, - null, - parent, - index - )); - } + async mount(parent: Element, index?: NumOrElement) { + return (this.el = this.$el( + "div", + { hidden: true }, + null, + parent, + index + )); + } - update(msg: NotifyOpts) { - const config = PRESETS[msg.type]; - this.$tree( - div( - { class: `w-100 ph3 pv2 mv2 br-pill ${config.class}` }, - span({ class: "icon mr2" }, withSize(config.icon, "16px")), - msg.msg - ), - this.$clear() - ); - this.$attribs({ hidden: false }); - clearTimeout(this.timeout); - // @ts-ignore - this.timeout = setTimeout(() => this.$attribs({ hidden: true }), 2000); - } + update(msg: NotifyOpts) { + const config = PRESETS[msg.type]; + this.$tree( + div( + { class: `w-100 ph3 pv2 mv2 br-pill ${config.class}` }, + span({ class: "icon mr2" }, withSize(config.icon, "16px")), + msg.msg + ), + this.$clear() + ); + this.$attribs({ hidden: false }); + clearTimeout(this.timeout); + // @ts-ignore + this.timeout = setTimeout(() => this.$attribs({ hidden: true }), 2000); + } - async unmount() { - clearTimeout(this.timeout); - super.unmount(); - } + async unmount() { + clearTimeout(this.timeout); + super.unmount(); + } } diff --git a/examples/rdom-dnd/tsconfig.json b/examples/rdom-dnd/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rdom-dnd/tsconfig.json +++ b/examples/rdom-dnd/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rdom-lissajous/package.json b/examples/rdom-lissajous/package.json index 4d0dcbe11c..b87fbab3ee 100644 --- a/examples/rdom-lissajous/package.json +++ b/examples/rdom-lissajous/package.json @@ -1,42 +1,42 @@ { - "name": "@example/rdom-lissajous", - "private": true, - "version": "0.0.1", - "description": "rdom & hiccup-canvas interop test", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/hiccup-carbon-icons": "workspace:^", - "@thi.ng/hiccup-html": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/rdom-canvas": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hiccup-html", - "rdom", - "rdom-canvas", - "rstream", - "transducers" - ], - "screenshot": "examples/rdom-lissajous.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/rdom-lissajous", + "private": true, + "version": "0.0.1", + "description": "rdom & hiccup-canvas interop test", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/hiccup-carbon-icons": "workspace:^", + "@thi.ng/hiccup-html": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/rdom-canvas": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hiccup-html", + "rdom", + "rdom-canvas", + "rstream", + "transducers" + ], + "screenshot": "examples/rdom-lissajous.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/rdom-lissajous/src/index.ts b/examples/rdom-lissajous/src/index.ts index ff628451f7..c11ab7bf19 100644 --- a/examples/rdom-lissajous/src/index.ts +++ b/examples/rdom-lissajous/src/index.ts @@ -14,38 +14,38 @@ import { map } from "@thi.ng/transducers/map"; import { slidingWindow } from "@thi.ng/transducers/sliding-window"; const slider = ( - dest: ISubscription, - desc: string, - tooltip: string, - attribs?: any + dest: ISubscription, + desc: string, + tooltip: string, + attribs?: any ) => - div( - null, - label( - { class: "dib w-50", for: `input-${desc}` }, - ["i.mr2", { data: { tooltip } }, withSize(INFORMATION, "12px")], - `${desc}: `, - dest.transform(map((x) => x.toFixed(2))) - ), - inputRange({ - id: `input-${desc}`, - class: "dib w-50", - min: 0, - max: 10, - step: 0.1, - ...attribs, - value: dest, - oninput: (e: InputEvent) => - dest.next(parseFloat((e.target).value)), - }) - ); + div( + null, + label( + { class: "dib w-50", for: `input-${desc}` }, + ["i.mr2", { data: { tooltip } }, withSize(INFORMATION, "12px")], + `${desc}: `, + dest.transform(map((x) => x.toFixed(2))) + ), + inputRange({ + id: `input-${desc}`, + class: "dib w-50", + min: 0, + max: 10, + step: 0.1, + ...attribs, + value: dest, + oninput: (e: InputEvent) => + dest.next(parseFloat((e.target).value)), + }) + ); const lissajous = ( - a: number, - b: number, - d: number, - scale: number, - t: number + a: number, + b: number, + d: number, + scale: number, + t: number ) => [scale * Math.sin(a * t + d), scale * Math.sin(b * t)]; const a = reactive(3); @@ -54,68 +54,68 @@ const num = reactive(25); const scale = reactive(0.5); const radius = reactive(20); const size = fromDOMEvent(window, "resize").transform( - map(() => [window.innerWidth, window.innerHeight - 100]) + map(() => [window.innerWidth, window.innerHeight - 100]) ); size.next(null); // combine various reactive parameters // and transform via transducers const dots: ISubscription = sync({ - src: { a, b, scale, size, time: fromRAF() }, + src: { a, b, scale, size, time: fromRAF() }, }).transform( - // compute next lissajous point - map(({ a, b, time, scale, size }) => - lissajous( - a, - b, - Math.PI / 4, - (scale / 2) * Math.min(size[0], size[1]), - time * 0.05 - ) - ), - // only keep `num` last points - slidingWindow(>num, true), - // transform into a group of hiccup circle shapes - // (drawing done thi.ng/hdom-canvas) - map((points: number[][]) => { - const [width, height] = size.deref()!; - const r = radius.deref()!; - return [ - "g", - { fill: "purple", translate: [width / 2, height / 2] }, - points.map((pos, i) => [ - "circle", - { alpha: (i + 1) / points.length }, - pos, - (r * (i + 1)) / points.length, - ]), - ]; - }) + // compute next lissajous point + map(({ a, b, time, scale, size }) => + lissajous( + a, + b, + Math.PI / 4, + (scale / 2) * Math.min(size[0], size[1]), + time * 0.05 + ) + ), + // only keep `num` last points + slidingWindow(>num, true), + // transform into a group of hiccup circle shapes + // (drawing done thi.ng/hdom-canvas) + map((points: number[][]) => { + const [width, height] = size.deref()!; + const r = radius.deref()!; + return [ + "g", + { fill: "purple", translate: [width / 2, height / 2] }, + points.map((pos, i) => [ + "circle", + { alpha: (i + 1) / points.length }, + pos, + (r * (i + 1)) / points.length, + ]), + ]; + }) ); // compile UI/DOM component from hiccup syntax $compile( - div( - null, - div( - { class: "w-50-ns center-ns ma3" }, - slider(a, "A", "Curve parameter"), - slider(b, "B", "Curve parameter"), - slider(num, "Length", "Trail length / number of dots", { - min: 1, - max: 100, - step: 1, - }), - slider(radius, "Radius", "Dot radius", { - min: 1, - max: 50, - }), - slider(scale, "Scale", "Scale factor", { - max: 1, - step: 0.01, - }) - ), - // subscribe canvas component to above reactive value - $canvas(dots, size) - ) + div( + null, + div( + { class: "w-50-ns center-ns ma3" }, + slider(a, "A", "Curve parameter"), + slider(b, "B", "Curve parameter"), + slider(num, "Length", "Trail length / number of dots", { + min: 1, + max: 100, + step: 1, + }), + slider(radius, "Radius", "Dot radius", { + min: 1, + max: 50, + }), + slider(scale, "Scale", "Scale factor", { + max: 1, + step: 0.01, + }) + ), + // subscribe canvas component to above reactive value + $canvas(dots, size) + ) ).mount(document.body); diff --git a/examples/rdom-lissajous/tsconfig.json b/examples/rdom-lissajous/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rdom-lissajous/tsconfig.json +++ b/examples/rdom-lissajous/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rdom-search-docs/package.json b/examples/rdom-search-docs/package.json index 07a1051c42..faf0ad109f 100644 --- a/examples/rdom-search-docs/package.json +++ b/examples/rdom-search-docs/package.json @@ -1,45 +1,45 @@ { - "name": "@example/rdom-search-docs", - "private": true, - "version": "0.0.1", - "description": "Full umbrella repo doc string search w/ paginated results", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/bench": "workspace:^", - "@thi.ng/compare": "workspace:^", - "@thi.ng/equiv": "workspace:^", - "@thi.ng/hiccup-html": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@ygoe/msgpack": "^1.0.3" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "bench", - "compare", - "hiccup-html", - "rdom", - "rstream", - "transducers" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/rdom-search-docs", + "private": true, + "version": "0.0.1", + "description": "Full umbrella repo doc string search w/ paginated results", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/bench": "workspace:^", + "@thi.ng/compare": "workspace:^", + "@thi.ng/equiv": "workspace:^", + "@thi.ng/hiccup-html": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@ygoe/msgpack": "^1.0.3" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "bench", + "compare", + "hiccup-html", + "rdom", + "rstream", + "transducers" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/rdom-search-docs/src/index.ts b/examples/rdom-search-docs/src/index.ts index a513db5c1f..22d671c9cd 100644 --- a/examples/rdom-search-docs/src/index.ts +++ b/examples/rdom-search-docs/src/index.ts @@ -23,135 +23,135 @@ const INITIAL_QUERY = "hdom"; const PAGE_SIZE = 25; class DocSearch extends Component { - wrapper!: IComponent; - inner!: IComponent; - pager!: Pagination; - query!: Stream; - queryResults!: ISubscription; + wrapper!: IComponent; + inner!: IComponent; + pager!: Pagination; + query!: Stream; + queryResults!: ISubscription; - updateQuery(e: InputEvent) { - const term = (e.target).value; - if (term.length > 0) { - this.query.next(term.toLowerCase()); - this.pager.setPage(0); - } - } + updateQuery(e: InputEvent) { + const term = (e.target).value; + if (term.length > 0) { + this.query.next(term.toLowerCase()); + this.pager.setPage(0); + } + } - async mount(parent: Element) { - this.wrapper = $compile( - div( - { class: "ma2 measure-ns center-ns f7 f6-ns" }, - ["h1.mv0", {}, "thi.ng/umbrella doc search"], - div( - { class: "mb2 f7" }, - anchor({ class: "link blue", href: SRC_URL }, "Source code") - ) - ) - ); - this.el = await this.wrapper.mount(parent); + async mount(parent: Element) { + this.wrapper = $compile( + div( + { class: "ma2 measure-ns center-ns f7 f6-ns" }, + ["h1.mv0", {}, "thi.ng/umbrella doc search"], + div( + { class: "mb2 f7" }, + anchor({ class: "link blue", href: SRC_URL }, "Source code") + ) + ) + ); + this.el = await this.wrapper.mount(parent); - // show preloader - const loader = this.$el( - "div.ma2.measure-ns.center-ns.f7.f6-ns", - {}, - "Loading search index...", - this.el - ); + // show preloader + const loader = this.$el( + "div.ma2.measure-ns.center-ns.f7.f6-ns", + {}, + "Loading search index...", + this.el + ); - try { - // load & decode msgpacked binary search index - const resp = await fetch(INDEX_URL); - if (resp.status >= 400) - throw new Error("Failed to load search index"); - const buf = await resp.arrayBuffer(); - const index: SearchIndex = timed(() => msgpack.deserialize(buf)); + try { + // load & decode msgpacked binary search index + const resp = await fetch(INDEX_URL); + if (resp.status >= 400) + throw new Error("Failed to load search index"); + const buf = await resp.arrayBuffer(); + const index: SearchIndex = timed(() => msgpack.deserialize(buf)); - // remove preloader - this.$remove(loader); + // remove preloader + this.$remove(loader); - // init local state - this.query = reactive(INITIAL_QUERY); - // build results as transformation of query stream - // first debounce query stream (for fast typers) - this.queryResults = this.query - .subscribe(debounce(100)) - .transform(map((q) => search(index, q, "") || [])); + // init local state + this.query = reactive(INITIAL_QUERY); + // build results as transformation of query stream + // first debounce query stream (for fast typers) + this.queryResults = this.query + .subscribe(debounce(100)) + .transform(map((q) => search(index, q, "") || [])); - // setup pagination - this.pager = new Pagination(this.queryResults, PAGE_SIZE); + // setup pagination + this.pager = new Pagination(this.queryResults, PAGE_SIZE); - // compile inner component tree, including embedded reactive - // values/streams and controlflow structures - this.inner = $compile( - div( - null, - inputText({ - class: "w-100 mv2 pa2", - type: "text", - autofocus: true, - oninput: this.updateQuery.bind(this), - value: this.query, - }), - // query result & search index stats - div( - { class: "mv2" }, - // derived view of result stream to compute number of results - this.queryResults.transform( - map((results) => results.length) - ), - " results", - div( - null, - `(total: ${index.numVals}, keys: ${index.numKeys} in ${index.packages.length} packages, ${index.numFiles} files)` - ) - ), - // pagination controls - pageControls(this.pager), - // reactive list component of paginated search results - // the function arg is used to create new list items if needed - $list( - this.pager.resultPage, - "ul.list.pl0", - {}, - ([suffix, file]) => [ - "li", - {}, - [ - "span", - {}, - // use .deref() here to avoid unnecessary subscriptions - [ - "span.b.bg-washed-green", - {}, - this.query.deref(), - ], - `${suffix}: `, - ], - anchor( - { - class: "link blue", - href: BASE_URL + file, - target: "_new", - }, - file - ), - ] - ) - ) - ); - this.inner.mount(this.el); - } catch (e) { - $text(loader, e); - } - return this.el; - } + // compile inner component tree, including embedded reactive + // values/streams and controlflow structures + this.inner = $compile( + div( + null, + inputText({ + class: "w-100 mv2 pa2", + type: "text", + autofocus: true, + oninput: this.updateQuery.bind(this), + value: this.query, + }), + // query result & search index stats + div( + { class: "mv2" }, + // derived view of result stream to compute number of results + this.queryResults.transform( + map((results) => results.length) + ), + " results", + div( + null, + `(total: ${index.numVals}, keys: ${index.numKeys} in ${index.packages.length} packages, ${index.numFiles} files)` + ) + ), + // pagination controls + pageControls(this.pager), + // reactive list component of paginated search results + // the function arg is used to create new list items if needed + $list( + this.pager.resultPage, + "ul.list.pl0", + {}, + ([suffix, file]) => [ + "li", + {}, + [ + "span", + {}, + // use .deref() here to avoid unnecessary subscriptions + [ + "span.b.bg-washed-green", + {}, + this.query.deref(), + ], + `${suffix}: `, + ], + anchor( + { + class: "link blue", + href: BASE_URL + file, + target: "_new", + }, + file + ), + ] + ) + ) + ); + this.inner.mount(this.el); + } catch (e) { + $text(loader, e); + } + return this.el; + } - // not needed here, just for reference... - async unmount() { - this.inner.unmount(); - this.wrapper.unmount(); - this.pager.release(); - } + // not needed here, just for reference... + async unmount() { + this.inner.unmount(); + this.wrapper.unmount(); + this.pager.release(); + } } new DocSearch().mount(document.getElementById("app")!); diff --git a/examples/rdom-search-docs/src/pagination.ts b/examples/rdom-search-docs/src/pagination.ts index fa2585d71c..60cfec8d54 100644 --- a/examples/rdom-search-docs/src/pagination.ts +++ b/examples/rdom-search-docs/src/pagination.ts @@ -12,87 +12,87 @@ import { map } from "@thi.ng/transducers/map"; import { page } from "@thi.ng/transducers/page"; export class Pagination implements IRelease { - page: Stream; - maxPage: ISubscription; - resultPage: ISubscription; + page: Stream; + maxPage: ISubscription; + resultPage: ISubscription; - constructor(src: ISubscription, pageSize: number) { - this.page = reactive(0); - this.maxPage = src.transform(map((res) => ~~(res.length / pageSize))); - // produce search result page using `page()` transducer - // the`sync()` construct is a stream combinator which requires all input - // streams to produce a value first before it emits its own initial - // result... - this.resultPage = sync({ - src: { - src, - pageID: this.page, - }, - xform: comp( - dedupe(equiv), - map( - ({ src, pageID }) => - [ - ...page( - clamp(pageID, 0, this.maxPage.deref() || 1), - pageSize, - src - ), - ] - ) - ), - }); - } + constructor(src: ISubscription, pageSize: number) { + this.page = reactive(0); + this.maxPage = src.transform(map((res) => ~~(res.length / pageSize))); + // produce search result page using `page()` transducer + // the`sync()` construct is a stream combinator which requires all input + // streams to produce a value first before it emits its own initial + // result... + this.resultPage = sync({ + src: { + src, + pageID: this.page, + }, + xform: comp( + dedupe(equiv), + map( + ({ src, pageID }) => + [ + ...page( + clamp(pageID, 0, this.maxPage.deref() || 1), + pageSize, + src + ), + ] + ) + ), + }); + } - release() { - this.resultPage.unsubscribe(); - return true; - } + release() { + this.resultPage.unsubscribe(); + return true; + } - updatePage(step: number) { - this.page.next( - clamp(this.page.deref()! + step, 0, this.maxPage.deref() || 0) - ); - } + updatePage(step: number) { + this.page.next( + clamp(this.page.deref()! + step, 0, this.maxPage.deref() || 0) + ); + } - setPage(id: number) { - this.page.next(id); - } + setPage(id: number) { + this.page.next(id); + } } /** * UI component for given pagination model. * - * @param pager - + * @param pager - */ export const pageControls = (pager: Pagination) => - div( - { - class: "mv3 w-100", - style: { - // only show if there's a need for it... - display: pager.maxPage.transform( - map((x) => (x > 0 ? "flex" : "none")) - ), - }, - }, - div( - { class: "w-33 tl" }, - bt(() => pager.setPage(0), "<<"), - bt(() => pager.updatePage(-1), "<") - ), - div( - { class: "w-34 tc" }, - pager.page.transform(map((x) => x + 1)), - " / ", - pager.maxPage.transform(map((x) => x + 1)) - ), - div( - { class: "w-33 tr" }, - bt(() => pager.updatePage(1), ">"), - bt(() => pager.setPage(pager.maxPage.deref()!), ">>") - ) - ); + div( + { + class: "mv3 w-100", + style: { + // only show if there's a need for it... + display: pager.maxPage.transform( + map((x) => (x > 0 ? "flex" : "none")) + ), + }, + }, + div( + { class: "w-33 tl" }, + bt(() => pager.setPage(0), "<<"), + bt(() => pager.updatePage(-1), "<") + ), + div( + { class: "w-34 tc" }, + pager.page.transform(map((x) => x + 1)), + " / ", + pager.maxPage.transform(map((x) => x + 1)) + ), + div( + { class: "w-33 tr" }, + bt(() => pager.updatePage(1), ">"), + bt(() => pager.setPage(pager.maxPage.deref()!), ">>") + ) + ); const bt = (onclick: EventListener, label: string) => - button({ onclick }, label); + button({ onclick }, label); diff --git a/examples/rdom-search-docs/src/search.ts b/examples/rdom-search-docs/src/search.ts index 859c9768c2..dab832313c 100644 --- a/examples/rdom-search-docs/src/search.ts +++ b/examples/rdom-search-docs/src/search.ts @@ -4,52 +4,52 @@ import { compareByKeys2 } from "@thi.ng/compare/keys"; export type PackedTrie = [IObjectOf, number[]?]; export interface SearchIndex { - bits: number[][]; - packages: string[]; - files: string[]; - root: PackedTrie; - numFiles: number; - numKeys: number; - numVals: number; + bits: number[][]; + packages: string[]; + files: string[]; + root: PackedTrie; + numFiles: number; + numKeys: number; + numVals: number; } const defDecoder = - ([[psh, pmsk], [fsh, fmsk], [lsh, lmsk]]: number[][]) => - (id: number) => - [(id >>> psh) & pmsk, (id >>> fsh) & fmsk, (id >>> lsh) & lmsk]; + ([[psh, pmsk], [fsh, fmsk], [lsh, lmsk]]: number[][]) => + (id: number) => + [(id >>> psh) & pmsk, (id >>> fsh) & fmsk, (id >>> lsh) & lmsk]; const find = (node: PackedTrie, key: string) => { - for (let i = 0, n = key.length; node && i < n; i++) { - node = node[0][key[i]]; - } - return node; + for (let i = 0, n = key.length; node && i < n; i++) { + node = node[0][key[i]]; + } + return node; }; const suffixes = ( - node: PackedTrie, - prefix: string, - acc: [string, number][] = [] + node: PackedTrie, + prefix: string, + acc: [string, number][] = [] ) => { - for (let k in node[0]) { - acc = suffixes(node[0][k], prefix + k, acc); - } - if (node[1]) return acc.concat(node[1].map((i) => [prefix, i])); - return acc; + for (let k in node[0]) { + acc = suffixes(node[0][k], prefix + k, acc); + } + if (node[1]) return acc.concat(node[1].map((i) => [prefix, i])); + return acc; }; export const search = ( - { bits, packages, files, root }: SearchIndex, - key: string, - prefix = key + { bits, packages, files, root }: SearchIndex, + key: string, + prefix = key ) => { - root = find(root, key); - const decode = defDecoder(bits); - return root - ? suffixes(root, prefix) - .map(([k, val]) => { - const [p, f, ln] = decode(val); - return [k, `${packages[p]}/src/${files[f]}.ts#L${ln}`]; - }) - .sort(compareByKeys2(0, 1)) - : undefined; + root = find(root, key); + const decode = defDecoder(bits); + return root + ? suffixes(root, prefix) + .map(([k, val]) => { + const [p, f, ln] = decode(val); + return [k, `${packages[p]}/src/${files[f]}.ts#L${ln}`]; + }) + .sort(compareByKeys2(0, 1)) + : undefined; }; diff --git a/examples/rdom-search-docs/tsconfig.json b/examples/rdom-search-docs/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rdom-search-docs/tsconfig.json +++ b/examples/rdom-search-docs/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rdom-svg-nodes/package.json b/examples/rdom-svg-nodes/package.json index 1512cd8824..ffa8dd53c5 100644 --- a/examples/rdom-svg-nodes/package.json +++ b/examples/rdom-svg-nodes/package.json @@ -1,42 +1,42 @@ { - "name": "@example/rdom-svg-nodes", - "private": true, - "version": "0.0.1", - "description": "rdom powered SVG graph with draggable nodes", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/atom": "workspace:^", - "@thi.ng/equiv": "workspace:^", - "@thi.ng/hiccup-svg": "workspace:^", - "@thi.ng/rdom": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "hiccup-svg", - "rdom", - "rstream", - "transducers" - ], - "screenshot": "examples/rdom-svg-nodes.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/rdom-svg-nodes", + "private": true, + "version": "0.0.1", + "description": "rdom powered SVG graph with draggable nodes", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/atom": "workspace:^", + "@thi.ng/equiv": "workspace:^", + "@thi.ng/hiccup-svg": "workspace:^", + "@thi.ng/rdom": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "hiccup-svg", + "rdom", + "rstream", + "transducers" + ], + "screenshot": "examples/rdom-svg-nodes.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/rdom-svg-nodes/src/index.ts b/examples/rdom-svg-nodes/src/index.ts index e3d9fb4be2..1d779a810b 100644 --- a/examples/rdom-svg-nodes/src/index.ts +++ b/examples/rdom-svg-nodes/src/index.ts @@ -22,54 +22,54 @@ const db = defAtom([...repeatedly(() => random2([], R, WIDTH - R), NUM)]); let clicked = -1; $compile( - svg( - { - width: WIDTH, - height: WIDTH, - viewBox: `0 0 ${WIDTH} ${WIDTH}`, - // mouse drag & release handlers are on SVG element itself - // for better UX when fast dragging - onmousemove: (e: MouseEvent) => - clicked !== -1 && db.resetIn([clicked], [e.clientX, e.clientY]), - onmouseup: () => (clicked = -1), - // add new point on double click - ondblclick: (e: MouseEvent) => - db.swap((pts) => [...pts, [e.clientX, e.clientY]]), - }, - // reactive "list" of lines - $list( - // transform atom view into consecutive pairs: - // e.g. [a,b,c,d] => [[a,b],[b,c],[c,d]] - fromAtom(db).map((pts) => [...partition(2, 1, pts)]), - // list wrapper element & its attribs - "g", - { stroke: "#00f" }, - // list item constructor (here an SVG line) - ([a, b]) => line(a, b), - // value based equivalence predicate - // applied to raw values from stream (here point pairs) - // item ctors are only called if predicate returns false - equivArrayLike - ), - // reactive list of circles - $list( - // transform atom view to label each point with its array index - // (needed to determine point selection in mouse event handler) - fromAtom(db).map((pts) => [...indexed(0, pts)]), - // list wrapper element & its attribs - "g", - { fill: "#00f" }, - // list items here are SVG circles - // update selection & point position in atom on mouse click - ([i, p]) => - circle(p, R, { - onmousedown: (e: MouseEvent) => { - clicked = i; - db.resetIn([i], [e.clientX, e.clientY]); - }, - }), - // value based equivalence predicate - equivArrayLike - ) - ) + svg( + { + width: WIDTH, + height: WIDTH, + viewBox: `0 0 ${WIDTH} ${WIDTH}`, + // mouse drag & release handlers are on SVG element itself + // for better UX when fast dragging + onmousemove: (e: MouseEvent) => + clicked !== -1 && db.resetIn([clicked], [e.clientX, e.clientY]), + onmouseup: () => (clicked = -1), + // add new point on double click + ondblclick: (e: MouseEvent) => + db.swap((pts) => [...pts, [e.clientX, e.clientY]]), + }, + // reactive "list" of lines + $list( + // transform atom view into consecutive pairs: + // e.g. [a,b,c,d] => [[a,b],[b,c],[c,d]] + fromAtom(db).map((pts) => [...partition(2, 1, pts)]), + // list wrapper element & its attribs + "g", + { stroke: "#00f" }, + // list item constructor (here an SVG line) + ([a, b]) => line(a, b), + // value based equivalence predicate + // applied to raw values from stream (here point pairs) + // item ctors are only called if predicate returns false + equivArrayLike + ), + // reactive list of circles + $list( + // transform atom view to label each point with its array index + // (needed to determine point selection in mouse event handler) + fromAtom(db).map((pts) => [...indexed(0, pts)]), + // list wrapper element & its attribs + "g", + { fill: "#00f" }, + // list items here are SVG circles + // update selection & point position in atom on mouse click + ([i, p]) => + circle(p, R, { + onmousedown: (e: MouseEvent) => { + clicked = i; + db.resetIn([i], [e.clientX, e.clientY]); + }, + }), + // value based equivalence predicate + equivArrayLike + ) + ) ).mount(document.getElementById("app")!); diff --git a/examples/rdom-svg-nodes/tsconfig.json b/examples/rdom-svg-nodes/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rdom-svg-nodes/tsconfig.json +++ b/examples/rdom-svg-nodes/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rotating-voronoi/package.json b/examples/rotating-voronoi/package.json index 56f738a357..ee826b7d2a 100644 --- a/examples/rotating-voronoi/package.json +++ b/examples/rotating-voronoi/package.json @@ -1,59 +1,59 @@ { - "name": "@example/rotating-voronoi", - "private": true, - "description": "Animated Voronoi diagram, cubic splines & SVG download", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Alberto Massa ", - "license": "Apache-2.0", - "keywords": [ - "canvas", - "svg", - "voronoi", - "delanuy", - "spline", - "rstream" - ], - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/dl-asset": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/geom-resample": "workspace:^", - "@thi.ng/geom-voronoi": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/random": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "dl-asset", - "geom", - "geom-resample", - "geom-voronoi", - "hdom-canvas", - "rstream", - "transducers", - "transducers-hdom", - "vectors" - ], - "screenshot": "examples/rotating-voronoi.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/rotating-voronoi", + "private": true, + "description": "Animated Voronoi diagram, cubic splines & SVG download", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Alberto Massa ", + "license": "Apache-2.0", + "keywords": [ + "canvas", + "svg", + "voronoi", + "delanuy", + "spline", + "rstream" + ], + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/dl-asset": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/geom-resample": "workspace:^", + "@thi.ng/geom-voronoi": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/random": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "dl-asset", + "geom", + "geom-resample", + "geom-voronoi", + "hdom-canvas", + "rstream", + "transducers", + "transducers-hdom", + "vectors" + ], + "screenshot": "examples/rotating-voronoi.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/rotating-voronoi/src/controllers.ts b/examples/rotating-voronoi/src/controllers.ts index e7f2888e80..43b7ae8cdf 100644 --- a/examples/rotating-voronoi/src/controllers.ts +++ b/examples/rotating-voronoi/src/controllers.ts @@ -1,52 +1,52 @@ export const slider = ( - value: number, - onChange: (n: number) => void, - min: number, - max: number, - step: number, - label: string + value: number, + onChange: (n: number) => void, + min: number, + max: number, + step: number, + label: string ) => { - return [ - "div.pv2", - ["div", label], - [ - "input.w-100", - { - type: "range", - value, - min, - max, - step, - oninput: (e: Event) => { - const target = e.target as HTMLInputElement; - const value = target && target.value; - onChange(parseFloat(value)); - }, - }, - ], - ]; + return [ + "div.pv2", + ["div", label], + [ + "input.w-100", + { + type: "range", + value, + min, + max, + step, + oninput: (e: Event) => { + const target = e.target as HTMLInputElement; + const value = target && target.value; + onChange(parseFloat(value)); + }, + }, + ], + ]; }; export const checkbox = ( - value: boolean, - onChange: (n: boolean) => void, - label: string + value: boolean, + onChange: (n: boolean) => void, + label: string ) => { - return [ - "div.pv2", - [ - "input.mr1", - { - id: label, - type: "checkbox", - checked: value, - oninput: (e: Event) => { - const target = e.target; - const checked = target && target.checked; - onChange(checked); - }, - }, - ], - ["label", { for: label }, label], - ]; + return [ + "div.pv2", + [ + "input.mr1", + { + id: label, + type: "checkbox", + checked: value, + oninput: (e: Event) => { + const target = e.target; + const checked = target && target.checked; + onChange(checked); + }, + }, + ], + ["label", { for: label }, label], + ]; }; diff --git a/examples/rotating-voronoi/src/index.ts b/examples/rotating-voronoi/src/index.ts index f137c4fdb9..c1f36202e9 100644 --- a/examples/rotating-voronoi/src/index.ts +++ b/examples/rotating-voronoi/src/index.ts @@ -20,10 +20,10 @@ import type { Vec } from "@thi.ng/vectors"; import { cartesian2 } from "@thi.ng/vectors/cartesian"; import { checkbox, slider } from "./controllers"; import { - type AppState, - animationStream, - mainStream, - scaleStream, + type AppState, + animationStream, + mainStream, + scaleStream, } from "./stream-state"; const edge = window.innerWidth * 0.7; @@ -35,142 +35,142 @@ const center = [width / 2, height / 2]; const rndInt = (min: number, max: number) => SYSTEM.minmax(min, max) | 0; const startingCircles: Array<[number, number, boolean]> = [ - [radius / 1, rndInt(4, 20), true], - [radius / 2, rndInt(4, 20), false], - [radius / 4, rndInt(4, 20), true], - [radius / 8, rndInt(4, 20), false], + [radius / 1, rndInt(4, 20), true], + [radius / 2, rndInt(4, 20), false], + [radius / 4, rndInt(4, 20), true], + [radius / 8, rndInt(4, 20), false], ]; const pointsInCircle = ( - _center: Vec, - _radius: number, - _num: number, - _angle: number + _center: Vec, + _radius: number, + _num: number, + _angle: number ) => [ - ...map( - (index) => cartesian2(null, [_radius, index * TAU + _angle], _center), - normRange(_num, false) - ), + ...map( + (index) => cartesian2(null, [_radius, index * TAU + _angle], _center), + normRange(_num, false) + ), ]; mainStream.transform(map(appRender), updateDOM()); function computeVoronoi(state: AppState) { - const delta = state.frameValue / 100; - const doSave = state.keyValue === "s"; - - const startPoints = [ - ...mapcat( - ([rad, density, clockwise]) => - pointsInCircle( - center, - rad, - density, - clockwise ? delta : PI - delta - ), - startingCircles - ), - ]; - - const bounds = rect([width, height], { fill: "black" }); - const mesh = new DVMesh(); - mesh.addKeys(startPoints, 0.01); - const cells = mesh.voronoi(vertices(bounds)); - - const voronoi = [ - bounds, - - group( - { fill: "white", "stroke-width": 1 }, - cells.map((cell) => - pathFromCubics( - asCubic(polygon(simplify(cell, 0.5, true)), { - scale: state.scaleValue, - }) - ) - ) - ), - - points(doSave ? [] : startPoints, { - size: 4, - shape: "circle", - fill: "gray", - }), - ]; - - if (doSave) { - const svg = asSvg( - svgDoc( - { - width, - height, - viewBox: `0 0 ${width} ${height}`, - "stroke-width": 0.25, - }, - ...voronoi - ) - ); - downloadWithMime(`${new Date().getTime()}-voronoi.svg`, svg, { - mime: "image/svg+xml", - }); - } - - return voronoi; + const delta = state.frameValue / 100; + const doSave = state.keyValue === "s"; + + const startPoints = [ + ...mapcat( + ([rad, density, clockwise]) => + pointsInCircle( + center, + rad, + density, + clockwise ? delta : PI - delta + ), + startingCircles + ), + ]; + + const bounds = rect([width, height], { fill: "black" }); + const mesh = new DVMesh(); + mesh.addKeys(startPoints, 0.01); + const cells = mesh.voronoi(vertices(bounds)); + + const voronoi = [ + bounds, + + group( + { fill: "white", "stroke-width": 1 }, + cells.map((cell) => + pathFromCubics( + asCubic(polygon(simplify(cell, 0.5, true)), { + scale: state.scaleValue, + }) + ) + ) + ), + + points(doSave ? [] : startPoints, { + size: 4, + shape: "circle", + fill: "gray", + }), + ]; + + if (doSave) { + const svg = asSvg( + svgDoc( + { + width, + height, + viewBox: `0 0 ${width} ${height}`, + "stroke-width": 0.25, + }, + ...voronoi + ) + ); + downloadWithMime(`${new Date().getTime()}-voronoi.svg`, svg, { + mime: "image/svg+xml", + }); + } + + return voronoi; } function appRender(state: AppState) { - return [ - "div.ma3.flex.flex-column.flex-row-l.flex-row-m.sans-serif", - [ - [ - "div.pr3.w-100.w-30-l.w-30-m", - ["h1", "Rotating voronoi"], - [ - "p", - "Based on a M. Bostock", - [ - "a", - { - href: "https://observablehq.com/@mbostock/rotating-voronoi", - }, - " observablehq sketch", - ], - ". ", - - "Originally from an ", - [ - "a", - { - href: "https://www.flickr.com/photos/quasimondo/8254540763/", - }, - "ornament", - ], - " by Mario Klingemann.", - ], - ["p", "Press `s` to save the SVG file."], - [ - "div.mv3", - slider( - state.scaleValue, - (x: number) => scaleStream.next(x), - 0, - 1.2, - 0.01, - "Tangent scale factor" - ), - checkbox( - state.animationValue, - (x: boolean) => animationStream.next(x), - "Animation" - ), - ], - ], - [ - "div.flex.justify-center", - [canvas, { width, height }, ...computeVoronoi(state)], - ], - ], - ]; + return [ + "div.ma3.flex.flex-column.flex-row-l.flex-row-m.sans-serif", + [ + [ + "div.pr3.w-100.w-30-l.w-30-m", + ["h1", "Rotating voronoi"], + [ + "p", + "Based on a M. Bostock", + [ + "a", + { + href: "https://observablehq.com/@mbostock/rotating-voronoi", + }, + " observablehq sketch", + ], + ". ", + + "Originally from an ", + [ + "a", + { + href: "https://www.flickr.com/photos/quasimondo/8254540763/", + }, + "ornament", + ], + " by Mario Klingemann.", + ], + ["p", "Press `s` to save the SVG file."], + [ + "div.mv3", + slider( + state.scaleValue, + (x: number) => scaleStream.next(x), + 0, + 1.2, + 0.01, + "Tangent scale factor" + ), + checkbox( + state.animationValue, + (x: boolean) => animationStream.next(x), + "Animation" + ), + ], + ], + [ + "div.flex.justify-center", + [canvas, { width, height }, ...computeVoronoi(state)], + ], + ], + ]; } // if (process.env.NODE_ENV !== "production") { diff --git a/examples/rotating-voronoi/src/stream-state.ts b/examples/rotating-voronoi/src/stream-state.ts index 096993daac..abce69d428 100644 --- a/examples/rotating-voronoi/src/stream-state.ts +++ b/examples/rotating-voronoi/src/stream-state.ts @@ -8,28 +8,28 @@ import { mapcat } from "@thi.ng/transducers/mapcat"; import { scan } from "@thi.ng/transducers/scan"; export const keyStreamConditional = fromDOMEvent(document, "keyup").transform( - mapcat((x) => [x.key, null]) + mapcat((x) => [x.key, null]) ); keyStreamConditional.next({}); export const scaleStream = reactive(1); export const animationStream = reactive(true); export const frameStreamConditional = fromRAF() - .subscribe(sidechainToggle(animationStream)) - .transform(scan(count())); + .subscribe(sidechainToggle(animationStream)) + .transform(scan(count())); export type AppState = { - scaleValue: number; - animationValue: boolean; - frameValue: number; - keyValue: string | null; + scaleValue: number; + animationValue: boolean; + frameValue: number; + keyValue: string | null; }; export const mainStream = sync({ - src: { - scaleValue: scaleStream, - animationValue: animationStream, - frameValue: frameStreamConditional, - keyValue: keyStreamConditional, - }, + src: { + scaleValue: scaleStream, + animationValue: animationStream, + frameValue: frameStreamConditional, + keyValue: keyStreamConditional, + }, }); diff --git a/examples/rotating-voronoi/tsconfig.json b/examples/rotating-voronoi/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rotating-voronoi/tsconfig.json +++ b/examples/rotating-voronoi/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/router-basics/package.json b/examples/router-basics/package.json index c9b0b969e3..acb3acfa29 100644 --- a/examples/router-basics/package.json +++ b/examples/router-basics/package.json @@ -1,40 +1,40 @@ { - "name": "@example/router-basics", - "private": true, - "version": "0.0.1", - "description": "Complete mini SPA app w/ router & async content loading", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./' && cp -R data dist", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/checks": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/interceptors": "workspace:^", - "@thi.ng/router": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "hdom", - "interceptors", - "router" - ], - "screenshot": "examples/router-basics.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/router-basics", + "private": true, + "version": "0.0.1", + "description": "Complete mini SPA app w/ router & async content loading", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./' && cp -R data dist", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/checks": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/interceptors": "workspace:^", + "@thi.ng/router": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "hdom", + "interceptors", + "router" + ], + "screenshot": "examples/router-basics.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/router-basics/src/api.ts b/examples/router-basics/src/api.ts index 6f4546cb09..fdac6a3df8 100644 --- a/examples/router-basics/src/api.ts +++ b/examples/router-basics/src/api.ts @@ -21,43 +21,43 @@ export type ViewSpec = string | Path | [string | Path, Fn]; * See `src/config.ts`. */ export interface AppConfig { - components: IObjectOf; - domRoot: string | Element; - effects: IObjectOf; - events: IObjectOf; - initialState: any; - router: HTMLRouterConfig; - ui: UIAttribs; - views: Partial>; + components: IObjectOf; + domRoot: string | Element; + effects: IObjectOf; + events: IObjectOf; + initialState: any; + router: HTMLRouterConfig; + ui: UIAttribs; + views: Partial>; } export type AppViewIDs = - | "route" - | "routeComponent" - | "users" - | "userlist" - | "status" - | "debug" - | "json"; + | "route" + | "routeComponent" + | "users" + | "userlist" + | "status" + | "debug" + | "json"; /** * Derived views exposed by the app. * Add more declarations here as needed. */ export interface AppViews extends Record> { - route: IView; - routeComponent: IView; - users: IView>; - userlist: IView; - status: IView; - debug: IView; - json: IView; + route: IView; + routeComponent: IView; + users: IView>; + userlist: IView; + status: IView; + debug: IView; + json: IView; } export interface AppContext { - bus: EventBus; - views: AppViews; - ui: UIAttribs; + bus: EventBus; + views: AppViews; + ui: UIAttribs; } /** @@ -68,41 +68,41 @@ export interface AppContext { * component functions. */ export interface UIAttribs { - bodyCopy: any; - bodyLink: any; - card: any; - code: any; - column: any; - contact: any; - debugToggle: any; - nav: any; - root: any; - status: any; - userlist: any; + bodyCopy: any; + bodyLink: any; + card: any; + code: any; + column: any; + contact: any; + debugToggle: any; + nav: any; + root: any; + status: any; + userlist: any; } /// demo app related types export interface User { - id: number; - name: string; - job: string; - img: string; - desc: string; - alias: string; + id: number; + name: string; + job: string; + img: string; + desc: string; + alias: string; } /** * Types for status line component */ export enum StatusType { - DONE, - INFO, - SUCCESS, - ERROR, + DONE, + INFO, + SUCCESS, + ERROR, } export interface Status extends Array { - [0]: StatusType; - [1]: string; + [0]: StatusType; + [1]: string; } diff --git a/examples/router-basics/src/app.ts b/examples/router-basics/src/app.ts index 565ef503aa..4dd4a25fe3 100644 --- a/examples/router-basics/src/app.ts +++ b/examples/router-basics/src/app.ts @@ -25,108 +25,108 @@ import * as ev from "./events"; * - start router, hdom render & event bus loop */ export class App { - config: AppConfig; - ctx: AppContext; - state: Atom; - router: HTMLRouter; + config: AppConfig; + ctx: AppContext; + state: Atom; + router: HTMLRouter; - constructor(config: AppConfig) { - this.config = config; - this.state = defAtom(config.initialState || {}); - this.ctx = { - bus: new EventBus(this.state, config.events, config.effects), - views: {}, - ui: config.ui, - }; - this.addViews(this.config.views); - this.router = new HTMLRouter(config.router); - // connect router to event bus so that routing events are processed - // as part of the normal batched event processing loop - this.router.addListener(EVENT_ROUTE_CHANGED, (e) => - this.ctx.bus.dispatch([EVENT_ROUTE_CHANGED, e.value]) - ); - // whenever the route has changed, record its details in the app - // state. likewise, when the user or a component triggers a the - // `ROUTE_TO` event we assign the target route details to a side - // effect which will cause a change in the router, which then in - // turn triggers the `EVENT_ROUTE_CHANGED`, completing the - // circle - this.ctx.bus.addHandlers({ - [EVENT_ROUTE_CHANGED]: valueSetter("route"), - [ev.ROUTE_TO]: (_, [__, route]) => ({ [fx.ROUTE_TO]: route }), - }); - this.ctx.bus.addEffect(fx.ROUTE_TO, ([id, params]) => - this.router.routeTo(this.router.format(id, params)) - ); + constructor(config: AppConfig) { + this.config = config; + this.state = defAtom(config.initialState || {}); + this.ctx = { + bus: new EventBus(this.state, config.events, config.effects), + views: {}, + ui: config.ui, + }; + this.addViews(this.config.views); + this.router = new HTMLRouter(config.router); + // connect router to event bus so that routing events are processed + // as part of the normal batched event processing loop + this.router.addListener(EVENT_ROUTE_CHANGED, (e) => + this.ctx.bus.dispatch([EVENT_ROUTE_CHANGED, e.value]) + ); + // whenever the route has changed, record its details in the app + // state. likewise, when the user or a component triggers a the + // `ROUTE_TO` event we assign the target route details to a side + // effect which will cause a change in the router, which then in + // turn triggers the `EVENT_ROUTE_CHANGED`, completing the + // circle + this.ctx.bus.addHandlers({ + [EVENT_ROUTE_CHANGED]: valueSetter("route"), + [ev.ROUTE_TO]: (_, [__, route]) => ({ [fx.ROUTE_TO]: route }), + }); + this.ctx.bus.addEffect(fx.ROUTE_TO, ([id, params]) => + this.router.routeTo(this.router.format(id, params)) + ); - // instrument all event handlers to trace events in console - this.ctx.bus.instrumentWith([trace]); + // instrument all event handlers to trace events in console + this.ctx.bus.instrumentWith([trace]); - this.addViews({ - route: "route", - routeComponent: [ - "route.id", - (id) => - ( - this.config.components[id] || - (() => ["div", `missing component for route: ${id}`]) - )(this.ctx), - ], - }); - } + this.addViews({ + route: "route", + routeComponent: [ + "route.id", + (id) => + ( + this.config.components[id] || + (() => ["div", `missing component for route: ${id}`]) + )(this.ctx), + ], + }); + } - /** - * Initializes given derived view specs and attaches them to app - * state atom. - * - * @param specs - - */ - addViews(specs: IObjectOf) { - const views: any = this.ctx.views; - for (let id in specs) { - const spec = specs[id]; - views[id] = isArray(spec) - ? defViewUnsafe(this.state, spec[0], >spec[1]) - : defViewUnsafe(this.state, spec); - } - } + /** + * Initializes given derived view specs and attaches them to app + * state atom. + * + * @param specs - + */ + addViews(specs: IObjectOf) { + const views: any = this.ctx.views; + for (let id in specs) { + const spec = specs[id]; + views[id] = isArray(spec) + ? defViewUnsafe(this.state, spec[0], >spec[1]) + : defViewUnsafe(this.state, spec); + } + } - /** - * Starts router and kicks off hdom render loop, including batched - * event processing and fast fail check if DOM updates are necessary - * (assumes ALL state is held in the app state atom. So if there - * weren't any events causing a state change since last frame, - * re-rendering is skipped without even attempting to diff DOM tree). - */ - start() { - this.router.start(); - start( - () => { - if (this.ctx.bus.processQueue()) { - return this.rootComponent(); - } - }, - { root: this.config.domRoot, ctx: this.ctx } - ); - } + /** + * Starts router and kicks off hdom render loop, including batched + * event processing and fast fail check if DOM updates are necessary + * (assumes ALL state is held in the app state atom. So if there + * weren't any events causing a state change since last frame, + * re-rendering is skipped without even attempting to diff DOM tree). + */ + start() { + this.router.start(); + start( + () => { + if (this.ctx.bus.processQueue()) { + return this.rootComponent(); + } + }, + { root: this.config.domRoot, ctx: this.ctx } + ); + } - /** - * User provided root component function defined - * by current route and the derived view defined above. - */ - rootComponent(): any { - const debug = this.ctx.views.debug.deref()!; - const ui = this.ctx.ui; - return [ - "div", - ui.root, - [ - "div", - ui.column.content[debug], - nav, - this.ctx.views.routeComponent, - ], - [debugContainer, debug, this.ctx.views.json], - ]; - } + /** + * User provided root component function defined + * by current route and the derived view defined above. + */ + rootComponent(): any { + const debug = this.ctx.views.debug.deref()!; + const ui = this.ctx.ui; + return [ + "div", + ui.root, + [ + "div", + ui.column.content[debug], + nav, + this.ctx.views.routeComponent, + ], + [debugContainer, debug, this.ctx.views.json], + ]; + } } diff --git a/examples/router-basics/src/components/all-users.ts b/examples/router-basics/src/components/all-users.ts index 56757440e4..4ad36764d7 100644 --- a/examples/router-basics/src/components/all-users.ts +++ b/examples/router-basics/src/components/all-users.ts @@ -11,12 +11,12 @@ import { status } from "./status"; * @param ctx - njected context object */ export function allUsers(ctx: AppContext) { - ctx.bus.dispatch( - ctx.views.userlist.deref()!.length - ? [SET_STATUS, [StatusType.SUCCESS, "list loaded from cache", true]] - : [LOAD_USER_LIST] - ); - return ["div", status, userList]; + ctx.bus.dispatch( + ctx.views.userlist.deref()!.length + ? [SET_STATUS, [StatusType.SUCCESS, "list loaded from cache", true]] + : [LOAD_USER_LIST] + ); + return ["div", status, userList]; } /** @@ -25,15 +25,15 @@ export function allUsers(ctx: AppContext) { * @param ctx - njected context object */ function userList(ctx: AppContext) { - const profiles = ctx.views.users.deref(); - const list = ctx.views.userlist.deref(); - return ( - list && [ - "section", - ctx.ui.userlist.root, - list.map((u) => [user, u, !!profiles![u.id]]), - ] - ); + const profiles = ctx.views.users.deref(); + const list = ctx.views.userlist.deref(); + return ( + list && [ + "section", + ctx.ui.userlist.root, + list.map((u) => [user, u, !!profiles![u.id]]), + ] + ); } /** @@ -43,25 +43,25 @@ function userList(ctx: AppContext) { * http://tachyons.io/components/lists/follower-notifications/index.html * * @param ctx - njected context object - * @param user - - * @param cached - + * @param user - + * @param cached - */ function user(ctx: AppContext, user: User, cached: boolean) { - const ui = ctx.ui.userlist; - return [ - "article", - ui.container, - ["div", ui.thumbWrapper, ["img", { ...ui.thumb, src: user.img }]], - [ - "div", - ui.body, - [ - "h1", - ui.title, - [routeLink, USER_PROFILE.id, { id: user.id }, null, user.name], - ], - ["h2", ui.subtitle, `@${user.alias}`], - ], - cached ? ["div", ui.meta, "cached"] : undefined, - ]; + const ui = ctx.ui.userlist; + return [ + "article", + ui.container, + ["div", ui.thumbWrapper, ["img", { ...ui.thumb, src: user.img }]], + [ + "div", + ui.body, + [ + "h1", + ui.title, + [routeLink, USER_PROFILE.id, { id: user.id }, null, user.name], + ], + ["h2", ui.subtitle, `@${user.alias}`], + ], + cached ? ["div", ui.meta, "cached"] : undefined, + ]; } diff --git a/examples/router-basics/src/components/contact.ts b/examples/router-basics/src/components/contact.ts index 8252a404df..db0240a2f9 100644 --- a/examples/router-basics/src/components/contact.ts +++ b/examples/router-basics/src/components/contact.ts @@ -7,17 +7,17 @@ import { externalLink } from "./external-link"; * @param ctx - njected context object */ export function contact(ctx: AppContext) { - return [ - "div", - ctx.ui.bodyCopy, - ["p", "Get in touch!"], - [ - "p", - [ - ["https://github.com/thi-ng/umbrella", "GitHub"], - ["https://twitter.com/toxi", "Twitter"], - ["https://medium.com/@thi.ng", "Medium"], - ].map((link) => [externalLink, ctx.ui.contact.link, ...link]), - ], - ]; + return [ + "div", + ctx.ui.bodyCopy, + ["p", "Get in touch!"], + [ + "p", + [ + ["https://github.com/thi-ng/umbrella", "GitHub"], + ["https://twitter.com/toxi", "Twitter"], + ["https://medium.com/@thi.ng", "Medium"], + ].map((link) => [externalLink, ctx.ui.contact.link, ...link]), + ], + ]; } diff --git a/examples/router-basics/src/components/debug-container.ts b/examples/router-basics/src/components/debug-container.ts index 97ed0c1ec1..330c14f6fe 100644 --- a/examples/router-basics/src/components/debug-container.ts +++ b/examples/router-basics/src/components/debug-container.ts @@ -6,19 +6,19 @@ import { eventLink } from "./event-link"; * Collapsible component showing stringified app state. * * @param ctx - njected context object - * @param debug - - * @param json - + * @param debug - + * @param json - */ export function debugContainer(ctx: AppContext, debug: any, json: string) { - return [ - "div#debug", - ctx.ui.column.debug[debug], - [ - eventLink, - [TOGGLE_DEBUG], - ctx.ui.debugToggle, - debug ? "close \u25bc" : "open \u25b2", - ], - ["pre", ctx.ui.code, json], - ]; + return [ + "div#debug", + ctx.ui.column.debug[debug], + [ + eventLink, + [TOGGLE_DEBUG], + ctx.ui.debugToggle, + debug ? "close \u25bc" : "open \u25b2", + ], + ["pre", ctx.ui.code, json], + ]; } diff --git a/examples/router-basics/src/components/event-link.ts b/examples/router-basics/src/components/event-link.ts index e3af09559b..43c4f680fa 100644 --- a/examples/router-basics/src/components/event-link.ts +++ b/examples/router-basics/src/components/event-link.ts @@ -11,20 +11,20 @@ import type { AppContext } from "../api"; * @param body - ink body */ export function eventLink( - ctx: AppContext, - event: Event, - attribs: any, - body: any + ctx: AppContext, + event: Event, + attribs: any, + body: any ) { - return [ - "a", - { - ...attribs, - onclick: (e: any) => { - e.preventDefault(); - ctx.bus.dispatch(event); - }, - }, - body, - ]; + return [ + "a", + { + ...attribs, + onclick: (e: any) => { + e.preventDefault(); + ctx.bus.dispatch(event); + }, + }, + body, + ]; } diff --git a/examples/router-basics/src/components/external-link.ts b/examples/router-basics/src/components/external-link.ts index f8c4cdcc59..22093e0823 100644 --- a/examples/router-basics/src/components/external-link.ts +++ b/examples/router-basics/src/components/external-link.ts @@ -9,10 +9,10 @@ import type { AppContext } from "../api"; * @param body - ink body */ export function externalLink( - _: AppContext, - attribs: any, - uri: string, - body: any + _: AppContext, + attribs: any, + uri: string, + body: any ) { - return ["a", { ...attribs, href: uri }, body]; + return ["a", { ...attribs, href: uri }, body]; } diff --git a/examples/router-basics/src/components/home.ts b/examples/router-basics/src/components/home.ts index acc461c1cb..f3ffa1207c 100644 --- a/examples/router-basics/src/components/home.ts +++ b/examples/router-basics/src/components/home.ts @@ -7,49 +7,49 @@ import { externalLink } from "./external-link"; * @param ctx - njected context object */ export function home(ctx: AppContext) { - return [ - "div", - ctx.ui.bodyCopy, - [ - "p", - "This is an example application to demonstrate common usage patterns for creating lightweight web apps with the ", - [ - externalLink, - ctx.ui.bodyLink, - "https://github.com/thi-ng/umbrella", - "@thi.ng/umbrella", - ], - " libraries.", - ], - [ - "p", - [ - "ul.list", - ["li", "App & component configuration"], - ["li", "Global context injection"], - ["li", "Pure ES6 UI components"], - ["li", "Central app state handling"], - ["li", "Derived views"], - ["li", "Route definition & validation"], - ["li", "Composable events, interceptors, side effects"], - ["li", "Async side effects"], - ["li", "Dynamic content loading / transformation"], - [ - "li", - "Component styling with ", - [ - externalLink, - ctx.ui.bodyLink, - "http://tachyons.io/", - "Tachyons CSS", - ], - ], - ], - ], - [ - "p", - "Please see the related blog post and the commented source code for more details.", - ], - ["p", "(total app file size: 11.2KB)"], - ]; + return [ + "div", + ctx.ui.bodyCopy, + [ + "p", + "This is an example application to demonstrate common usage patterns for creating lightweight web apps with the ", + [ + externalLink, + ctx.ui.bodyLink, + "https://github.com/thi-ng/umbrella", + "@thi.ng/umbrella", + ], + " libraries.", + ], + [ + "p", + [ + "ul.list", + ["li", "App & component configuration"], + ["li", "Global context injection"], + ["li", "Pure ES6 UI components"], + ["li", "Central app state handling"], + ["li", "Derived views"], + ["li", "Route definition & validation"], + ["li", "Composable events, interceptors, side effects"], + ["li", "Async side effects"], + ["li", "Dynamic content loading / transformation"], + [ + "li", + "Component styling with ", + [ + externalLink, + ctx.ui.bodyLink, + "http://tachyons.io/", + "Tachyons CSS", + ], + ], + ], + ], + [ + "p", + "Please see the related blog post and the commented source code for more details.", + ], + ["p", "(total app file size: 11.2KB)"], + ]; } diff --git a/examples/router-basics/src/components/nav.ts b/examples/router-basics/src/components/nav.ts index 671884566f..a3eb18bd84 100644 --- a/examples/router-basics/src/components/nav.ts +++ b/examples/router-basics/src/components/nav.ts @@ -8,16 +8,16 @@ import { routeLink } from "./route-link"; * @param ctx - njected context object */ export function nav(ctx: AppContext) { - const ui = ctx.ui.nav; - return [ - "nav", - ["h1", ui.title, "Demo app"], - [ - "div", - ui.inner, - [routeLink, HOME.id, null, ui.link, "Home"], - [routeLink, USER_LIST.id, null, ui.link, "Users"], - [routeLink, CONTACT.id, null, ui.linkLast, "Contact"], - ], - ]; + const ui = ctx.ui.nav; + return [ + "nav", + ["h1", ui.title, "Demo app"], + [ + "div", + ui.inner, + [routeLink, HOME.id, null, ui.link, "Home"], + [routeLink, USER_LIST.id, null, ui.link, "Users"], + [routeLink, CONTACT.id, null, ui.linkLast, "Contact"], + ], + ]; } diff --git a/examples/router-basics/src/components/route-link.ts b/examples/router-basics/src/components/route-link.ts index 5c4aa4281f..a667b3bc36 100644 --- a/examples/router-basics/src/components/route-link.ts +++ b/examples/router-basics/src/components/route-link.ts @@ -11,21 +11,21 @@ import { ROUTE_TO } from "../events"; * @param body - ink body */ export function routeLink( - ctx: AppContext, - routeID: PropertyKey, - routeParams: any, - attribs: any, - body: any + ctx: AppContext, + routeID: PropertyKey, + routeParams: any, + attribs: any, + body: any ) { - return [ - "a", - { - ...attribs, - onclick: (e: Event) => { - e.preventDefault(); - ctx.bus.dispatch([ROUTE_TO, [routeID, routeParams]]); - }, - }, - body, - ]; + return [ + "a", + { + ...attribs, + onclick: (e: Event) => { + e.preventDefault(); + ctx.bus.dispatch([ROUTE_TO, [routeID, routeParams]]); + }, + }, + body, + ]; } diff --git a/examples/router-basics/src/components/status.ts b/examples/router-basics/src/components/status.ts index 29c4a8e412..9bf0c12d66 100644 --- a/examples/router-basics/src/components/status.ts +++ b/examples/router-basics/src/components/status.ts @@ -6,6 +6,6 @@ import type { AppContext } from "../api"; * @param ctx - njected context object */ export function status(ctx: AppContext) { - const [type, msg] = ctx.views.status.deref()!; - return ["p", ctx.ui.status[type], msg]; + const [type, msg] = ctx.views.status.deref()!; + return ["p", ctx.ui.status[type], msg]; } diff --git a/examples/router-basics/src/components/user-profile.ts b/examples/router-basics/src/components/user-profile.ts index ba4b1974d2..d5cb55b636 100644 --- a/examples/router-basics/src/components/user-profile.ts +++ b/examples/router-basics/src/components/user-profile.ts @@ -9,28 +9,28 @@ import { status } from "./status"; * @param ctx - njected context object */ export function userProfile(ctx: AppContext) { - const id = ctx.views.route.deref()!.params.id; - ctx.bus.dispatch( - ctx.views.users.deref()![id] - ? [SET_STATUS, [StatusType.SUCCESS, "loaded from cache", true]] - : [LOAD_USER, id] - ); - return ["div", [status], [userCard, id]]; + const id = ctx.views.route.deref()!.params.id; + ctx.bus.dispatch( + ctx.views.users.deref()![id] + ? [SET_STATUS, [StatusType.SUCCESS, "loaded from cache", true]] + : [LOAD_USER, id] + ); + return ["div", [status], [userCard, id]]; } // based on: http://tachyons.io/components/cards/profile-card/index.html function userCard(ctx: AppContext, id: number) { - const user = ctx.views.users.deref()![id]; - const ui = ctx.ui.card; - return user - ? [ - "div", - ui.container, - ["img", { ...ui.thumb, src: user.img }], - ["h3", ui.title, user.name], - user.job, - ["hr", ui.sep], - ["p", ui.body, user.desc], - ] - : undefined; + const user = ctx.views.users.deref()![id]; + const ui = ctx.ui.card; + return user + ? [ + "div", + ui.container, + ["img", { ...ui.thumb, src: user.img }], + ["h3", ui.title, user.name], + user.job, + ["hr", ui.sep], + ["p", ui.body, user.desc], + ] + : undefined; } diff --git a/examples/router-basics/src/config.ts b/examples/router-basics/src/config.ts index 88d2324b56..be15ad71b1 100644 --- a/examples/router-basics/src/config.ts +++ b/examples/router-basics/src/config.ts @@ -1,10 +1,10 @@ import { - type Event, - EV_SET_VALUE, - FX_DELAY, - FX_DISPATCH_ASYNC, - FX_DISPATCH_NOW, - valueUpdater, + type Event, + EV_SET_VALUE, + FX_DELAY, + FX_DISPATCH_ASYNC, + FX_DISPATCH_NOW, + valueUpdater, } from "@thi.ng/interceptors"; import { type AppConfig, StatusType } from "./api"; import { allUsers } from "./components/all-users"; @@ -17,236 +17,236 @@ import * as routes from "./routes"; // main App configuration export const CONFIG: AppConfig = { - // router configuration - // docs here: - // https://docs.thi.ng/umbrella/router/ - router: { - // use URI hash for routes (KISS) - useFragment: true, - // route ID if no other matches (MUST be non-parametric!) - defaultRouteID: routes.HOME.id, - // IMPORTANT: rules with common prefixes MUST be specified in - // order of highest precision / longest path - routes: [ - routes.HOME, - routes.CONTACT, - routes.USER_PROFILE, - routes.USER_LIST, - ], - }, - - // event handlers events are queued and batch processed in app's RAF - // render loop event handlers can be single functions, interceptor - // objects with `pre`/`post` keys or arrays of either. - - // the event handlers' only task is to transform the event into a - // number of side effects. event handlers should be pure functions - // and only side effect functions execute any "real" work. - - // see EventBus docs here: - // https://docs.thi.ng/umbrella/interceptors/#event-bus-interceptors-side-effects - - events: { - // sets status to "done" - [ev.DONE]: () => ({ - [FX_DISPATCH_NOW]: [ev.SET_STATUS, [StatusType.DONE, "done"]], - }), - - // sets status to thrown error's message - [ev.ERROR]: (_, [__, err]) => ({ - [FX_DISPATCH_NOW]: [ev.SET_STATUS, [StatusType.ERROR, err.message]], - }), - - // triggers loading of JSON for single user, sets status - [ev.LOAD_USER]: (_, [__, id]) => ({ - [FX_DISPATCH_NOW]: [ - ev.SET_STATUS, - [StatusType.INFO, `loading user data...`], - ], - [FX_DISPATCH_ASYNC]: [ - fx.JSON, - `data/user-${id}.json`, - ev.RECEIVE_USER, - ev.LOAD_USER_ERROR, - ], - }), - - // triggered after successful IO - // stores received user data under `users.{id}`, sets status - // note: we assign multiple value/events as array to the FX_DISPATCH_NOW side effect - [ev.RECEIVE_USER]: (_, [__, json]) => ({ - [FX_DISPATCH_NOW]: [ - [EV_SET_VALUE, [["users", json.id], json]], - [ - ev.SET_STATUS, - [StatusType.SUCCESS, "JSON successfully loaded", true], - ], - ], - }), - - // error event for user profile IO requests (i.e. in this demo for user ID 3) - // set status, then redirects to /users after 1sec - [ev.LOAD_USER_ERROR]: (_, [__, err]) => ({ - [FX_DISPATCH_NOW]: [ev.SET_STATUS, [StatusType.ERROR, err.message]], - [FX_DISPATCH_ASYNC]: [ - FX_DELAY, - [1000, [routes.USER_LIST.id]], - ev.ROUTE_TO, - ev.ERROR, - ], - }), - - // triggers loading of JSON summary of all users, sets status - [ev.LOAD_USER_LIST]: () => ({ - [FX_DISPATCH_NOW]: [ - ev.SET_STATUS, - [StatusType.INFO, `loading user data...`], - ], - [FX_DISPATCH_ASYNC]: [ - fx.JSON, - "data/users.json", - ev.RECEIVE_USERS, - ev.ERROR, - ], - }), - - // triggered after successful IO - // note: we assign multiple value/events as array to the FX_DISPATCH_NOW side effect - [ev.RECEIVE_USERS]: (_, [__, json]) => ({ - [FX_DISPATCH_NOW]: [ - [EV_SET_VALUE, ["userlist", json]], - [ - ev.SET_STATUS, - [StatusType.SUCCESS, "JSON successfully loaded", true], - ], - ], - }), - - // stores status (a tuple of `[type, message, done?]`) in app state - // if status type != DONE & `done` == true, also triggers delayed EV_DONE - // Note: we inject the `trace` interceptor to log the event to the console - [ev.SET_STATUS]: (_, [__, status]) => ({ - [FX_DISPATCH_NOW]: [EV_SET_VALUE, ["status", status]], - [FX_DISPATCH_ASYNC]: - status[0] !== StatusType.DONE && status[2] - ? [FX_DELAY, [1000], ev.DONE, ev.ERROR] - : undefined, - }), - - // toggles debug state flag on/off - [ev.TOGGLE_DEBUG]: valueUpdater("debug", (x) => x ^ 1), - }, - - // side effects - effects: { - // generic JSON loader via fetch() - [fx.JSON]: (req) => - fetch(req).then((resp) => { - if (!resp.ok) { - throw new Error(resp.statusText); - } - return resp.json(); - }), - }, - - // mapping route IDs to their respective UI component functions - // those functions are called automatically by the app's root component - // base on the currently active route - components: { - [routes.HOME.id]: home, - [routes.CONTACT.id]: contact, - [routes.USER_LIST.id]: allUsers, - [routes.USER_PROFILE.id]: userProfile, - }, - - // DOM root element (or ID) - domRoot: "app", - - // initial app state - initialState: { - status: [StatusType.INFO, "running"], - users: {}, - userlist: [], - route: {}, - debug: 1, - }, - - // derived view declarations - // each key specifies the name of the view and its value - // the state path or `[path, transformer]` - // docs here: - // https://github.com/thi-ng/umbrella/tree/develop/packages/atom#derived-views - views: { - json: ["", (state) => JSON.stringify(state, null, 2)], - users: ["users", (users) => users || {}], - userlist: "userlist", - status: "status", - debug: "debug", - }, - - // component CSS class config using tachyons-css - // these attribs are being passed to all/most components - // with a bit more thought this can still be simplified a lot more - // and repetitions minimized... - - // looks at first somewhat cryptic, but it's a great/powerful system - // http://tachyons.io/ - ui: { - bodyCopy: { - class: "center measure-narrow measure-ns tc lh-copy black-70", - }, - bodyLink: { class: "link dim black" }, - card: { - container: { - class: "mw5 center bg-white br3 pa3 pa4-ns mv3 ba b--black-10 tc", - }, - thumb: { class: "br-100 h3 w3 dib" }, - title: { class: "ma1" }, - sep: { class: "mt3 mw3 bb bw1 b--black-10" }, - body: { class: "lh-copy measure center f6 black-70" }, - }, - code: { class: "ma0 ml4 pa2 f7 bg-light-gray code overflow-x-hidden" }, - column: { - content: [{ class: "w-90-ns ma2" }, { class: "w-50-ns ma2" }], - debug: [ - { class: "w-10-ns ma2 close" }, - { class: "w-50-ns ma2 open" }, - ], - }, - contact: { - link: { class: "db pb2 link dim black" }, - }, - debugToggle: { class: "toggle pointer" }, - nav: { - inner: { class: "tc pb3" }, - title: { class: "black f1 lh-title tc db mb2 mb2-ns" }, - link: { class: "pointer link dim gray f6 f5-ns dib mr3" }, - linkLast: { class: "pointer link dim gray f6 f5-ns dib" }, - }, - root: { class: "flex-ns sans-serif ma0" }, - status: { - [StatusType.DONE]: { - class: "pa2 bg-light-yellow gold tc fadeout bg-animate", - }, - [StatusType.INFO]: { - class: "pa2 bg-light-yellow gold tc bg-animate", - }, - [StatusType.SUCCESS]: { - class: "pa2 bg-light-green green tc bg-animate", - }, - [StatusType.ERROR]: { - class: "pa2 bg-light-red dark-red tc bg-animate", - }, - }, - userlist: { - root: { class: "measure center" }, - container: { class: "dt w-100 bb b--black-05 pb2 mt2" }, - thumbWrapper: { class: "dtc w2 w3-ns v-mid" }, - thumb: { class: "ba b--black-10 db br-100 w2 w3-ns h2 h3-ns" }, - body: { class: "dtc v-mid pl3" }, - title: { class: "pointer f6 f5-ns fw6 lh-title black mv0" }, - subtitle: { class: "f6 fw4 mt0 mb0 black-60" }, - meta: { class: "dtc tr v-mid black-60 f7" }, - }, - }, + // router configuration + // docs here: + // https://docs.thi.ng/umbrella/router/ + router: { + // use URI hash for routes (KISS) + useFragment: true, + // route ID if no other matches (MUST be non-parametric!) + defaultRouteID: routes.HOME.id, + // IMPORTANT: rules with common prefixes MUST be specified in + // order of highest precision / longest path + routes: [ + routes.HOME, + routes.CONTACT, + routes.USER_PROFILE, + routes.USER_LIST, + ], + }, + + // event handlers events are queued and batch processed in app's RAF + // render loop event handlers can be single functions, interceptor + // objects with `pre`/`post` keys or arrays of either. + + // the event handlers' only task is to transform the event into a + // number of side effects. event handlers should be pure functions + // and only side effect functions execute any "real" work. + + // see EventBus docs here: + // https://docs.thi.ng/umbrella/interceptors/#event-bus-interceptors-side-effects + + events: { + // sets status to "done" + [ev.DONE]: () => ({ + [FX_DISPATCH_NOW]: [ev.SET_STATUS, [StatusType.DONE, "done"]], + }), + + // sets status to thrown error's message + [ev.ERROR]: (_, [__, err]) => ({ + [FX_DISPATCH_NOW]: [ev.SET_STATUS, [StatusType.ERROR, err.message]], + }), + + // triggers loading of JSON for single user, sets status + [ev.LOAD_USER]: (_, [__, id]) => ({ + [FX_DISPATCH_NOW]: [ + ev.SET_STATUS, + [StatusType.INFO, `loading user data...`], + ], + [FX_DISPATCH_ASYNC]: [ + fx.JSON, + `data/user-${id}.json`, + ev.RECEIVE_USER, + ev.LOAD_USER_ERROR, + ], + }), + + // triggered after successful IO + // stores received user data under `users.{id}`, sets status + // note: we assign multiple value/events as array to the FX_DISPATCH_NOW side effect + [ev.RECEIVE_USER]: (_, [__, json]) => ({ + [FX_DISPATCH_NOW]: [ + [EV_SET_VALUE, [["users", json.id], json]], + [ + ev.SET_STATUS, + [StatusType.SUCCESS, "JSON successfully loaded", true], + ], + ], + }), + + // error event for user profile IO requests (i.e. in this demo for user ID 3) + // set status, then redirects to /users after 1sec + [ev.LOAD_USER_ERROR]: (_, [__, err]) => ({ + [FX_DISPATCH_NOW]: [ev.SET_STATUS, [StatusType.ERROR, err.message]], + [FX_DISPATCH_ASYNC]: [ + FX_DELAY, + [1000, [routes.USER_LIST.id]], + ev.ROUTE_TO, + ev.ERROR, + ], + }), + + // triggers loading of JSON summary of all users, sets status + [ev.LOAD_USER_LIST]: () => ({ + [FX_DISPATCH_NOW]: [ + ev.SET_STATUS, + [StatusType.INFO, `loading user data...`], + ], + [FX_DISPATCH_ASYNC]: [ + fx.JSON, + "data/users.json", + ev.RECEIVE_USERS, + ev.ERROR, + ], + }), + + // triggered after successful IO + // note: we assign multiple value/events as array to the FX_DISPATCH_NOW side effect + [ev.RECEIVE_USERS]: (_, [__, json]) => ({ + [FX_DISPATCH_NOW]: [ + [EV_SET_VALUE, ["userlist", json]], + [ + ev.SET_STATUS, + [StatusType.SUCCESS, "JSON successfully loaded", true], + ], + ], + }), + + // stores status (a tuple of `[type, message, done?]`) in app state + // if status type != DONE & `done` == true, also triggers delayed EV_DONE + // Note: we inject the `trace` interceptor to log the event to the console + [ev.SET_STATUS]: (_, [__, status]) => ({ + [FX_DISPATCH_NOW]: [EV_SET_VALUE, ["status", status]], + [FX_DISPATCH_ASYNC]: + status[0] !== StatusType.DONE && status[2] + ? [FX_DELAY, [1000], ev.DONE, ev.ERROR] + : undefined, + }), + + // toggles debug state flag on/off + [ev.TOGGLE_DEBUG]: valueUpdater("debug", (x) => x ^ 1), + }, + + // side effects + effects: { + // generic JSON loader via fetch() + [fx.JSON]: (req) => + fetch(req).then((resp) => { + if (!resp.ok) { + throw new Error(resp.statusText); + } + return resp.json(); + }), + }, + + // mapping route IDs to their respective UI component functions + // those functions are called automatically by the app's root component + // base on the currently active route + components: { + [routes.HOME.id]: home, + [routes.CONTACT.id]: contact, + [routes.USER_LIST.id]: allUsers, + [routes.USER_PROFILE.id]: userProfile, + }, + + // DOM root element (or ID) + domRoot: "app", + + // initial app state + initialState: { + status: [StatusType.INFO, "running"], + users: {}, + userlist: [], + route: {}, + debug: 1, + }, + + // derived view declarations + // each key specifies the name of the view and its value + // the state path or `[path, transformer]` + // docs here: + // https://github.com/thi-ng/umbrella/tree/develop/packages/atom#derived-views + views: { + json: ["", (state) => JSON.stringify(state, null, 2)], + users: ["users", (users) => users || {}], + userlist: "userlist", + status: "status", + debug: "debug", + }, + + // component CSS class config using tachyons-css + // these attribs are being passed to all/most components + // with a bit more thought this can still be simplified a lot more + // and repetitions minimized... + + // looks at first somewhat cryptic, but it's a great/powerful system + // http://tachyons.io/ + ui: { + bodyCopy: { + class: "center measure-narrow measure-ns tc lh-copy black-70", + }, + bodyLink: { class: "link dim black" }, + card: { + container: { + class: "mw5 center bg-white br3 pa3 pa4-ns mv3 ba b--black-10 tc", + }, + thumb: { class: "br-100 h3 w3 dib" }, + title: { class: "ma1" }, + sep: { class: "mt3 mw3 bb bw1 b--black-10" }, + body: { class: "lh-copy measure center f6 black-70" }, + }, + code: { class: "ma0 ml4 pa2 f7 bg-light-gray code overflow-x-hidden" }, + column: { + content: [{ class: "w-90-ns ma2" }, { class: "w-50-ns ma2" }], + debug: [ + { class: "w-10-ns ma2 close" }, + { class: "w-50-ns ma2 open" }, + ], + }, + contact: { + link: { class: "db pb2 link dim black" }, + }, + debugToggle: { class: "toggle pointer" }, + nav: { + inner: { class: "tc pb3" }, + title: { class: "black f1 lh-title tc db mb2 mb2-ns" }, + link: { class: "pointer link dim gray f6 f5-ns dib mr3" }, + linkLast: { class: "pointer link dim gray f6 f5-ns dib" }, + }, + root: { class: "flex-ns sans-serif ma0" }, + status: { + [StatusType.DONE]: { + class: "pa2 bg-light-yellow gold tc fadeout bg-animate", + }, + [StatusType.INFO]: { + class: "pa2 bg-light-yellow gold tc bg-animate", + }, + [StatusType.SUCCESS]: { + class: "pa2 bg-light-green green tc bg-animate", + }, + [StatusType.ERROR]: { + class: "pa2 bg-light-red dark-red tc bg-animate", + }, + }, + userlist: { + root: { class: "measure center" }, + container: { class: "dt w-100 bb b--black-05 pb2 mt2" }, + thumbWrapper: { class: "dtc w2 w3-ns v-mid" }, + thumb: { class: "ba b--black-10 db br-100 w2 w3-ns h2 h3-ns" }, + body: { class: "dtc v-mid pl3" }, + title: { class: "pointer f6 f5-ns fw6 lh-title black mv0" }, + subtitle: { class: "f6 fw4 mt0 mb0 black-60" }, + meta: { class: "dtc tr v-mid black-60 f7" }, + }, + }, }; diff --git a/examples/router-basics/src/routes.ts b/examples/router-basics/src/routes.ts index 20ec8420bd..ee96175a33 100644 --- a/examples/router-basics/src/routes.ts +++ b/examples/router-basics/src/routes.ts @@ -10,18 +10,18 @@ import type { Route } from "@thi.ng/router"; // https://docs.thi.ng/umbrella/router/interfaces/Route.html export const HOME: Route = { - id: "home", - match: ["home"], + id: "home", + match: ["home"], }; export const CONTACT: Route = { - id: "contact", - match: ["contact"], + id: "contact", + match: ["contact"], }; export const USER_LIST: Route = { - id: "user-list", - match: ["users"], + id: "user-list", + match: ["users"], }; // this is a parametric route w/ parameter coercion & validation @@ -30,12 +30,12 @@ export const USER_LIST: Route = { // be used (see full router config further below) export const USER_PROFILE: Route = { - id: "user-profile", - match: ["users", "?id"], - validate: { - id: { - coerce: (x) => parseInt(x), - check: (x) => x > 0 && x < 100, - }, - }, + id: "user-profile", + match: ["users", "?id"], + validate: { + id: { + coerce: (x) => parseInt(x), + check: (x) => x > 0 && x < 100, + }, + }, }; diff --git a/examples/router-basics/tsconfig.json b/examples/router-basics/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/router-basics/tsconfig.json +++ b/examples/router-basics/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rstream-dataflow/package.json b/examples/rstream-dataflow/package.json index 1b315a3de1..3a46d7ceb4 100644 --- a/examples/rstream-dataflow/package.json +++ b/examples/rstream-dataflow/package.json @@ -1,44 +1,44 @@ { - "name": "@example/rstream-dataflow", - "private": true, - "description": "Minimal rstream dataflow graph", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/atom": "workspace:^", - "@thi.ng/equiv": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/paths": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-dot": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^", - "@thi.ng/rstream-graph": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "hdom", - "rstream-dot", - "rstream-gestures", - "rstream-graph", - "transducers" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/rstream-dataflow", + "private": true, + "description": "Minimal rstream dataflow graph", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/atom": "workspace:^", + "@thi.ng/equiv": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/paths": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-dot": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^", + "@thi.ng/rstream-graph": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "hdom", + "rstream-dot", + "rstream-gestures", + "rstream-graph", + "transducers" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/rstream-dataflow/src/circle.ts b/examples/rstream-dataflow/src/circle.ts index d379b4738c..5de6aa3289 100644 --- a/examples/rstream-dataflow/src/circle.ts +++ b/examples/rstream-dataflow/src/circle.ts @@ -2,16 +2,16 @@ const px = (x: number) => x.toFixed(3) + "px"; // @thi.ng/hdom UI component function export const circle = (col: string, x: number, y: number, w: number, h = w) => [ - "div", - { - class: "absolute z-1 white f7 tc br-100 o-80 " + col, - style: { - left: px(x - w / 2), - top: px(y - h / 2), - width: px(w), - height: px(h), - "line-height": px(h), - }, - }, - `${x};${y}`, + "div", + { + class: "absolute z-1 white f7 tc br-100 o-80 " + col, + style: { + left: px(x - w / 2), + top: px(y - h / 2), + width: px(w), + height: px(h), + "line-height": px(h), + }, + }, + `${x};${y}`, ]; diff --git a/examples/rstream-dataflow/src/index.ts b/examples/rstream-dataflow/src/index.ts index 6c62d3e622..6476032ee9 100644 --- a/examples/rstream-dataflow/src/index.ts +++ b/examples/rstream-dataflow/src/index.ts @@ -17,14 +17,14 @@ import { circle } from "./circle"; // infinite iterator of randomized colors (Tachyons CSS class names) // used by `color` graph node below const colors = choices([ - "bg-red", - "bg-blue", - "bg-gold", - "bg-light-green", - "bg-pink", - "bg-light-purple", - "bg-orange", - "bg-gray", + "bg-red", + "bg-blue", + "bg-gold", + "bg-light-green", + "bg-pink", + "bg-light-purple", + "bg-orange", + "bg-gray", ]); // atom for storing dataflow results (optional, here only for @@ -58,112 +58,112 @@ const raf = fromRAF(); // current internal state of the graph and is useful for debugging / // backup etc. const graph = initGraph(db, { - // extracts current mouse/touch position from gesture tuple - // the `[1, 0]` is the lookup path, i.e. `gesture[1][0]` - mpos: { - fn: extract(["pos"]), - ins: { src: { stream: () => gestures } }, - outs: { "*": "mpos" }, - }, + // extracts current mouse/touch position from gesture tuple + // the `[1, 0]` is the lookup path, i.e. `gesture[1][0]` + mpos: { + fn: extract(["pos"]), + ins: { src: { stream: () => gestures } }, + outs: { "*": "mpos" }, + }, - // extracts last click position from gesture tuple - // the `[1, 1]` is the lookup path, i.e. `gesture[1][1]` - // (only defined during drag gestures) - clickpos: { - fn: extract(["active", 0, "start"]), - ins: { src: { stream: () => gestures } }, - outs: { "*": "clickpos" }, - }, + // extracts last click position from gesture tuple + // the `[1, 1]` is the lookup path, i.e. `gesture[1][1]` + // (only defined during drag gestures) + clickpos: { + fn: extract(["active", 0, "start"]), + ins: { src: { stream: () => gestures } }, + outs: { "*": "clickpos" }, + }, - // extracts & computes length of `delta` vector in gesture tuple - // i.e. the distance between `clickpos` and current `mpos` - // (`delta` is only defined during drag gestures) - // `node1` is a helper function for nodes using only a single input - dist: { - fn: node1( - map((gesture) => { - const delta = getIn(gesture, ["active", 0, "delta"]); - return delta && Math.hypot.apply(null, delta) | 0; - }) - ), - ins: { src: { stream: () => gestures } }, - outs: { "*": "dist" }, - }, + // extracts & computes length of `delta` vector in gesture tuple + // i.e. the distance between `clickpos` and current `mpos` + // (`delta` is only defined during drag gestures) + // `node1` is a helper function for nodes using only a single input + dist: { + fn: node1( + map((gesture) => { + const delta = getIn(gesture, ["active", 0, "delta"]); + return delta && Math.hypot.apply(null, delta) | 0; + }) + ), + ins: { src: { stream: () => gestures } }, + outs: { "*": "dist" }, + }, - // combines `clickpos`, `dist` and `color` streams to produce a - // stream of @thi.ng/hdom UI components (a circle around clickpos). - // the resulting stream is then directly included in this app's root - // component below... all inputs are locally renamed using the - // stated input `id`s - // `node` is a helper function to create a `StreamSync` based node - // with multiple inputs - circle: { - fn: node( - map((ins) => { - // console.log(ins); - const { click, radius, color } = ins; - return click && radius && color - ? circle(color, click[0], click[1], radius * 2) - : undefined; - }) - ), - ins: { - click: { stream: "/clickpos/node" }, - radius: { stream: "/radius/node" }, - color: { stream: "/color/node" }, - }, - outs: { "*": "circle" }, - }, + // combines `clickpos`, `dist` and `color` streams to produce a + // stream of @thi.ng/hdom UI components (a circle around clickpos). + // the resulting stream is then directly included in this app's root + // component below... all inputs are locally renamed using the + // stated input `id`s + // `node` is a helper function to create a `StreamSync` based node + // with multiple inputs + circle: { + fn: node( + map((ins) => { + // console.log(ins); + const { click, radius, color } = ins; + return click && radius && color + ? circle(color, click[0], click[1], radius * 2) + : undefined; + }) + ), + ins: { + click: { stream: "/clickpos/node" }, + radius: { stream: "/radius/node" }, + color: { stream: "/color/node" }, + }, + outs: { "*": "circle" }, + }, - // produces a new random color for each new drag gesture (and - // therefore each new circle will have a potentially different - // color). transformation is done using a composed transducer which - // first dedupes click pos values and emits a new random color each - // time clickpos is redefined (remember, clickpos is only defined - // during drag gestures) - color: { - fn: node1( - comp( - dedupe(equiv), - map((x) => x && colors.next().value) - ) - ), - ins: { src: { stream: "/clickpos/node" } }, - outs: { "*": "color" }, - }, + // produces a new random color for each new drag gesture (and + // therefore each new circle will have a potentially different + // color). transformation is done using a composed transducer which + // first dedupes click pos values and emits a new random color each + // time clickpos is redefined (remember, clickpos is only defined + // during drag gestures) + color: { + fn: node1( + comp( + dedupe(equiv), + map((x) => x && colors.next().value) + ) + ), + ins: { src: { stream: "/clickpos/node" } }, + outs: { "*": "color" }, + }, - // transforms a `requestAnimationFrame` event stream (frame counter @ 60fps) - // into a sine wave with 0.6 .. 1.0 interval - sine: { - fn: node1(map((x: number) => 0.8 + 0.2 * Math.sin(x * 0.05))), - ins: { src: { stream: () => raf } }, - outs: { "*": "sin" }, - }, + // transforms a `requestAnimationFrame` event stream (frame counter @ 60fps) + // into a sine wave with 0.6 .. 1.0 interval + sine: { + fn: node1(map((x: number) => 0.8 + 0.2 * Math.sin(x * 0.05))), + ins: { src: { stream: () => raf } }, + outs: { "*": "sin" }, + }, - // multiplies `dist` and `sine` streams to produce an animated - // radius value for `circle` - radius: { - fn: mul, - ins: { - a: { stream: "/sine/node" }, - b: { stream: "/dist/node" }, - }, - outs: { "*": "radius" }, - }, + // multiplies `dist` and `sine` streams to produce an animated + // radius value for `circle` + radius: { + fn: mul, + ins: { + a: { stream: "/sine/node" }, + b: { stream: "/dist/node" }, + }, + outs: { "*": "radius" }, + }, }); // start @thi.ng/hdom update loop start(() => [ - "div", - [ - "pre.absolute.top-1.left-1.pa0.ma0.z-2.f7", - JSON.stringify(db.deref(), null, 2), - ], - // note: direct embedding of result stream below. this works - // since all @thi.ng/rstream subscriptions implement the - // @thi.ng/api/IDeref interface (like several other types, e.g. - // @thi.ng/atom's Atom, Cursor, View etc.) - graph.circle.node, + "div", + [ + "pre.absolute.top-1.left-1.pa0.ma0.z-2.f7", + JSON.stringify(db.deref(), null, 2), + ], + // note: direct embedding of result stream below. this works + // since all @thi.ng/rstream subscriptions implement the + // @thi.ng/api/IDeref interface (like several other types, e.g. + // @thi.ng/atom's Atom, Cursor, View etc.) + graph.circle.node, ]); // create a GraphViz DOT file of the entire dataflow graph diff --git a/examples/rstream-dataflow/tsconfig.json b/examples/rstream-dataflow/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rstream-dataflow/tsconfig.json +++ b/examples/rstream-dataflow/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rstream-event-loop/package.json b/examples/rstream-event-loop/package.json index ed9a8145a9..cbada28a72 100644 --- a/examples/rstream-event-loop/package.json +++ b/examples/rstream-event-loop/package.json @@ -1,40 +1,40 @@ { - "name": "@example/rstream-event-loop", - "private": true, - "version": "0.0.1", - "description": "Minimal demo of using rstream constructs to form an interceptor-style event loop", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/paths": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom", - "paths", - "rstream", - "transducers", - "transducers-hdom" - ], - "screenshot": "examples/rstream-event-loop.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/rstream-event-loop", + "private": true, + "version": "0.0.1", + "description": "Minimal demo of using rstream constructs to form an interceptor-style event loop", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/paths": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom", + "paths", + "rstream", + "transducers", + "transducers-hdom" + ], + "screenshot": "examples/rstream-event-loop.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/rstream-event-loop/src/api.ts b/examples/rstream-event-loop/src/api.ts index 7f39543174..882e383ecc 100644 --- a/examples/rstream-event-loop/src/api.ts +++ b/examples/rstream-event-loop/src/api.ts @@ -7,9 +7,9 @@ export const PAGE_READY = "page-ready"; // alias of possible event structures/signatures export interface EventTypeMap { - [PREV]: [typeof PREV, number]; - [NEXT]: [typeof NEXT, number]; - [PAGE_READY]: [typeof PAGE_READY]; + [PREV]: [typeof PREV, number]; + [NEXT]: [typeof NEXT, number]; + [PAGE_READY]: [typeof PAGE_READY]; } export type EventType = keyof EventTypeMap; @@ -17,8 +17,8 @@ export type EventType = keyof EventTypeMap; export type Event = Val1; export interface AppState { - pageID: number; - nextPageID: number; - isLoading: boolean; - timeoutID?: number; + pageID: number; + nextPageID: number; + isLoading: boolean; + timeoutID?: number; } diff --git a/examples/rstream-event-loop/src/events.ts b/examples/rstream-event-loop/src/events.ts index fe0caebb8e..5ddea9b305 100644 --- a/examples/rstream-event-loop/src/events.ts +++ b/examples/rstream-event-loop/src/events.ts @@ -6,7 +6,14 @@ import { stream } from "@thi.ng/rstream/stream"; import { trace } from "@thi.ng/rstream/trace"; import type { Transducer } from "@thi.ng/transducers"; import { filter } from "@thi.ng/transducers/filter"; -import { type Event, type EventType, type EventTypeMap, NEXT, PAGE_READY, PREV } from "./api"; +import { + type Event, + type EventType, + type EventTypeMap, + NEXT, + PAGE_READY, + PREV, +} from "./api"; import { state } from "./state"; /** @@ -31,7 +38,7 @@ events.subscribe(eventProc); /** * Event dispatch function. Sends given event into the event stream. * - * @param e - + * @param e - */ export const dispatch = (e: Event) => events.next(e); @@ -41,23 +48,23 @@ export const dispatch = (e: Event) => events.next(e); * The handler's subscription also includes an error handler to display * errors in the console. * - * @param id - - * @param handler - - * @param xform - + * @param id - + * @param handler - + * @param xform - */ export const defHandler = ( - id: E, - handler: Fn, - xform?: Transducer + id: E, + handler: Fn, + xform?: Transducer ) => { - const sub: ISubscriber = { - next: >handler, - error: (e) => { - console.warn(e); - return false; - }, - }; - return eventProc.subscribeTopic(id, sub, { xform }); + const sub: ISubscriber = { + next: >handler, + error: (e) => { + console.warn(e); + return false; + }, + }; + return eventProc.subscribeTopic(id, sub, { xform }); }; /** @@ -65,52 +72,52 @@ export const defHandler = ( * app state and simulates a pre-loading step (with delay), after which * it emits a `PAGE_READY` event. * - * @param offset - + * @param offset - */ const requestPage = (offset: number) => { - // get current app state - const curr = state.deref()!; - // just for illustration, not actually required in current example - // clear any active timeout before creating new one... - curr.timeoutID !== undefined && clearTimeout(curr.timeoutID); - // simulate pre-loading delay - const timeoutID = setTimeout(() => dispatch([PAGE_READY]), 250); - // IMMUTABLY(!) update app state - state.next( - setInManyUnsafe( - curr, - "nextPageID", - curr.pageID + offset, - "timeoutID", - timeoutID, - "isLoading", - true - ) - ); + // get current app state + const curr = state.deref()!; + // just for illustration, not actually required in current example + // clear any active timeout before creating new one... + curr.timeoutID !== undefined && clearTimeout(curr.timeoutID); + // simulate pre-loading delay + const timeoutID = setTimeout(() => dispatch([PAGE_READY]), 250); + // IMMUTABLY(!) update app state + state.next( + setInManyUnsafe( + curr, + "nextPageID", + curr.pageID + offset, + "timeoutID", + timeoutID, + "isLoading", + true + ) + ); }; // event handlers defHandler( - PREV, - ([_, step]) => requestPage(-step!), - // don't allow event if new page ID would be negative - filter(([_, x]) => state.deref()!.pageID >= x!) - // alternatively, use `map()` transducer to clamp new pageID to 0 - // map((e) => state.deref()!.pageID < e[1]! ? [PREV, state.deref()!.pageID] : e) + PREV, + ([_, step]) => requestPage(-step!), + // don't allow event if new page ID would be negative + filter(([_, x]) => state.deref()!.pageID >= x!) + // alternatively, use `map()` transducer to clamp new pageID to 0 + // map((e) => state.deref()!.pageID < e[1]! ? [PREV, state.deref()!.pageID] : e) ); defHandler( - NEXT, - ([_, step]) => requestPage(step!), - // don't allow event if new page ID would be >= 20 - filter(([_, x]) => state.deref()!.pageID < 20 - x!) + NEXT, + ([_, step]) => requestPage(step!), + // don't allow event if new page ID would be >= 20 + filter(([_, x]) => state.deref()!.pageID < 20 - x!) ); defHandler(PAGE_READY, () => { - const curr = state.deref()!; - // apply `nextPageID` and clear preload flag - state.next( - setInManyUnsafe(curr, "pageID", curr.nextPageID, "isLoading", false) - ); + const curr = state.deref()!; + // apply `nextPageID` and clear preload flag + state.next( + setInManyUnsafe(curr, "pageID", curr.nextPageID, "isLoading", false) + ); }); diff --git a/examples/rstream-event-loop/src/index.ts b/examples/rstream-event-loop/src/index.ts index 91650b93e9..9669d6b28a 100644 --- a/examples/rstream-event-loop/src/index.ts +++ b/examples/rstream-event-loop/src/index.ts @@ -8,63 +8,63 @@ import { state } from "./state"; /** * Main/root UI component, receives app state and returns hdom component tree. * - * @param state - + * @param state - */ const app = ({ pageID, isLoading }: AppState) => - isLoading - ? [ - "div.w-100.vh-100.flex.items-center.justify-center.bg-black.white", - ["div", "Loading..."], - ] - : [ - "div.ma3", - // delegate to child component - [page, pageID], - // navigation buttons w/ event dispatch - [ - "div", - [ - "button", - { - disabled: pageID < 5, - onclick: () => dispatch([PREV, 5]), - }, - "<<", - ], - [ - "button", - { - disabled: pageID === 0, - onclick: () => dispatch([PREV, 1]), - }, - "<", - ], - [ - "button", - { - disabled: pageID === 19, - onclick: () => dispatch([NEXT, 1]), - }, - ">", - ], - [ - "button", - { - disabled: pageID >= 15, - onclick: () => dispatch([NEXT, 5]), - }, - ">>", - ], - ], - // only here to show timestamp of last DOM update - ["div.mt3", new Date().toString()], - ]; + isLoading + ? [ + "div.w-100.vh-100.flex.items-center.justify-center.bg-black.white", + ["div", "Loading..."], + ] + : [ + "div.ma3", + // delegate to child component + [page, pageID], + // navigation buttons w/ event dispatch + [ + "div", + [ + "button", + { + disabled: pageID < 5, + onclick: () => dispatch([PREV, 5]), + }, + "<<", + ], + [ + "button", + { + disabled: pageID === 0, + onclick: () => dispatch([PREV, 1]), + }, + "<", + ], + [ + "button", + { + disabled: pageID === 19, + onclick: () => dispatch([NEXT, 1]), + }, + ">", + ], + [ + "button", + { + disabled: pageID >= 15, + onclick: () => dispatch([NEXT, 5]), + }, + ">>", + ], + ], + // only here to show timestamp of last DOM update + ["div.mt3", new Date().toString()], + ]; /** * Dummy page content. * * @param _ - hdom user context (unused) - * @param pageID - + * @param pageID - */ const page = (_: any, pageID: number) => ["h1", `Page: ${pageID}`]; diff --git a/examples/rstream-event-loop/src/state.ts b/examples/rstream-event-loop/src/state.ts index c0f2930be2..0d2f84ee71 100644 --- a/examples/rstream-event-loop/src/state.ts +++ b/examples/rstream-event-loop/src/state.ts @@ -5,7 +5,7 @@ import type { AppState } from "./api"; * Stream of app state values. */ export const state = reactive({ - pageID: 0, - nextPageID: 0, - isLoading: false, + pageID: 0, + nextPageID: 0, + isLoading: false, }); diff --git a/examples/rstream-event-loop/tsconfig.json b/examples/rstream-event-loop/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rstream-event-loop/tsconfig.json +++ b/examples/rstream-event-loop/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rstream-grid/package.json b/examples/rstream-grid/package.json index 20e10c1759..7c49a22490 100644 --- a/examples/rstream-grid/package.json +++ b/examples/rstream-grid/package.json @@ -1,53 +1,53 @@ { - "name": "@example/rstream-grid", - "private": true, - "version": "0.0.1", - "description": "Interactive grid generator, SVG generation & export, undo/redo support", - "repository": "https://github.com/[your-gh-username]/rs-undo", - "author": "Karsten Schmidt", - "license": "MIT", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/checks": "workspace:^", - "@thi.ng/dl-asset": "workspace:^", - "@thi.ng/expose": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hiccup": "workspace:^", - "@thi.ng/hiccup-svg": "workspace:^", - "@thi.ng/interceptors": "workspace:^", - "@thi.ng/paths": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-graph": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "dl-asset", - "expose", - "hdom", - "hiccup", - "hiccup-svg", - "interceptors", - "rstream", - "rstream-graph", - "transducers" - ], - "screenshot": "examples/rstream-grid.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/rstream-grid", + "private": true, + "version": "0.0.1", + "description": "Interactive grid generator, SVG generation & export, undo/redo support", + "repository": "https://github.com/[your-gh-username]/rs-undo", + "author": "Karsten Schmidt", + "license": "MIT", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/checks": "workspace:^", + "@thi.ng/dl-asset": "workspace:^", + "@thi.ng/expose": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hiccup": "workspace:^", + "@thi.ng/hiccup-svg": "workspace:^", + "@thi.ng/interceptors": "workspace:^", + "@thi.ng/paths": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-graph": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "dl-asset", + "expose", + "hdom", + "hiccup", + "hiccup-svg", + "interceptors", + "rstream", + "rstream-graph", + "transducers" + ], + "screenshot": "examples/rstream-grid.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/rstream-grid/src/api.ts b/examples/rstream-grid/src/api.ts index f5e3561990..d7e7f8c466 100644 --- a/examples/rstream-grid/src/api.ts +++ b/examples/rstream-grid/src/api.ts @@ -18,13 +18,13 @@ export type ViewSpec = string | Path | [string | Path, Fn]; * See `src/config.ts`. */ export interface AppConfig { - rootComponent: AppComponent; - domRoot: string | Element; - effects: IObjectOf; - events: IObjectOf; - initialState: any; - ui: UIAttribs; - views: Partial>; + rootComponent: AppComponent; + domRoot: string | Element; + effects: IObjectOf; + events: IObjectOf; + initialState: any; + ui: UIAttribs; + views: Partial>; } export type AppViewIDs = "svg" | "cols" | "rows" | "theta" | "stroke"; @@ -34,11 +34,11 @@ export type AppViewIDs = "svg" | "cols" | "rows" | "theta" | "stroke"; * Add more declarations here as needed. */ export interface AppViews extends Record> { - svg: IView; - cols: IView; - rows: IView; - theta: IView; - stroke: IView; + svg: IView; + cols: IView; + rows: IView; + theta: IView; + stroke: IView; } /** @@ -49,20 +49,20 @@ export interface AppViews extends Record> { * component functions. */ export interface UIAttribs { - button: any; - buttongroup: any; - footer: any; - link: any; - root: any; - slider: { root: any; range: any; label: any; number: any }; - sidebar: any; + button: any; + buttongroup: any; + footer: any; + link: any; + root: any; + slider: { root: any; range: any; label: any; number: any }; + sidebar: any; } /** * Structure of the context object passed to all component functions */ export interface AppContext { - bus: EventBus; - views: AppViews; - ui: UIAttribs; + bus: EventBus; + views: AppViews; + ui: UIAttribs; } diff --git a/examples/rstream-grid/src/app.ts b/examples/rstream-grid/src/app.ts index 8d1df22aef..a8b9cdb5b0 100644 --- a/examples/rstream-grid/src/app.ts +++ b/examples/rstream-grid/src/app.ts @@ -19,97 +19,97 @@ import { PARAM_BASE } from "./paths"; * - start hdom render & event bus loop */ export class App { - config: AppConfig; - ctx: AppContext; - state: Atom; - history: History; + config: AppConfig; + ctx: AppContext; + state: Atom; + history: History; - constructor(config: AppConfig) { - this.config = config; - this.state = defAtom(config.initialState || {}); - // note: the undo history only records the `PARAM_BASE` branch - // in the app state atom. this is so we don't include the generated - // SVG in the history and therefore save a lot of RAM - // furthermore, the param changes trigger updates in the dataflow graph - // (see `init()` method below) and will regenerate the SVG anyway - this.history = defHistory( - defCursorUnsafe(this.state, PARAM_BASE), - 1000 - ); - // define context object passed to all UI component functions - this.ctx = { - bus: new EventBus(this.state, config.events, config.effects), - views: {}, - ui: config.ui, - }; - // initialize derived views - this.addViews(this.config.views); - } + constructor(config: AppConfig) { + this.config = config; + this.state = defAtom(config.initialState || {}); + // note: the undo history only records the `PARAM_BASE` branch + // in the app state atom. this is so we don't include the generated + // SVG in the history and therefore save a lot of RAM + // furthermore, the param changes trigger updates in the dataflow graph + // (see `init()` method below) and will regenerate the SVG anyway + this.history = defHistory( + defCursorUnsafe(this.state, PARAM_BASE), + 1000 + ); + // define context object passed to all UI component functions + this.ctx = { + bus: new EventBus(this.state, config.events, config.effects), + views: {}, + ui: config.ui, + }; + // initialize derived views + this.addViews(this.config.views); + } - /** - * Initializes given derived view specs and attaches them to app - * state atom. - * - * @param specs - - */ - addViews(specs: IObjectOf) { - const views: any = this.ctx.views; - for (let id in specs) { - const spec = specs[id]; - views[id] = isArray(spec) - ? defViewUnsafe(this.state, spec[0], >spec[1]) - : defViewUnsafe(this.state, spec); - } - } + /** + * Initializes given derived view specs and attaches them to app + * state atom. + * + * @param specs - + */ + addViews(specs: IObjectOf) { + const views: any = this.ctx.views; + for (let id in specs) { + const spec = specs[id]; + views[id] = isArray(spec) + ? defViewUnsafe(this.state, spec[0], >spec[1]) + : defViewUnsafe(this.state, spec); + } + } - /** - * Calls `init()` and kicks off hdom render loop, including batched - * event processing and fast fail check if DOM updates are necessary - * (assumes ALL state is held in the app state atom). So if there - * weren't any events causing a state change since last frame, - * re-rendering is skipped without even attempting to diff DOM - * tree). - */ - start() { - this.init(); - // assume main root component is a higher order function - // call it here to pre-initialize - const root = this.config.rootComponent(this.ctx); - let firstFrame = true; - start( - () => { - if ( - this.ctx.bus.processQueue({ history: this.history }) || - firstFrame - ) { - firstFrame = false; - return root(); - } - }, - { root: this.config.domRoot, ctx: this.ctx } - ); - } + /** + * Calls `init()` and kicks off hdom render loop, including batched + * event processing and fast fail check if DOM updates are necessary + * (assumes ALL state is held in the app state atom). So if there + * weren't any events causing a state change since last frame, + * re-rendering is skipped without even attempting to diff DOM + * tree). + */ + start() { + this.init(); + // assume main root component is a higher order function + // call it here to pre-initialize + const root = this.config.rootComponent(this.ctx); + let firstFrame = true; + start( + () => { + if ( + this.ctx.bus.processQueue({ history: this.history }) || + firstFrame + ) { + firstFrame = false; + return root(); + } + }, + { root: this.config.domRoot, ctx: this.ctx } + ); + } - /** - * User initialization hook. - * Automatically called from `start()` - */ - init() { - // initialize dataflow graph - initDataflow(this.ctx.bus); + /** + * User initialization hook. + * Automatically called from `start()` + */ + init() { + // initialize dataflow graph + initDataflow(this.ctx.bus); - // initialize key event handlers for undo/redo - document.addEventListener("keypress", (e) => { - if (e.ctrlKey) { - if (e.key === "z") { - this.ctx.bus.dispatch([ev.UNDO]); - } else if (e.key === "y") { - this.ctx.bus.dispatch([ev.REDO]); - } - } - }); + // initialize key event handlers for undo/redo + document.addEventListener("keypress", (e) => { + if (e.ctrlKey) { + if (e.key === "z") { + this.ctx.bus.dispatch([ev.UNDO]); + } else if (e.key === "y") { + this.ctx.bus.dispatch([ev.REDO]); + } + } + }); - // record snapshot of initial state - this.history.record(); - } + // record snapshot of initial state + this.history.record(); + } } diff --git a/examples/rstream-grid/src/components/button-group.ts b/examples/rstream-grid/src/components/button-group.ts index ee339072f4..a67b86ed91 100644 --- a/examples/rstream-grid/src/components/button-group.ts +++ b/examples/rstream-grid/src/components/button-group.ts @@ -2,7 +2,7 @@ import type { AppContext } from "../api"; import { button } from "./button"; export const buttonGroup = (ctx: AppContext, ...buttons: any[]) => [ - "section", - ctx.ui.buttongroup, - buttons.map((bt) => [button, ...bt]), + "section", + ctx.ui.buttongroup, + buttons.map((bt) => [button, ...bt]), ]; diff --git a/examples/rstream-grid/src/components/button.ts b/examples/rstream-grid/src/components/button.ts index 63039ba2e8..3412c681e4 100644 --- a/examples/rstream-grid/src/components/button.ts +++ b/examples/rstream-grid/src/components/button.ts @@ -2,8 +2,8 @@ import type { AppContext } from "../api"; import { eventLink } from "./event-link"; export const button = (ctx: AppContext, event: Event, label: string) => [ - eventLink, - ctx.ui.button, - event, - label, + eventLink, + ctx.ui.button, + event, + label, ]; diff --git a/examples/rstream-grid/src/components/event-link.ts b/examples/rstream-grid/src/components/event-link.ts index f6965f8600..2b92adec98 100644 --- a/examples/rstream-grid/src/components/event-link.ts +++ b/examples/rstream-grid/src/components/event-link.ts @@ -5,24 +5,24 @@ import type { AppContext } from "../api"; * Customizable hyperlink component emitting given event on event bus * when clicked. * - * @param ctx - + * @param ctx - * @param event - vent tuple of `[event-id, payload]` * @param attribs - lement attribs * @param body - ink body */ export const eventLink = ( - ctx: AppContext, - attribs: any, - event: Event, - body: any + ctx: AppContext, + attribs: any, + event: Event, + body: any ) => [ - "a", - { - ...attribs, - onclick: (e: any) => { - e.preventDefault(); - ctx.bus.dispatch(event); - }, - }, - body, + "a", + { + ...attribs, + onclick: (e: any) => { + e.preventDefault(); + ctx.bus.dispatch(event); + }, + }, + body, ]; diff --git a/examples/rstream-grid/src/components/link.ts b/examples/rstream-grid/src/components/link.ts index 0afd195db1..bcee02538a 100644 --- a/examples/rstream-grid/src/components/link.ts +++ b/examples/rstream-grid/src/components/link.ts @@ -1,7 +1,7 @@ import type { AppContext } from "../api"; export const link = (ctx: AppContext, href: string, ...body: any[]) => [ - "a", - { ...ctx.ui.link, href }, - ...body, + "a", + { ...ctx.ui.link, href }, + ...body, ]; diff --git a/examples/rstream-grid/src/components/main.ts b/examples/rstream-grid/src/components/main.ts index 74a9d4e5af..d522e7e40b 100644 --- a/examples/rstream-grid/src/components/main.ts +++ b/examples/rstream-grid/src/components/main.ts @@ -3,6 +3,6 @@ import { SLIDERS } from "../sliders"; import { sidebar } from "./sidebar"; export const main = (ctx: AppContext) => { - const bar = sidebar(ctx, ...SLIDERS); - return () => ["div", ctx.ui.root, bar, ctx.views.svg]; + const bar = sidebar(ctx, ...SLIDERS); + return () => ["div", ctx.ui.root, bar, ctx.views.svg]; }; diff --git a/examples/rstream-grid/src/components/sidebar.ts b/examples/rstream-grid/src/components/sidebar.ts index 8359868a24..1951195f2f 100644 --- a/examples/rstream-grid/src/components/sidebar.ts +++ b/examples/rstream-grid/src/components/sidebar.ts @@ -5,34 +5,34 @@ import { link } from "./link"; import { slider, type SliderOpts } from "./slider"; export const sidebar = (ctx: AppContext, ...specs: SliderOpts[]) => { - const sliders = specs.map((s) => slider(ctx, s)); - return [ - "div", - ctx.ui.sidebar.root, - ["h3", ctx.ui.sidebar.title, "@thi.ng/rstream grid"], - ...sliders, - [buttonGroup, [[ev.UNDO], "undo"], [[ev.REDO], "redo"]], - [buttonGroup, [[ev.SAVE_SVG], "download svg"]], - [buttonGroup, [[ev.SAVE_ANIM], "download anim"]], - [ - "div", - "Undo / Redo can also be triggered via ", - ["code", "Ctrl+Z"], - " / ", - ["code", "Ctrl+Y"], - ". The last 1000 edits are stored.", - ], - [ - "div", - ctx.ui.footer, - [ - link, - "https://github.com/thi-ng/umbrella/tree/develop/examples/rstream-grid", - "Source", - ], - ["br"], - "Made with ", - [link, "https://github.com/thi-ng/umbrella/", "@thi.ng/umbrella"], - ], - ]; + const sliders = specs.map((s) => slider(ctx, s)); + return [ + "div", + ctx.ui.sidebar.root, + ["h3", ctx.ui.sidebar.title, "@thi.ng/rstream grid"], + ...sliders, + [buttonGroup, [[ev.UNDO], "undo"], [[ev.REDO], "redo"]], + [buttonGroup, [[ev.SAVE_SVG], "download svg"]], + [buttonGroup, [[ev.SAVE_ANIM], "download anim"]], + [ + "div", + "Undo / Redo can also be triggered via ", + ["code", "Ctrl+Z"], + " / ", + ["code", "Ctrl+Y"], + ". The last 1000 edits are stored.", + ], + [ + "div", + ctx.ui.footer, + [ + link, + "https://github.com/thi-ng/umbrella/tree/develop/examples/rstream-grid", + "Source", + ], + ["br"], + "Made with ", + [link, "https://github.com/thi-ng/umbrella/", "@thi.ng/umbrella"], + ], + ]; }; diff --git a/examples/rstream-grid/src/components/slider.ts b/examples/rstream-grid/src/components/slider.ts index a6fc4d6016..2d0918bb6f 100644 --- a/examples/rstream-grid/src/components/slider.ts +++ b/examples/rstream-grid/src/components/slider.ts @@ -1,12 +1,12 @@ import type { AppContext } from "../api"; export interface SliderOpts { - event: PropertyKey; - view: PropertyKey; - label: string; - min?: number; - max?: number; - step?: number; + event: PropertyKey; + view: PropertyKey; + label: string; + min?: number; + max?: number; + step?: number; } /** @@ -20,49 +20,49 @@ export interface SliderOpts { * * See `main.ts` for usage. * - * @param ctx - - * @param opts - + * @param ctx - + * @param opts - */ export const slider = (ctx: AppContext, opts: SliderOpts) => { - opts = Object.assign( - { - oninput: (e: Event) => - ctx.bus.dispatch([ - opts.event, - parseFloat((e.target).value), - ]), - min: 0, - max: 100, - step: 1, - }, - opts - ); - const ui = ctx.ui.slider; - return () => [ - "section", - ui.root, - [ - "input", - { - ...ui.range, - ...opts, - type: "range", - value: (ctx.views)[opts.view].deref(), - }, - ], - [ - "div", - ui.label, - opts.label, - [ - "input", - { - ...ui.number, - ...opts, - type: "number", - value: (ctx.views)[opts.view].deref(), - }, - ], - ], - ]; + opts = Object.assign( + { + oninput: (e: Event) => + ctx.bus.dispatch([ + opts.event, + parseFloat((e.target).value), + ]), + min: 0, + max: 100, + step: 1, + }, + opts + ); + const ui = ctx.ui.slider; + return () => [ + "section", + ui.root, + [ + "input", + { + ...ui.range, + ...opts, + type: "range", + value: (ctx.views)[opts.view].deref(), + }, + ], + [ + "div", + ui.label, + opts.label, + [ + "input", + { + ...ui.number, + ...opts, + type: "number", + value: (ctx.views)[opts.view].deref(), + }, + ], + ], + ]; }; diff --git a/examples/rstream-grid/src/config.ts b/examples/rstream-grid/src/config.ts index 88d130181b..ef1999a295 100644 --- a/examples/rstream-grid/src/config.ts +++ b/examples/rstream-grid/src/config.ts @@ -16,106 +16,106 @@ const LINK_COL = "white"; // main App configuration export const CONFIG: AppConfig = { - // event handlers events are queued and batch processed in app's RAF - // render loop event handlers can be single functions, interceptor - // objects with `pre`/`post` keys or arrays of either. + // event handlers events are queued and batch processed in app's RAF + // render loop event handlers can be single functions, interceptor + // objects with `pre`/`post` keys or arrays of either. - // the event handlers' only task is to transform the event into a - // number of side effects. event handlers should be pure functions - // and only side effect functions execute any "real" work. + // the event handlers' only task is to transform the event into a + // number of side effects. event handlers should be pure functions + // and only side effect functions execute any "real" work. - // Docs here: - // https://docs.thi.ng/umbrella/interceptors/#event-bus-interceptors-side-effects - events: { - // generate slider event handlers. each uses the `snapshot()` - // interceptor to record a snapshot of the current app state - // before applying new slider value - ...SLIDERS.reduce( - (events: any, spec) => ( - (events[spec.event] = [snapshot(), valueSetter(spec.path)]), - events - ), - {} - ), - [ev.UPDATE_SVG]: [valueSetter(paths.SVG)], - [ev.SAVE_SVG]: (state) => ({ - [fx.SAVE_SVG]: getInUnsafe(state, paths.SVG), - }), - [ev.SAVE_ANIM]: () => ({ [fx.SAVE_ANIM]: true }), - }, + // Docs here: + // https://docs.thi.ng/umbrella/interceptors/#event-bus-interceptors-side-effects + events: { + // generate slider event handlers. each uses the `snapshot()` + // interceptor to record a snapshot of the current app state + // before applying new slider value + ...SLIDERS.reduce( + (events: any, spec) => ( + (events[spec.event] = [snapshot(), valueSetter(spec.path)]), + events + ), + {} + ), + [ev.UPDATE_SVG]: [valueSetter(paths.SVG)], + [ev.SAVE_SVG]: (state) => ({ + [fx.SAVE_SVG]: getInUnsafe(state, paths.SVG), + }), + [ev.SAVE_ANIM]: () => ({ [fx.SAVE_ANIM]: true }), + }, - // custom side effects - effects: { - // prepares given hiccup format SVG doc with bounds - // then uses @thi.ng/hiccup to serialize to XML syntax - // finally triggers download to local disk - [fx.SAVE_SVG]: (src) => { - src = src.slice(); - src[1] = { ...src[1], width: 1000, height: 1000 }; - downloadWithMime(`grid-${Date.now()}.svg`, serialize(src), { - mime: "image/svg+xml", - }); - }, - // triggers download of 18 svg files (each delayed by 1sec), - // each with a different rotation in the 0-90 degrees interval - [fx.SAVE_ANIM]: (_, bus) => - fromIterable(range(0, 90, 5), { delay: 1000 }).subscribe({ - next: (x) => { - bus.dispatch([ev.SET_THETA, x]); - bus.dispatchLater([ev.SAVE_SVG]); - }, - }), - }, + // custom side effects + effects: { + // prepares given hiccup format SVG doc with bounds + // then uses @thi.ng/hiccup to serialize to XML syntax + // finally triggers download to local disk + [fx.SAVE_SVG]: (src) => { + src = src.slice(); + src[1] = { ...src[1], width: 1000, height: 1000 }; + downloadWithMime(`grid-${Date.now()}.svg`, serialize(src), { + mime: "image/svg+xml", + }); + }, + // triggers download of 18 svg files (each delayed by 1sec), + // each with a different rotation in the 0-90 degrees interval + [fx.SAVE_ANIM]: (_, bus) => + fromIterable(range(0, 90, 5), { delay: 1000 }).subscribe({ + next: (x) => { + bus.dispatch([ev.SET_THETA, x]); + bus.dispatchLater([ev.SAVE_SVG]); + }, + }), + }, - rootComponent: main, + rootComponent: main, - // DOM root element (or ID) - domRoot: "app", + // DOM root element (or ID) + domRoot: "app", - // initial app state - initialState: { - [paths.PARAM_BASE]: { - cols: 5, - rows: 5, - theta: 35, - stroke: 0.3, - }, - }, + // initial app state + initialState: { + [paths.PARAM_BASE]: { + cols: 5, + rows: 5, + theta: 35, + stroke: 0.3, + }, + }, - // derived view declarations - // each key specifies the name of the view and each value is - // a state path or `[path, transformer]` tuple + // derived view declarations + // each key specifies the name of the view and each value is + // a state path or `[path, transformer]` tuple - // docs here: - // https://github.com/thi-ng/umbrella/tree/develop/packages/atom#derived-views - views: { - cols: paths.COLS, - rows: paths.ROWS, - stroke: paths.STROKE, - theta: paths.THETA, - svg: paths.SVG, - }, + // docs here: + // https://github.com/thi-ng/umbrella/tree/develop/packages/atom#derived-views + views: { + cols: paths.COLS, + rows: paths.ROWS, + stroke: paths.STROKE, + theta: paths.THETA, + svg: paths.SVG, + }, - // component CSS class config using http://tachyons.io/ - // these attribs are being passed to all component functions - // as part of the AppContext object - ui: { - button: { - class: `pointer bg-${FG_COL} hover-bg-${LINK_COL} bg-animate black pa2 mr1 w-100 ttu b tracked-tight`, - }, - buttongroup: { class: "flex mb1" }, - footer: { class: "absolute bottom-1" }, - link: { class: `pointer link dim ${LINK_COL} b` }, - root: { class: "vw-100 vh-100 flex" }, - sidebar: { - root: { class: `bg-near-black pa2 pt3 w5 f7 ${FG_COL}` }, - title: { class: `mt0 ${FG_COL}` }, - }, - slider: { - root: { class: `mb3 ttu b tracked-tight ${FG_COL}` }, - range: { class: "w-100" }, - label: { class: "pl2" }, - number: { class: `fr w3 tr ttu bn bg-transparent ${FG_COL}` }, - }, - }, + // component CSS class config using http://tachyons.io/ + // these attribs are being passed to all component functions + // as part of the AppContext object + ui: { + button: { + class: `pointer bg-${FG_COL} hover-bg-${LINK_COL} bg-animate black pa2 mr1 w-100 ttu b tracked-tight`, + }, + buttongroup: { class: "flex mb1" }, + footer: { class: "absolute bottom-1" }, + link: { class: `pointer link dim ${LINK_COL} b` }, + root: { class: "vw-100 vh-100 flex" }, + sidebar: { + root: { class: `bg-near-black pa2 pt3 w5 f7 ${FG_COL}` }, + title: { class: `mt0 ${FG_COL}` }, + }, + slider: { + root: { class: `mb3 ttu b tracked-tight ${FG_COL}` }, + range: { class: "w-100" }, + label: { class: "pl2" }, + number: { class: `fr w3 tr ttu bn bg-transparent ${FG_COL}` }, + }, + }, }; diff --git a/examples/rstream-grid/src/dataflow.ts b/examples/rstream-grid/src/dataflow.ts index 2e717de42c..eb3180867e 100644 --- a/examples/rstream-grid/src/dataflow.ts +++ b/examples/rstream-grid/src/dataflow.ts @@ -16,54 +16,54 @@ import * as paths from "./paths"; * produced. In turn, this event will update the app state and so * trigger a DOM update to display the new result. * - * @param bus - + * @param bus - */ export function initDataflow(bus: EventBus) { - const graph = initGraph(bus.state, { - grid: { - fn: grid, - ins: { - cols: { path: paths.COLS }, - rows: { path: paths.ROWS }, - }, - }, - rotation: { - fn: rotate, - ins: { - shapes: { stream: "/grid/node" }, - theta: { path: paths.THETA }, - }, - }, - svg: { - fn: createSVG, - ins: { - shapes: { stream: "/rotation/node" }, - cols: { path: paths.COLS }, - rows: { path: paths.ROWS }, - stroke: { path: paths.STROKE }, - }, - // dispatch SVG result doc as event - outs: { - "*": (node) => - node.subscribe({ - next: (svg) => bus.dispatch([ev.UPDATE_SVG, svg]), - }), - }, - }, - }); - return graph; + const graph = initGraph(bus.state, { + grid: { + fn: grid, + ins: { + cols: { path: paths.COLS }, + rows: { path: paths.ROWS }, + }, + }, + rotation: { + fn: rotate, + ins: { + shapes: { stream: "/grid/node" }, + theta: { path: paths.THETA }, + }, + }, + svg: { + fn: createSVG, + ins: { + shapes: { stream: "/rotation/node" }, + cols: { path: paths.COLS }, + rows: { path: paths.ROWS }, + stroke: { path: paths.STROKE }, + }, + // dispatch SVG result doc as event + outs: { + "*": (node) => + node.subscribe({ + next: (svg) => bus.dispatch([ev.UPDATE_SVG, svg]), + }), + }, + }, + }); + return graph; } /** * Implementation for grid generator node. */ const grid = node( - map(({ cols, rows }) => [ - ...map( - ([x, y]) => ["rect", { x, y, width: 1, height: 1 }], - range2d(cols, rows) - ), - ]) + map(({ cols, rows }) => [ + ...map( + ([x, y]) => ["rect", { x, y, width: 1, height: 1 }], + range2d(cols, rows) + ), + ]) ); /** @@ -71,16 +71,16 @@ const grid = node( * all received shapes. */ const rotate = node( - map(({ shapes, theta }) => - shapes.map( - (s: any) => ( - (s[1].transform = `rotate(${theta} ${s[1].x + 0.5} ${ - s[1].y + 0.5 - })`), - s - ) - ) - ) + map(({ shapes, theta }) => + shapes.map( + (s: any) => ( + (s[1].transform = `rotate(${theta} ${s[1].x + 0.5} ${ + s[1].y + 0.5 + })`), + s + ) + ) + ) ); /** @@ -88,22 +88,22 @@ const rotate = node( * complete svg document in hiccup format. */ const createSVG = node( - map(({ shapes, cols, rows, stroke }) => - svg( - { - class: "w-100 h-100", - preserveAspectRatio: "xMidYMid", - viewBox: `-1 -1 ${cols + 2} ${rows + 2}`, - }, - rect([-1, -1], cols + 2, rows + 2, { fill: "black" }), - group( - { - stroke: "white", - fill: "none", - "stroke-width": stroke, - }, - ...shapes - ) - ) - ) + map(({ shapes, cols, rows, stroke }) => + svg( + { + class: "w-100 h-100", + preserveAspectRatio: "xMidYMid", + viewBox: `-1 -1 ${cols + 2} ${rows + 2}`, + }, + rect([-1, -1], cols + 2, rows + 2, { fill: "black" }), + group( + { + stroke: "white", + fill: "none", + "stroke-width": stroke, + }, + ...shapes + ) + ) + ) ); diff --git a/examples/rstream-grid/src/sliders.ts b/examples/rstream-grid/src/sliders.ts index 0a67321fd7..47085387cd 100644 --- a/examples/rstream-grid/src/sliders.ts +++ b/examples/rstream-grid/src/sliders.ts @@ -6,37 +6,37 @@ import * as paths from "./paths"; * respective event handlers. */ export const SLIDERS = [ - { - event: ev.SET_COLS, - path: paths.COLS, - view: "cols", - label: "cols", - min: 1, - max: 16, - }, - { - event: ev.SET_ROWS, - path: paths.ROWS, - view: "rows", - label: "rows", - min: 1, - max: 16, - }, - { - event: ev.SET_THETA, - path: paths.THETA, - view: "theta", - label: "rotate", - min: 0, - max: 360, - }, - { - event: ev.SET_STROKE, - path: paths.STROKE, - view: "stroke", - label: "stroke weight", - min: 0.01, - max: 0.5, - step: 0.01, - }, + { + event: ev.SET_COLS, + path: paths.COLS, + view: "cols", + label: "cols", + min: 1, + max: 16, + }, + { + event: ev.SET_ROWS, + path: paths.ROWS, + view: "rows", + label: "rows", + min: 1, + max: 16, + }, + { + event: ev.SET_THETA, + path: paths.THETA, + view: "theta", + label: "rotate", + min: 0, + max: 360, + }, + { + event: ev.SET_STROKE, + path: paths.STROKE, + view: "stroke", + label: "stroke weight", + min: 0.01, + max: 0.5, + step: 0.01, + }, ]; diff --git a/examples/rstream-grid/tsconfig.json b/examples/rstream-grid/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rstream-grid/tsconfig.json +++ b/examples/rstream-grid/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rstream-hdom/package.json b/examples/rstream-hdom/package.json index 7463907965..377713479f 100644 --- a/examples/rstream-hdom/package.json +++ b/examples/rstream-hdom/package.json @@ -1,31 +1,31 @@ { - "name": "@example/rstream-hdom", - "private": true, - "description": "rstream based UI updates & state handling", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "thi.ng": { - "readme": true - } + "name": "@example/rstream-hdom", + "private": true, + "description": "rstream based UI updates & state handling", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "thi.ng": { + "readme": true + } } diff --git a/examples/rstream-hdom/src/index.ts b/examples/rstream-hdom/src/index.ts index dff3e5f451..1306f27520 100644 --- a/examples/rstream-hdom/src/index.ts +++ b/examples/rstream-hdom/src/index.ts @@ -13,14 +13,14 @@ import { vals } from "@thi.ng/transducers/vals"; // here only used to provide style / theme config using // Tachyons CSS classes const ctx = { - ui: { - root: { - class: "pa2", - }, - button: { - class: "w4 h2 bg-black white bn br2 mr2 pointer", - }, - }, + ui: { + root: { + class: "pa2", + }, + button: { + class: "w4 h2 bg-black white bn br2 mr2 pointer", + }, + }, }; /** @@ -46,7 +46,7 @@ const ctx = { * @param ctx - ser context object */ const domUpdate = (root: HTMLElement, tree: ISubscribable, ctx?: any) => - sidechainPartitionRAF(tree).transform(updateDOM({ root, ctx })); + sidechainPartitionRAF(tree).transform(updateDOM({ root, ctx })); /** * Generic button component. @@ -56,9 +56,9 @@ const domUpdate = (root: HTMLElement, tree: ISubscribable, ctx?: any) => * @param body - utton body */ const button = (ctx: any, onclick: EventListener, body: any) => [ - "button", - { ...ctx.ui.button, onclick }, - body, + "button", + { ...ctx.ui.button, onclick }, + body, ]; /** @@ -68,9 +68,9 @@ const button = (ctx: any, onclick: EventListener, body: any) => [ * @param stream - ounter stream */ const clickButton = (_: any, stream: ISubscriber) => [ - button, - () => stream.next(true), - stream.deref(), + button, + () => stream.next(true), + stream.deref(), ]; /** @@ -80,9 +80,9 @@ const clickButton = (_: any, stream: ISubscriber) => [ * @param counters - treams to reset */ const resetButton = (_: any, counters: ISubscriber[]) => [ - button, - () => counters.forEach((c) => c.next(false)), - "reset", + button, + () => counters.forEach((c) => c.next(false)), + "reset", ]; /** @@ -90,25 +90,25 @@ const resetButton = (_: any, counters: ISubscriber[]) => [ * the stream, the counter increases by given step value. If false is * written, the counter resets to the `start` value. * - * @param start - - * @param step - + * @param start - + * @param step - */ const counter = (start: number, step: number) => { - const s = subscription( - undefined, - // the `scan` transducer is used to provide counter functionality - // see: https://docs.thi.ng/umbrella/transducers/modules.html#scan - { - xform: scan( - reducer( - () => start, - (x, y) => (y ? x + step : start) - ) - ), - } - ); - s.next(false); - return s; + const s = subscription( + undefined, + // the `scan` transducer is used to provide counter functionality + // see: https://docs.thi.ng/umbrella/transducers/modules.html#scan + { + xform: scan( + reducer( + () => start, + (x, y) => (y ? x + step : start) + ) + ), + } + ); + s.next(false); + return s; }; /** @@ -120,40 +120,40 @@ const counter = (start: number, step: number) => { * @param initial - nitial counter configs */ const app = (ctx: any, initial: number[][]) => { - const counters = initial.map(([start, step]) => counter(start, step)); - return sync({ - src: autoObj( - "", - counters.map((c) => c.transform(map(() => [clickButton, c]))) - ), - xform: map( - // build the app's actual root component - (buttons) => [ - "div", - ctx.ui.root, - ...vals(buttons), - [resetButton, counters], - ] - ), - // this config ensures that only at the very beginning *all* - // inputs must have delivered a value (i.e. stream - // synchronization) before this stream itself delivers a value. - // however, by stating `reset: false` (actually the default) any - // subsequent changes to any of the inputs will not be - // synchronized see here for further details: - // https://docs.thi.ng/umbrella/rstream/interfaces/StreamSyncOpts.html - // https://docs.thi.ng/umbrella/transducers/modules.html#partitionSync - reset: false, - }); + const counters = initial.map(([start, step]) => counter(start, step)); + return sync({ + src: autoObj( + "", + counters.map((c) => c.transform(map(() => [clickButton, c]))) + ), + xform: map( + // build the app's actual root component + (buttons) => [ + "div", + ctx.ui.root, + ...vals(buttons), + [resetButton, counters], + ] + ), + // this config ensures that only at the very beginning *all* + // inputs must have delivered a value (i.e. stream + // synchronization) before this stream itself delivers a value. + // however, by stating `reset: false` (actually the default) any + // subsequent changes to any of the inputs will not be + // synchronized see here for further details: + // https://docs.thi.ng/umbrella/rstream/interfaces/StreamSyncOpts.html + // https://docs.thi.ng/umbrella/transducers/modules.html#partitionSync + reset: false, + }); }; // start app & DOM updates domUpdate( - document.getElementById("app")!, - app(ctx, [ - [10, 1], - [20, 5], - [30, 10], - ]), - ctx + document.getElementById("app")!, + app(ctx, [ + [10, 1], + [20, 5], + [30, 10], + ]), + ctx ); diff --git a/examples/rstream-hdom/tsconfig.json b/examples/rstream-hdom/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rstream-hdom/tsconfig.json +++ b/examples/rstream-hdom/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/rstream-spreadsheet/package.json b/examples/rstream-spreadsheet/package.json index 361d47f9ed..9fa963f6e6 100644 --- a/examples/rstream-spreadsheet/package.json +++ b/examples/rstream-spreadsheet/package.json @@ -1,56 +1,56 @@ { - "name": "@example/rstream-spreadsheet", - "private": true, - "description": "rstream based spreadsheet w/ S-expression formula DSL", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/checks": "workspace:^", - "@thi.ng/defmulti": "workspace:^", - "@thi.ng/errors": "workspace:^", - "@thi.ng/expose": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/memoize": "workspace:^", - "@thi.ng/paths": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-graph": "workspace:^", - "@thi.ng/sexpr": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "defmulti", - "expose", - "hdom", - "memoize", - "rstream", - "rstream-graph", - "sexpr", - "strings", - "transducers-hdom" - ], - "screenshot": "examples/rstream-spreadsheet.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/rstream-spreadsheet", + "private": true, + "description": "rstream based spreadsheet w/ S-expression formula DSL", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/checks": "workspace:^", + "@thi.ng/defmulti": "workspace:^", + "@thi.ng/errors": "workspace:^", + "@thi.ng/expose": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/memoize": "workspace:^", + "@thi.ng/paths": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-graph": "workspace:^", + "@thi.ng/sexpr": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "defmulti", + "expose", + "hdom", + "memoize", + "rstream", + "rstream-graph", + "sexpr", + "strings", + "transducers-hdom" + ], + "screenshot": "examples/rstream-spreadsheet.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/rstream-spreadsheet/src/api.ts b/examples/rstream-spreadsheet/src/api.ts index 1a4c90b258..103bdf1451 100644 --- a/examples/rstream-spreadsheet/src/api.ts +++ b/examples/rstream-spreadsheet/src/api.ts @@ -11,14 +11,14 @@ export const RE_CELL_ID = /^([A-Z])(\d+)$/i; export const RE_CELL_RANGE = /^([A-Z])(\d+):([A-Z])(\d+)$/i; export interface Cell { - formula: string; - value: string | number; - error: string | null; - backup: string; - focus: boolean; + formula: string; + value: string | number; + error: string | null; + backup: string; + focus: boolean; } export interface UICell extends ILifecycle { - element?: HTMLDivElement; - focus?: boolean; + element?: HTMLDivElement; + focus?: boolean; } diff --git a/examples/rstream-spreadsheet/src/dsl.ts b/examples/rstream-spreadsheet/src/dsl.ts index 2bf1b7a0e7..abcc91e4c3 100644 --- a/examples/rstream-spreadsheet/src/dsl.ts +++ b/examples/rstream-spreadsheet/src/dsl.ts @@ -38,8 +38,8 @@ import { DB, graph, removeCell } from "./state"; * currently interpreted S-expression. */ interface Env { - id: string; - depth: number; + id: string; + depth: number; } // dynamic-dispatch function to delegate to actual DSL formula operators @@ -48,23 +48,23 @@ const builtins = defmulti((x) => x.value); // init S-expression interpreter. this results in another // dynamic-dispatch function which delegates based on AST node type const rt = runtime, Env, any>({ - // As per Lisp convention, S-expressions are treated as function - // calls with 1st child item used as function name and rest as - // arguments. E.g. `(+ a1 b1 c1)` is a function call to the `+` - // function, with `a1`, `b1`, `c1` being arguments. - expr: (x, env) => - builtins(x.children[0], x.children, { - ...env, - depth: env.depth + 1, - }), - // other symbols are interpreted as cell IDs - sym: (x) => - RE_CELL_ID.test(x.value) - ? cellInput(x.value) - : illegalArgs("invalid cell ID"), - // strings & number used verbatim - str: (x) => ({ const: x.value }), - num: (x) => ({ const: x.value }), + // As per Lisp convention, S-expressions are treated as function + // calls with 1st child item used as function name and rest as + // arguments. E.g. `(+ a1 b1 c1)` is a function call to the `+` + // function, with `a1`, `b1`, `c1` being arguments. + expr: (x, env) => + builtins(x.children[0], x.children, { + ...env, + depth: env.depth + 1, + }), + // other symbols are interpreted as cell IDs + sym: (x) => + RE_CELL_ID.test(x.value) + ? cellInput(x.value) + : illegalArgs("invalid cell ID"), + // strings & number used verbatim + str: (x) => ({ const: x.value }), + num: (x) => ({ const: x.value }), }); /** @@ -72,11 +72,11 @@ const rt = runtime, Env, any>({ * so creates/updates the spreadsheet's dataflow graph. The `cellID` is * used to store the result in the DB state atom. * - * @param src - - * @param cellID - + * @param src - + * @param cellID - */ export const $eval = (src: string, cellID: string) => - rt(parse(tokenize(src)).children[0], { id: cellID, depth: 0 }); + rt(parse(tokenize(src)).children[0], { id: cellID, depth: 0 }); /** * Takes a rstream-graph `NodeSpec` and array of AST nodes and an @@ -89,26 +89,26 @@ export const $eval = (src: string, cellID: string) => * Any previously existing node for the resulting ID is first removed * before the new one is created/added. * - * @param spec - - * @param vals - - * @param env - + * @param spec - + * @param vals - + * @param env - */ const defNode = (spec: NodeSpec, vals: ASTNode[], env: Env) => { - let id: string; - if (env.depth === 1) { - id = env.id; - spec.outs = { - "*": [id, "value"], - }; - } else { - id = JSON.stringify(vals); - if (graph[id]) { - return { stream: () => graph[id].node }; - } - } - removeCell(id); - const node = addNode(graph, DB, id, spec); - return { stream: () => node.node }; + let id: string; + if (env.depth === 1) { + id = env.id; + spec.outs = { + "*": [id, "value"], + }; + } else { + id = JSON.stringify(vals); + if (graph[id]) { + return { stream: () => graph[id].node }; + } + } + removeCell(id); + const node = addNode(graph, DB, id, spec); + return { stream: () => node.node }; }; /** @@ -128,43 +128,43 @@ const defNode = (spec: NodeSpec, vals: ASTNode[], env: Env) => { * } * ``` * - * @param fn - + * @param fn - */ const defBuiltin = - (fn: Fn, any>) => - (_: ASTNode, vals: ASTNode[], env: Env) => - defNode( - { - // wrapped transformation fn - fn: node(map(fn)), - // compile all s-expr arguments into a single object of input stream defs. - // - cell ranges yield multiple inputs - // - single cell IDs yield stream of cell's value - // - numeric args yield a single-item stream def of the given number - ins: transduce( - comp( - mapcat((i) => { - try { - return cellRangeInputs(i); - } catch (e) { - return [rt(i, env)]; - } - }), - // form pairs of [numbered-arg, input] - mapIndexed( - (i, input) => - <[string, NodeInputSpec]>[Z2(i), input] - ) - ), - // build object - assocObj(), - // only process s-expr args - vals.slice(1) - ), - }, - vals, - env - ); + (fn: Fn, any>) => + (_: ASTNode, vals: ASTNode[], env: Env) => + defNode( + { + // wrapped transformation fn + fn: node(map(fn)), + // compile all s-expr arguments into a single object of input stream defs. + // - cell ranges yield multiple inputs + // - single cell IDs yield stream of cell's value + // - numeric args yield a single-item stream def of the given number + ins: transduce( + comp( + mapcat((i) => { + try { + return cellRangeInputs(i); + } catch (e) { + return [rt(i, env)]; + } + }), + // form pairs of [numbered-arg, input] + mapIndexed( + (i, input) => + <[string, NodeInputSpec]>[Z2(i), input] + ) + ), + // build object + assocObj(), + // only process s-expr args + vals.slice(1) + ), + }, + vals, + env + ); /** * Similar to `defBuiltin()`, but for reducer-based computations. Takes @@ -172,26 +172,26 @@ const defBuiltin = * pre-transformer. The resulting transformation function filters out * all empty cells. * - * @param rfn - - * @param xf - + * @param rfn - + * @param xf - */ const defReducer = ( - rfn: () => Reducer, - xf: Fn = (x) => x + rfn: () => Reducer, + xf: Fn = (x) => x ) => - defBuiltin((ports: IObjectOf) => { - const keys = Object.keys(ports).sort(); - return transduce( - comp( - map((k) => ports[k]), - filter((x) => x != null), - map(xf) - ), - rfn(), - xf(ports[keys.shift()!]), - keys - ); - }); + defBuiltin((ports: IObjectOf) => { + const keys = Object.keys(ports).sort(); + return transduce( + comp( + map((k) => ports[k]), + filter((x) => x != null), + map(xf) + ), + rfn(), + xf(ports[keys.shift()!]), + keys + ); + }); /** * Returns a rstream-graph NodeInputSpec linked to the cell value given @@ -199,59 +199,59 @@ const defReducer = ( * configured to attempt string-to-number conversion of its values. */ const cellInput = memoize1( - (id: string): NodeInputSpec => ({ - stream: () => - fromView(DB, { - path: [id.toUpperCase(), "value"], - tx: (x) => maybeParseFloat(x, null), - }), - }) + (id: string): NodeInputSpec => ({ + stream: () => + fromView(DB, { + path: [id.toUpperCase(), "value"], + tx: (x) => maybeParseFloat(x, null), + }), + }) ); /** * Returns iterator of NodeInputSpecs for given cell range string. * - * @param x - + * @param x - */ const cellRangeInputs = (x: ASTNode) => { - const [acol, arow, bcol, brow] = parseCellIDRange(x); - return map<[string, number], NodeInputSpec>( - ([c, r]) => cellInput(`${c}${r}`), - permutations( - charRange(acol.toUpperCase(), bcol.toUpperCase()), - range(parseInt(arow), parseInt(brow) + 1) - ) - ); + const [acol, arow, bcol, brow] = parseCellIDRange(x); + return map<[string, number], NodeInputSpec>( + ([c, r]) => cellInput(`${c}${r}`), + permutations( + charRange(acol.toUpperCase(), bcol.toUpperCase()), + range(parseInt(arow), parseInt(brow) + 1) + ) + ); }; /** * Parses cell range string, e.g. `a5:c10` => ["a",5,"c",10] * - * @param x - + * @param x - */ const parseCellIDRange = (x: ASTNode) => { - const match = RE_CELL_RANGE.exec((x).value); - if (!match) illegalArgs("invalid cell range"); - return match!.slice(1, 5); + const match = RE_CELL_RANGE.exec((x).value); + if (!match) illegalArgs("invalid cell range"); + return match!.slice(1, 5); }; /** * Register built-ins. */ builtins.addAll({ - "+": defReducer(add), - "*": defReducer(mul), - "-": defReducer(sub), - "/": defReducer(() => div(1)), - min: defReducer(min), - max: defReducer(max), - avg: defReducer(() => mean()), - mag: defReducer( - () => [() => 0, (acc) => Math.sqrt(acc), (acc, x) => acc + x], - (x) => x * x - ), - abs: defBuiltin(({ "00": x }) => Math.abs(x)), - fit: defBuiltin(({ "00": x, "01": a, "02": b, "03": c, "04": d }) => - fit(x, a, b, c, d) - ), + "+": defReducer(add), + "*": defReducer(mul), + "-": defReducer(sub), + "/": defReducer(() => div(1)), + min: defReducer(min), + max: defReducer(max), + avg: defReducer(() => mean()), + mag: defReducer( + () => [() => 0, (acc) => Math.sqrt(acc), (acc, x) => acc + x], + (x) => x * x + ), + abs: defBuiltin(({ "00": x }) => Math.abs(x)), + fit: defBuiltin(({ "00": x, "01": a, "02": b, "03": c, "04": d }) => + fit(x, a, b, c, d) + ), }); diff --git a/examples/rstream-spreadsheet/src/index.ts b/examples/rstream-spreadsheet/src/index.ts index 71e77d4a08..3f6e246b3b 100644 --- a/examples/rstream-spreadsheet/src/index.ts +++ b/examples/rstream-spreadsheet/src/index.ts @@ -13,12 +13,12 @@ import { range } from "@thi.ng/transducers/range"; import { transduce } from "@thi.ng/transducers/transduce"; import { CELL_STYLE, MAX_COL, NUM_COLS, NUM_ROWS, type UICell } from "./api"; import { - blurCell, - cancelCell, - DB, - focusCell, - graph, - updateCell, + blurCell, + cancelCell, + DB, + focusCell, + graph, + updateCell, } from "./state"; const formatCell = (x: string | number) => (isNumber(x) ? x.toFixed(2) : x); @@ -26,16 +26,16 @@ const formatCell = (x: string | number) => (isNumber(x) ? x.toFixed(2) : x); /** * Choose background color based on cell state. * - * @param cell - + * @param cell - */ const cellBackground = (cell: any) => - cell.focus - ? "bg-yellow" - : cell.formula - ? cell.error - ? "bg-red white" - : "bg-light-green" - : ""; + cell.focus + ? "bg-yellow" + : cell.formula + ? cell.error + ? "bg-red white" + : "bg-light-green" + : ""; /** * thi.ng/hdom cell component with lifecycle methods. (The current @@ -44,56 +44,56 @@ const cellBackground = (cell: any) => * @param cellid - uple */ const cell = ([row, col]: [number, string]) => - { - init(el: HTMLDivElement) { - this.element = el; - this.focus = false; - }, - render(_: any, cells: any) { - const id = `${col}${row}`; - const cell = cells[id]; - return [ - `${CELL_STYLE}.w4.overflow-y-hidden.overflow-x-scroll`, - { - class: cellBackground(cell), - contenteditable: true, - title: cell.formula, - onfocus: () => { - this.focus = true; - focusCell(id); - }, - onblur: () => { - if (this.focus) { - updateCell(id, this.element!.textContent!.trim()); - this.focus = false; - } - blurCell(id); - }, - onkeydown: (e: KeyboardEvent) => { - switch (e.key) { - case "Enter": - case "Tab": - updateCell( - id, - this.element!.textContent!.trim() - ); - this.element!.blur(); - break; - case "Escape": - this.focus = false; - cancelCell(id); - this.element!.blur(); - } - }, - }, - String( - cell.focus && cell.formula - ? cell.formula - : cell.error || formatCell(cell.value) - ), - ]; - }, - }; + { + init(el: HTMLDivElement) { + this.element = el; + this.focus = false; + }, + render(_: any, cells: any) { + const id = `${col}${row}`; + const cell = cells[id]; + return [ + `${CELL_STYLE}.w4.overflow-y-hidden.overflow-x-scroll`, + { + class: cellBackground(cell), + contenteditable: true, + title: cell.formula, + onfocus: () => { + this.focus = true; + focusCell(id); + }, + onblur: () => { + if (this.focus) { + updateCell(id, this.element!.textContent!.trim()); + this.focus = false; + } + blurCell(id); + }, + onkeydown: (e: KeyboardEvent) => { + switch (e.key) { + case "Enter": + case "Tab": + updateCell( + id, + this.element!.textContent!.trim() + ); + this.element!.blur(); + break; + case "Escape": + this.focus = false; + cancelCell(id); + this.element!.blur(); + } + }, + }, + String( + cell.focus && cell.formula + ? cell.formula + : cell.error || formatCell(cell.value) + ), + ]; + }, + }; /** * Main UI component HOF. Attached to to `main` rstream (defined below) @@ -102,33 +102,33 @@ const cell = ([row, col]: [number, string]) => * entire spreadsheet. */ const app = () => { - const CELLS: UICell[][] = transduce( - comp(map(cell), partition(NUM_COLS)), - push(), - permutations(range(1, NUM_ROWS + 1), charRange("A", MAX_COL)) - ); - return (state: any) => [ - "div", - {}, - [`${CELL_STYLE}.w2.b.bg-moon-gray`, "\u00a0"], - map( - (col) => [`${CELL_STYLE}.w4.b.bg-moon-gray`, {}, col], - charRange("A", MAX_COL) - ), - mapIndexed( - (i, rowid) => [ - "div", - {}, - [ - `${CELL_STYLE}.w2.b.bg-moon-gray.overflow-y-hidden.overflow-x-scroll`, - {}, - rowid, - ], - ...CELLS[i].map((cell) => [cell, state]), - ], - range(1, NUM_ROWS + 1) - ), - ]; + const CELLS: UICell[][] = transduce( + comp(map(cell), partition(NUM_COLS)), + push(), + permutations(range(1, NUM_ROWS + 1), charRange("A", MAX_COL)) + ); + return (state: any) => [ + "div", + {}, + [`${CELL_STYLE}.w2.b.bg-moon-gray`, "\u00a0"], + map( + (col) => [`${CELL_STYLE}.w4.b.bg-moon-gray`, {}, col], + charRange("A", MAX_COL) + ), + mapIndexed( + (i, rowid) => [ + "div", + {}, + [ + `${CELL_STYLE}.w2.b.bg-moon-gray.overflow-y-hidden.overflow-x-scroll`, + {}, + rowid, + ], + ...CELLS[i].map((cell) => [cell, state]), + ], + range(1, NUM_ROWS + 1) + ), + ]; }; // setLogger(new ConsoleLogger("rstream")); diff --git a/examples/rstream-spreadsheet/src/state.ts b/examples/rstream-spreadsheet/src/state.ts index 8bbc143e47..902aecdb64 100644 --- a/examples/rstream-spreadsheet/src/state.ts +++ b/examples/rstream-spreadsheet/src/state.ts @@ -19,23 +19,23 @@ import { $eval } from "./dsl"; * then attach subscriptions manipulate individual cell values. */ export const DB = new Atom>( - transduce( - map( - ([col, row]) => - <[string, Cell]>[ - `${col}${row}`, - { - formula: "", - value: "", - backup: "", - focus: false, - error: "", - }, - ] - ), - assocObj(), - permutations(charRange("A", MAX_COL), range(1, NUM_ROWS + 1)) - ) + transduce( + map( + ([col, row]) => + <[string, Cell]>[ + `${col}${row}`, + { + formula: "", + value: "", + backup: "", + focus: false, + error: "", + }, + ] + ), + assocObj(), + permutations(charRange("A", MAX_COL), range(1, NUM_ROWS + 1)) + ) ); /** @@ -48,32 +48,32 @@ export const removeCell = (id: string) => removeNode(graph, id); /** * Enables focus flag for given cell * - * @param id - + * @param id - */ export const focusCell = (id: string) => { - DB.swapIn([id], (cell) => - setInManyUnsafe(cell, "focus", true, "backup", cell.formula) - ); + DB.swapIn([id], (cell) => + setInManyUnsafe(cell, "focus", true, "backup", cell.formula) + ); }; /** * Disables focus flag for given cell * - * @param id - + * @param id - */ export const blurCell = (id: string) => { - DB.swapIn([id], (cell) => setIn(cell, ["focus"], false)); + DB.swapIn([id], (cell) => setIn(cell, ["focus"], false)); }; /** * Restores cell value to `backup` string and clears focus. * - * @param id - + * @param id - */ export const cancelCell = (id: string) => { - DB.swapIn([id], (cell) => - setInManyUnsafe(cell, "focus", false, "formula", cell.backup) - ); + DB.swapIn([id], (cell) => + setInManyUnsafe(cell, "focus", false, "formula", cell.backup) + ); }; /** @@ -82,22 +82,22 @@ export const cancelCell = (id: string) => { * error if failed. If not an s-expr, updates cell value and clears * error. * - * @param id - - * @param val - + * @param id - + * @param val - */ export const updateCell = (id: string, val: string) => { - if (val.startsWith("(")) { - DB.resetIn([id, "formula"], val); - try { - $eval(val, id); - DB.resetIn([id, "error"], null); - } catch (e) { - DB.resetIn([id, "error"], (e).message); - } - } else { - removeCell(id); - DB.swapIn([id], (cell) => - setInManyUnsafe(cell, "value", val, "formula", "", "error", null) - ); - } + if (val.startsWith("(")) { + DB.resetIn([id, "formula"], val); + try { + $eval(val, id); + DB.resetIn([id, "error"], null); + } catch (e) { + DB.resetIn([id, "error"], (e).message); + } + } else { + removeCell(id); + DB.swapIn([id], (cell) => + setInManyUnsafe(cell, "value", val, "formula", "", "error", null) + ); + } }; diff --git a/examples/rstream-spreadsheet/tsconfig.json b/examples/rstream-spreadsheet/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/rstream-spreadsheet/tsconfig.json +++ b/examples/rstream-spreadsheet/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/scenegraph-image/package.json b/examples/scenegraph-image/package.json index 32a15d9812..f65bdbb582 100644 --- a/examples/scenegraph-image/package.json +++ b/examples/scenegraph-image/package.json @@ -1,47 +1,47 @@ { - "name": "@example/scenegraph-image", - "private": true, - "description": "2D scenegraph & image map based geometry manipulation", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/dsp": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/matrices": "workspace:^", - "@thi.ng/pixel": "workspace:^", - "@thi.ng/scenegraph": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom", - "hdom", - "hdom-canvas", - "math", - "matrices", - "pixel", - "scenegraph", - "vectors" - ], - "screenshot": "examples/scenegraph-image.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/scenegraph-image", + "private": true, + "description": "2D scenegraph & image map based geometry manipulation", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/dsp": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/matrices": "workspace:^", + "@thi.ng/pixel": "workspace:^", + "@thi.ng/scenegraph": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom", + "hdom", + "hdom-canvas", + "math", + "matrices", + "pixel", + "scenegraph", + "vectors" + ], + "screenshot": "examples/scenegraph-image.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/scenegraph-image/src/index.ts b/examples/scenegraph-image/src/index.ts index 0613911247..b08a79a05f 100644 --- a/examples/scenegraph-image/src/index.ts +++ b/examples/scenegraph-image/src/index.ts @@ -18,124 +18,124 @@ import LOGO from "./logo-256.png"; * Specialized scene graph node for images. */ class ImgNode extends Node2D { - img: HTMLImageElement; + img: HTMLImageElement; - constructor( - id: string, - parent: Node2D, - t: Vec, - r: number, - s: Vec | number, - img: HTMLImageElement, - size: Vec, - alpha = 1 - ) { - super(id, parent, t, r, s, [ - "img", - { alpha, width: size[0], height: size[1] }, - img, - [0, 0], - ]); - this.img = img; - } + constructor( + id: string, + parent: Node2D, + t: Vec, + r: number, + s: Vec | number, + img: HTMLImageElement, + size: Vec, + alpha = 1 + ) { + super(id, parent, t, r, s, [ + "img", + { alpha, width: size[0], height: size[1] }, + img, + [0, 0], + ]); + this.img = img; + } - containsLocalPoint(p: ReadonlyVec) { - return ( - !!this.img && - p[0] >= 0 && - p[0] < this.img.width && - p[1] >= 0 && - p[1] < this.img.height - ); - } + containsLocalPoint(p: ReadonlyVec) { + return ( + !!this.img && + p[0] >= 0 && + p[0] < this.img.width && + p[1] >= 0 && + p[1] < this.img.height + ); + } } imagePromise(LOGO).then((img) => { - // mouse pos - let mouse: Vec = [0, 0]; + // mouse pos + let mouse: Vec = [0, 0]; - // scene graph definition - // set root node scale to window.devicePixelRatio - const root = new Node2D("root", null); + // scene graph definition + // set root node scale to window.devicePixelRatio + const root = new Node2D("root", null); - const main = new Node2D("main", root, [300, 300], 0, 1); - const imgRoot = new Node2D("imgroot", main, [0, 0], 0, 2); - const geom = new Node2D("waves", main, [0, 0], 0, 1, null); + const main = new Node2D("main", root, [300, 300], 0, 1); + const imgRoot = new Node2D("imgroot", main, [0, 0], 0, 2); + const geom = new Node2D("waves", main, [0, 0], 0, 1, null); - const imgMap = intBufferFromImage(img, GRAY8, 256, 256); - const imgNode = new ImgNode( - "img", - imgRoot, - [-imgMap.width / 2, -imgMap.height / 2], - 0, - 1, - img, - [imgMap.width, imgMap.height], - 0.5 - ); - imgNode.display = false; + const imgMap = intBufferFromImage(img, GRAY8, 256, 256); + const imgNode = new ImgNode( + "img", + imgRoot, + [-imgMap.width / 2, -imgMap.height / 2], + 0, + 1, + img, + [imgMap.width, imgMap.height], + 0.5 + ); + imgNode.display = false; - // mousemove event handler - const updateMouse = (e: MouseEvent) => { - mouse = [e.offsetX, e.offsetY]; - if (imgRoot) { - mulV23(imgRoot.translate, geom.invMat, mouse); - } - }; + // mousemove event handler + const updateMouse = (e: MouseEvent) => { + mouse = [e.offsetX, e.offsetY]; + if (imgRoot) { + mulV23(imgRoot.translate, geom.invMat, mouse); + } + }; - // onclick handler to toggle image display - const toggleImage = () => (imgNode.display = !imgNode.display); + // onclick handler to toggle image display + const toggleImage = () => (imgNode.display = !imgNode.display); - // main hdom root component / app - const app = () => { - imgRoot.rotate += 0.04; - imgRoot.scale = setN2([], sin(imgRoot.rotate, 0.5, 2, 3.5)); - imgRoot.update(); + // main hdom root component / app + const app = () => { + imgRoot.rotate += 0.04; + imgRoot.scale = setN2([], sin(imgRoot.rotate, 0.5, 2, 3.5)); + imgRoot.update(); - const waves = map( - (y) => - polyline([ - ...map((x) => { - const q = geom.mapLocalPointToNode(imgNode, [x, y]); - const r = (imgMap.getAt(q[0], q[1]) * 5) / 255; - return [x, sin(x, 0.05, r, y)]; - }, range(-200, 200)), - ]), - range(-200, 200, 5) - ); + const waves = map( + (y) => + polyline([ + ...map((x) => { + const q = geom.mapLocalPointToNode(imgNode, [x, y]); + const r = (imgMap.getAt(q[0], q[1]) * 5) / 255; + return [x, sin(x, 0.05, r, y)]; + }, range(-200, 200)), + ]), + range(-200, 200, 5) + ); - geom.body = group({ fill: "none", stroke: "#fff", weight: 0.5 }, [ - ...waves, - ]); - return [ - "div.sans-serif.pl3", - ["h1", "scenegraph node-to-node UV mapping"], - [ - "p", - "using ", - ["b", "@thi.ng/geom"], - ", ", - ["b", "@thi.ng/pixel"], - " and ", - ["b", "@thi.ng/hdom-canvas"], - ], - ["p", "Click to toggle image overlay"], - [ - // hdom-canvas component - // translates all shapes/attribs into canvas2d draw calls - canvas, - { - width: 600, - height: 600, - onmousemove: updateMouse, - onclick: toggleImage, - }, - // only need to pass root node which then expands itself via - // .toHiccup() during rendering - root, - ], - ]; - }; + geom.body = group({ fill: "none", stroke: "#fff", weight: 0.5 }, [ + ...waves, + ]); + return [ + "div.sans-serif.pl3", + ["h1", "scenegraph node-to-node UV mapping"], + [ + "p", + "using ", + ["b", "@thi.ng/geom"], + ", ", + ["b", "@thi.ng/pixel"], + " and ", + ["b", "@thi.ng/hdom-canvas"], + ], + ["p", "Click to toggle image overlay"], + [ + // hdom-canvas component + // translates all shapes/attribs into canvas2d draw calls + canvas, + { + width: 600, + height: 600, + onmousemove: updateMouse, + onclick: toggleImage, + }, + // only need to pass root node which then expands itself via + // .toHiccup() during rendering + root, + ], + ]; + }; - start(app); + start(app); }); diff --git a/examples/scenegraph-image/tsconfig.json b/examples/scenegraph-image/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/scenegraph-image/tsconfig.json +++ b/examples/scenegraph-image/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/scenegraph/package.json b/examples/scenegraph/package.json index 2065206d7a..92d183966d 100644 --- a/examples/scenegraph/package.json +++ b/examples/scenegraph/package.json @@ -1,46 +1,46 @@ { - "name": "@example/scenegraph", - "private": true, - "description": "2D scenegraph & shape picking", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/geom": "workspace:^", - "@thi.ng/geom-api": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-canvas": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/scenegraph": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom", - "hdom", - "hdom-canvas", - "math", - "matrices", - "scenegraph", - "transducers", - "vectors" - ], - "screenshot": "examples/scenegraph.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/scenegraph", + "private": true, + "description": "2D scenegraph & shape picking", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/geom": "workspace:^", + "@thi.ng/geom-api": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-canvas": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/scenegraph": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom", + "hdom", + "hdom-canvas", + "math", + "matrices", + "scenegraph", + "transducers", + "vectors" + ], + "screenshot": "examples/scenegraph.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/scenegraph/src/index.ts b/examples/scenegraph/src/index.ts index 6a41039fa0..c7e5f6c16e 100644 --- a/examples/scenegraph/src/index.ts +++ b/examples/scenegraph/src/index.ts @@ -19,26 +19,26 @@ import { mulN2 } from "@thi.ng/vectors/muln"; * Specialized scene graph node using @thi.ng/geom shapes as body. */ class GeomNode extends Node2D { - constructor( - id: string, - parent: Node2D, - t: Vec, - r: number, - s: Vec | number, - body: IShape - ) { - super(id, parent, t, r, s, body); - } + constructor( + id: string, + parent: Node2D, + t: Vec, + r: number, + s: Vec | number, + body: IShape + ) { + super(id, parent, t, r, s, body); + } - /** - * Override method to check for actual point containment with body - * shape. - * - * @param p - - */ - containsLocalPoint(p: ReadonlyVec) { - return pointInside(this.body, p); - } + /** + * Override method to check for actual point containment with body + * shape. + * + * @param p - + */ + containsLocalPoint(p: ReadonlyVec) { + return pointInside(this.body, p); + } } // mouse pos @@ -48,15 +48,15 @@ let info: NodeInfo | undefined; // color iterator (used for node highlighting) const colors = cycle([ - "#f00", - "#ff0", - "#06f", - "#70f", - "#666", - "#9f0", - "#0ff", - "#090", - "#f60", + "#f00", + "#ff0", + "#06f", + "#70f", + "#666", + "#9f0", + "#0ff", + "#090", + "#f60", ]); // scene graph definition @@ -66,103 +66,103 @@ const root = new Node2D("root", null, [0, 0], 0, 1); // main geometry node w/ origin at canvas center const hex = new GeomNode( - "main", - root, - [300, 300], - 0, - 200, - asPolygon(circle(0.5, { fill: "#0ff" }), 6) + "main", + root, + [300, 300], + 0, + 200, + asPolygon(circle(0.5, { fill: "#0ff" }), 6) ); // rotated child node const triangle = new GeomNode( - "tri", - hex, - [0, 0], - PI / 4, - 1, - asPolygon(circle(0.5, { fill: "#f0f" }), 3) + "tri", + hex, + [0, 0], + PI / 4, + 1, + asPolygon(circle(0.5, { fill: "#f0f" }), 3) ); // secondary children const satellites = [ - ...map( - (i) => - new GeomNode( - `sat-${i}`, - triangle, - cartesian2([], [1, i * HALF_PI]), - 0, - 0.2, - rect([-0.5, -0.5], [1, 1], { fill: "#cf0" }) - ), - range(4) - ), + ...map( + (i) => + new GeomNode( + `sat-${i}`, + triangle, + cartesian2([], [1, i * HALF_PI]), + 0, + 0.2, + rect([-0.5, -0.5], [1, 1], { fill: "#cf0" }) + ), + range(4) + ), ]; // this node uses a hdom component function as body to create the dynamic // crosshair and node info overlay const infoNode = new Node2D("info", root, mouse, 0, 1, () => [ - "g", - {}, - // crosshair - ["g", { stroke: "#999", dash: [2, 2] }, ["hline", {}, 0], ["vline", {}, 0]], - // only show text overlay if info present - info - ? [ - "g", - { fill: "#fff" }, - rect([0, -40], [68, 40], { fill: "rgba(0,0,0,0.8)" }), - [ - "text", - {}, - [8, -10], - `${info.p![0].toFixed(2)}, ${info.p![1].toFixed(2)}`, - ], - ["text", {}, [8, -24], `ID: ${info.node.id}`], - ] - : undefined, + "g", + {}, + // crosshair + ["g", { stroke: "#999", dash: [2, 2] }, ["hline", {}, 0], ["vline", {}, 0]], + // only show text overlay if info present + info + ? [ + "g", + { fill: "#fff" }, + rect([0, -40], [68, 40], { fill: "rgba(0,0,0,0.8)" }), + [ + "text", + {}, + [8, -10], + `${info.p![0].toFixed(2)}, ${info.p![1].toFixed(2)}`, + ], + ["text", {}, [8, -24], `ID: ${info.node.id}`], + ] + : undefined, ]); // mousemove event handler const updateMouse = (e: MouseEvent) => { - mouse = [e.offsetX, e.offsetY]; - info = root.childForPoint(mouse); - infoNode.translate = mouse; + mouse = [e.offsetX, e.offsetY]; + info = root.childForPoint(mouse); + infoNode.translate = mouse; }; // onclick handler, assigns new fill color to selected node (if any) const selectNode = () => - info && (info.node.body.attribs.fill = colors.next().value); + info && (info.node.body.attribs.fill = colors.next().value); // main hdom root component / app const app = () => { - // update scene graph nodes - hex.rotate += 0.005; - mulN2(triangle.scale, [1, 1], Math.sin(hex.rotate * 5) * 0.3 + 0.7); - satellites.forEach((s) => (s.rotate += 0.02)); - // recompute matrices - root.update(); + // update scene graph nodes + hex.rotate += 0.005; + mulN2(triangle.scale, [1, 1], Math.sin(hex.rotate * 5) * 0.3 + 0.7); + satellites.forEach((s) => (s.rotate += 0.02)); + // recompute matrices + root.update(); - return [ - "div.sans-serif.pl3", - ["h1", "hdom canvas scene graph demo"], - ["p", "click on shapes to change their color..."], - // hdom-canvas component - // translates all shapes/attribs into canvas2d draw calls - [ - canvas, - { - width: 600, - height: 600, - onmousemove: updateMouse, - onclick: selectNode, - }, - // only need to pass root node which then expands itself via - // .toHiccup() during rendering - root, - ], - ]; + return [ + "div.sans-serif.pl3", + ["h1", "hdom canvas scene graph demo"], + ["p", "click on shapes to change their color..."], + // hdom-canvas component + // translates all shapes/attribs into canvas2d draw calls + [ + canvas, + { + width: 600, + height: 600, + onmousemove: updateMouse, + onclick: selectNode, + }, + // only need to pass root node which then expands itself via + // .toHiccup() during rendering + root, + ], + ]; }; start(app); diff --git a/examples/scenegraph/tsconfig.json b/examples/scenegraph/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/scenegraph/tsconfig.json +++ b/examples/scenegraph/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/shader-ast-canvas2d/package.json b/examples/shader-ast-canvas2d/package.json index 7972b8aa79..a52ac03d88 100644 --- a/examples/shader-ast-canvas2d/package.json +++ b/examples/shader-ast-canvas2d/package.json @@ -1,33 +1,33 @@ { - "name": "@example/shader-ast-canvas2d", - "private": true, - "description": "2D canvas shader emulation", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/shader-ast-glsl": "workspace:^", - "@thi.ng/shader-ast-js": "workspace:^", - "@thi.ng/shader-ast-stdlib": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true, - "screenshot": "shader-ast/shader-ast-01.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/shader-ast-canvas2d", + "private": true, + "description": "2D canvas shader emulation", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/shader-ast-glsl": "workspace:^", + "@thi.ng/shader-ast-js": "workspace:^", + "@thi.ng/shader-ast-stdlib": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true, + "screenshot": "shader-ast/shader-ast-01.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/shader-ast-canvas2d/src/index.ts b/examples/shader-ast-canvas2d/src/index.ts index 4186d3c0ab..14800505a9 100644 --- a/examples/shader-ast-canvas2d/src/index.ts +++ b/examples/shader-ast-canvas2d/src/index.ts @@ -14,48 +14,48 @@ const gl = targetGLSL(); // ported from: http://glslsandbox.com/e#55242.0 const main = defn( - // return type - "vec4", - // func name - "main", - // args (names are optional) - [ - ["vec2", "fragCoord"], - ["vec2", "res"], - ["float", "time"], - ], - // bound args given to function body - (frag, res, time) => { - let a: FloatSym; - let p: FloatSym; - let uv: Vec2Sym; - let sp: Vec2Sym; - let dp: FloatSym; - let p2: FloatSym; - let m: FloatSym; - return [ - (a = sym(add(mul(sin(time), float(2)), float(3)))), - (p = sym(add(mul($y(frag), $x(res)), $x(frag)))), - (uv = sym(fit0111(div(frag, res)))), - (sp = sym(mul(uv, a))), - (dp = sym(dot(sp, sp))), - (p2 = sym(add(mul($y(sp), $x(res)), $x(sp)))), - (m = sym( - mul( - div(add(p2, mul(p, a)), mul($x(res), $y(res))), - mul($x(sp), $y(sp)) - ) - )), - ret( - vec4( - fit1101( - cos(mul(add(sin(mul(vec3(1, 2, 3), m)), dp), float(2))) - ), - 1 - ) - ), - ]; - } + // return type + "vec4", + // func name + "main", + // args (names are optional) + [ + ["vec2", "fragCoord"], + ["vec2", "res"], + ["float", "time"], + ], + // bound args given to function body + (frag, res, time) => { + let a: FloatSym; + let p: FloatSym; + let uv: Vec2Sym; + let sp: Vec2Sym; + let dp: FloatSym; + let p2: FloatSym; + let m: FloatSym; + return [ + (a = sym(add(mul(sin(time), float(2)), float(3)))), + (p = sym(add(mul($y(frag), $x(res)), $x(frag)))), + (uv = sym(fit0111(div(frag, res)))), + (sp = sym(mul(uv, a))), + (dp = sym(dot(sp, sp))), + (p2 = sym(add(mul($y(sp), $x(res)), $x(sp)))), + (m = sym( + mul( + div(add(p2, mul(p, a)), mul($x(res), $y(res))), + mul($x(sp), $y(sp)) + ) + )), + ret( + vec4( + fit1101( + cos(mul(add(sin(mul(vec3(1, 2, 3), m)), dp), float(2))) + ), + 1 + ) + ), + ]; + } ); console.log("JS:"); @@ -77,6 +77,6 @@ const rt = canvasRenderer(canvas); const t0 = Date.now(); setInterval(() => { - const time = (Date.now() - t0) * 0.001; - rt((frag) => fn(frag, size, time)); + const time = (Date.now() - t0) * 0.001; + rt((frag) => fn(frag, size, time)); }, 16); diff --git a/examples/shader-ast-canvas2d/tsconfig.json b/examples/shader-ast-canvas2d/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/shader-ast-canvas2d/tsconfig.json +++ b/examples/shader-ast-canvas2d/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/shader-ast-evo/package.json b/examples/shader-ast-evo/package.json index 0100f220b6..3f787315ec 100644 --- a/examples/shader-ast-evo/package.json +++ b/examples/shader-ast-evo/package.json @@ -1,45 +1,45 @@ { - "name": "@example/shader-ast-evo", - "private": true, - "description": "Evolutionary shader generation using genetic programming", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/gp": "workspace:^", - "@thi.ng/logger": "workspace:^", - "@thi.ng/random": "workspace:^", - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/shader-ast-optimize": "workspace:^", - "@thi.ng/shader-ast-stdlib": "workspace:^", - "@thi.ng/webgl": "workspace:^", - "@thi.ng/webgl-shadertoy": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "gp", - "shader-ast", - "shader-ast-optimize", - "shader-ast-stdlib", - "random", - "webgl", - "webgl-shadertoy" - ], - "screenshot": "examples/shader-ast-evo.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/shader-ast-evo", + "private": true, + "description": "Evolutionary shader generation using genetic programming", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/gp": "workspace:^", + "@thi.ng/logger": "workspace:^", + "@thi.ng/random": "workspace:^", + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/shader-ast-optimize": "workspace:^", + "@thi.ng/shader-ast-stdlib": "workspace:^", + "@thi.ng/webgl": "workspace:^", + "@thi.ng/webgl-shadertoy": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "gp", + "shader-ast", + "shader-ast-optimize", + "shader-ast-stdlib", + "random", + "webgl", + "webgl-shadertoy" + ], + "screenshot": "examples/shader-ast-evo.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/shader-ast-evo/src/index.ts b/examples/shader-ast-evo/src/index.ts index 70c785beac..c73631bdef 100644 --- a/examples/shader-ast-evo/src/index.ts +++ b/examples/shader-ast-evo/src/index.ts @@ -15,28 +15,28 @@ import { add, div, mul, neg, sub } from "@thi.ng/shader-ast/ast/ops"; import { $ } from "@thi.ng/shader-ast/ast/swizzle"; import { sym } from "@thi.ng/shader-ast/ast/sym"; import { - abs, - acos, - asin, - cos, - distance, - exp, - fract, - inversesqrt, - length, - log, - mix, - mod, - normalize, - pow, - sin, - sqrt, - tan, + abs, + acos, + asin, + cos, + distance, + exp, + fract, + inversesqrt, + length, + log, + mix, + mod, + normalize, + pow, + sin, + sqrt, + tan, } from "@thi.ng/shader-ast/builtin/math"; import { - type MainImageFn, - shaderToy, - type ShaderToyUniforms, + type MainImageFn, + shaderToy, + type ShaderToyUniforms, } from "@thi.ng/webgl-shadertoy"; import { glCanvas } from "@thi.ng/webgl/canvas"; import { setLogger } from "@thi.ng/webgl/logger"; @@ -49,39 +49,39 @@ const NORM_SCALE = 1; // unary functions const OP1 = [ - abs, - exp, - neg, - normalize, - sin, - cos, - snoiseVec3, - tan, - fract, - (x: Vec3Term) => vec3(snoise3(x)), - (x: Vec3Term) => sub(1, clamp11(x)), - (x: Vec3Term) => vec3(length(x)), - (x: Vec3Term) => log(abs(x)), - (x: Vec3Term) => inversesqrt(abs(x)), - (x: Vec3Term) => sqrt(abs(x)), - (x: Vec3Term) => asin(clamp11(x)), - (x: Vec3Term) => acos(clamp11(x)), - (x: Vec3Term) => $(x, "zyx"), - (x: Vec3Term) => $(x, "yzx"), - (x: Vec3Term) => $(x, "yxz"), - (x: Vec3Term) => $(x, "xxx"), - (x: Vec3Term) => $(x, "yyy"), - (x: Vec3Term) => $(x, "zzz"), + abs, + exp, + neg, + normalize, + sin, + cos, + snoiseVec3, + tan, + fract, + (x: Vec3Term) => vec3(snoise3(x)), + (x: Vec3Term) => sub(1, clamp11(x)), + (x: Vec3Term) => vec3(length(x)), + (x: Vec3Term) => log(abs(x)), + (x: Vec3Term) => inversesqrt(abs(x)), + (x: Vec3Term) => sqrt(abs(x)), + (x: Vec3Term) => asin(clamp11(x)), + (x: Vec3Term) => acos(clamp11(x)), + (x: Vec3Term) => $(x, "zyx"), + (x: Vec3Term) => $(x, "yzx"), + (x: Vec3Term) => $(x, "yxz"), + (x: Vec3Term) => $(x, "xxx"), + (x: Vec3Term) => $(x, "yyy"), + (x: Vec3Term) => $(x, "zzz"), ]; // binary functions const OP2 = [ - add, - sub, - mul, - div, - mod, - pow, - (x: Vec3Term, y: Vec3Term) => vec3(distance(x, y)), + add, + sub, + mul, + div, + mod, + pow, + (x: Vec3Term, y: Vec3Term) => vec3(distance(x, y)), ]; // ternary functions const OP3 = [mix]; @@ -90,76 +90,76 @@ const OP3 = [mix]; const UV: Vec3Sym = sym(vec3()); const randomFn = (ops: Function[]) => (rnd: IRandom) => - ops[rnd.int() % ops.length]; + ops[rnd.int() % ops.length]; // AST generation config const AST_OPTS: ASTOpts = { - terminal: (rnd) => - rnd.float() < 0.5 - ? UV - : vec3( - rnd.norm(NORM_SCALE), - rnd.norm(NORM_SCALE), - rnd.norm(NORM_SCALE) - ), - ops: [ - { fn: randomFn(OP1), arity: 1, prob: 0.4 }, - { fn: randomFn(OP2), arity: 2, prob: 0.4 }, - { fn: randomFn(OP3), arity: 3, prob: 0.1 }, - ], - maxDepth: MAX_DEPTH, - probMutate: 0.01, + terminal: (rnd) => + rnd.float() < 0.5 + ? UV + : vec3( + rnd.norm(NORM_SCALE), + rnd.norm(NORM_SCALE), + rnd.norm(NORM_SCALE) + ), + ops: [ + { fn: randomFn(OP1), arity: 1, prob: 0.4 }, + { fn: randomFn(OP2), arity: 2, prob: 0.4 }, + { fn: randomFn(OP3), arity: 3, prob: 0.1 }, + ], + maxDepth: MAX_DEPTH, + probMutate: 0.01, }; const transpile = (node: ASTNode): Term => - node.type === "op" - ? node.op.apply(null, node.args.map(transpile)) - : node.value; + node.type === "op" + ? node.op.apply(null, node.args.map(transpile)) + : node.value; const shaderFunction = - (ast: ASTNode): MainImageFn => - (gl, unis) => { - return [ - UV, - assign( - UV, - vec3( - fragUV(gl.gl_FragCoord, unis.resolution), - mul(1, fract(unis.time)) - ) - ), - // transpile & optimize generated AST - ret(vec4(abs(constantFolding(transpile(ast))), 1)), - // ret(vec4(fit1101(normalize(transpile(ast))), 1)) - ]; - }; + (ast: ASTNode): MainImageFn => + (gl, unis) => { + return [ + UV, + assign( + UV, + vec3( + fragUV(gl.gl_FragCoord, unis.resolution), + mul(1, fract(unis.time)) + ) + ), + // transpile & optimize generated AST + ret(vec4(abs(constantFolding(transpile(ast))), 1)), + // ret(vec4(fit1101(normalize(transpile(ast))), 1)) + ]; + }; const ast = new AST(AST_OPTS); let currTree = ast.randomAST(); const canvas = glCanvas({ - width: 640, - height: 640, - parent: document.body, - version: 1, + width: 640, + height: 640, + parent: document.body, + version: 1, }); const toy = shaderToy({ - canvas: canvas.canvas, - gl: canvas.gl, - main: shaderFunction(currTree), + canvas: canvas.canvas, + gl: canvas.gl, + main: shaderFunction(currTree), }); toy.start(); const update = () => { - console.clear(); - // currTree = ast.randomAST(); - currTree = - SYSTEM.float() < 0.9 - ? ast.mutate(currTree, SYSTEM.minmax(1, 4)) - : ast.crossoverSingle(currTree, ast.randomAST())[0]; - toy.recompile(shaderFunction(currTree), { prec: 4 }); + console.clear(); + // currTree = ast.randomAST(); + currTree = + SYSTEM.float() < 0.9 + ? ast.mutate(currTree, SYSTEM.minmax(1, 4)) + : ast.crossoverSingle(currTree, ast.randomAST())[0]; + toy.recompile(shaderFunction(currTree), { prec: 4 }); }; setInterval(update, 500); diff --git a/examples/shader-ast-evo/tsconfig.json b/examples/shader-ast-evo/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/shader-ast-evo/tsconfig.json +++ b/examples/shader-ast-evo/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/shader-ast-noise/package.json b/examples/shader-ast-noise/package.json index 3f08c9f9b2..73d2c60c30 100644 --- a/examples/shader-ast-noise/package.json +++ b/examples/shader-ast-noise/package.json @@ -1,34 +1,34 @@ { - "name": "@example/shader-ast-noise", - "private": true, - "description": "HOF shader procedural noise function composition", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/shader-ast-glsl": "workspace:^", - "@thi.ng/shader-ast-js": "workspace:^", - "@thi.ng/shader-ast-stdlib": "workspace:^", - "@thi.ng/webgl": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true, - "screenshot": "examples/shader-ast-noise.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/shader-ast-noise", + "private": true, + "description": "HOF shader procedural noise function composition", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/shader-ast-glsl": "workspace:^", + "@thi.ng/shader-ast-js": "workspace:^", + "@thi.ng/shader-ast-stdlib": "workspace:^", + "@thi.ng/webgl": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true, + "screenshot": "examples/shader-ast-noise.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/shader-ast-noise/src/index.ts b/examples/shader-ast-noise/src/index.ts index 02860b2489..165d5ef88b 100644 --- a/examples/shader-ast-noise/src/index.ts +++ b/examples/shader-ast-noise/src/index.ts @@ -26,23 +26,23 @@ const JS = targetJS(); // https://www.shadertoy.com/view/Ms2SWW (by iq) const mainImage = defn( - "vec4", - "mainImage", - ["vec2", "vec2", "float"], - (fragCoord, res, time) => { - let uv: Vec2Sym; - let col: FloatSym; - return [ - (uv = sym(aspectCorrectedUV(fragCoord, res))), - // dynamically create a multi-octave version of `snoise2` - // computed over 4 octaves w/ given phase shift and decay - // factor (both per octave) - (col = sym( - additive("vec2", snoise2, 4)(add(uv, time), vec2(2), float(0.5)) - )), - ret(vec4(vec3(fit1101(col)), 1)), - ]; - } + "vec4", + "mainImage", + ["vec2", "vec2", "float"], + (fragCoord, res, time) => { + let uv: Vec2Sym; + let col: FloatSym; + return [ + (uv = sym(aspectCorrectedUV(fragCoord, res))), + // dynamically create a multi-octave version of `snoise2` + // computed over 4 octaves w/ given phase shift and decay + // factor (both per octave) + (col = sym( + additive("vec2", snoise2, 4)(add(uv, time), vec2(2), float(0.5)) + )), + ret(vec4(vec3(fit1101(col)), 1)), + ]; + } ); // build call graph for given entry function, sort in topological order @@ -66,56 +66,56 @@ info.innerText = (JS_MODE ? "Canvas2D" : "WebGL") + " version"; document.body.appendChild(info); if (JS_MODE) { - // - // JS Canvas 2D shader emulation from here... - // - const fn = JS.compile(shaderProgram).mainImage; - const rt = canvasRenderer(canvas); - let time = 0; + // + // JS Canvas 2D shader emulation from here... + // + const fn = JS.compile(shaderProgram).mainImage; + const rt = canvasRenderer(canvas); + let time = 0; - setInterval(() => { - time += 0.01; - rt((frag) => fn(frag, size, time)); - }, 16); + setInterval(() => { + time += 0.01; + rt((frag) => fn(frag, size, time)); + }, 16); } else { - // - // WebGL mode... - // - const ctx: WebGLRenderingContext = canvas.getContext("webgl")!; - // build fullscreen quad - const model = defQuadModel({ uv: false }); - // set shader - model.shader = defShader(ctx, { - vs: (gl, _, attribs) => [ - defMain(() => [ - assign(gl.gl_Position, vec4(attribs.position, 0, 1)), - ]), - ], - fs: (gl, unis, _, outs) => [ - mainImage, - defMain(() => [ - assign( - outs.fragColor, - mainImage($xy(gl.gl_FragCoord), unis.resolution, unis.time) - ), - ]), - ], - attribs: { - position: "vec2", - }, - uniforms: { - resolution: ["vec2", [W, H]], - time: "float", - }, - }); - // compile model (attrib buffers) - compileModel(ctx, model); + // + // WebGL mode... + // + const ctx: WebGLRenderingContext = canvas.getContext("webgl")!; + // build fullscreen quad + const model = defQuadModel({ uv: false }); + // set shader + model.shader = defShader(ctx, { + vs: (gl, _, attribs) => [ + defMain(() => [ + assign(gl.gl_Position, vec4(attribs.position, 0, 1)), + ]), + ], + fs: (gl, unis, _, outs) => [ + mainImage, + defMain(() => [ + assign( + outs.fragColor, + mainImage($xy(gl.gl_FragCoord), unis.resolution, unis.time) + ), + ]), + ], + attribs: { + position: "vec2", + }, + uniforms: { + resolution: ["vec2", [W, H]], + time: "float", + }, + }); + // compile model (attrib buffers) + compileModel(ctx, model); - const t0 = Date.now(); - // render loop - setInterval(() => { - const time = (Date.now() - t0) * 0.001; - model.uniforms!.time = time; - draw(model); - }); + const t0 = Date.now(); + // render loop + setInterval(() => { + const time = (Date.now() - t0) * 0.001; + model.uniforms!.time = time; + draw(model); + }); } diff --git a/examples/shader-ast-noise/tsconfig.json b/examples/shader-ast-noise/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/shader-ast-noise/tsconfig.json +++ b/examples/shader-ast-noise/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/shader-ast-raymarch/package.json b/examples/shader-ast-raymarch/package.json index bbb5b475c9..184966b328 100644 --- a/examples/shader-ast-raymarch/package.json +++ b/examples/shader-ast-raymarch/package.json @@ -1,34 +1,34 @@ { - "name": "@example/shader-ast-raymarch", - "private": true, - "description": "WebGL & JS canvas2D raymarch shader cross-compilation", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/shader-ast-glsl": "workspace:^", - "@thi.ng/shader-ast-js": "workspace:^", - "@thi.ng/shader-ast-stdlib": "workspace:^", - "@thi.ng/webgl": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true, - "screenshot": "shader-ast/shader-ast-raymarch.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/shader-ast-raymarch", + "private": true, + "description": "WebGL & JS canvas2D raymarch shader cross-compilation", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/shader-ast-glsl": "workspace:^", + "@thi.ng/shader-ast-js": "workspace:^", + "@thi.ng/shader-ast-stdlib": "workspace:^", + "@thi.ng/webgl": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true, + "screenshot": "shader-ast/shader-ast-raymarch.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/shader-ast-raymarch/src/index.ts b/examples/shader-ast-raymarch/src/index.ts index 9ba783db26..2a0fd0b772 100644 --- a/examples/shader-ast-raymarch/src/index.ts +++ b/examples/shader-ast-raymarch/src/index.ts @@ -4,8 +4,8 @@ import { canvasRenderer } from "@thi.ng/shader-ast-js/runtime"; import { targetJS } from "@thi.ng/shader-ast-js/target"; import { fogExp2 } from "@thi.ng/shader-ast-stdlib/fog/exp2"; import { - diffuseLighting, - halfLambert, + diffuseLighting, + halfLambert, } from "@thi.ng/shader-ast-stdlib/light/lambert"; import { clamp01 } from "@thi.ng/shader-ast-stdlib/math/clamp"; import { fit1101 } from "@thi.ng/shader-ast-stdlib/math/fit"; @@ -44,103 +44,103 @@ const JS = targetJS(); // scene definition for raymarch function. uses SDF primitive functions // included in "standard library" bundled with shader-ast pkg const scene = defn("vec2", "scene", ["vec3"], (pos) => { - let d1: FloatSym; - let d2: FloatSym; - let d3: FloatSym; - let d4: FloatSym; - return [ - assign(pos, sdfRepeat3(pos, vec3(2.1))), - (d1 = sym(sdfSphere(pos, float(0.5)))), - (d2 = sym(sdfBox3(pos, vec3(1, 0.2, 0.2)))), - (d3 = sym(sdfBox3(pos, vec3(0.2, 0.2, 1)))), - (d4 = sym(sdfBox3(pos, vec3(0.2, 1, 0.2)))), - ret( - vec2( - sdfSmoothUnion( - sdfSmoothUnion( - sdfSmoothUnion(d1, d2, float(0.2)), - d3, - float(0.2) - ), - d4, - float(0.2) - ), - 1 - ) - ), - ]; + let d1: FloatSym; + let d2: FloatSym; + let d3: FloatSym; + let d4: FloatSym; + return [ + assign(pos, sdfRepeat3(pos, vec3(2.1))), + (d1 = sym(sdfSphere(pos, float(0.5)))), + (d2 = sym(sdfBox3(pos, vec3(1, 0.2, 0.2)))), + (d3 = sym(sdfBox3(pos, vec3(0.2, 0.2, 1)))), + (d4 = sym(sdfBox3(pos, vec3(0.2, 1, 0.2)))), + ret( + vec2( + sdfSmoothUnion( + sdfSmoothUnion( + sdfSmoothUnion(d1, d2, float(0.2)), + d3, + float(0.2) + ), + d4, + float(0.2) + ), + 1 + ) + ), + ]; }); // main fragment shader function // again uses several shader-ast std lib helpers const mainImage = defn( - "vec4", - "mainImage", - ["vec2", "vec2", "vec3", "vec3"], - (frag, res, eyePos, lightDir) => { - let dir: Vec3Sym; - let result: Vec2Sym; - let isec: Vec3Sym; - let norm: Vec3Sym; - let material: Vec3Sym; - let diffuse: FloatSym; - // background color - const bg = vec3(1.5, 0.6, 0); - const ambient = vec3(0.15, 0.06, 0); - return [ - // compute ray dir from fragCoord, viewport res and FOV - // then apply basic camera settings (eye, target, up) - (dir = sym( - $xyz( - mul( - lookat(eyePos, vec3(), vec3(0, 1, 0)), - vec4(raymarchDir(frag, res, float(120)), 0) - ) - ) - )), - // perform raymarch - (result = sym( - // `raymarchScene` is a higher-order, configurable function which constructs - // a raymarch function using our supplied scene fn - raymarchScene(scene, { steps: JS_MODE ? 60 : 80, eps: 0.005 })( - eyePos, - dir - ) - )), - // early bailout if nothing hit - ifThen(gte($x(result), float(10)), [ret(vec4(bg, 1))]), - // set intersection pos - (isec = sym(rayPointAt(eyePos, dir, $x(result)))), - // surface normal - (norm = sym( - // higher-order fn to compute surface normal - raymarchNormal(scene)(isec, float(0.01)) - )), - // set material color - (material = sym(fit1101(isec))), - // compute diffuse term - (diffuse = sym( - mul( - halfLambert(norm, lightDir), - // higher order fn to compute ambient occlusion - raymarchAO(scene)(isec, norm) - ) - )), - // combine lighting & material colors - ret( - vec4( - mix( - clamp01( - diffuseLighting(diffuse, material, vec3(1), ambient) - ), - bg, - fogExp2($x(result), float(0.2)) - ), - 1 - ) - ), - ]; - } + "vec4", + "mainImage", + ["vec2", "vec2", "vec3", "vec3"], + (frag, res, eyePos, lightDir) => { + let dir: Vec3Sym; + let result: Vec2Sym; + let isec: Vec3Sym; + let norm: Vec3Sym; + let material: Vec3Sym; + let diffuse: FloatSym; + // background color + const bg = vec3(1.5, 0.6, 0); + const ambient = vec3(0.15, 0.06, 0); + return [ + // compute ray dir from fragCoord, viewport res and FOV + // then apply basic camera settings (eye, target, up) + (dir = sym( + $xyz( + mul( + lookat(eyePos, vec3(), vec3(0, 1, 0)), + vec4(raymarchDir(frag, res, float(120)), 0) + ) + ) + )), + // perform raymarch + (result = sym( + // `raymarchScene` is a higher-order, configurable function which constructs + // a raymarch function using our supplied scene fn + raymarchScene(scene, { steps: JS_MODE ? 60 : 80, eps: 0.005 })( + eyePos, + dir + ) + )), + // early bailout if nothing hit + ifThen(gte($x(result), float(10)), [ret(vec4(bg, 1))]), + // set intersection pos + (isec = sym(rayPointAt(eyePos, dir, $x(result)))), + // surface normal + (norm = sym( + // higher-order fn to compute surface normal + raymarchNormal(scene)(isec, float(0.01)) + )), + // set material color + (material = sym(fit1101(isec))), + // compute diffuse term + (diffuse = sym( + mul( + halfLambert(norm, lightDir), + // higher order fn to compute ambient occlusion + raymarchAO(scene)(isec, norm) + ) + )), + // combine lighting & material colors + ret( + vec4( + mix( + clamp01( + diffuseLighting(diffuse, material, vec3(1), ambient) + ), + bg, + fogExp2($x(result), float(0.2)) + ), + 1 + ) + ), + ]; + } ); // build call graph for given entry function, sort in topological order @@ -166,71 +166,71 @@ document.body.appendChild(info); const lightDir = [0.707, 0.707, 0]; if (JS_MODE) { - // - // JS Canvas 2D shader emulation from here... - // - const fn = JS.compile(shaderProgram).mainImage; - const rt = canvasRenderer(canvas); - let time = 0; + // + // JS Canvas 2D shader emulation from here... + // + const fn = JS.compile(shaderProgram).mainImage; + const rt = canvasRenderer(canvas); + let time = 0; - setInterval(() => { - time += 0.1; - const eyePos = [ - Math.cos(time) * 2.5, - Math.cos(time / 2) * 0.7, - Math.sin(time) * 2.5, - ]; - rt((frag) => fn(frag, size, eyePos, lightDir)); - }, 16); + setInterval(() => { + time += 0.1; + const eyePos = [ + Math.cos(time) * 2.5, + Math.cos(time / 2) * 0.7, + Math.sin(time) * 2.5, + ]; + rt((frag) => fn(frag, size, eyePos, lightDir)); + }, 16); } else { - // - // WebGL mode... - // - const ctx: WebGLRenderingContext = canvas.getContext("webgl")!; - // build fullscreen quad - const model = defQuadModel({ uv: false }); - // set shader - model.shader = defShader(ctx, { - vs: (_, __, attribs) => [ - defMain(() => [ - assign(GL.gl_Position, vec4(attribs.position, 0, 1)), - ]), - ], - fs: (gl, unis, _, outputs) => [ - mainImage, - defMain(() => [ - assign( - outputs.fragColor, - mainImage( - $xy(gl.gl_FragCoord), - unis.resolution, - unis.eyePos, - unis.lightDir - ) - ), - ]), - ], - attribs: { - position: "vec2", - }, - uniforms: { - eyePos: "vec3", - lightDir: ["vec3", lightDir], - resolution: ["vec2", [W, H]], - }, - }); - // compile model (attrib buffers) - compileModel(ctx, model); + // + // WebGL mode... + // + const ctx: WebGLRenderingContext = canvas.getContext("webgl")!; + // build fullscreen quad + const model = defQuadModel({ uv: false }); + // set shader + model.shader = defShader(ctx, { + vs: (_, __, attribs) => [ + defMain(() => [ + assign(GL.gl_Position, vec4(attribs.position, 0, 1)), + ]), + ], + fs: (gl, unis, _, outputs) => [ + mainImage, + defMain(() => [ + assign( + outputs.fragColor, + mainImage( + $xy(gl.gl_FragCoord), + unis.resolution, + unis.eyePos, + unis.lightDir + ) + ), + ]), + ], + attribs: { + position: "vec2", + }, + uniforms: { + eyePos: "vec3", + lightDir: ["vec3", lightDir], + resolution: ["vec2", [W, H]], + }, + }); + // compile model (attrib buffers) + compileModel(ctx, model); - const t0 = Date.now(); - // render loop - setInterval(() => { - const time = (Date.now() - t0) * 0.001; - model.uniforms!.eyePos = [ - Math.cos(time) * 2.5, - Math.cos(time / 2) * 0.7, - Math.sin(time) * 2.5, - ]; - draw(model); - }); + const t0 = Date.now(); + // render loop + setInterval(() => { + const time = (Date.now() - t0) * 0.001; + model.uniforms!.eyePos = [ + Math.cos(time) * 2.5, + Math.cos(time / 2) * 0.7, + Math.sin(time) * 2.5, + ]; + draw(model); + }); } diff --git a/examples/shader-ast-raymarch/tsconfig.json b/examples/shader-ast-raymarch/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/shader-ast-raymarch/tsconfig.json +++ b/examples/shader-ast-raymarch/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/shader-ast-sdf2d/package.json b/examples/shader-ast-sdf2d/package.json index 61877de7e6..a43f4b6a8a 100644 --- a/examples/shader-ast-sdf2d/package.json +++ b/examples/shader-ast-sdf2d/package.json @@ -1,34 +1,34 @@ { - "name": "@example/shader-ast-sdf2d", - "private": true, - "description": "WebGL & JS canvas 2D SDF", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/shader-ast-glsl": "workspace:^", - "@thi.ng/shader-ast-js": "workspace:^", - "@thi.ng/shader-ast-stdlib": "workspace:^", - "@thi.ng/webgl": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true, - "screenshot": "examples/shader-ast-sdf2d.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/shader-ast-sdf2d", + "private": true, + "description": "WebGL & JS canvas 2D SDF", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/shader-ast-glsl": "workspace:^", + "@thi.ng/shader-ast-js": "workspace:^", + "@thi.ng/shader-ast-stdlib": "workspace:^", + "@thi.ng/webgl": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true, + "screenshot": "examples/shader-ast-sdf2d.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/shader-ast-sdf2d/src/index.ts b/examples/shader-ast-sdf2d/src/index.ts index 068181e1c7..b396ed9849 100644 --- a/examples/shader-ast-sdf2d/src/index.ts +++ b/examples/shader-ast-sdf2d/src/index.ts @@ -29,51 +29,51 @@ const JS = targetJS(); // scene definition for raymarch function. uses SDF primitive functions // included in "standard library" bundled with shader-ast pkg const scene = defn("float", "scene", ["vec2"], (pos) => { - let d1: FloatSym; - let d2: FloatSym; - let d3: FloatSym; - return [ - // assign(pos, sdTxRepeat2(pos, vec2(2))), - (d1 = sym( - sdfTriangle2(pos, vec2(1, 0.7), vec2(0, -1.3), vec2(-1, 0.7)) - )), - assign( - d1, - min( - d1, - sdfTriangle2(pos, vec2(1, -0.7), vec2(0, 1.3), vec2(-1, -0.7)) - ) - ), - (d2 = sym(sdfBox2(pos, vec2(1.5, 0.2)))), - (d3 = sym(sdfBox2(pos, vec2(0.2, 1.5)))), - assign( - d1, - sdfSmoothUnion(sdfSmoothUnion(d3, d2, float(0.5)), d1, float(0.5)) - ), - ret(d1), - ]; + let d1: FloatSym; + let d2: FloatSym; + let d3: FloatSym; + return [ + // assign(pos, sdTxRepeat2(pos, vec2(2))), + (d1 = sym( + sdfTriangle2(pos, vec2(1, 0.7), vec2(0, -1.3), vec2(-1, 0.7)) + )), + assign( + d1, + min( + d1, + sdfTriangle2(pos, vec2(1, -0.7), vec2(0, 1.3), vec2(-1, -0.7)) + ) + ), + (d2 = sym(sdfBox2(pos, vec2(1.5, 0.2)))), + (d3 = sym(sdfBox2(pos, vec2(0.2, 1.5)))), + assign( + d1, + sdfSmoothUnion(sdfSmoothUnion(d3, d2, float(0.5)), d1, float(0.5)) + ), + ret(d1), + ]; }); // main fragment shader function // again uses several shader-ast std lib helpers const mainImage = defn("vec4", "mainImage", ["vec2", "vec2"], (frag, res) => { - let uv: Vec2Sym; - let d: FloatSym; - let f = 100; - return [ - (uv = sym(mul(aspectCorrectedUV(frag, res), float(2)))), - (d = sym(scene(uv))), - ret( - vec4( - vec3( - fit1101(cos(mul(d, float(f)))), - fit1101(cos(mul(d, float(f * 1.02)))), - fit1101(cos(mul(d, float(f * 1.05)))) - ), - 1 - ) - ), - ]; + let uv: Vec2Sym; + let d: FloatSym; + let f = 100; + return [ + (uv = sym(mul(aspectCorrectedUV(frag, res), float(2)))), + (d = sym(scene(uv))), + ret( + vec4( + vec3( + fit1101(cos(mul(d, float(f)))), + fit1101(cos(mul(d, float(f * 1.02)))), + fit1101(cos(mul(d, float(f * 1.05)))) + ), + 1 + ) + ), + ]; }); // build call graph for given entry function, sort in topological order @@ -97,50 +97,50 @@ info.innerText = (JS_MODE ? "Canvas2D" : "WebGL2") + " version"; document.body.appendChild(info); if (JS_MODE) { - // - // JS Canvas 2D shader emulation from here... - // - const fn = JS.compile(shaderProgram).mainImage; - const rt = canvasRenderer(canvas); + // + // JS Canvas 2D shader emulation from here... + // + const fn = JS.compile(shaderProgram).mainImage; + const rt = canvasRenderer(canvas); - setInterval(() => { - rt((frag) => fn(frag, size)); - }, 16); + setInterval(() => { + rt((frag) => fn(frag, size)); + }, 16); } else { - // - // WebGL mode... - // - const ctx: WebGLRenderingContext = canvas.getContext("webgl")!; - // build fullscreen quad - const model = defQuadModel({ uv: false }); - // set shader - model.shader = defShader(ctx, { - vs: (gl, _, attribs) => [ - defMain(() => [ - assign(gl.gl_Position, vec4(attribs.position, 0, 1)), - ]), - ], - fs: (gl, unis, _, outs) => [ - mainImage, - defMain(() => [ - assign( - outs.fragColor, - mainImage($xy(gl.gl_FragCoord), unis.resolution) - ), - ]), - ], - attribs: { - position: "vec2", - }, - uniforms: { - resolution: ["vec2", [W, H]], - }, - }); - // compile model (attrib buffers) - compileModel(ctx, model); + // + // WebGL mode... + // + const ctx: WebGLRenderingContext = canvas.getContext("webgl")!; + // build fullscreen quad + const model = defQuadModel({ uv: false }); + // set shader + model.shader = defShader(ctx, { + vs: (gl, _, attribs) => [ + defMain(() => [ + assign(gl.gl_Position, vec4(attribs.position, 0, 1)), + ]), + ], + fs: (gl, unis, _, outs) => [ + mainImage, + defMain(() => [ + assign( + outs.fragColor, + mainImage($xy(gl.gl_FragCoord), unis.resolution) + ), + ]), + ], + attribs: { + position: "vec2", + }, + uniforms: { + resolution: ["vec2", [W, H]], + }, + }); + // compile model (attrib buffers) + compileModel(ctx, model); - // render loop - setInterval(() => { - draw(model); - }); + // render loop + setInterval(() => { + draw(model); + }); } diff --git a/examples/shader-ast-sdf2d/tsconfig.json b/examples/shader-ast-sdf2d/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/shader-ast-sdf2d/tsconfig.json +++ b/examples/shader-ast-sdf2d/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/shader-ast-tunnel/package.json b/examples/shader-ast-tunnel/package.json index b59bd8a6d0..e239121899 100644 --- a/examples/shader-ast-tunnel/package.json +++ b/examples/shader-ast-tunnel/package.json @@ -1,42 +1,42 @@ { - "name": "@example/shader-ast-tunnel", - "private": true, - "description": "WebGL & Canvas2D textured tunnel shader", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/color": "workspace:^", - "@thi.ng/pixel": "workspace:^", - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/shader-ast-glsl": "workspace:^", - "@thi.ng/shader-ast-js": "workspace:^", - "@thi.ng/webgl": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "pixel", - "shader-ast", - "shader-ast-glsl", - "shader-ast-js", - "shader-ast-stdlib", - "webgl" - ], - "screenshot": "examples/shader-ast-tunnel.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/shader-ast-tunnel", + "private": true, + "description": "WebGL & Canvas2D textured tunnel shader", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/color": "workspace:^", + "@thi.ng/pixel": "workspace:^", + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/shader-ast-glsl": "workspace:^", + "@thi.ng/shader-ast-js": "workspace:^", + "@thi.ng/webgl": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "pixel", + "shader-ast", + "shader-ast-glsl", + "shader-ast-js", + "shader-ast-stdlib", + "webgl" + ], + "screenshot": "examples/shader-ast-tunnel.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/shader-ast-tunnel/src/index.ts b/examples/shader-ast-tunnel/src/index.ts index fe203e1b22..85f53b502c 100644 --- a/examples/shader-ast-tunnel/src/index.ts +++ b/examples/shader-ast-tunnel/src/index.ts @@ -5,9 +5,9 @@ import { defSampler } from "@thi.ng/pixel/sample"; import type { FloatSym, Vec2Sym } from "@thi.ng/shader-ast"; import { GLSLVersion, targetGLSL } from "@thi.ng/shader-ast-glsl"; import { - canvasRenderer, - JS_DEFAULT_ENV, - targetJS, + canvasRenderer, + JS_DEFAULT_ENV, + targetJS, } from "@thi.ng/shader-ast-js"; import { assign } from "@thi.ng/shader-ast/ast/assign"; import { defMain, defn, ret } from "@thi.ng/shader-ast/ast/function"; @@ -36,27 +36,27 @@ const JS = targetJS(); // https://www.shadertoy.com/view/Ms2SWW (by iq) const mainImage = defn( - "vec4", - "mainImage", - ["vec2", "vec2", "float", "sampler2D"], - (frag, res, time, tex) => { - let p: Vec2Sym; - let q: Vec2Sym; - let uv: Vec2Sym; - let r: FloatSym; - return [ - (p = sym(div(add(neg(res), mul(frag, 2)), $y(res)))), - (q = sym(pow(mul(p, p), vec2(4)))), - (r = sym(pow(add($x(q), $y(q)), float(1 / 8)))), - (uv = sym( - vec2( - add(div(0.3, r), time), - div(atan($y(p), $x(p)), float(Math.PI)) - ) - )), - ret(vec4(mul($xyz(texture(tex, uv)), r), 1)), - ]; - } + "vec4", + "mainImage", + ["vec2", "vec2", "float", "sampler2D"], + (frag, res, time, tex) => { + let p: Vec2Sym; + let q: Vec2Sym; + let uv: Vec2Sym; + let r: FloatSym; + return [ + (p = sym(div(add(neg(res), mul(frag, 2)), $y(res)))), + (q = sym(pow(mul(p, p), vec2(4)))), + (r = sym(pow(add($x(q), $y(q)), float(1 / 8)))), + (uv = sym( + vec2( + add(div(0.3, r), time), + div(atan($y(p), $x(p)), float(Math.PI)) + ) + )), + ret(vec4(mul($xyz(texture(tex, uv)), r), 1)), + ]; + } ); // assemble all functions in a global scope for code generation... @@ -82,106 +82,106 @@ const tex = new Image(); // preload texture const preload = (async () => { - tex.src = TEX_URL; - await tex.decode(); + tex.src = TEX_URL; + await tex.decode(); })(); if (JS_MODE) { - // - // JS Canvas 2D shader emulation from here... - // - preload.then(() => { - const texCanv = document.createElement("canvas"); - const TW = (texCanv.width = tex.width); - const TH = (texCanv.height = tex.height); - const texCtx = texCanv.getContext("2d")!; - texCtx.drawImage(tex, 0, 0); - const texData = new Uint32Array( - texCtx!.getImageData(0, 0, TW, TH).data.buffer - ); + // + // JS Canvas 2D shader emulation from here... + // + preload.then(() => { + const texCanv = document.createElement("canvas"); + const TW = (texCanv.width = tex.width); + const TH = (texCanv.height = tex.height); + const texCtx = texCanv.getContext("2d")!; + texCtx.drawImage(tex, 0, 0); + const texData = new Uint32Array( + texCtx!.getImageData(0, 0, TW, TH).data.buffer + ); - // since texture sampling is not (yet) supported for the JS - // codegen target, we're patching in a simple wrap-around 2D - // lookup ourselves... - // JS_DEFAULT_ENV.sampler2D.texture = (_, uv) => { - // let x = ((uv[0] * TW) | 0) % TW; - // let y = ((uv[1] * TH) | 0) % TH; - // x < 0 && (x += TW); - // y < 0 && (y += TH); - // return intAbgr32Srgb([], texData[y * TW + x]); - // }; + // since texture sampling is not (yet) supported for the JS + // codegen target, we're patching in a simple wrap-around 2D + // lookup ourselves... + // JS_DEFAULT_ENV.sampler2D.texture = (_, uv) => { + // let x = ((uv[0] * TW) | 0) % TW; + // let y = ((uv[1] * TH) | 0) % TH; + // x < 0 && (x += TW); + // y < 0 && (y += TH); + // return intAbgr32Srgb([], texData[y * TW + x]); + // }; - // alternatively use custom image sampler to perform - // filtered texture lookups: - const sampler = defSampler( - intBuffer(TW, TH, ABGR8888, texData), - "nearest", - "wrap" - ); - JS_DEFAULT_ENV.sampler2D.texture = (_, uv) => - intAbgr32Srgb([], sampler(uv[0] * TW, uv[1] * TH)); + // alternatively use custom image sampler to perform + // filtered texture lookups: + const sampler = defSampler( + intBuffer(TW, TH, ABGR8888, texData), + "nearest", + "wrap" + ); + JS_DEFAULT_ENV.sampler2D.texture = (_, uv) => + intAbgr32Srgb([], sampler(uv[0] * TW, uv[1] * TH)); - // compile AST to actual JS: - // under the hood all vector & matrix operations delegate to - // thi.ng/vectors and thi.ng/matrices packages by default - const fn = JS.compile(shaderProgram).mainImage; - const rt = canvasRenderer(canvas); - let time = 0; + // compile AST to actual JS: + // under the hood all vector & matrix operations delegate to + // thi.ng/vectors and thi.ng/matrices packages by default + const fn = JS.compile(shaderProgram).mainImage; + const rt = canvasRenderer(canvas); + let time = 0; - setInterval(() => { - time += 0.05; - rt((frag) => fn(frag, size, time)); - }, 16); - }); + setInterval(() => { + time += 0.05; + rt((frag) => fn(frag, size, time)); + }, 16); + }); } else { - // - // WebGL mode... - // - preload.then(() => { - const ctx: WebGLRenderingContext = canvas.getContext("webgl")!; - // build fullscreen quad - const model = { - ...defQuadModel({ uv: false }), - shader: defShader(ctx, { - ...FX_SHADER_SPEC, - fs: (gl, unis, _, outs) => [ - mainImage, - defMain(() => [ - assign( - outs.fragColor, - mainImage( - $xy(gl.gl_FragCoord), - unis.resolution, - unis.time, - unis.tex - ) - ), - ]), - ], - uniforms: { - resolution: ["vec2", [W, H]], - time: "float", - tex: ["sampler2D", 0], - }, - }), - textures: [ - defTexture(ctx, { - image: tex, - filter: TextureFilter.LINEAR, - wrap: TextureRepeat.REPEAT, - }), - ], - }; + // + // WebGL mode... + // + preload.then(() => { + const ctx: WebGLRenderingContext = canvas.getContext("webgl")!; + // build fullscreen quad + const model = { + ...defQuadModel({ uv: false }), + shader: defShader(ctx, { + ...FX_SHADER_SPEC, + fs: (gl, unis, _, outs) => [ + mainImage, + defMain(() => [ + assign( + outs.fragColor, + mainImage( + $xy(gl.gl_FragCoord), + unis.resolution, + unis.time, + unis.tex + ) + ), + ]), + ], + uniforms: { + resolution: ["vec2", [W, H]], + time: "float", + tex: ["sampler2D", 0], + }, + }), + textures: [ + defTexture(ctx, { + image: tex, + filter: TextureFilter.LINEAR, + wrap: TextureRepeat.REPEAT, + }), + ], + }; - // compile model (attrib buffers) - compileModel(ctx, model); + // compile model (attrib buffers) + compileModel(ctx, model); - const t0 = Date.now(); - // render loop - setInterval(() => { - const time = (Date.now() - t0) * 0.001; - model.uniforms!.time = time; - draw(model); - }); - }); + const t0 = Date.now(); + // render loop + setInterval(() => { + const time = (Date.now() - t0) * 0.001; + model.uniforms!.time = time; + draw(model); + }); + }); } diff --git a/examples/shader-ast-tunnel/tsconfig.json b/examples/shader-ast-tunnel/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/shader-ast-tunnel/tsconfig.json +++ b/examples/shader-ast-tunnel/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/shader-ast-workers/package.json b/examples/shader-ast-workers/package.json index c87d6c5fb6..65faf38298 100644 --- a/examples/shader-ast-workers/package.json +++ b/examples/shader-ast-workers/package.json @@ -1,52 +1,52 @@ { - "name": "@example/shader-ast-workers", - "private": true, - "description": "Fork-join worker-based raymarch renderer (JS/CPU only)", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/bench": "workspace:^", - "@thi.ng/color": "workspace:^", - "@thi.ng/hiccup-canvas": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/pixel": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/shader-ast-js": "workspace:^", - "@thi.ng/shader-ast-stdlib": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-stats": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "bench", - "color", - "hdom-canvas", - "pixel", - "rstream", - "shader-ast", - "shader-ast-js", - "shader-ast-stdlib", - "transducers", - "transducers-stats" - ], - "screenshot": "examples/shader-ast-workers.jpg" - }, - "devDependencies": { - "esbuild": "^0.14.49", - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/shader-ast-workers", + "private": true, + "description": "Fork-join worker-based raymarch renderer (JS/CPU only)", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/bench": "workspace:^", + "@thi.ng/color": "workspace:^", + "@thi.ng/hiccup-canvas": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/pixel": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/shader-ast-js": "workspace:^", + "@thi.ng/shader-ast-stdlib": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-stats": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "bench", + "color", + "hdom-canvas", + "pixel", + "rstream", + "shader-ast", + "shader-ast-js", + "shader-ast-stdlib", + "transducers", + "transducers-stats" + ], + "screenshot": "examples/shader-ast-workers.jpg" + }, + "devDependencies": { + "esbuild": "^0.14.49", + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/shader-ast-workers/src/api.ts b/examples/shader-ast-workers/src/api.ts index 9605d7faa1..52201bf144 100644 --- a/examples/shader-ast-workers/src/api.ts +++ b/examples/shader-ast-workers/src/api.ts @@ -1,15 +1,15 @@ export interface WorkerJob { - id: number; - width: number; - height: number; - time: number; - y1: number; - y2: number; + id: number; + width: number; + height: number; + time: number; + y1: number; + y2: number; } export interface WorkerResult { - buf: Uint32Array; - stats: number[]; + buf: Uint32Array; + stats: number[]; } export const NUM_WORKERS = navigator.hardwareConcurrency || 4; diff --git a/examples/shader-ast-workers/src/index.ts b/examples/shader-ast-workers/src/index.ts index efa77298f0..dcc02faa41 100644 --- a/examples/shader-ast-workers/src/index.ts +++ b/examples/shader-ast-workers/src/index.ts @@ -22,49 +22,49 @@ const time = reactive(0); // fork worker jobs & re-join results forkJoin({ - src: time, - // WorkerJob preparation - // this function is called for each worker ID to define a region of - // the image to compute. the results of that function are the messages - // sent to the workers... - fork: (id, _, time) => ({ - width: W, - height: H, - y1: id * rowsPerSlice, - y2: (id + 1) * rowsPerSlice, - id, - time, - }), - // re-join partial results (here, update canvas) - join: (parts) => { - updatePixels(parts); - drawStats(parts); - // trigger next update - time.next(time.deref()! + 0.05); - }, - worker: () => new WORKER(), - numWorkers: NUM_WORKERS, + src: time, + // WorkerJob preparation + // this function is called for each worker ID to define a region of + // the image to compute. the results of that function are the messages + // sent to the workers... + fork: (id, _, time) => ({ + width: W, + height: H, + y1: id * rowsPerSlice, + y2: (id + 1) * rowsPerSlice, + id, + time, + }), + // re-join partial results (here, update canvas) + join: (parts) => { + updatePixels(parts); + drawStats(parts); + // trigger next update + time.next(time.deref()! + 0.05); + }, + worker: () => new WORKER(), + numWorkers: NUM_WORKERS, }); const updatePixels = (parts: WorkerResult[]) => { - for (let i = 0; i < NUM_WORKERS; i++) { - imgU32.set(parts[i].buf, i * pixelsPerSlice); - } - canvas.ctx.putImageData(canvas.img, 0, 0); + for (let i = 0; i < NUM_WORKERS; i++) { + imgU32.set(parts[i].buf, i * pixelsPerSlice); + } + canvas.ctx.putImageData(canvas.img, 0, 0); }; const drawStats = (parts: WorkerResult[]) => { - canvas.ctx.strokeStyle = "white"; - for (let i = 0; i < NUM_WORKERS; i++) { - const x = i * 32 + 4; - const stats = parts[i].stats; - if (stats && x < W) { - const [min, max] = bounds(stats); - polyline( - canvas.ctx, - {}, - stats.map((y, j) => [x + j, fitClamped(y, min, max, 28, 4)]) - ); - } - } + canvas.ctx.strokeStyle = "white"; + for (let i = 0; i < NUM_WORKERS; i++) { + const x = i * 32 + 4; + const stats = parts[i].stats; + if (stats && x < W) { + const [min, max] = bounds(stats); + polyline( + canvas.ctx, + {}, + stats.map((y, j) => [x + j, fitClamped(y, min, max, 28, 4)]) + ); + } + } }; diff --git a/examples/shader-ast-workers/src/worker.ts b/examples/shader-ast-workers/src/worker.ts index 6fd75acaa6..0623fab39d 100644 --- a/examples/shader-ast-workers/src/worker.ts +++ b/examples/shader-ast-workers/src/worker.ts @@ -4,8 +4,8 @@ import type { FloatSym, Vec2Sym, Vec3Sym } from "@thi.ng/shader-ast"; import { renderPixels, targetJS } from "@thi.ng/shader-ast-js"; import { fogExp2 } from "@thi.ng/shader-ast-stdlib/fog/exp2"; import { - diffuseLighting, - halfLambert, + diffuseLighting, + halfLambert, } from "@thi.ng/shader-ast-stdlib/light/lambert"; import { clamp01 } from "@thi.ng/shader-ast-stdlib/math/clamp"; import { fit1101 } from "@thi.ng/shader-ast-stdlib/math/fit"; @@ -41,31 +41,31 @@ const COLORS = [...map((i) => hueRgb([], i), normRange(NUM_WORKERS))]; // shader AST functions from the shader-ast-raymarch example const scene = defn("vec2", "scene", ["vec3"], (pos) => { - let d1: FloatSym; - let d2: FloatSym; - let d3: FloatSym; - let d4: FloatSym; - return [ - assign(pos, sdfRepeat3(pos, vec3(2.1))), - (d1 = sym(sdfSphere(pos, float(0.5)))), - (d2 = sym(sdfBox3(pos, vec3(1, 0.2, 0.2)))), - (d3 = sym(sdfBox3(pos, vec3(0.2, 0.2, 1)))), - (d4 = sym(sdfBox3(pos, vec3(0.2, 1, 0.2)))), - ret( - vec2( - sdfSmoothUnion( - sdfSmoothUnion( - sdfSmoothUnion(d1, d2, float(0.2)), - d3, - float(0.2) - ), - d4, - float(0.2) - ), - 1 - ) - ), - ]; + let d1: FloatSym; + let d2: FloatSym; + let d3: FloatSym; + let d4: FloatSym; + return [ + assign(pos, sdfRepeat3(pos, vec3(2.1))), + (d1 = sym(sdfSphere(pos, float(0.5)))), + (d2 = sym(sdfBox3(pos, vec3(1, 0.2, 0.2)))), + (d3 = sym(sdfBox3(pos, vec3(0.2, 0.2, 1)))), + (d4 = sym(sdfBox3(pos, vec3(0.2, 1, 0.2)))), + ret( + vec2( + sdfSmoothUnion( + sdfSmoothUnion( + sdfSmoothUnion(d1, d2, float(0.2)), + d3, + float(0.2) + ), + d4, + float(0.2) + ), + 1 + ) + ), + ]; }); // main fragment shader function @@ -73,78 +73,78 @@ const scene = defn("vec2", "scene", ["vec3"], (pos) => { // the only difference to the original example is the addition of the `tint` // color param to uniquely color each worker's computed region const mainImage = defn( - "vec4", - "mainImage", - ["vec2", "vec2", "vec3", "vec3", "vec3"], - (frag, res, eyePos, lightDir, tint) => { - let dir: Vec3Sym; - let result: Vec2Sym; - let isec: Vec3Sym; - let norm: Vec3Sym; - let material: Vec3Sym; - let diffuse: FloatSym; - // background color - const bg = vec3(0.9); - const ambient = vec3(0.15); - return [ - // compute ray dir from fragCoord, viewport res and FOV - // then apply basic camera settings (eye, target, up) - (dir = sym( - $xyz( - mul( - lookat(eyePos, vec3(), vec3(0, 1, 0)), - vec4(raymarchDir(frag, res, float(120)), 0) - ) - ) - )), - // perform raymarch - (result = sym( - // `raymarchScene` is a higher-order, configurable function which constructs - // a raymarch function using our supplied scene fn - raymarchScene(scene, { steps: 80, eps: 0.005 })(eyePos, dir) - )), - // early bailout if nothing hit - ifThen(gte($x(result), float(10)), [ret(vec4(mul(bg, tint), 1))]), - // set intersection pos - (isec = sym(rayPointAt(eyePos, dir, $x(result)))), - // surface normal - (norm = sym( - // higher-order fn to compute surface normal - raymarchNormal(scene)(isec, float(0.01)) - )), - // set material color - (material = sym(fit1101(isec))), - // compute diffuse term - (diffuse = sym( - mul( - halfLambert(norm, lightDir), - // higher order fn to compute ambient occlusion - raymarchAO(scene)(isec, norm) - ) - )), - // combine lighting & material colors - ret( - vec4( - mul( - mix( - clamp01( - diffuseLighting( - diffuse, - material, - vec3(1), - ambient - ) - ), - bg, - fogExp2($x(result), float(0.2)) - ), - tint - ), - 1 - ) - ), - ]; - } + "vec4", + "mainImage", + ["vec2", "vec2", "vec3", "vec3", "vec3"], + (frag, res, eyePos, lightDir, tint) => { + let dir: Vec3Sym; + let result: Vec2Sym; + let isec: Vec3Sym; + let norm: Vec3Sym; + let material: Vec3Sym; + let diffuse: FloatSym; + // background color + const bg = vec3(0.9); + const ambient = vec3(0.15); + return [ + // compute ray dir from fragCoord, viewport res and FOV + // then apply basic camera settings (eye, target, up) + (dir = sym( + $xyz( + mul( + lookat(eyePos, vec3(), vec3(0, 1, 0)), + vec4(raymarchDir(frag, res, float(120)), 0) + ) + ) + )), + // perform raymarch + (result = sym( + // `raymarchScene` is a higher-order, configurable function which constructs + // a raymarch function using our supplied scene fn + raymarchScene(scene, { steps: 80, eps: 0.005 })(eyePos, dir) + )), + // early bailout if nothing hit + ifThen(gte($x(result), float(10)), [ret(vec4(mul(bg, tint), 1))]), + // set intersection pos + (isec = sym(rayPointAt(eyePos, dir, $x(result)))), + // surface normal + (norm = sym( + // higher-order fn to compute surface normal + raymarchNormal(scene)(isec, float(0.01)) + )), + // set material color + (material = sym(fit1101(isec))), + // compute diffuse term + (diffuse = sym( + mul( + halfLambert(norm, lightDir), + // higher order fn to compute ambient occlusion + raymarchAO(scene)(isec, norm) + ) + )), + // combine lighting & material colors + ret( + vec4( + mul( + mix( + clamp01( + diffuseLighting( + diffuse, + material, + vec3(1), + ambient + ) + ), + bg, + fogExp2($x(result), float(0.2)) + ), + tint + ), + 1 + ) + ), + ]; + } ); // compile shader AST function to JS @@ -155,45 +155,45 @@ const stats = step(comp(sma(10), slidingWindow(24))); const $self: Worker = self; self.addEventListener("message", (e) => { - const job = e.data; - const h = job.y2 - job.y1; - // render pixel shader function based on worker job spec - const [buf, time] = timedResult(() => - renderPixels( - (frag) => - shaderFunc( - // frag coord - frag, - // image size - [job.width, job.height], - // camera / eye pos - [ - Math.cos(job.time) * 2.5, - Math.cos(job.time / 2) * 0.7, - Math.sin(job.time) * 2.5, - ], - // light dir - [0.707, 0.707, 0], - // worker color - COLORS[job.id] - ), - // pixel buffer - new Uint32Array(job.width * h), - // image size - job.width, - h, - // region - 0, - 0, - job.width, - h, - // buffer XY offset in image - 0, - job.y1, - // image height - job.height - ) - ); - // submit result - $self.postMessage({ buf, stats: stats(time) }, [buf.buffer]); + const job = e.data; + const h = job.y2 - job.y1; + // render pixel shader function based on worker job spec + const [buf, time] = timedResult(() => + renderPixels( + (frag) => + shaderFunc( + // frag coord + frag, + // image size + [job.width, job.height], + // camera / eye pos + [ + Math.cos(job.time) * 2.5, + Math.cos(job.time / 2) * 0.7, + Math.sin(job.time) * 2.5, + ], + // light dir + [0.707, 0.707, 0], + // worker color + COLORS[job.id] + ), + // pixel buffer + new Uint32Array(job.width * h), + // image size + job.width, + h, + // region + 0, + 0, + job.width, + h, + // buffer XY offset in image + 0, + job.y1, + // image height + job.height + ) + ); + // submit result + $self.postMessage({ buf, stats: stats(time) }, [buf.buffer]); }); diff --git a/examples/shader-ast-workers/tsconfig.json b/examples/shader-ast-workers/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/shader-ast-workers/tsconfig.json +++ b/examples/shader-ast-workers/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/shader-graph/package.json b/examples/shader-graph/package.json index c0db67bfaf..7d8f00d228 100644 --- a/examples/shader-graph/package.json +++ b/examples/shader-graph/package.json @@ -1,48 +1,48 @@ { - "name": "@example/shader-graph", - "private": true, - "version": "0.0.1", - "description": "Minimal shader graph developed during livestream #2", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/expose": "workspace:^", - "@thi.ng/matrices": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/rstream-gestures": "workspace:^", - "@thi.ng/scenegraph": "workspace:^", - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/shader-ast-glsl": "workspace:^", - "@thi.ng/shader-ast-stdlib": "workspace:^", - "@thi.ng/vectors": "workspace:^", - "@thi.ng/webgl": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "expose", - "matrices", - "rstream-gestures", - "scenegraph", - "shader-ast", - "vectors", - "webgl" - ], - "screenshot": "examples/shader-graph.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/shader-graph", + "private": true, + "version": "0.0.1", + "description": "Minimal shader graph developed during livestream #2", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/expose": "workspace:^", + "@thi.ng/matrices": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/rstream-gestures": "workspace:^", + "@thi.ng/scenegraph": "workspace:^", + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/shader-ast-glsl": "workspace:^", + "@thi.ng/shader-ast-stdlib": "workspace:^", + "@thi.ng/vectors": "workspace:^", + "@thi.ng/webgl": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "expose", + "matrices", + "rstream-gestures", + "scenegraph", + "shader-ast", + "vectors", + "webgl" + ], + "screenshot": "examples/shader-graph.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/shader-graph/src/api.ts b/examples/shader-graph/src/api.ts index 63e960fdf3..0d68f7a165 100644 --- a/examples/shader-graph/src/api.ts +++ b/examples/shader-graph/src/api.ts @@ -1,80 +1,80 @@ import type { Fn4 } from "@thi.ng/api"; import type { Node2D } from "@thi.ng/scenegraph"; import type { - FloatSym, - Func, - Sampler2DSym, - Sym, - Vec2Sym, - Vec4Sym, + FloatSym, + Func, + Sampler2DSym, + Sym, + Vec2Sym, + Vec4Sym, } from "@thi.ng/shader-ast"; import type { GLSLTarget } from "@thi.ng/shader-ast-glsl"; import type { ModelSpec, Texture } from "@thi.ng/webgl"; export interface AppCtx { - /** - * Main canvas width - */ - width: number; - /** - * Main canvas height - */ - height: number; - /** - * Offscreen texture size (square) - */ - texSize: number; - canvas: HTMLCanvasElement; - gl: WebGLRenderingContext; - /** - * Pre-compiled geometry for offscreen drawing - */ - opQuad: ModelSpec; - /** - * Pre-compiled geometry for drawing to main canvas - */ - mainQuad: ModelSpec; + /** + * Main canvas width + */ + width: number; + /** + * Main canvas height + */ + height: number; + /** + * Offscreen texture size (square) + */ + texSize: number; + canvas: HTMLCanvasElement; + gl: WebGLRenderingContext; + /** + * Pre-compiled geometry for offscreen drawing + */ + opQuad: ModelSpec; + /** + * Pre-compiled geometry for drawing to main canvas + */ + mainQuad: ModelSpec; } /** * Options / specification for shader node */ export interface OpSpec { - /** - * Shader function (will be transpiled to GLSL) - */ - main: OpShaderFn; - /** - * Additional custom uniforms - */ - unis: T; - /** - * Max. 4 texture inputs from other shader nodes - */ - inputs: Texture[]; - /** - * Scenegraph node for controlling where to draw in main canvas - */ - node: Node2D; + /** + * Shader function (will be transpiled to GLSL) + */ + main: OpShaderFn; + /** + * Additional custom uniforms + */ + unis: T; + /** + * Max. 4 texture inputs from other shader nodes + */ + inputs: Texture[]; + /** + * Scenegraph node for controlling where to draw in main canvas + */ + node: Node2D; } /** * Type alias for OpNode shaders */ export type OpShaderFn = Fn4< - GLSLTarget, - OpUniforms & UserUniformTypes, - { v_uv: Vec2Sym }, - { fragColor: Vec4Sym }, - (Sym | Func)[] + GLSLTarget, + OpUniforms & UserUniformTypes, + { v_uv: Vec2Sym }, + { fragColor: Vec4Sym }, + (Sym | Func)[] >; export interface OpUniforms { - u_in0: Sampler2DSym; - u_in1: Sampler2DSym; - u_in2: Sampler2DSym; - u_in3: Sampler2DSym; - u_time: FloatSym; + u_in0: Sampler2DSym; + u_in1: Sampler2DSym; + u_in2: Sampler2DSym; + u_in3: Sampler2DSym; + u_time: FloatSym; } type UType = "vec2" | "vec3" | "vec4" | "float"; @@ -82,5 +82,5 @@ type UType = "vec2" | "vec3" | "vec4" | "float"; export type UserUniforms = Record; export type UserUniformTypes = { - [k in keyof T]: Sym; + [k in keyof T]: Sym; }; diff --git a/examples/shader-graph/src/index.ts b/examples/shader-graph/src/index.ts index b8a898d551..f2d5eed7c5 100644 --- a/examples/shader-graph/src/index.ts +++ b/examples/shader-graph/src/index.ts @@ -29,67 +29,67 @@ import { OpNode } from "./opnode"; // setLogger(new ConsoleLogger("webgl", LogLevel.DEBUG)); const { canvas, gl } = glCanvas({ - width: 1280, - height: 720, - autoScale: false, - ext: ["WEBGL_draw_buffers"], - version: 1, - parent: document.body, + width: 1280, + height: 720, + autoScale: false, + ext: ["WEBGL_draw_buffers"], + version: 1, + parent: document.body, }); const CTX: AppCtx = { - canvas, - gl, - width: canvas.width, - height: canvas.height, - texSize: 256, + canvas, + gl, + width: canvas.width, + height: canvas.height, + texSize: 256, - // geometry for offscreen rendering (shader nodes) - opQuad: compileModel(gl, defQuadModel()), + // geometry for offscreen rendering (shader nodes) + opQuad: compileModel(gl, defQuadModel()), - // geometry + shader for drawing to main window - mainQuad: { - ...compileModel(gl, defQuadModel({ size: 1 })), - shader: defShader(gl, { - // vertex shader - // (will be transpiled to GLSL) - vs: (gl, unis, ins, outs) => [ - defMain(() => [ - assign(outs.v_uv, ins.uv), - assign( - gl.gl_Position, - mul( - unis.proj, - mul(unis.model, vec4(ins.position, 0, 1)) - ) - ), - ]), - ], - // fragment shader (same as in FX_SHADER_SPEC_UV in webgl pkg) - // (will be transpiled to GLSL) - fs: (_, unis, ins, outs) => [ - defMain(() => [ - assign(outs.fragColor, texture(unis.tex, ins.v_uv)), - ]), - ], - attribs: { - position: "vec2", - uv: "vec2", - }, - varying: { - v_uv: "vec2", - }, - uniforms: { - tex: "sampler2D", - model: "mat4", - // 2D projection matrix - proj: [ - "mat4", - ortho([], 0, canvas.width, canvas.height, 0, -1, 1), - ], - }, - }), - }, + // geometry + shader for drawing to main window + mainQuad: { + ...compileModel(gl, defQuadModel({ size: 1 })), + shader: defShader(gl, { + // vertex shader + // (will be transpiled to GLSL) + vs: (gl, unis, ins, outs) => [ + defMain(() => [ + assign(outs.v_uv, ins.uv), + assign( + gl.gl_Position, + mul( + unis.proj, + mul(unis.model, vec4(ins.position, 0, 1)) + ) + ), + ]), + ], + // fragment shader (same as in FX_SHADER_SPEC_UV in webgl pkg) + // (will be transpiled to GLSL) + fs: (_, unis, ins, outs) => [ + defMain(() => [ + assign(outs.fragColor, texture(unis.tex, ins.v_uv)), + ]), + ], + attribs: { + position: "vec2", + uv: "vec2", + }, + varying: { + v_uv: "vec2", + }, + uniforms: { + tex: "sampler2D", + model: "mat4", + // 2D projection matrix + proj: [ + "mat4", + ortho([], 0, canvas.width, canvas.height, 0, -1, 1), + ], + }, + }), + }, }; // scenegraph root node (no spatial transformation, purely used as reference frame) @@ -101,195 +101,195 @@ const CONTENT = new Node2D("content", ROOT, [CTX.width / 2, CTX.height / 2]); // scenegraph node for centered quad of unit size class QuadNode extends Node2D { - constructor( - id: string, - parent?: Nullable, - translate: Vec = [0, 0], - rotate = 0, - scale: Vec | number = 1 - ) { - super(id, parent, translate, rotate, scale); - } + constructor( + id: string, + parent?: Nullable, + translate: Vec = [0, 0], + rotate = 0, + scale: Vec | number = 1 + ) { + super(id, parent, translate, rotate, scale); + } - // hit test impl - containsLocalPoint([x, y]: ReadonlyVec) { - return x >= -0.5 && x <= 0.5 && y >= -0.5 && y <= 0.5; - } + // hit test impl + containsLocalPoint([x, y]: ReadonlyVec) { + return x >= -0.5 && x <= 0.5 && y >= -0.5 && y <= 0.5; + } } // ring / diamond morph pattern shader node const op1 = new OpNode(CTX, { - // shader function - // (will be transpiled to GLSL) - main: (_, unis, ins, outs) => [ - defMain(() => [ - assign( - outs.fragColor, - vec4( - vec3( - // map -1 .. +1 => 0 .. 1 interval - fit1101( - sin( - madd( - mix( - // circular (eucledian) distance - distance(ins.v_uv, unis.center), - // diamond (manhattan) distance - distManhattan2(ins.v_uv, unis.center), - // morph factor - fit1101(sin(mul(unis.u_time, 0.01))) - ), - // frequency scale (number of rings) - float(unis.rings), - // phase shift (animation) - mul(unis.u_time, unis.speed) - ) - ) - ) - ), - 1 - ) - ), - ]), - ], - // will be exposed as user controllable parameters - unis: { - center: ["vec2", [0.5, 0.5]], - rings: ["float", 16], - speed: ["float", -0.1], - }, - // texture inputs from other shader nodes - inputs: [], - // scene graph node for drawing in main canvas - node: new QuadNode("op1", CONTENT, [-264, 0], 0, CTX.texSize), + // shader function + // (will be transpiled to GLSL) + main: (_, unis, ins, outs) => [ + defMain(() => [ + assign( + outs.fragColor, + vec4( + vec3( + // map -1 .. +1 => 0 .. 1 interval + fit1101( + sin( + madd( + mix( + // circular (eucledian) distance + distance(ins.v_uv, unis.center), + // diamond (manhattan) distance + distManhattan2(ins.v_uv, unis.center), + // morph factor + fit1101(sin(mul(unis.u_time, 0.01))) + ), + // frequency scale (number of rings) + float(unis.rings), + // phase shift (animation) + mul(unis.u_time, unis.speed) + ) + ) + ) + ), + 1 + ) + ), + ]), + ], + // will be exposed as user controllable parameters + unis: { + center: ["vec2", [0.5, 0.5]], + rings: ["float", 16], + speed: ["float", -0.1], + }, + // texture inputs from other shader nodes + inputs: [], + // scene graph node for drawing in main canvas + node: new QuadNode("op1", CONTENT, [-264, 0], 0, CTX.texSize), }); // chromatic aberration shader const op2 = new OpNode(CTX, { - main: (_, unis, ins, outs) => [ - defMain(() => [ - assign( - outs.fragColor, - vec4( - // read RGB color channels individually - // each with different offset vector - $x(texture(unis.u_in0, add(ins.v_uv, unis.shiftR))), - $x(texture(unis.u_in0, add(ins.v_uv, unis.shiftG))), - $x(texture(unis.u_in0, add(ins.v_uv, unis.shiftB))), - 1 - ) - ), - ]), - ], - unis: { - shiftR: ["vec2", [0.1, 0]], - shiftG: ["vec2", [0.05, 0]], - shiftB: ["vec2", [0.02, 0]], - }, - inputs: [op1.tex], - node: new QuadNode("op2", CONTENT, [0, 132], 0, CTX.texSize), + main: (_, unis, ins, outs) => [ + defMain(() => [ + assign( + outs.fragColor, + vec4( + // read RGB color channels individually + // each with different offset vector + $x(texture(unis.u_in0, add(ins.v_uv, unis.shiftR))), + $x(texture(unis.u_in0, add(ins.v_uv, unis.shiftG))), + $x(texture(unis.u_in0, add(ins.v_uv, unis.shiftB))), + 1 + ) + ), + ]), + ], + unis: { + shiftR: ["vec2", [0.1, 0]], + shiftG: ["vec2", [0.05, 0]], + shiftB: ["vec2", [0.02, 0]], + }, + inputs: [op1.tex], + node: new QuadNode("op2", CONTENT, [0, 132], 0, CTX.texSize), }); // noise shader node const op3 = new OpNode(CTX, { - main: (_, unis, ins, outs) => [ - defMain(() => [ - assign( - outs.fragColor, - vec4( - vec3( - fit1101( - // functional programming / composition for shaders - // additive is a HOF to calculate multi-octave noise - // with configurable behavior - additive("vec3", snoise3, 2)( - vec3(ins.v_uv, mul(unis.u_time, 0.005)), - vec3(2), - float(1) - ) - ) - ), - 1 - ) - ), - ]), - ], - unis: {}, - inputs: [], - node: new QuadNode("op3", CONTENT, [0, -132], 0, CTX.texSize), + main: (_, unis, ins, outs) => [ + defMain(() => [ + assign( + outs.fragColor, + vec4( + vec3( + fit1101( + // functional programming / composition for shaders + // additive is a HOF to calculate multi-octave noise + // with configurable behavior + additive("vec3", snoise3, 2)( + vec3(ins.v_uv, mul(unis.u_time, 0.005)), + vec3(2), + float(1) + ) + ) + ), + 1 + ) + ), + ]), + ], + unis: {}, + inputs: [], + node: new QuadNode("op3", CONTENT, [0, -132], 0, CTX.texSize), }); // displacement shader node // uses value from 2nd texture as displacement factor to manipulate // lookup position in 1st texture const op4 = new OpNode(CTX, { - main: (_, unis, ins, outs) => [ - defMain(() => [ - assign( - outs.fragColor, - texture( - unis.u_in0, - mul(ins.v_uv, $x(texture(unis.u_in1, ins.v_uv))) - ) - ), - ]), - ], - unis: {}, - inputs: [op2.tex, op3.tex], - node: new QuadNode("op4", CONTENT, [264, 0], 0, CTX.texSize), + main: (_, unis, ins, outs) => [ + defMain(() => [ + assign( + outs.fragColor, + texture( + unis.u_in0, + mul(ins.v_uv, $x(texture(unis.u_in1, ins.v_uv))) + ) + ), + ]), + ], + unis: {}, + inputs: [op2.tex, op3.tex], + node: new QuadNode("op4", CONTENT, [264, 0], 0, CTX.texSize), }); // update loop fromRAF().subscribe({ - next(t) { - // update all shader nodes, render to their FBOs - op1.update(t); - op2.update(t); - op3.update(t); - op4.update(t); + next(t) { + // update all shader nodes, render to their FBOs + op1.update(t); + op2.update(t); + op3.update(t); + op4.update(t); - // then draw all in main canvas - gl.viewport(0, 0, CTX.width, CTX.height); - gl.clearColor(0, 0, 0, 1); - gl.clear(gl.COLOR_BUFFER_BIT); - op1.draw(); - op2.draw(); - op3.draw(); - op4.draw(); - }, + // then draw all in main canvas + gl.viewport(0, 0, CTX.width, CTX.height); + gl.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + op1.draw(); + op2.draw(); + op3.draw(); + op4.draw(); + }, }); // mouse event handling gestureStream(CTX.canvas, { minZoom: 0.1, maxZoom: 4, smooth: 0.05 }).subscribe( - { - next(e) { - switch (e.type) { - case "start": - const info = ROOT.childForPoint(e.pos); - this.sel = info ? info.node : CONTENT; - this.startTheta = this.sel.rotate; - this.startPos = this.sel.parent.mapLocalPointToNode( - ROOT, - copy(this.sel.translate) - ); - break; - case "drag": - if (e.buttons == 2) { - this.sel.rotate = - this.startTheta + e.active[0].delta![0] * 0.01; - } else { - const pos = add2([], this.startPos, e.active[0].delta!); - this.sel.translate = - this.sel.parent.mapGlobalPoint(pos); - } - CONTENT.update(); - break; - case "zoom": - CONTENT.scale = e.zoom; - CONTENT.update(); - } - }, - } + { + next(e) { + switch (e.type) { + case "start": + const info = ROOT.childForPoint(e.pos); + this.sel = info ? info.node : CONTENT; + this.startTheta = this.sel.rotate; + this.startPos = this.sel.parent.mapLocalPointToNode( + ROOT, + copy(this.sel.translate) + ); + break; + case "drag": + if (e.buttons == 2) { + this.sel.rotate = + this.startTheta + e.active[0].delta![0] * 0.01; + } else { + const pos = add2([], this.startPos, e.active[0].delta!); + this.sel.translate = + this.sel.parent.mapGlobalPoint(pos); + } + CONTENT.update(); + break; + case "zoom": + CONTENT.scale = e.zoom; + CONTENT.update(); + } + }, + } ); // expose shader nodes in devtools / console diff --git a/examples/shader-graph/src/opnode.ts b/examples/shader-graph/src/opnode.ts index f1cede1bb5..cdd33677b0 100644 --- a/examples/shader-graph/src/opnode.ts +++ b/examples/shader-graph/src/opnode.ts @@ -10,86 +10,86 @@ import { defTexture, Texture } from "@thi.ng/webgl/texture"; import type { AppCtx, OpSpec, UserUniforms } from "./api"; export class OpNode { - tex: Texture; - fbo: FBO; - shader: Shader; - params: IObjectOf; + tex: Texture; + fbo: FBO; + shader: Shader; + params: IObjectOf; - updateSpec: ModelSpec; - drawSpec: ModelSpec; + updateSpec: ModelSpec; + drawSpec: ModelSpec; - constructor(public ctx: AppCtx, public spec: OpSpec) { - // create texture as render target - this.tex = defTexture(ctx.gl, { - width: ctx.texSize, - height: ctx.texSize, - filter: TextureFilter.LINEAR, - image: null, - }); + constructor(public ctx: AppCtx, public spec: OpSpec) { + // create texture as render target + this.tex = defTexture(ctx.gl, { + width: ctx.texSize, + height: ctx.texSize, + filter: TextureFilter.LINEAR, + image: null, + }); - // wrap texture in frame buffer object - this.fbo = defFBO(ctx.gl, { tex: [this.tex] }); + // wrap texture in frame buffer object + this.fbo = defFBO(ctx.gl, { tex: [this.tex] }); - // compile shader, incl. user provided fragment shader parts - this.shader = defShader(ctx.gl, { - ...FX_SHADER_SPEC_UV, - fs: spec.main, - uniforms: { - u_in0: ["sampler2D", 0], - u_in1: ["sampler2D", 1], - u_in2: ["sampler2D", 2], - u_in3: ["sampler2D", 3], - u_time: ["float", 0], - ...spec.unis, - }, - }); + // compile shader, incl. user provided fragment shader parts + this.shader = defShader(ctx.gl, { + ...FX_SHADER_SPEC_UV, + fs: spec.main, + uniforms: { + u_in0: ["sampler2D", 0], + u_in1: ["sampler2D", 1], + u_in2: ["sampler2D", 2], + u_in3: ["sampler2D", 3], + u_time: ["float", 0], + ...spec.unis, + }, + }); - // expose uniforms as plain JS object - this.params = Object.entries(spec.unis).reduce((acc, [id, val]) => { - acc[id] = val[1]; - return acc; - }, {}); + // expose uniforms as plain JS object + this.params = Object.entries(spec.unis).reduce((acc, [id, val]) => { + acc[id] = val[1]; + return acc; + }, {}); - // define stub ModelSpec's for drawing - // re-use pre-defined geometries defined in AppCtx - this.updateSpec = { - ...this.ctx.opQuad, - shader: this.shader, - textures: this.spec.inputs, - uniforms: { u_time: 0 }, - }; - this.drawSpec = { - ...ctx.mainQuad, - textures: [this.tex], - uniforms: { - model: mat23to44([], this.spec.node.mat), - }, - }; - } + // define stub ModelSpec's for drawing + // re-use pre-defined geometries defined in AppCtx + this.updateSpec = { + ...this.ctx.opQuad, + shader: this.shader, + textures: this.spec.inputs, + uniforms: { u_time: 0 }, + }; + this.drawSpec = { + ...ctx.mainQuad, + textures: [this.tex], + uniforms: { + model: mat23to44([], this.spec.node.mat), + }, + }; + } - /** - * Takes time value (in frames) and renders shader to offscreen texture. - * - * @param time - - */ - update(time: number) { - const unis = this.updateSpec.uniforms!; - unis.u_time = time; - Object.assign(unis, this.params); - this.fbo.bind(); - this.ctx.gl.viewport(0, 0, this.ctx.texSize, this.ctx.texSize); - draw(this.updateSpec); - this.fbo.unbind(); - } + /** + * Takes time value (in frames) and renders shader to offscreen texture. + * + * @param time - + */ + update(time: number) { + const unis = this.updateSpec.uniforms!; + unis.u_time = time; + Object.assign(unis, this.params); + this.fbo.bind(); + this.ctx.gl.viewport(0, 0, this.ctx.texSize, this.ctx.texSize); + draw(this.updateSpec); + this.fbo.unbind(); + } - /** - * Draws texture into main canvas at position, size & rotation defined by - * associated scenegraph node - */ - draw() { - this.drawSpec.uniforms!.model = ( - mat23to44([], this.spec.node.mat) - ); - draw(this.drawSpec); - } + /** + * Draws texture into main canvas at position, size & rotation defined by + * associated scenegraph node + */ + draw() { + this.drawSpec.uniforms!.model = ( + mat23to44([], this.spec.node.mat) + ); + draw(this.drawSpec); + } } diff --git a/examples/shader-graph/tsconfig.json b/examples/shader-graph/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/shader-graph/tsconfig.json +++ b/examples/shader-graph/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/soa-ecs/package.json b/examples/soa-ecs/package.json index 22ffc0cc94..bbf0a09353 100644 --- a/examples/soa-ecs/package.json +++ b/examples/soa-ecs/package.json @@ -1,48 +1,48 @@ { - "name": "@example/soa-ecs", - "private": true, - "description": "Entity Component System w/ 100k 3D particles", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/adapt-dpi": "workspace:^", - "@thi.ng/ecs": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/matrices": "workspace:^", - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/vectors": "workspace:^", - "@thi.ng/webgl": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "adapt-dpi", - "ecs", - "hdom", - "matrices", - "soa", - "shader-ast", - "shader-ast-glsl", - "vectors", - "webgl" - ], - "screenshot": "examples/soa-ecs-100k.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/soa-ecs", + "private": true, + "description": "Entity Component System w/ 100k 3D particles", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/adapt-dpi": "workspace:^", + "@thi.ng/ecs": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/matrices": "workspace:^", + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/vectors": "workspace:^", + "@thi.ng/webgl": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "adapt-dpi", + "ecs", + "hdom", + "matrices", + "soa", + "shader-ast", + "shader-ast-glsl", + "vectors", + "webgl" + ], + "screenshot": "examples/soa-ecs-100k.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/soa-ecs/src/index.ts b/examples/soa-ecs/src/index.ts index 470a557ddd..6e4e5d49f6 100644 --- a/examples/soa-ecs/src/index.ts +++ b/examples/soa-ecs/src/index.ts @@ -46,29 +46,29 @@ const COLOR2 = [0.1, 0.9, 1, 0.001]; // component type mapping used to configure ECS and all of its derived types interface CompSpecs { - pos: Float32Array; - vel: Float32Array; + pos: Float32Array; + vel: Float32Array; } const ecs = new ECS({ capacity: NUM }); const pos = ecs.defComponent({ - id: "pos", - type: "f32", - size: 2, + id: "pos", + type: "f32", + size: 2, })!; const vel = ecs.defComponent({ - id: "vel", - type: "f32", - size: 2, - default: () => randNormS2([0, 0]), + id: "vel", + type: "f32", + size: 2, + default: () => randNormS2([0, 0]), })!; const group = ecs.defGroup([pos, vel]); for (let i = 0; i < NUM; i++) { - ecs.defEntity([pos, vel]); + ecs.defEntity([pos, vel]); } const dir = [0, 0]; @@ -77,140 +77,140 @@ const dir = [0, 0]; // uses strided vector ops to update the flat component buffers // on my MBP2015 this is about 1.5 - 2x faster const updateBatch = ( - info: GroupInfo, - num: number, - t: number, - amp: number + info: GroupInfo, + num: number, + t: number, + amp: number ) => { - const { values: pos, stride: ps } = info.pos; - const { values: vel, stride: vs } = info.vel; - const invNum = 1 / num; - for (let i = 0; i < num; i++) { - const ip = i * ps; - const iv = i * vs; - const m = magSqS2(pos, ip); - rotateS2(pos, pos, m * amp, ip, ip); - if (m < 4e4) { - mixNS2(vel, vel, dir, 0.01 + 0.2 * fract((i + t) * invNum), iv, iv); - normalizeS2(vel, vel, 1, iv, iv); - } else { - mulNS2(pos, pos, 0.98, ip, ip); - randNormS2(vel, 1, undefined, iv); - } - addS2(pos, pos, vel, ip, ip, iv); - } + const { values: pos, stride: ps } = info.pos; + const { values: vel, stride: vs } = info.vel; + const invNum = 1 / num; + for (let i = 0; i < num; i++) { + const ip = i * ps; + const iv = i * vs; + const m = magSqS2(pos, ip); + rotateS2(pos, pos, m * amp, ip, ip); + if (m < 4e4) { + mixNS2(vel, vel, dir, 0.01 + 0.2 * fract((i + t) * invNum), iv, iv); + normalizeS2(vel, vel, 1, iv, iv); + } else { + mulNS2(pos, pos, 0.98, ip, ip); + randNormS2(vel, 1, undefined, iv); + } + addS2(pos, pos, vel, ip, ip, iv); + } }; const updateSingle = ( - { pos, vel }: GroupTuple, - i: number, - t: number, - amp: number + { pos, vel }: GroupTuple, + i: number, + t: number, + amp: number ) => { - const m = magSq2(pos); - rotate(pos, pos, m * amp); - if (m < 4e4) { - normalize(vel, mixN2(vel, vel, dir, 0.01 + 0.2 * fract((i + t) / NUM))); - } else { - mulN2(pos, pos, 0.98); - randNorm(vel); - } - add2(pos, pos, vel); + const m = magSq2(pos); + rotate(pos, pos, m * amp); + if (m < 4e4) { + normalize(vel, mixN2(vel, vel, dir, 0.01 + 0.2 * fract((i + t) / NUM))); + } else { + mulN2(pos, pos, 0.98); + randNorm(vel); + } + add2(pos, pos, vel); }; const pointShader: ShaderSpec = { - vs: (gl, unis, ins, outs) => [ - defMain(() => [ - assign( - outs.vcol, - mix( - unis.color, - unis.color2, - mul(0.006, $y(ins.position)) - ) - ), - assign( - gl.gl_Position, - mul(unis.proj, vec4(ins.position, 0, 1)) - ), - assign(gl.gl_PointSize, float(2)), - ]), - ], - fs: (gl, unis, ins, outs) => [ - defMain(() => [assign(outs.fragColor, ins.vcol)]), - ], - attribs: { - position: "vec2", - }, - varying: { - vcol: "vec4", - }, - uniforms: { - proj: "mat4", - color: "vec4", - color2: "vec4", - }, - state: { - depth: false, - blend: true, - blendFn: BLEND_ADD, - }, + vs: (gl, unis, ins, outs) => [ + defMain(() => [ + assign( + outs.vcol, + mix( + unis.color, + unis.color2, + mul(0.006, $y(ins.position)) + ) + ), + assign( + gl.gl_Position, + mul(unis.proj, vec4(ins.position, 0, 1)) + ), + assign(gl.gl_PointSize, float(2)), + ]), + ], + fs: (gl, unis, ins, outs) => [ + defMain(() => [assign(outs.fragColor, ins.vcol)]), + ], + attribs: { + position: "vec2", + }, + varying: { + vcol: "vec4", + }, + uniforms: { + proj: "mat4", + color: "vec4", + color2: "vec4", + }, + state: { + depth: false, + blend: true, + blendFn: BLEND_ADD, + }, }; const app = () => { - let model: ModelSpec; - let targetDir = [0, 0]; - const canvas = canvasWebGL({ - init: (_, gl) => { - model = compileModel(gl, { - attribs: { - position: { - data: pos.packedValues(), - size: 2, - }, - }, - shader: defShader(gl, pointShader), - uniforms: { - proj: ortho([], -W2, W2, -W2, W2, 0, 1), - color: COLOR, - color2: COLOR2, - }, - num: NUM, - mode: DrawMode.POINTS, - }); - }, - update: (el, gl, __, time) => { - // nothing to be done in first frame - if (!model) { - adaptDPI(el, W, W); - return; - } - time *= 0.001; - mixN2(dir, dir, randNormS2(targetDir), 0.1); - - // animate particles and update WebGL buffer - BATCH_UPDATE - ? group.run(updateBatch, time, Math.sin(time / 8) * 4e-7) - : group.forEach(updateSingle, time, Math.sin(time / 8) * 4e-7); - model.attribs.position.buffer!.set(model.attribs.position.data!); - - const alpha = Math.pow(Math.min(time / 5, 1), 3) * ALPHA; - if (alpha < 1) { - const col1 = model.uniforms!.color; - const col2 = model.uniforms!.color2; - setVN4(col1, col1, alpha); - setVN4(col2, col2, alpha); - } - - gl.clearColor(0, 0, 0, 1); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - draw(model); - }, - }); - return () => [ - "div.bg-black.vh-100.flex.flex-column.items-center.justify-center", - [canvas, { width: W, height: W }], - ]; + let model: ModelSpec; + let targetDir = [0, 0]; + const canvas = canvasWebGL({ + init: (_, gl) => { + model = compileModel(gl, { + attribs: { + position: { + data: pos.packedValues(), + size: 2, + }, + }, + shader: defShader(gl, pointShader), + uniforms: { + proj: ortho([], -W2, W2, -W2, W2, 0, 1), + color: COLOR, + color2: COLOR2, + }, + num: NUM, + mode: DrawMode.POINTS, + }); + }, + update: (el, gl, __, time) => { + // nothing to be done in first frame + if (!model) { + adaptDPI(el, W, W); + return; + } + time *= 0.001; + mixN2(dir, dir, randNormS2(targetDir), 0.1); + + // animate particles and update WebGL buffer + BATCH_UPDATE + ? group.run(updateBatch, time, Math.sin(time / 8) * 4e-7) + : group.forEach(updateSingle, time, Math.sin(time / 8) * 4e-7); + model.attribs.position.buffer!.set(model.attribs.position.data!); + + const alpha = Math.pow(Math.min(time / 5, 1), 3) * ALPHA; + if (alpha < 1) { + const col1 = model.uniforms!.color; + const col2 = model.uniforms!.color2; + setVN4(col1, col1, alpha); + setVN4(col2, col2, alpha); + } + + gl.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + draw(model); + }, + }); + return () => [ + "div.bg-black.vh-100.flex.flex-column.items-center.justify-center", + [canvas, { width: W, height: W }], + ]; }; start(app()); diff --git a/examples/soa-ecs/tsconfig.json b/examples/soa-ecs/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/soa-ecs/tsconfig.json +++ b/examples/soa-ecs/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/spline-tangent/package.json b/examples/spline-tangent/package.json index 8ad40873a0..69e1dd9f7c 100644 --- a/examples/spline-tangent/package.json +++ b/examples/spline-tangent/package.json @@ -1,39 +1,39 @@ { - "name": "@example/spline-tangent", - "private": true, - "version": "0.0.1", - "description": "Compute cubic spline position & tangent using Dual Numbers", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/dual-algebra": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "dual-algebra", - "geom", - "vectors" - ], - "screenshot": "examples/spline-tangent.png" - } + "name": "@example/spline-tangent", + "private": true, + "version": "0.0.1", + "description": "Compute cubic spline position & tangent using Dual Numbers", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/dual-algebra": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "dual-algebra", + "geom", + "vectors" + ], + "screenshot": "examples/spline-tangent.png" + } } diff --git a/examples/spline-tangent/src/index.ts b/examples/spline-tangent/src/index.ts index ec5a22140e..36246c2af3 100644 --- a/examples/spline-tangent/src/index.ts +++ b/examples/spline-tangent/src/index.ts @@ -18,27 +18,27 @@ import { sub2 } from "@thi.ng/vectors/sub"; * numbers, i.e. it computes (jointly) both the position **AND** the first * derivative (aka tangent) at this point. * - * @param a - - * @param b - - * @param c - - * @param d - - * @param t - + * @param a - + * @param b - + * @param c - + * @param d - + * @param t - */ const splinePosAndTangent: FnU5 = (a, b, c, d, _t) => { - const t = $(_t, 1); // dual variable - const s = sub($(1), t); // dual variable (1 - t) - const s2 = mul(s, s); // squared - const t2 = mul(t, t); // ... - // dot product of 2 dual number vectors... - // the real part of the result contains the position - // the dual part the tangent - return dot( - // vector of coordinates (as dual numbers) - [$(a), $(b), $(c), $(d)], - // Bernstein spline coefficients (also dual numbers) - // see: https://en.wikipedia.org/wiki/Bernstein_polynomial - [mul(s, s2), mul(mul(s2, t), $(3)), mul(mul(t2, s), $(3)), mul(t, t2)] - ); + const t = $(_t, 1); // dual variable + const s = sub($(1), t); // dual variable (1 - t) + const s2 = mul(s, s); // squared + const t2 = mul(t, t); // ... + // dot product of 2 dual number vectors... + // the real part of the result contains the position + // the dual part the tangent + return dot( + // vector of coordinates (as dual numbers) + [$(a), $(b), $(c), $(d)], + // Bernstein spline coefficients (also dual numbers) + // see: https://en.wikipedia.org/wiki/Bernstein_polynomial + [mul(s, s2), mul(mul(s2, t), $(3)), mul(mul(t2, s), $(3)), mul(t, t2)] + ); }; // curve definition @@ -53,32 +53,32 @@ let t = 0; const root = document.getElementById("app")!; setInterval(() => { - t = fract(t + 0.001); + t = fract(t + 0.001); - const [x, dx] = splinePosAndTangent(a[0], b[0], c[0], d[0], t); - const [y, dy] = splinePosAndTangent(a[1], b[1], c[1], d[1], t); + const [x, dx] = splinePosAndTangent(a[0], b[0], c[0], d[0], t); + const [y, dy] = splinePosAndTangent(a[1], b[1], c[1], d[1], t); - const pos = [x, y]; - const dir = normalize(null, [dx, dy], 20); + const pos = [x, y]; + const dir = normalize(null, [dx, dy], 20); - root.innerHTML = asSvg( - svgDoc( - { - width: 600, - height: 600, - viewBox: "0 0 100 100", - fill: "none", - "stroke-width": 0.2, - }, - curve, - circle(pos, 1, { stroke: "blue" }), - line(sub2([], pos, dir), add2([], pos, dir), { - stroke: "blue", - }), - text([5, 95], vector(2)(dir), { - fill: "black", - "font-size": "2px", - }) - ) - ); + root.innerHTML = asSvg( + svgDoc( + { + width: 600, + height: 600, + viewBox: "0 0 100 100", + fill: "none", + "stroke-width": 0.2, + }, + curve, + circle(pos, 1, { stroke: "blue" }), + line(sub2([], pos, dir), add2([], pos, dir), { + stroke: "blue", + }), + text([5, 95], vector(2)(dir), { + fill: "black", + "font-size": "2px", + }) + ) + ); }, 16); diff --git a/examples/spline-tangent/tsconfig.json b/examples/spline-tangent/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/spline-tangent/tsconfig.json +++ b/examples/spline-tangent/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/stratified-grid/package.json b/examples/stratified-grid/package.json index dff49e970c..08cfec6536 100644 --- a/examples/stratified-grid/package.json +++ b/examples/stratified-grid/package.json @@ -1,37 +1,37 @@ { - "name": "@example/stratified-grid", - "private": true, - "version": "0.0.1", - "description": "2D Stratified grid sampling example", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/geom": "workspace:^", - "@thi.ng/geom-accel": "workspace:^", - "@thi.ng/poisson": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom-accel", - "poisson" - ], - "screenshot": "poisson/stratified-grid.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/stratified-grid", + "private": true, + "version": "0.0.1", + "description": "2D Stratified grid sampling example", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/geom": "workspace:^", + "@thi.ng/geom-accel": "workspace:^", + "@thi.ng/poisson": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom-accel", + "poisson" + ], + "screenshot": "poisson/stratified-grid.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/stratified-grid/src/index.ts b/examples/stratified-grid/src/index.ts index fd360d8efd..f64d14bd9e 100644 --- a/examples/stratified-grid/src/index.ts +++ b/examples/stratified-grid/src/index.ts @@ -9,21 +9,21 @@ const index = new KdTreeSet(2); index.into(stratifiedGrid({ dim: [50, 50], samples: 1 })); document.body.innerHTML = asSvg( - svgDoc( - { - width: 600, - height: 600, - fill: "none", - stroke: "blue", - "stroke-width": 0.1, - }, - ...map( - (p) => - circle( - p, - dist(p, index.queryKeys(p, 2 * Math.SQRT2, 2)[1]) / 2 - ), - index.keys() - ) - ) + svgDoc( + { + width: 600, + height: 600, + fill: "none", + stroke: "blue", + "stroke-width": 0.1, + }, + ...map( + (p) => + circle( + p, + dist(p, index.queryKeys(p, 2 * Math.SQRT2, 2)[1]) / 2 + ), + index.keys() + ) + ) ); diff --git a/examples/stratified-grid/tsconfig.json b/examples/stratified-grid/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/stratified-grid/tsconfig.json +++ b/examples/stratified-grid/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/svg-barchart/package.json b/examples/svg-barchart/package.json index 60b6293185..63cef6a5ed 100644 --- a/examples/svg-barchart/package.json +++ b/examples/svg-barchart/package.json @@ -1,32 +1,32 @@ { - "name": "@example/svg-barchart", - "private": true, - "description": "Simplistic SVG bar chart component", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true, - "screenshot": "examples/svg-barchart.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/svg-barchart", + "private": true, + "description": "Simplistic SVG bar chart component", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true, + "screenshot": "examples/svg-barchart.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/svg-barchart/src/index.ts b/examples/svg-barchart/src/index.ts index 986f3c3866..a49f3a1e92 100644 --- a/examples/svg-barchart/src/index.ts +++ b/examples/svg-barchart/src/index.ts @@ -6,108 +6,108 @@ import { range } from "@thi.ng/transducers/range"; // iterator of range mapped tuples: `[mapped, orig]` const mappedRange = ( - from: number, - to: number, - step: number, - start: number, - end: number + from: number, + to: number, + step: number, + start: number, + end: number ) => map((n) => [fit(n, from, to, start, end), n], range(from, to, step)); // syntax sugar to create SVG line const line = (x1: number, y1: number, x2: number, y2: number) => [ - "line", - { x1, y1, x2, y2 }, + "line", + { x1, y1, x2, y2 }, ]; // reusuable axis tick & label combo const tick = ( - x1: number, - y1: number, - x2: number, - y2: number, - tx: number, - ty: number, - label: string + x1: number, + y1: number, + x2: number, + y2: number, + tx: number, + ty: number, + label: string ) => [line(x1, y1, x2, y2), ["text", { x: tx, y: ty, stroke: "none" }, label]]; // mapping fn for x-axis ticks const tickX = - (y: number) => - ([x, n]: any) => - tick(x, y, x, y + 10, x, y + 20, n); + (y: number) => + ([x, n]: any) => + tick(x, y, x, y + 10, x, y + 20, n); // mapping fn for y-axis ticks const tickY = - (x: number) => - ([y, n]: any) => - tick(x - 10, y, x, y, x - 15, y, n); + (x: number) => + ([y, n]: any) => + tick(x - 10, y, x, y, x - 15, y, n); // x-axis with ticks as SVG group const axisX = ({ axis: a, domain: d, range: r }: any) => [ - "g", - { "text-anchor": "middle" }, - line(a[0], a[2], a[1], a[2]), - mapcat(tickX(a[2]), mappedRange(d[0], d[1], d[2], r[0], r[1])), + "g", + { "text-anchor": "middle" }, + line(a[0], a[2], a[1], a[2]), + mapcat(tickX(a[2]), mappedRange(d[0], d[1], d[2], r[0], r[1])), ]; // y-axis with ticks as SVG group const axisY = ({ axis: a, domain: d, range: r }: any) => [ - "g", - { "text-anchor": "end" }, - line(a[2], a[0], a[2], a[1]), - mapcat(tickY(a[2]), mappedRange(d[0], d[1], d[2], r[0], r[1])), + "g", + { "text-anchor": "end" }, + line(a[2], a[0], a[2], a[1]), + mapcat(tickY(a[2]), mappedRange(d[0], d[1], d[2], r[0], r[1])), ]; // mapping fn to create a single bar from `[domainPos, value]` const bar = - ({ domain: xd, range: xr }: any, { domain: yd, range: yr }: any) => - ([xx, yy]: number[]) => { - const y = fit(yy, yd[0], yd[1], yr[0], yr[1]); - return [ - "rect", - { - x: fit(xx, xd[0], xd[1], xr[0], xr[1]) - 5, - y, - width: 10, - height: yr[0] - y, - }, - ]; - }; + ({ domain: xd, range: xr }: any, { domain: yd, range: yr }: any) => + ([xx, yy]: number[]) => { + const y = fit(yy, yd[0], yd[1], yr[0], yr[1]); + return [ + "rect", + { + x: fit(xx, xd[0], xd[1], xr[0], xr[1]) - 5, + y, + width: 10, + height: yr[0] - y, + }, + ]; + }; // complete bar chart component const barChart = (_: any, opts: any, values: any) => [ - "svg", - opts.attribs, - ["g", { stroke: opts.axis, fill: opts.axis }, axisX(opts.x), axisY(opts.y)], - ["g", { fill: opts.fill }, map(bar(opts.x, opts.y), values)], + "svg", + opts.attribs, + ["g", { stroke: opts.axis, fill: opts.axis }, axisX(opts.x), axisY(opts.y)], + ["g", { fill: opts.fill }, map(bar(opts.x, opts.y), values)], ]; // one-off DOM creation renderOnce([ - "div.ma2.sans-serif", - ["h1", "Bar chart example"], - [ - barChart, - { - attribs: { - width: 500, - height: 200, - "font-size": "10px", - "font-family": "Menlo, sans-serif", - }, - x: { - axis: [40, 490, 170], - domain: [1980, 2021, 10], - range: [60, 480], - }, - y: { - axis: [170, 10, 40], - domain: [0, 101, 25], - range: [160, 20], - }, - axis: "#666", - fill: "#0cc", - }, - map((year) => [year, Math.random() * 100], range(1980, 2020, 2)), - ], + "div.ma2.sans-serif", + ["h1", "Bar chart example"], + [ + barChart, + { + attribs: { + width: 500, + height: 200, + "font-size": "10px", + "font-family": "Menlo, sans-serif", + }, + x: { + axis: [40, 490, 170], + domain: [1980, 2021, 10], + range: [60, 480], + }, + y: { + axis: [170, 10, 40], + domain: [0, 101, 25], + range: [160, 20], + }, + axis: "#666", + fill: "#0cc", + }, + map((year) => [year, Math.random() * 100], range(1980, 2020, 2)), + ], ]); diff --git a/examples/svg-barchart/tsconfig.json b/examples/svg-barchart/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/svg-barchart/tsconfig.json +++ b/examples/svg-barchart/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/svg-particles/package.json b/examples/svg-particles/package.json index 2a3eb9113f..4064c63517 100644 --- a/examples/svg-particles/package.json +++ b/examples/svg-particles/package.json @@ -1,30 +1,30 @@ { - "name": "@example/svg-particles", - "private": true, - "description": "Basic 2D particle system w/ SVG shapes", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/random": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": {}, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/svg-particles", + "private": true, + "description": "Basic 2D particle system w/ SVG shapes", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/random": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": {}, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/svg-particles/src/index.ts b/examples/svg-particles/src/index.ts index 235b8a9a21..dfdf70b55d 100644 --- a/examples/svg-particles/src/index.ts +++ b/examples/svg-particles/src/index.ts @@ -7,44 +7,44 @@ const width = window.innerWidth; const height = window.innerHeight; const updateParticle = (p: any, v: number[]) => { - let x = p.cx + v[0]; - let y = p.cy + v[1]; - x < 0 && ((x *= -1), (v[0] *= -1)); - y < 0 && ((y *= -1), (v[1] *= -1)); - x > width && ((x = width - (x - width)), (v[0] *= -1)); - y > height && ((y = height - (y - height)), (v[1] *= -1)); - p.cx = x | 0; - p.cy = y | 0; + let x = p.cx + v[0]; + let y = p.cy + v[1]; + x < 0 && ((x *= -1), (v[0] *= -1)); + y < 0 && ((y *= -1), (v[1] *= -1)); + x > width && ((x = width - (x - width)), (v[0] *= -1)); + y > height && ((y = height - (y - height)), (v[1] *= -1)); + p.cx = x | 0; + p.cy = y | 0; }; const rand = (a: number, b: number) => - SYSTEM.minmax(a, b) * (SYSTEM.float() < 0.5 ? 1 : -1); + SYSTEM.minmax(a, b) * (SYSTEM.float() < 0.5 ? 1 : -1); const randomParticle = () => { - velocities.push([rand(1, 5), rand(1, 5)]); - return [ - "circle", - { - cx: Math.random() * width, - cy: Math.random() * height, - r: (Math.random() * 6 + 3) | 0, - fill: "#" + U24((Math.random() * 0x1000000) | 0), - }, - ]; + velocities.push([rand(1, 5), rand(1, 5)]); + return [ + "circle", + { + cx: Math.random() * width, + cy: Math.random() * height, + r: (Math.random() * 6 + 3) | 0, + fill: "#" + U24((Math.random() * 0x1000000) | 0), + }, + ]; }; const velocities: any[] = [null, null]; const particles: any[] = ["g", {}, ...repeatedly(randomParticle, 200)]; const app = () => { - for (let i = particles.length - 1; i > 1; i--) { - updateParticle(particles[i][1], velocities[i]); - } - return [ - "svg", - { width, height, __diff: false, __normalize: false }, - particles, - ]; + for (let i = particles.length - 1; i > 1; i--) { + updateParticle(particles[i][1], velocities[i]); + } + return [ + "svg", + { width, height, __diff: false, __normalize: false }, + particles, + ]; }; start(app); diff --git a/examples/svg-particles/tsconfig.json b/examples/svg-particles/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/svg-particles/tsconfig.json +++ b/examples/svg-particles/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/svg-waveform/package.json b/examples/svg-waveform/package.json index 8af4849ff8..1af8c5de8c 100644 --- a/examples/svg-waveform/package.json +++ b/examples/svg-waveform/package.json @@ -1,43 +1,43 @@ { - "name": "@example/svg-waveform", - "private": true, - "version": "0.0.1", - "description": "Additive waveform synthesis & SVG visualization with undo/redo", - "repository": "https://github.com/[your-gh-username]/rs-icep", - "author": "Karsten Schmidt", - "license": "MIT", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/checks": "workspace:^", - "@thi.ng/expose": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hiccup-svg": "workspace:^", - "@thi.ng/interceptors": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "hdom", - "hiccup-svg", - "interceptors", - "transducers" - ], - "screenshot": "examples/svg-waveform.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/svg-waveform", + "private": true, + "version": "0.0.1", + "description": "Additive waveform synthesis & SVG visualization with undo/redo", + "repository": "https://github.com/[your-gh-username]/rs-icep", + "author": "Karsten Schmidt", + "license": "MIT", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/checks": "workspace:^", + "@thi.ng/expose": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hiccup-svg": "workspace:^", + "@thi.ng/interceptors": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "hdom", + "hiccup-svg", + "interceptors", + "transducers" + ], + "screenshot": "examples/svg-waveform.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/svg-waveform/src/api.ts b/examples/svg-waveform/src/api.ts index cc4ae69b57..44b53c7168 100644 --- a/examples/svg-waveform/src/api.ts +++ b/examples/svg-waveform/src/api.ts @@ -17,13 +17,13 @@ export type ViewSpec = string | Path | [string | Path, Fn]; * See `src/config.ts`. */ export interface AppConfig { - events: IObjectOf; - effects: IObjectOf; - domRoot: string | Element; - initialState: any; - rootComponent: AppComponent; - ui: UIAttribs; - views: Partial>; + events: IObjectOf; + effects: IObjectOf; + domRoot: string | Element; + initialState: any; + rootComponent: AppComponent; + ui: UIAttribs; + views: Partial>; } export type AppViewIDs = "amp" | "freq" | "phase" | "harmonics" | "hstep"; @@ -33,11 +33,11 @@ export type AppViewIDs = "amp" | "freq" | "phase" | "harmonics" | "hstep"; * Add more declarations here as needed. */ export interface AppViews extends Record> { - amp: IView; - freq: IView; - phase: IView; - harmonics: IView; - hstep: IView; + amp: IView; + freq: IView; + phase: IView; + harmonics: IView; + hstep: IView; } /** @@ -48,21 +48,21 @@ export interface AppViews extends Record> { * component functions. */ export interface UIAttribs { - button: any; - buttongroup: any; - footer: any; - link: any; - slider: { root: any; range: any; number: any }; - root: any; - sidebar: any; - waveform: any; + button: any; + buttongroup: any; + footer: any; + link: any; + slider: { root: any; range: any; number: any }; + root: any; + sidebar: any; + waveform: any; } /** * Structure of the context object passed to all component functions */ export interface AppContext { - bus: EventBus; - views: AppViews; - ui: UIAttribs; + bus: EventBus; + views: AppViews; + ui: UIAttribs; } diff --git a/examples/svg-waveform/src/app.ts b/examples/svg-waveform/src/app.ts index 356fee0952..918bde2d97 100644 --- a/examples/svg-waveform/src/app.ts +++ b/examples/svg-waveform/src/app.ts @@ -20,86 +20,86 @@ import * as ev from "./events"; * - start hdom render & event bus loop */ export class App { - config: AppConfig; - ctx: AppContext; - state: Atom; - history: History; + config: AppConfig; + ctx: AppContext; + state: Atom; + history: History; - constructor(config: AppConfig) { - this.config = config; - this.state = defAtom(config.initialState || {}); - this.history = defHistory(this.state, 1000); - this.ctx = { - bus: new EventBus(this.state, config.events, config.effects), - views: {}, - ui: config.ui, - }; - this.addViews(this.config.views); - } + constructor(config: AppConfig) { + this.config = config; + this.state = defAtom(config.initialState || {}); + this.history = defHistory(this.state, 1000); + this.ctx = { + bus: new EventBus(this.state, config.events, config.effects), + views: {}, + ui: config.ui, + }; + this.addViews(this.config.views); + } - /** - * Initializes given derived view specs and attaches them to app - * state atom. - * - * @param specs - - */ - addViews(specs: IObjectOf) { - const views: any = this.ctx.views; - for (let id in specs) { - const spec = specs[id]; - views[id] = isArray(spec) - ? defViewUnsafe(this.state, spec[0], >spec[1]) - : defViewUnsafe(this.state, spec); - } - } + /** + * Initializes given derived view specs and attaches them to app + * state atom. + * + * @param specs - + */ + addViews(specs: IObjectOf) { + const views: any = this.ctx.views; + for (let id in specs) { + const spec = specs[id]; + views[id] = isArray(spec) + ? defViewUnsafe(this.state, spec[0], >spec[1]) + : defViewUnsafe(this.state, spec); + } + } - /** - * Calls `init()` and kicks off hdom render loop, including batched - * event processing and fast fail check if DOM updates are necessary - * (assumes ALL state is held in the app state atom). So if there - * weren't any events causing a state change since last frame, - * re-rendering is skipped without even attempting to diff DOM - * tree). - */ - start() { - this.init(); - // assume main root component is a higher order function - // call it here to pre-initialize it - const root = this.config.rootComponent(this.ctx); - let firstFrame = true; - start( - () => { - if ( - this.ctx.bus.processQueue({ history: this.history }) || - firstFrame - ) { - firstFrame = false; - return root(); - } - }, - { root: this.config.domRoot, ctx: this.ctx } - ); - } + /** + * Calls `init()` and kicks off hdom render loop, including batched + * event processing and fast fail check if DOM updates are necessary + * (assumes ALL state is held in the app state atom). So if there + * weren't any events causing a state change since last frame, + * re-rendering is skipped without even attempting to diff DOM + * tree). + */ + start() { + this.init(); + // assume main root component is a higher order function + // call it here to pre-initialize it + const root = this.config.rootComponent(this.ctx); + let firstFrame = true; + start( + () => { + if ( + this.ctx.bus.processQueue({ history: this.history }) || + firstFrame + ) { + firstFrame = false; + return root(); + } + }, + { root: this.config.domRoot, ctx: this.ctx } + ); + } - /** - * User initialization hook. - * Automatically called from `start()` - */ - init() { - // initialize key event handlers for undo/redo - document.addEventListener("keypress", (e) => { - // e.preventDefault(); - if (e.ctrlKey) { - if (e.key === "z") { - this.ctx.bus.dispatch([ev.UNDO]); - } else if (e.key === "y") { - this.ctx.bus.dispatch([ev.REDO]); - } - } - }); - // ...add init tasks here + /** + * User initialization hook. + * Automatically called from `start()` + */ + init() { + // initialize key event handlers for undo/redo + document.addEventListener("keypress", (e) => { + // e.preventDefault(); + if (e.ctrlKey) { + if (e.key === "z") { + this.ctx.bus.dispatch([ev.UNDO]); + } else if (e.key === "y") { + this.ctx.bus.dispatch([ev.REDO]); + } + } + }); + // ...add init tasks here - // record snapshot of initial state - this.history.record(); - } + // record snapshot of initial state + this.history.record(); + } } diff --git a/examples/svg-waveform/src/components/button-group.ts b/examples/svg-waveform/src/components/button-group.ts index 446eed82d0..e20c3a2037 100644 --- a/examples/svg-waveform/src/components/button-group.ts +++ b/examples/svg-waveform/src/components/button-group.ts @@ -2,9 +2,9 @@ import type { AppContext } from "../api"; import { button } from "./button"; export function buttonGroup(ctx: AppContext, ...buttons: any[]) { - return [ - "section", - ctx.ui.buttongroup, - buttons.map((bt) => [button, ...bt]), - ]; + return [ + "section", + ctx.ui.buttongroup, + buttons.map((bt) => [button, ...bt]), + ]; } diff --git a/examples/svg-waveform/src/components/button.ts b/examples/svg-waveform/src/components/button.ts index 7cb1083df6..40c7e84e49 100644 --- a/examples/svg-waveform/src/components/button.ts +++ b/examples/svg-waveform/src/components/button.ts @@ -2,5 +2,5 @@ import type { AppContext } from "../api"; import { eventLink } from "./event-link"; export function button(ctx: AppContext, event: Event, label: string) { - return [eventLink, ctx.ui.button, event, label]; + return [eventLink, ctx.ui.button, event, label]; } diff --git a/examples/svg-waveform/src/components/event-link.ts b/examples/svg-waveform/src/components/event-link.ts index e71b4e0ef9..f8e45c1b75 100644 --- a/examples/svg-waveform/src/components/event-link.ts +++ b/examples/svg-waveform/src/components/event-link.ts @@ -5,26 +5,26 @@ import type { AppContext } from "../api"; * Customizable hyperlink component emitting given event on event bus * when clicked. * - * @param ctx - + * @param ctx - * @param event - vent tuple of `[event-id, payload]` * @param attribs - lement attribs * @param body - ink body */ export function eventLink( - ctx: AppContext, - attribs: any, - event: Event, - body: any + ctx: AppContext, + attribs: any, + event: Event, + body: any ) { - return [ - "a", - { - ...attribs, - onclick: (e: any) => { - e.preventDefault(); - ctx.bus.dispatch(event); - }, - }, - body, - ]; + return [ + "a", + { + ...attribs, + onclick: (e: any) => { + e.preventDefault(); + ctx.bus.dispatch(event); + }, + }, + body, + ]; } diff --git a/examples/svg-waveform/src/components/link.ts b/examples/svg-waveform/src/components/link.ts index a9d250debb..b3783b2063 100644 --- a/examples/svg-waveform/src/components/link.ts +++ b/examples/svg-waveform/src/components/link.ts @@ -1,5 +1,5 @@ import type { AppContext } from "../api"; export function link(ctx: AppContext, href: string, ...body: any[]) { - return ["a", { ...ctx.ui.link, href }, ...body]; + return ["a", { ...ctx.ui.link, href }, ...body]; } diff --git a/examples/svg-waveform/src/components/main.ts b/examples/svg-waveform/src/components/main.ts index 52b800a24b..0d6ddeeed6 100644 --- a/examples/svg-waveform/src/components/main.ts +++ b/examples/svg-waveform/src/components/main.ts @@ -4,21 +4,21 @@ import { sidebar } from "./sidebar"; import { waveform } from "./waveform"; export function main(ctx: AppContext) { - const bar = sidebar(ctx, ...SLIDERS); - return () => [ - "div", - ctx.ui.root, - bar, - waveform(ctx, { - phase: ctx.views.phase.deref()!, - freq: ctx.views.freq.deref()!, - amp: ctx.views.amp.deref()!, - harmonics: ctx.views.harmonics.deref()!, - hstep: ctx.views.hstep.deref()!, - res: 1000, - stroke: "#f04", - fill1: "#f04", - fill2: "#ff0", - }), - ]; + const bar = sidebar(ctx, ...SLIDERS); + return () => [ + "div", + ctx.ui.root, + bar, + waveform(ctx, { + phase: ctx.views.phase.deref()!, + freq: ctx.views.freq.deref()!, + amp: ctx.views.amp.deref()!, + harmonics: ctx.views.harmonics.deref()!, + hstep: ctx.views.hstep.deref()!, + res: 1000, + stroke: "#f04", + fill1: "#f04", + fill2: "#ff0", + }), + ]; } diff --git a/examples/svg-waveform/src/components/sidebar.ts b/examples/svg-waveform/src/components/sidebar.ts index 255c708502..b36cf04a07 100644 --- a/examples/svg-waveform/src/components/sidebar.ts +++ b/examples/svg-waveform/src/components/sidebar.ts @@ -5,36 +5,36 @@ import { link } from "./link"; import { slider, type SliderOpts } from "./slider"; export function sidebar(ctx: AppContext, ...specs: SliderOpts[]) { - const sliders = specs.map((s) => slider(ctx, s)); - return [ - "div", - ctx.ui.sidebar, - ["h2.mt0", "Additive synthesis"], - ...sliders, - [buttonGroup, [[ev.UNDO], "undo"], [[ev.REDO], "redo"]], - [ - "div", - "Undo / Redo can also be triggered via ", - ["code", "Ctrl+Z"], - " / ", - ["code", "Ctrl+Y"], - ". The last 1000 edits are stored.", - ], - [ - "div", - ctx.ui.footer, - [ - link, - "https://github.com/thi-ng/umbrella/tree/develop/examples/svg-waveform", - "Source", - ], - ["br"], - "Made with ", - [ - link, - "https://github.com/thi-ng/umbrella/tree/develop/packages/hdom", - "@thi.ng/hdom", - ], - ], - ]; + const sliders = specs.map((s) => slider(ctx, s)); + return [ + "div", + ctx.ui.sidebar, + ["h2.mt0", "Additive synthesis"], + ...sliders, + [buttonGroup, [[ev.UNDO], "undo"], [[ev.REDO], "redo"]], + [ + "div", + "Undo / Redo can also be triggered via ", + ["code", "Ctrl+Z"], + " / ", + ["code", "Ctrl+Y"], + ". The last 1000 edits are stored.", + ], + [ + "div", + ctx.ui.footer, + [ + link, + "https://github.com/thi-ng/umbrella/tree/develop/examples/svg-waveform", + "Source", + ], + ["br"], + "Made with ", + [ + link, + "https://github.com/thi-ng/umbrella/tree/develop/packages/hdom", + "@thi.ng/hdom", + ], + ], + ]; } diff --git a/examples/svg-waveform/src/components/slider.ts b/examples/svg-waveform/src/components/slider.ts index 8f175d9180..44be26bc99 100644 --- a/examples/svg-waveform/src/components/slider.ts +++ b/examples/svg-waveform/src/components/slider.ts @@ -1,12 +1,12 @@ import type { AppContext } from "../api"; export interface SliderOpts { - event: PropertyKey; - view: PropertyKey; - label: string; - min?: number; - max?: number; - step?: number; + event: PropertyKey; + view: PropertyKey; + label: string; + min?: number; + max?: number; + step?: number; } /** @@ -20,47 +20,47 @@ export interface SliderOpts { * * See `main.ts` for usage. * - * @param ctx - - * @param opts - + * @param ctx - + * @param opts - */ export function slider(ctx: AppContext, opts: SliderOpts) { - opts = Object.assign( - { - oninput: (e: Event) => - ctx.bus.dispatch([ - opts.event, - parseFloat((e.target).value), - ]), - min: 0, - max: 100, - step: 1, - }, - opts - ); - return (ctx: AppContext) => [ - "section", - ctx.ui.slider.root, - [ - "input", - { - ...ctx.ui.slider.range, - ...opts, - type: "range", - value: (ctx.views)[opts.view].deref(), - }, - ], - [ - "div", - opts.label, - [ - "input", - { - ...ctx.ui.slider.number, - ...opts, - type: "number", - value: (ctx.views)[opts.view].deref(), - }, - ], - ], - ]; + opts = Object.assign( + { + oninput: (e: Event) => + ctx.bus.dispatch([ + opts.event, + parseFloat((e.target).value), + ]), + min: 0, + max: 100, + step: 1, + }, + opts + ); + return (ctx: AppContext) => [ + "section", + ctx.ui.slider.root, + [ + "input", + { + ...ctx.ui.slider.range, + ...opts, + type: "range", + value: (ctx.views)[opts.view].deref(), + }, + ], + [ + "div", + opts.label, + [ + "input", + { + ...ctx.ui.slider.number, + ...opts, + type: "number", + value: (ctx.views)[opts.view].deref(), + }, + ], + ], + ]; } diff --git a/examples/svg-waveform/src/components/waveform.ts b/examples/svg-waveform/src/components/waveform.ts index c1edea9933..fbd8c3d37e 100644 --- a/examples/svg-waveform/src/components/waveform.ts +++ b/examples/svg-waveform/src/components/waveform.ts @@ -10,78 +10,78 @@ import type { AppContext } from "../api"; const TAU = Math.PI * 2; export interface WaveformOpts { - phase: number; - freq: number; - amp: number; - harmonics: number; - hstep: number; - res: number; - fill1: string; - fill2: string; - stroke: string; + phase: number; + freq: number; + amp: number; + harmonics: number; + hstep: number; + res: number; + fill1: string; + fill2: string; + stroke: string; } /** * Additive synthesis and waveform visualization as SVG * - * @param opts - + * @param opts - */ export function waveform(ctx: AppContext, opts: WaveformOpts) { - const phase = (opts.phase * Math.PI) / 180; - const amp = opts.amp * 50; - const fscale = (1 / opts.res) * TAU * opts.freq; - return svg( - { ...ctx.ui.waveform, viewBox: `0 -5 ${opts.res} 10` }, - defs( - linearGradient( - "grad", - [0, 0], - [0, 1], - [ - [0, opts.fill2], - [0.5, opts.fill1], - [1, opts.fill2], - ] - ) - ), - polyline( - [ - [0, 0], - ...map( - (x) => [ - x, - osc(x, phase, fscale, amp, opts.harmonics, opts.hstep), - ], - range(opts.res) - ), - [opts.res, 0], - ], - { - stroke: opts.stroke, - fill: "url(#grad)", - "stoke-linejoin": "round", - } - ) - ); + const phase = (opts.phase * Math.PI) / 180; + const amp = opts.amp * 50; + const fscale = (1 / opts.res) * TAU * opts.freq; + return svg( + { ...ctx.ui.waveform, viewBox: `0 -5 ${opts.res} 10` }, + defs( + linearGradient( + "grad", + [0, 0], + [0, 1], + [ + [0, opts.fill2], + [0.5, opts.fill1], + [1, opts.fill2], + ] + ) + ), + polyline( + [ + [0, 0], + ...map( + (x) => [ + x, + osc(x, phase, fscale, amp, opts.harmonics, opts.hstep), + ], + range(opts.res) + ), + [opts.res, 0], + ], + { + stroke: opts.stroke, + fill: "url(#grad)", + "stoke-linejoin": "round", + } + ) + ); } function osc( - x: number, - phase: number, - fscale: number, - amp: number, - harmonics: number, - hstep: number + x: number, + phase: number, + fscale: number, + amp: number, + harmonics: number, + hstep: number ) { - const f = x * fscale; - return reduce( - reducer( - () => 0, - (sum, i) => { - const k = 1 + i * hstep; - return sum + (Math.sin(phase + f * k) * amp) / k; - } - ), - range(0, harmonics + 1) - ); + const f = x * fscale; + return reduce( + reducer( + () => 0, + (sum, i) => { + const k = 1 + i * hstep; + return sum + (Math.sin(phase + f * k) * amp) / k; + } + ), + range(0, harmonics + 1) + ); } diff --git a/examples/svg-waveform/src/config.ts b/examples/svg-waveform/src/config.ts index 79fc1ecc54..d9ef905b60 100644 --- a/examples/svg-waveform/src/config.ts +++ b/examples/svg-waveform/src/config.ts @@ -7,86 +7,86 @@ import { SLIDERS } from "./sliders"; // main App configuration export const CONFIG: AppConfig = { - // event handlers events are queued and batch processed in app's RAF - // render loop event handlers can be single functions, interceptor - // objects with `pre`/`post` keys or arrays of either. + // event handlers events are queued and batch processed in app's RAF + // render loop event handlers can be single functions, interceptor + // objects with `pre`/`post` keys or arrays of either. - // the event handlers' only task is to transform the event into a - // number of side effects. event handlers should be pure functions - // and only side effect functions execute any "real" work. + // the event handlers' only task is to transform the event into a + // number of side effects. event handlers should be pure functions + // and only side effect functions execute any "real" work. - // Docs here: - // https://docs.thi.ng/umbrella/interceptors/#event-bus-interceptors-side-effects + // Docs here: + // https://docs.thi.ng/umbrella/interceptors/#event-bus-interceptors-side-effects - events: { - // generate event handlers from imported slider definitions - // the same defs are used in the main root component (main.ts) to generate - // their respective UI components - // each of these handlers is dynamically composed of 3 interceptors: - // 1) validate the event param - // 2) record undo history snapshot - // 3) update param in app state - // the last 2 steps are only be executed if validation succeeded - // else the event is canceled - ...SLIDERS.reduce((events: any, spec) => { - events[spec.event] = [ - ensureParamRange(spec.min, spec.max), - snapshot(), - valueSetter(spec.view), - ]; - return events; - }, {}), - }, + events: { + // generate event handlers from imported slider definitions + // the same defs are used in the main root component (main.ts) to generate + // their respective UI components + // each of these handlers is dynamically composed of 3 interceptors: + // 1) validate the event param + // 2) record undo history snapshot + // 3) update param in app state + // the last 2 steps are only be executed if validation succeeded + // else the event is canceled + ...SLIDERS.reduce((events: any, spec) => { + events[spec.event] = [ + ensureParamRange(spec.min, spec.max), + snapshot(), + valueSetter(spec.view), + ]; + return events; + }, {}), + }, - // custom side effects - effects: {}, + // custom side effects + effects: {}, - // DOM root element (or ID) - domRoot: "app", + // DOM root element (or ID) + domRoot: "app", - // root component function used by the app - rootComponent: main, + // root component function used by the app + rootComponent: main, - // initial app state - initialState: { - amp: 2.5, - freq: 3, - harmonics: 20, - hstep: 2, - phase: 0, - }, + // initial app state + initialState: { + amp: 2.5, + freq: 3, + harmonics: 20, + hstep: 2, + phase: 0, + }, - // derived view declarations - // each key specifies the name of the view and each value is - // a state path or `[path, transformer]` tuple - // docs here: - // https://github.com/thi-ng/umbrella/tree/develop/packages/atom#derived-views - // also see `app.ts` for view initialization - views: { - amp: "amp", - freq: "freq", - phase: "phase", - harmonics: "harmonics", - hstep: "hstep", - }, + // derived view declarations + // each key specifies the name of the view and each value is + // a state path or `[path, transformer]` tuple + // docs here: + // https://github.com/thi-ng/umbrella/tree/develop/packages/atom#derived-views + // also see `app.ts` for view initialization + views: { + amp: "amp", + freq: "freq", + phase: "phase", + harmonics: "harmonics", + hstep: "hstep", + }, - // component CSS class config using http://tachyons.io/ these - // attribs are made available to all components and allow for easy - // re-skinning of the whole app - ui: { - button: { - class: "pointer bg-black hover-bg-blue bg-animate white pa2 mr1 w-100 ttu b tracked-tight", - }, - buttongroup: { class: "flex mb2" }, - footer: { class: "absolute bottom-1" }, - link: { class: "pointer link dim black b" }, - root: { class: "vw-100 vh-100 flex" }, - sidebar: { class: "bg-light-gray pa2 pt3 w5 f7" }, - slider: { - root: { class: "mb3 ttu b tracked-tight" }, - range: { class: "w-100" }, - number: { class: "fr w3 tr ttu bn bg-transparent" }, - }, - waveform: { class: "w-100 h-100" }, - }, + // component CSS class config using http://tachyons.io/ these + // attribs are made available to all components and allow for easy + // re-skinning of the whole app + ui: { + button: { + class: "pointer bg-black hover-bg-blue bg-animate white pa2 mr1 w-100 ttu b tracked-tight", + }, + buttongroup: { class: "flex mb2" }, + footer: { class: "absolute bottom-1" }, + link: { class: "pointer link dim black b" }, + root: { class: "vw-100 vh-100 flex" }, + sidebar: { class: "bg-light-gray pa2 pt3 w5 f7" }, + slider: { + root: { class: "mb3 ttu b tracked-tight" }, + range: { class: "w-100" }, + number: { class: "fr w3 tr ttu bn bg-transparent" }, + }, + waveform: { class: "w-100 h-100" }, + }, }; diff --git a/examples/svg-waveform/src/sliders.ts b/examples/svg-waveform/src/sliders.ts index 646f027aa8..ca29bd9553 100644 --- a/examples/svg-waveform/src/sliders.ts +++ b/examples/svg-waveform/src/sliders.ts @@ -4,36 +4,36 @@ import * as ev from "./events"; * Slider definitions used to generate UI components and their event handlers */ export const SLIDERS = [ - { event: ev.SET_PHASE, view: "phase", label: "phase", min: 0, max: 360 }, - { - event: ev.SET_FREQ, - view: "freq", - label: "frequency", - min: 1, - max: 10, - step: 0.01, - }, - { - event: ev.SET_AMP, - view: "amp", - label: "amplitude", - min: 0, - max: 4, - step: 0.01, - }, - { - event: ev.SET_HARMONICS, - view: "harmonics", - label: "harmonics", - min: 1, - max: 20, - }, - { - event: ev.SET_HSTEP, - view: "hstep", - label: "h step", - min: 1, - max: 4, - step: 0.01, - }, + { event: ev.SET_PHASE, view: "phase", label: "phase", min: 0, max: 360 }, + { + event: ev.SET_FREQ, + view: "freq", + label: "frequency", + min: 1, + max: 10, + step: 0.01, + }, + { + event: ev.SET_AMP, + view: "amp", + label: "amplitude", + min: 0, + max: 4, + step: 0.01, + }, + { + event: ev.SET_HARMONICS, + view: "harmonics", + label: "harmonics", + min: 1, + max: 20, + }, + { + event: ev.SET_HSTEP, + view: "hstep", + label: "h step", + min: 1, + max: 4, + step: 0.01, + }, ]; diff --git a/examples/svg-waveform/tsconfig.json b/examples/svg-waveform/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/svg-waveform/tsconfig.json +++ b/examples/svg-waveform/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/talk-slides/package.json b/examples/talk-slides/package.json index 897516860d..4fffe41767 100644 --- a/examples/talk-slides/package.json +++ b/examples/talk-slides/package.json @@ -1,40 +1,40 @@ { - "name": "@example/talk-slides", - "private": true, - "description": "hdom based slide deck viewer & slides from my ClojureX 2018 keynote", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "hdom", - "rstream", - "transducers", - "transducers-hdom" - ], - "screenshot": "examples/talk-slides.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/talk-slides", + "private": true, + "description": "hdom based slide deck viewer & slides from my ClojureX 2018 keynote", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "hdom", + "rstream", + "transducers", + "transducers-hdom" + ], + "screenshot": "examples/talk-slides.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/talk-slides/src/components.ts b/examples/talk-slides/src/components.ts index f2625e8539..77c4aa3f50 100644 --- a/examples/talk-slides/src/components.ts +++ b/examples/talk-slides/src/components.ts @@ -1,134 +1,133 @@ export const link = (ctx: any, href: string, body?: any) => [ - "a", - { ...ctx.link, href }, - body || href, + "a", + { ...ctx.link, href }, + body || href, ]; export const twitterLink = (_: any, username: string) => [ - link, - `https://twitter.com/${username}`, - "@" + username, + link, + `https://twitter.com/${username}`, + "@" + username, ]; export const navButton = (ctx: any, step: number) => [ - "a", - { - ...ctx.navButton[step < 0 ? "prev" : "next"], - onclick: () => ctx.slide.next(step), - }, - step < 0 ? "<" : ">", + "a", + { + ...ctx.navButton[step < 0 ? "prev" : "next"], + onclick: () => ctx.slide.next(step), + }, + step < 0 ? "<" : ">", ]; export const footer = (ctx: any, ...body: any[]) => [ - "footer", - ctx.footer, - ...body, + "footer", + ctx.footer, + ...body, ]; export const codeBlock = (ctx: any, body: string) => [ - "pre", - ctx.code, - body.trim(), + "pre", + ctx.code, + body.trim(), ]; export const list = (ctx: any, ...items: any[]) => [ - "ul", - ctx.list, - ...items.map((i) => ["li", i]), + "ul", + ctx.list, + ...items.map((i) => ["li", i]), ]; export const titlePage = ( - ctx: any, - clazz: string, - title: string, - ...body: any[] + ctx: any, + clazz: string, + title: string, + ...body: any[] ) => [ - "div", - { - ...ctx.titlePage.root, - class: `${ctx.titlePage.root.class} ${clazz}`, - }, - [ - "div", - ctx.titlePage.wrapper, - [ - "div", - ["h1", ctx.titlePage.title, title], - ["div", ctx.titlePage.body, ...body], - ], - ], + "div", + { + ...ctx.titlePage.root, + class: `${ctx.titlePage.root.class} ${clazz}`, + }, + [ + "div", + ctx.titlePage.wrapper, + [ + "div", + ["h1", ctx.titlePage.title, title], + ["div", ctx.titlePage.body, ...body], + ], + ], ]; export const contentPage = (ctx: any, title: string, ...body: any[]) => [ - "div", - ctx.contentPage.root, - ["h2", ctx.contentPage.title, title], - ["div", ctx.contentPage.body, ...body], + "div", + ctx.contentPage.root, + ["h2", ctx.contentPage.title, title], + ["div", ctx.contentPage.body, ...body], ]; export const quotePage = (ctx: any, quote: any[], author: string) => [ - "div", - ctx.quotePage.root, - ["div", ctx.quotePage.quote, ...quote.map((x) => ["div", x])], - ["div", ctx.quotePage.author, `— ${author}`], + "div", + ctx.quotePage.root, + ["div", ctx.quotePage.quote, ...quote.map((x) => ["div", x])], + ["div", ctx.quotePage.author, `— ${author}`], ]; export const bgImagePage = ( - ctx: any, - clazz: string, - src: string, - ...extra: any[] + ctx: any, + clazz: string, + src: string, + ...extra: any[] ) => [ - "div", - { - class: `${ctx.bgImagePage.class} ${clazz}`, - style: { - "background-image": `url(${src})`, - "background-size": "cover", - "background-position": "center", - }, - }, - ...extra, + "div", + { + class: `${ctx.bgImagePage.class} ${clazz}`, + style: { + "background-image": `url(${src})`, + "background-size": "cover", + "background-position": "center", + }, + }, + ...extra, ]; export const imagePage = (ctx: any, clazz: string, src: string) => [ - "div", - { ...ctx.imagePage.root, class: `${ctx.imagePage.root.class} ${clazz}` }, - ["div.w-100", ["img", { ...ctx.imagePage.img, src }]], + "div", + { ...ctx.imagePage.root, class: `${ctx.imagePage.root.class} ${clazz}` }, + ["div.w-100", ["img", { ...ctx.imagePage.img, src }]], ]; export const ytVideo = (ctx: any, id: string) => [ - "div", - [ - "iframe", - { - ...ctx.youtube, - src: `https://www.youtube.com/embed/${id}?rel=0&showinfo=0`, - frameborder: 0, - allowfullscreen: true, - }, - ], - [navButton, -1], - [navButton, 1], + "div", + [ + "iframe", + { + ...ctx.youtube, + src: `https://www.youtube.com/embed/${id}?rel=0&showinfo=0`, + frameborder: 0, + allowfullscreen: true, + }, + ], + [navButton, -1], + [navButton, 1], ]; -export const app = (slideCount: number, ctx: any) => ({ - slideID, - content, - time, -}: any) => [ - "div", - ctx.app.root, - content, - [ - footer, - ["div.w-33.tl", ctx.app.credits], - ["div.w-34.tc", time], - ["div.w-33.tr", `${slideID} / ${slideCount - 1}`], - ], -]; +export const app = + (slideCount: number, ctx: any) => + ({ slideID, content, time }: any) => + [ + "div", + ctx.app.root, + content, + [ + footer, + ["div.w-33.tl", ctx.app.credits], + ["div.w-34.tc", time], + ["div.w-33.tr", `${slideID} / ${slideCount - 1}`], + ], + ]; export const printApp = (ctx: any, slides: any[]) => [ - "div", - ...slides.map((content) => ["div.slide", ctx.app.root, content]), + "div", + ...slides.map((content) => ["div.slide", ctx.app.root, content]), ]; diff --git a/examples/talk-slides/src/config.ts b/examples/talk-slides/src/config.ts index b23bbc485f..3c2af4998a 100644 --- a/examples/talk-slides/src/config.ts +++ b/examples/talk-slides/src/config.ts @@ -1,62 +1,60 @@ export const ctx: any = { - // place holder for slide number stream - slide: null, - - // theme attribs for various components - app: { - root: { class: "w-100 vh-100 sans-serif bg-lightest-blue" }, - credits: "ClojureX 2018", - }, - - footer: { class: "fixed bottom-1 f7 gray flex w-100 ph5 noprint" }, - - link: { class: "link black hover-blue" }, - - navButton: { - prev: { - class: - "fixed top-50 left-0 z-999 pa3 bg-black white link f3 noprint", - href: "#", - }, - next: { - class: - "fixed top-50 right-0 z-999 pa3 bg-black white link f3 noprint", - href: "#", - }, - }, - - code: { class: "pa3 f3 bg-dark-blue white code" }, - - titlePage: { - root: { class: "flex items-center vh-100" }, - wrapper: { class: "tc w-100" }, - title: { class: "f-headline lh-title ma0" }, - body: { class: "f2 lh-copy" }, - }, - - contentPage: { - root: { class: "pa5" }, - title: { class: "ma0 f-subheadline" }, - body: { class: "mt3 f2 lh-copy" }, - }, - - quotePage: { - root: { class: "vh-100 pa5 bg-yellow" }, - quote: { class: "georgia f1 lh-copy measure-narrow i" }, - author: { class: "mt3 f2" }, - }, - - bgImagePage: { class: "vh-100 pa5 f2 lh-copy" }, - - imagePage: { - root: { class: "vh-100 flex items-center tc" }, - img: { - class: "mw-100", - style: { "max-height": "90vh" }, - }, - }, - - youtube: { - class: "w-100 vh-100 bg-black", - }, + // place holder for slide number stream + slide: null, + + // theme attribs for various components + app: { + root: { class: "w-100 vh-100 sans-serif bg-lightest-blue" }, + credits: "ClojureX 2018", + }, + + footer: { class: "fixed bottom-1 f7 gray flex w-100 ph5 noprint" }, + + link: { class: "link black hover-blue" }, + + navButton: { + prev: { + class: "fixed top-50 left-0 z-999 pa3 bg-black white link f3 noprint", + href: "#", + }, + next: { + class: "fixed top-50 right-0 z-999 pa3 bg-black white link f3 noprint", + href: "#", + }, + }, + + code: { class: "pa3 f3 bg-dark-blue white code" }, + + titlePage: { + root: { class: "flex items-center vh-100" }, + wrapper: { class: "tc w-100" }, + title: { class: "f-headline lh-title ma0" }, + body: { class: "f2 lh-copy" }, + }, + + contentPage: { + root: { class: "pa5" }, + title: { class: "ma0 f-subheadline" }, + body: { class: "mt3 f2 lh-copy" }, + }, + + quotePage: { + root: { class: "vh-100 pa5 bg-yellow" }, + quote: { class: "georgia f1 lh-copy measure-narrow i" }, + author: { class: "mt3 f2" }, + }, + + bgImagePage: { class: "vh-100 pa5 f2 lh-copy" }, + + imagePage: { + root: { class: "vh-100 flex items-center tc" }, + img: { + class: "mw-100", + style: { "max-height": "90vh" }, + }, + }, + + youtube: { + class: "w-100 vh-100 bg-black", + }, }; diff --git a/examples/talk-slides/src/index.ts b/examples/talk-slides/src/index.ts index eb96582781..b4ce870578 100644 --- a/examples/talk-slides/src/index.ts +++ b/examples/talk-slides/src/index.ts @@ -18,57 +18,57 @@ import { SLIDES } from "./slides"; const INTERACTIVE = true; const initKeys = (stream: Stream) => - fromDOMEvent(window, "keydown").transform( - map((e) => { - // console.log(e.code); - switch (e.code) { - case "KeyR": - stream.next(-1000); - break; - case "ArrowLeft": - stream.next(-1); - break; - case "ArrowRight": - case "Space": - stream.next(1); - break; - } - }) - ); + fromDOMEvent(window, "keydown").transform( + map((e) => { + // console.log(e.code); + switch (e.code) { + case "KeyR": + stream.next(-1000); + break; + case "ArrowLeft": + stream.next(-1); + break; + case "ArrowRight": + case "Space": + stream.next(1); + break; + } + }) + ); const parseSlideID = (str: string) => { - const id = parseInt(str); - return isNaN(id) ? 0 : id; + const id = parseInt(str); + return isNaN(id) ? 0 : id; }; const slideCTRL = (ctx.slide = stream()); const slideID = slideCTRL.transform( - scan( - reducer( - () => 0, - (x, y) => clamp(x + y, 0, SLIDES.length - 1) - ) - ), - dedupe(), - sideEffect((id) => (location.hash = "#" + id)) + scan( + reducer( + () => 0, + (x, y) => clamp(x + y, 0, SLIDES.length - 1) + ) + ), + dedupe(), + sideEffect((id) => (location.hash = "#" + id)) ); const main = sync({ - src: { - slideID, - content: slideID.transform(map((id: number) => SLIDES[id])), - time: fromInterval(1000).transform( - map((x: number) => `${Z2((x / 60) | 0)}:${Z2(x % 60)}`) - ), - }, + src: { + slideID, + content: slideID.transform(map((id: number) => SLIDES[id])), + time: fromInterval(1000).transform( + map((x: number) => `${Z2((x / 60) | 0)}:${Z2(x % 60)}`) + ), + }, }); if (INTERACTIVE) { - main.transform(map(app(SLIDES.length, ctx)), updateDOM({ ctx })); - initKeys(slideCTRL); - slideCTRL.next(parseSlideID(location.hash.substring(1))); + main.transform(map(app(SLIDES.length, ctx)), updateDOM({ ctx })); + initKeys(slideCTRL); + slideCTRL.next(parseSlideID(location.hash.substring(1))); } else { - renderOnce(() => [printApp, SLIDES], { ctx }); + renderOnce(() => [printApp, SLIDES], { ctx }); } // if (process.env.NODE_ENV !== "production") { diff --git a/examples/talk-slides/src/slides.ts b/examples/talk-slides/src/slides.ts index 8cfc5c8067..223e3a8677 100644 --- a/examples/talk-slides/src/slides.ts +++ b/examples/talk-slides/src/slides.ts @@ -1,214 +1,214 @@ import { - bgImagePage, - codeBlock, - contentPage, - imagePage, - link, - list, - quotePage, - titlePage, - twitterLink, - ytVideo, + bgImagePage, + codeBlock, + contentPage, + imagePage, + link, + list, + quotePage, + titlePage, + twitterLink, + ytVideo, } from "./components"; // each item in this array is an hdom tree of a single slide export const SLIDES: any[] = [ - [ - titlePage, - "", - "The Spirit Of Clojure", - "Karsten Schmidt", - ["br"], - [twitterLink, "toxi"], - ["br"], - [twitterLink, "thing_umbrella"], - ], - - [ - titlePage, - "bg-dark-red white", - "The Advent Spirit Of Clojure", - "Karsten Schmidt", - ["br"], - [twitterLink, "toxi"], - ["br"], - [twitterLink, "thing_umbrella"], - ], - - [ - titlePage, - "", - "In The Spirit Of Clojure", - "Karsten Schmidt", - ["br"], - [twitterLink, "toxi"], - ["br"], - [twitterLink, "thing_umbrella"], - ], - - [ - titlePage, - "", - "Clojure without Clojure", - "Karsten Schmidt", - ["br"], - [twitterLink, "toxi"], - ["br"], - [twitterLink, "thing_umbrella"], - ], - - [ - titlePage, - "", - "Clojurians Anonymous", - "Karsten Schmidt", - ["br"], - [twitterLink, "toxi"], - ["br"], - [twitterLink, "thing_umbrella"], - ], - - [ - contentPage, - "What brought you to Clojure?", - [ - "small", - "Filling in this pre-meeting questionnaire is mandatory! Answers will be reviewed during session.", - ], - [ - list, - "The Joy of...", - `"Rich" core API / Sequence abstractions`, - "Concision / Expressiveness", - "Immutability by default", - "Community / Maturity", - "Hosted / x-platform", - "Amount & clarity of thought", - "Transducers, core.async, clojure.spec", - "Gateway to various awesome tools / concepts", - ], - ], - - [ - contentPage, - "What brought you to Clojure?", - "...also", - [ - list, - "Rich Hickey (obviously!)", - "Paul Graham", - "Chris Houser", - "Fogus", - "James Reeves", - "Chris Granger", - "Anthony Grimes (R.I.P.)", - "Christophe Grand", - ["strong", "...all of you!"], - ], - ], - - [ - contentPage, - "Patient file: KS", - "Pre-meeting notes from therapy session: 2018-12-03", - [ - list, - "1988 - 1994 : Z80 / 6502 assembly, Forth", - "1995 - 2011 : C89, Java, JavaScript, Lingo, ActionScript, Processing", - "1995 - 1997 : Scheme, Common Lisp", - "2011 - 2017 : Clojure/script, C11, Forth, ARM", - "2016 - ... : TypeScript, C11, Go, Clojurescript", - ], - ], - - [ - contentPage, - "Most formative periods", - "To help frame discussion during the therapy session...", - [ - "ul", - ["li.transition.red", "1988 - 1994 : Z80 / 6502 assembly, Forth"], - [ - "li", - "1995 - 2011 : C89, Java, JavaScript, Lingo, ActionScript, Processing", - ], - ["li", "1995 - 1997 : Scheme, Common Lisp"], - [ - "li.transition.red", - "2011 - 2017 : Clojure/script, C11, Forth, ARM", - ], - ["li", "2016 - ... : TypeScript, C11, Go, Clojurescript"], - ], - ], - - [ - contentPage, - "Most formative periods", - "To help frame discussion during the therapy session...", - [ - "ul", - ["li.transition", "1988 - 1994 : Z80 / 6502 assembly, Forth"], - [ - "li.transition.red", - "1995 - 2011 : C89, Java, JavaScript, Lingo, ActionScript, Processing", - ], - ["li", "1995 - 1997 : Scheme, Common Lisp"], - ["li.transition", "2011 - 2017 : Clojure/script, C11, Forth, ARM"], - ["li", "2016 - ... : TypeScript, C11, Go, Clojurescript"], - ], - ], - - [contentPage, "And so it begins..."], - - [imagePage, "bg-black", "./assets/logo-1280.jpg"], - - [imagePage, "bg-black", "./assets/timeline.svg"], - - [ - contentPage, - "clojure.thi.ng", - [ - list, - "Started as successor of toxiclibs.org (in 2011)", - "Grew into collection of ~20 libraries (largely CLJC)", - "Mostly written in LP-style (org-mode)", - "2D/3D geometry generation / transformation", - "nD-matrix data types (prior to core.matrix)", - "Data visualization (SVG / WebGL / rendered)", - "Declarative WebGL, OpenGL & GPGPU/OpenCL wrappers", - "Linked data / graph tools, query engines, SPARQL-like DSLs", - "Monthly workshops (2015 - 2016)", - ], - ], - - [titlePage, "bg-black white", "Exponential Growth"], - - [imagePage, "bg-black", "./assets/svo/grapher.jpg"], - [imagePage, "bg-black", "./assets/svo/grapher2.jpg"], - [imagePage, "bg-black", "./assets/svo/grapher3.jpg"], - - [ - contentPage, - "Gyroid formula", - [codeBlock, `g(x, y, z) = abs(dot(cos([x y z]), sin([z x y])))`], - ], - - [ - contentPage, - "Gyroid formula", - [ - codeBlock, - `g(x, y, z) = abs(cosx * sinz + cosy * sinx + cosz * siny)`, - ], - ], - - [ - contentPage, - "Gyroid formula", - [ - codeBlock, - ` + [ + titlePage, + "", + "The Spirit Of Clojure", + "Karsten Schmidt", + ["br"], + [twitterLink, "toxi"], + ["br"], + [twitterLink, "thing_umbrella"], + ], + + [ + titlePage, + "bg-dark-red white", + "The Advent Spirit Of Clojure", + "Karsten Schmidt", + ["br"], + [twitterLink, "toxi"], + ["br"], + [twitterLink, "thing_umbrella"], + ], + + [ + titlePage, + "", + "In The Spirit Of Clojure", + "Karsten Schmidt", + ["br"], + [twitterLink, "toxi"], + ["br"], + [twitterLink, "thing_umbrella"], + ], + + [ + titlePage, + "", + "Clojure without Clojure", + "Karsten Schmidt", + ["br"], + [twitterLink, "toxi"], + ["br"], + [twitterLink, "thing_umbrella"], + ], + + [ + titlePage, + "", + "Clojurians Anonymous", + "Karsten Schmidt", + ["br"], + [twitterLink, "toxi"], + ["br"], + [twitterLink, "thing_umbrella"], + ], + + [ + contentPage, + "What brought you to Clojure?", + [ + "small", + "Filling in this pre-meeting questionnaire is mandatory! Answers will be reviewed during session.", + ], + [ + list, + "The Joy of...", + `"Rich" core API / Sequence abstractions`, + "Concision / Expressiveness", + "Immutability by default", + "Community / Maturity", + "Hosted / x-platform", + "Amount & clarity of thought", + "Transducers, core.async, clojure.spec", + "Gateway to various awesome tools / concepts", + ], + ], + + [ + contentPage, + "What brought you to Clojure?", + "...also", + [ + list, + "Rich Hickey (obviously!)", + "Paul Graham", + "Chris Houser", + "Fogus", + "James Reeves", + "Chris Granger", + "Anthony Grimes (R.I.P.)", + "Christophe Grand", + ["strong", "...all of you!"], + ], + ], + + [ + contentPage, + "Patient file: KS", + "Pre-meeting notes from therapy session: 2018-12-03", + [ + list, + "1988 - 1994 : Z80 / 6502 assembly, Forth", + "1995 - 2011 : C89, Java, JavaScript, Lingo, ActionScript, Processing", + "1995 - 1997 : Scheme, Common Lisp", + "2011 - 2017 : Clojure/script, C11, Forth, ARM", + "2016 - ... : TypeScript, C11, Go, Clojurescript", + ], + ], + + [ + contentPage, + "Most formative periods", + "To help frame discussion during the therapy session...", + [ + "ul", + ["li.transition.red", "1988 - 1994 : Z80 / 6502 assembly, Forth"], + [ + "li", + "1995 - 2011 : C89, Java, JavaScript, Lingo, ActionScript, Processing", + ], + ["li", "1995 - 1997 : Scheme, Common Lisp"], + [ + "li.transition.red", + "2011 - 2017 : Clojure/script, C11, Forth, ARM", + ], + ["li", "2016 - ... : TypeScript, C11, Go, Clojurescript"], + ], + ], + + [ + contentPage, + "Most formative periods", + "To help frame discussion during the therapy session...", + [ + "ul", + ["li.transition", "1988 - 1994 : Z80 / 6502 assembly, Forth"], + [ + "li.transition.red", + "1995 - 2011 : C89, Java, JavaScript, Lingo, ActionScript, Processing", + ], + ["li", "1995 - 1997 : Scheme, Common Lisp"], + ["li.transition", "2011 - 2017 : Clojure/script, C11, Forth, ARM"], + ["li", "2016 - ... : TypeScript, C11, Go, Clojurescript"], + ], + ], + + [contentPage, "And so it begins..."], + + [imagePage, "bg-black", "./assets/logo-1280.jpg"], + + [imagePage, "bg-black", "./assets/timeline.svg"], + + [ + contentPage, + "clojure.thi.ng", + [ + list, + "Started as successor of toxiclibs.org (in 2011)", + "Grew into collection of ~20 libraries (largely CLJC)", + "Mostly written in LP-style (org-mode)", + "2D/3D geometry generation / transformation", + "nD-matrix data types (prior to core.matrix)", + "Data visualization (SVG / WebGL / rendered)", + "Declarative WebGL, OpenGL & GPGPU/OpenCL wrappers", + "Linked data / graph tools, query engines, SPARQL-like DSLs", + "Monthly workshops (2015 - 2016)", + ], + ], + + [titlePage, "bg-black white", "Exponential Growth"], + + [imagePage, "bg-black", "./assets/svo/grapher.jpg"], + [imagePage, "bg-black", "./assets/svo/grapher2.jpg"], + [imagePage, "bg-black", "./assets/svo/grapher3.jpg"], + + [ + contentPage, + "Gyroid formula", + [codeBlock, `g(x, y, z) = abs(dot(cos([x y z]), sin([z x y])))`], + ], + + [ + contentPage, + "Gyroid formula", + [ + codeBlock, + `g(x, y, z) = abs(cosx * sinz + cosy * sinx + cosz * siny)`, + ], + ], + + [ + contentPage, + "Gyroid formula", + [ + codeBlock, + ` (defn cossin [a b] (* (Math/cos a) (Math/sin b))) @@ -221,15 +221,15 @@ export const SLIDES: any[] = [ [[x y z] t] "Evaluates gyroid function at point p and subtracts iso threshold t." (- (abs-gyroid-sum x y z) t))`, - ], - ], - - [ - contentPage, - "Gyroid formula", - [ - codeBlock, - ` + ], + ], + + [ + contentPage, + "Gyroid formula", + [ + codeBlock, + ` (defn gyroid [[x y z] t] "Evaluates gyroid function at point p and subtracts iso threshold t." @@ -238,121 +238,121 @@ export const SLIDES: any[] = [ (* (Math/cos y) (Math/sin x)) (* (Math/cos z) (Math/sin y)))) t))`, - ], - "Now evaluate for each XYZ cell in a volumetric grid...", - ], - - [ - bgImagePage, - "bg-black", - "./assets/svo/0000.jpg", - ["div.f1.gray", "2", ["sup", "3", ["sup", 3]], ["br"], "8 x 8 x 8"], - ], - [ - bgImagePage, - "bg-black", - "./assets/svo/0001.jpg", - ["div.f1.gray", "2", ["sup", "4", ["sup", 3]], ["br"], "16 x 16 x 16"], - ], - [ - bgImagePage, - "bg-black", - "./assets/svo/0002.jpg", - ["div.f1.gray", "2", ["sup", "5", ["sup", 3]], ["br"], "32 x 32 x 32"], - ], - [ - bgImagePage, - "bg-black", - "./assets/svo/0003.jpg", - ["div.f1.gray", "2", ["sup", "6", ["sup", 3]], ["br"], "64 x 64 x 64"], - ], - [ - bgImagePage, - "bg-black", - "./assets/svo/0004.jpg", - [ - "div.f1.gray", - "2", - ["sup", "7", ["sup", 3]], - ["br"], - "128 x 128 x 128", - ], - ], - [ - bgImagePage, - "bg-black", - "./assets/svo/0005.jpg", - [ - "div.f1.gray", - "2", - ["sup", "8", ["sup", 3]], - ["br"], - "256 x 256 x 256", - ], - ], - [ - bgImagePage, - "bg-black", - "./assets/svo/0006.jpg", - [ - "div.f1.gray", - "2", - ["sup", "9", ["sup", 3]], - ["br"], - "512 x 512 x 512", - ], - ], - [ - bgImagePage, - "bg-black", - "./assets/svo/0007.jpg", - [ - "div.f1.gray", - "2", - ["sup", "10", ["sup", 3]], - ["br"], - "(1+ billion samples!)", - ], - ], - [bgImagePage, "bg-black", "./assets/svo/0008.jpg"], - [bgImagePage, "bg-black", "./assets/svo/0009.jpg"], - [bgImagePage, "bg-black", "./assets/svo/0010.jpg"], - - [bgImagePage, "bg-black", "./assets/raymarch/raymarch-cl-01.jpg"], - [bgImagePage, "bg-black", "./assets/raymarch/gyroid-metal75.jpg"], - [bgImagePage, "bg-black", "./assets/raymarch/raymarch-cl-08.jpg"], - [bgImagePage, "bg-black", "./assets/raymarch/raymarch-cl-13.jpg"], - - // [titlePage, "", [link, "http://thi.ng/raymarchcl", "thi.ng/raymarchcl"]], - - [imagePage, "bg-black", "./assets/holo/0004.jpg"], - - [titlePage, "bg-black white", "Evolutionary Programming"], - - [imagePage, "bg-black", "./assets/holo/0002.jpg"], - [imagePage, "bg-black", "./assets/holo/0003.jpg"], - - [bgImagePage, "bg-black", "./assets/holo/0005.jpg"], - [bgImagePage, "bg-black", "./assets/holo/0006.jpg"], - [bgImagePage, "bg-black", "./assets/holo/0007.jpg"], - [bgImagePage, "bg-black", "./assets/holo/0008.jpg"], - [bgImagePage, "bg-black", "./assets/holo/0009.jpg"], - [bgImagePage, "bg-black", "./assets/holo/bismut.jpg"], - [bgImagePage, "bg-black", "./assets/holo/0010.jpg"], - - [imagePage, "bg-black", "./assets/holo/barricelli.jpg"], - - [ - contentPage, - "Barricellian reproduction", - [ - link, - "http://www.chilton-computing.org.uk/acl/literature/books/gamesplaying/p004.htm", - "chilton-computing.org.uk/acl/literature/books/gamesplaying/", - ], - [ - codeBlock, - ` + ], + "Now evaluate for each XYZ cell in a volumetric grid...", + ], + + [ + bgImagePage, + "bg-black", + "./assets/svo/0000.jpg", + ["div.f1.gray", "2", ["sup", "3", ["sup", 3]], ["br"], "8 x 8 x 8"], + ], + [ + bgImagePage, + "bg-black", + "./assets/svo/0001.jpg", + ["div.f1.gray", "2", ["sup", "4", ["sup", 3]], ["br"], "16 x 16 x 16"], + ], + [ + bgImagePage, + "bg-black", + "./assets/svo/0002.jpg", + ["div.f1.gray", "2", ["sup", "5", ["sup", 3]], ["br"], "32 x 32 x 32"], + ], + [ + bgImagePage, + "bg-black", + "./assets/svo/0003.jpg", + ["div.f1.gray", "2", ["sup", "6", ["sup", 3]], ["br"], "64 x 64 x 64"], + ], + [ + bgImagePage, + "bg-black", + "./assets/svo/0004.jpg", + [ + "div.f1.gray", + "2", + ["sup", "7", ["sup", 3]], + ["br"], + "128 x 128 x 128", + ], + ], + [ + bgImagePage, + "bg-black", + "./assets/svo/0005.jpg", + [ + "div.f1.gray", + "2", + ["sup", "8", ["sup", 3]], + ["br"], + "256 x 256 x 256", + ], + ], + [ + bgImagePage, + "bg-black", + "./assets/svo/0006.jpg", + [ + "div.f1.gray", + "2", + ["sup", "9", ["sup", 3]], + ["br"], + "512 x 512 x 512", + ], + ], + [ + bgImagePage, + "bg-black", + "./assets/svo/0007.jpg", + [ + "div.f1.gray", + "2", + ["sup", "10", ["sup", 3]], + ["br"], + "(1+ billion samples!)", + ], + ], + [bgImagePage, "bg-black", "./assets/svo/0008.jpg"], + [bgImagePage, "bg-black", "./assets/svo/0009.jpg"], + [bgImagePage, "bg-black", "./assets/svo/0010.jpg"], + + [bgImagePage, "bg-black", "./assets/raymarch/raymarch-cl-01.jpg"], + [bgImagePage, "bg-black", "./assets/raymarch/gyroid-metal75.jpg"], + [bgImagePage, "bg-black", "./assets/raymarch/raymarch-cl-08.jpg"], + [bgImagePage, "bg-black", "./assets/raymarch/raymarch-cl-13.jpg"], + + // [titlePage, "", [link, "http://thi.ng/raymarchcl", "thi.ng/raymarchcl"]], + + [imagePage, "bg-black", "./assets/holo/0004.jpg"], + + [titlePage, "bg-black white", "Evolutionary Programming"], + + [imagePage, "bg-black", "./assets/holo/0002.jpg"], + [imagePage, "bg-black", "./assets/holo/0003.jpg"], + + [bgImagePage, "bg-black", "./assets/holo/0005.jpg"], + [bgImagePage, "bg-black", "./assets/holo/0006.jpg"], + [bgImagePage, "bg-black", "./assets/holo/0007.jpg"], + [bgImagePage, "bg-black", "./assets/holo/0008.jpg"], + [bgImagePage, "bg-black", "./assets/holo/0009.jpg"], + [bgImagePage, "bg-black", "./assets/holo/bismut.jpg"], + [bgImagePage, "bg-black", "./assets/holo/0010.jpg"], + + [imagePage, "bg-black", "./assets/holo/barricelli.jpg"], + + [ + contentPage, + "Barricellian reproduction", + [ + link, + "http://www.chilton-computing.org.uk/acl/literature/books/gamesplaying/p004.htm", + "chilton-computing.org.uk/acl/literature/books/gamesplaying/", + ], + [ + codeBlock, + ` integer array this generation, next generation [1 :512]; begin loop: for i : = 1 step 1 until 512 do @@ -369,21 +369,21 @@ begin copy next generation into this generation; goto loop; end;`, - ], - ], + ], + ], - [imagePage, "bg-black", "./assets/holo/0001.jpg"], - [imagePage, "bg-black", "./assets/holo/0013.jpg"], + [imagePage, "bg-black", "./assets/holo/0001.jpg"], + [imagePage, "bg-black", "./assets/holo/0013.jpg"], - [titlePage, "bg-black white", "Iterative Systems"], + [titlePage, "bg-black white", "Iterative Systems"], - [ - contentPage, - "Iterative Systems", - "DeJong strange attractor", - [ - codeBlock, - ` + [ + contentPage, + "Iterative Systems", + "DeJong strange attractor", + [ + codeBlock, + ` (defn compute-dejong "Computes a single DeJong 2d point vector for given params and XY pos" [a b c d x y] @@ -391,43 +391,43 @@ end;`, (+ (Math/sin (* a y)) (Math/cos (* (* b x) x))) (+ (Math/sin (* (* c x) x)) (Math/cos (* d y))) ])`, - ], - ], - - [imagePage, "bg-black", "./assets/lcom/0000.jpg"], - [imagePage, "bg-black", "./assets/lcom/0001.jpg"], - [bgImagePage, "bg-black", "./assets/lcom/0005.jpg"], - [bgImagePage, "bg-black", "./assets/lcom/0007.jpg"], - - [ytVideo, "WyVI5vnp570"], - - [imagePage, "bg-white", "./assets/bot/03.jpg"], - [imagePage, "bg-white", "./assets/bot/00.jpg"], - [imagePage, "bg-white", "./assets/bot/01.jpg"], - [imagePage, "bg-white", "./assets/bot/02.jpg"], - - [ - contentPage, - "L-Systems", - "Exponential growth through rule re-writing:", - [codeBlock, "S = f-f-f-fs"], - "Defines a rule named S which is iteratively expanded into:", - [ - list, - ["code", "f-f-f-fs"], - ["code", "f-f-f-ff-f-f-fs"], - ["code", "f-f-f-ff-f-f-ff-f-f-fs"], - " etc.", - ], - ], - - [ - contentPage, - "L-Systems", - "Exponential growth through rule re-writing", - [ - codeBlock, - ` + ], + ], + + [imagePage, "bg-black", "./assets/lcom/0000.jpg"], + [imagePage, "bg-black", "./assets/lcom/0001.jpg"], + [bgImagePage, "bg-black", "./assets/lcom/0005.jpg"], + [bgImagePage, "bg-black", "./assets/lcom/0007.jpg"], + + [ytVideo, "WyVI5vnp570"], + + [imagePage, "bg-white", "./assets/bot/03.jpg"], + [imagePage, "bg-white", "./assets/bot/00.jpg"], + [imagePage, "bg-white", "./assets/bot/01.jpg"], + [imagePage, "bg-white", "./assets/bot/02.jpg"], + + [ + contentPage, + "L-Systems", + "Exponential growth through rule re-writing:", + [codeBlock, "S = f-f-f-fs"], + "Defines a rule named S which is iteratively expanded into:", + [ + list, + ["code", "f-f-f-fs"], + ["code", "f-f-f-ff-f-f-fs"], + ["code", "f-f-f-ff-f-f-ff-f-f-fs"], + " etc.", + ], + ], + + [ + contentPage, + "L-Systems", + "Exponential growth through rule re-writing", + [ + codeBlock, + ` (def valid-syms { \\s :start, \\f :fwd, \\+ :right, \\- :left, @@ -440,32 +440,32 @@ end;`, :fwd [:fwd], :left [:left], :right [:right] :push [:push], :pop [:pop] })`, - ], - ], - - [ - contentPage, - "L-Systems", - "Exponential growth through rule re-writing", - [ - codeBlock, - ` + ], + ], + + [ + contentPage, + "L-Systems", + "Exponential growth through rule re-writing", + [ + codeBlock, + ` (defn parse [src] (replace valid-syms src)) (parse "-yf+xfx+fy-") ; (:left :y :fwd :right :x :fwd :x :right :fwd :y :left)`, - ], - ], - - [ - contentPage, - "L-Systems", - "Exponential growth through rule re-writing", - [ - codeBlock, - ` + ], + ], + + [ + contentPage, + "L-Systems", + "Exponential growth through rule re-writing", + [ + codeBlock, + ` (defn rewrite-symbols [rules syms] (mapcat (merge default-rules rules) syms)) @@ -479,16 +479,16 @@ end;`, (def rules {:x (parse "-yf+xfx+fy-") :y (parse "+xf-yfy-fx+") :start [:x]})`, - ], - ], - - [ - contentPage, - "L-Systems", - "Exponential growth through rule re-writing", - [ - codeBlock, - ` + ], + ], + + [ + contentPage, + "L-Systems", + "Exponential growth through rule re-writing", + [ + codeBlock, + ` (expand-with rules 2) ; (:x) @@ -503,233 +503,233 @@ end;`, (count (expand-with rules 10)) ; 218451`, - ], - ], - - [bgImagePage, "bg-black", "./assets/morphogen/20140925-desertrose.jpg"], - [imagePage, "bg-white", "./assets/morphogen/morphogen-flower-graph.jpg"], - [bgImagePage, "bg-black", "./assets/morphogen/morphogen-flower.gif"], - [bgImagePage, "bg-black", "./assets/morphogen/morphogen-virus.jpg"], - [bgImagePage, "bg-black", "./assets/morphogen/morphogen-virus.gif"], - - [titlePage, "bg-black white", "Linked Data / Visualizations"], - - [bgImagePage, "bg-black", "./assets/odi/20131011-white-agfa-optima200.jpg"], - [bgImagePage, "bg-black", "./assets/odi/frame-0800-690spp-1280x960.jpg"], - [imagePage, "bg-black", "./assets/odi/datagrid-women.jpg"], - [imagePage, "bg-black", "./assets/odi/datagrid-knife.jpg"], - [imagePage, "bg-black", "./assets/odi/datagrid-binge.jpg"], - [imagePage, "bg-white", "./assets/ws2/query-example01.jpg"], - [imagePage, "bg-white", "./assets/ws2/query-example02.jpg"], - - [imagePage, "bg-white", "./assets/ws2/edit.jpg"], - [imagePage, "bg-white", "./assets/ws2/avgprice.jpg"], - [imagePage, "bg-white", "./assets/ws2/chelsea.jpg"], - [imagePage, "bg-white", "./assets/ws2/kingston.jpg"], - [imagePage, "bg-white", "./assets/ws2/dotgraph.jpg"], - [imagePage, "bg-white", "./assets/ws2/linegraph.jpg"], - [imagePage, "bg-white", "./assets/ws2/airports.jpg"], - [imagePage, "bg-white", "./assets/ws2/gradients.jpg"], - [imagePage, "bg-white", "./assets/ws2/cosine.jpg"], - - [titlePage, "bg-black white", "Realtime"], - - [bgImagePage, "bg-black", "./assets/sjo2.jpg"], - - [titlePage, "bg-black white", "Personal Experience Report"], - - [ - quotePage, - [ - `“I still believe in abstraction, but now I know that one ends with + ], + ], + + [bgImagePage, "bg-black", "./assets/morphogen/20140925-desertrose.jpg"], + [imagePage, "bg-white", "./assets/morphogen/morphogen-flower-graph.jpg"], + [bgImagePage, "bg-black", "./assets/morphogen/morphogen-flower.gif"], + [bgImagePage, "bg-black", "./assets/morphogen/morphogen-virus.jpg"], + [bgImagePage, "bg-black", "./assets/morphogen/morphogen-virus.gif"], + + [titlePage, "bg-black white", "Linked Data / Visualizations"], + + [bgImagePage, "bg-black", "./assets/odi/20131011-white-agfa-optima200.jpg"], + [bgImagePage, "bg-black", "./assets/odi/frame-0800-690spp-1280x960.jpg"], + [imagePage, "bg-black", "./assets/odi/datagrid-women.jpg"], + [imagePage, "bg-black", "./assets/odi/datagrid-knife.jpg"], + [imagePage, "bg-black", "./assets/odi/datagrid-binge.jpg"], + [imagePage, "bg-white", "./assets/ws2/query-example01.jpg"], + [imagePage, "bg-white", "./assets/ws2/query-example02.jpg"], + + [imagePage, "bg-white", "./assets/ws2/edit.jpg"], + [imagePage, "bg-white", "./assets/ws2/avgprice.jpg"], + [imagePage, "bg-white", "./assets/ws2/chelsea.jpg"], + [imagePage, "bg-white", "./assets/ws2/kingston.jpg"], + [imagePage, "bg-white", "./assets/ws2/dotgraph.jpg"], + [imagePage, "bg-white", "./assets/ws2/linegraph.jpg"], + [imagePage, "bg-white", "./assets/ws2/airports.jpg"], + [imagePage, "bg-white", "./assets/ws2/gradients.jpg"], + [imagePage, "bg-white", "./assets/ws2/cosine.jpg"], + + [titlePage, "bg-black white", "Realtime"], + + [bgImagePage, "bg-black", "./assets/sjo2.jpg"], + + [titlePage, "bg-black white", "Personal Experience Report"], + + [ + quotePage, + [ + `“I still believe in abstraction, but now I know that one ends with abstraction, not starts with it. I learned that one has to adapt abstractions to reality and not the other way around.”`, - ], - " Alexander Stepanov", - ], - - [ - quotePage, - [ - `"I'm never bored by simplicity. Show me a simpler way to do anything + ], + " Alexander Stepanov", + ], + + [ + quotePage, + [ + `"I'm never bored by simplicity. Show me a simpler way to do anything that I'm doing. I will jump on it."`, - ], - "Charles Moore", - ], - - [ - contentPage, - "The Joy of Clojure", - [ - list, - "Concision / Expressiveness", - "Rich core API", - "Sequence abstractions", - "Immutability by default", - "Hosted / x-platform", - "Threading macros", - "Transducers, core.async, clojure.spec", - "Amount & quality of innovation", - "Maturity / Community", - "Gateway to various awesome tools / concepts", - ], - ], - - [ - contentPage, - "(Unnecessary) uphill battle(s)", - [ - "ul", - ["li", "Concision / Expressiveness"], - ["li.transition.o-0", "Rich core API"], - ["li.transition.o-0", "Sequence abstractions"], - ["li", "Immutability by default"], - ["li", "Hosted / x-platform"], - ["li.transition.o-0", "Threading macros"], - ["li.transition.o-0", "Transducers, core.async, clojure.spec"], - ["li.transition.o-0", "Amount & quality of innovation"], - ["li", "Maturity / Community"], - [ - "li.transition.o-0", - "Gateway to various awesome tools / concepts", - ], - ], - ], - - [ - quotePage, - [`"Weeks of coding can save you`, `hours of planning."`], - "Unknown", - ], - - [ - quotePage, - [`"Weeks of planning can save you`, `hours of coding."`], - "Unknown", - ], - - [ - contentPage, - "(Unnecessary) uphill battle(s)", - [ - list, - "Immutability by default", - "Almost zero docs about core internals", - `Hard to optimize / "swimming against the stream"`, - "Non-idiomatic, verbose syntax (when optimized)", - "Implementation effort of custom datatypes", - "Have to resort to macros to work around quirks / achieve DRY", - `Effort vs. gain unpredictable/unacceptable (IMHO)`, - `Protocol discrepancies between CLJ/CLJS`, - ], - ], - - [ - contentPage, - "Immutability by default", - "Areas where undesired / cumbersome:", - [ - list, - "Graphics", - "DSP", - "ML", - "Tree editing (GA/GP, spatial accel etc.)", - "Media processing", - "Web workers (e.g. w/ SharedArrayBuffer)", - "GPU / hardware / low-level interop (e.g. WASM)", - ], - ["div.f4", ["sup", "*"], "realtime"], - ], - - [ - quotePage, - [ - `"I would guess that most computers don't compute, they move bytes around."`, - ], - "Charles Moore", - ], - - [titlePage, "bg-black white", "CLJ(S) vs ES6"], - - [ - contentPage, - "Multi-methods (CLJS)", - "Vector addition", - [codeBlock, `(defmulti vadd (fn [a b] (count a)))`], - ], - - [ - contentPage, - "Multi-methods (CLJS)", - "Vector addition", - [ - codeBlock, - ` + ], + "Charles Moore", + ], + + [ + contentPage, + "The Joy of Clojure", + [ + list, + "Concision / Expressiveness", + "Rich core API", + "Sequence abstractions", + "Immutability by default", + "Hosted / x-platform", + "Threading macros", + "Transducers, core.async, clojure.spec", + "Amount & quality of innovation", + "Maturity / Community", + "Gateway to various awesome tools / concepts", + ], + ], + + [ + contentPage, + "(Unnecessary) uphill battle(s)", + [ + "ul", + ["li", "Concision / Expressiveness"], + ["li.transition.o-0", "Rich core API"], + ["li.transition.o-0", "Sequence abstractions"], + ["li", "Immutability by default"], + ["li", "Hosted / x-platform"], + ["li.transition.o-0", "Threading macros"], + ["li.transition.o-0", "Transducers, core.async, clojure.spec"], + ["li.transition.o-0", "Amount & quality of innovation"], + ["li", "Maturity / Community"], + [ + "li.transition.o-0", + "Gateway to various awesome tools / concepts", + ], + ], + ], + + [ + quotePage, + [`"Weeks of coding can save you`, `hours of planning."`], + "Unknown", + ], + + [ + quotePage, + [`"Weeks of planning can save you`, `hours of coding."`], + "Unknown", + ], + + [ + contentPage, + "(Unnecessary) uphill battle(s)", + [ + list, + "Immutability by default", + "Almost zero docs about core internals", + `Hard to optimize / "swimming against the stream"`, + "Non-idiomatic, verbose syntax (when optimized)", + "Implementation effort of custom datatypes", + "Have to resort to macros to work around quirks / achieve DRY", + `Effort vs. gain unpredictable/unacceptable (IMHO)`, + `Protocol discrepancies between CLJ/CLJS`, + ], + ], + + [ + contentPage, + "Immutability by default", + "Areas where undesired / cumbersome:", + [ + list, + "Graphics", + "DSP", + "ML", + "Tree editing (GA/GP, spatial accel etc.)", + "Media processing", + "Web workers (e.g. w/ SharedArrayBuffer)", + "GPU / hardware / low-level interop (e.g. WASM)", + ], + ["div.f4", ["sup", "*"], "realtime"], + ], + + [ + quotePage, + [ + `"I would guess that most computers don't compute, they move bytes around."`, + ], + "Charles Moore", + ], + + [titlePage, "bg-black white", "CLJ(S) vs ES6"], + + [ + contentPage, + "Multi-methods (CLJS)", + "Vector addition", + [codeBlock, `(defmulti vadd (fn [a b] (count a)))`], + ], + + [ + contentPage, + "Multi-methods (CLJS)", + "Vector addition", + [ + codeBlock, + ` (defmethod vadd 2 [a b] [(+ (a 0) (b 0)) (+ (a 1) (b 1))]) (time (dotimes [i 1e7] (vadd [1 2] [10 20]))) ; "Elapsed time: 3362.786341 msecs"`, - ], - ], - - [ - contentPage, - "Multi-methods (CLJS)", - "Vector addition", - [ - codeBlock, - ` + ], + ], + + [ + contentPage, + "Multi-methods (CLJS)", + "Vector addition", + [ + codeBlock, + ` (defmethod vadd 3 [a b] [(+ (a 0) (b 0)) (+ (a 1) (b 1)) (+ (a 2) (b 2))]) (time (dotimes [i 1e7] (vadd [1 2 3] [10 20 30]))) ; "Elapsed time: 3942.254367 msecs"`, - ], - ], - - [ - contentPage, - "Multi-methods (CLJS)", - "Using native JS arrays...", - [ - codeBlock, - ` + ], + ], + + [ + contentPage, + "Multi-methods (CLJS)", + "Using native JS arrays...", + [ + codeBlock, + ` (defmethod vadd 2 [a b] #js [(+ (aget a 0) (aget b 0)) (+ (aget a 1) (aget b 1))]) (time (dotimes [i 1e7] (vadd #js [1 2] #js [10 20]))) ; "Elapsed time: 1905.684968 msecs"`, - ], - "~1.8x faster, but brittle solution", - ], - - [ - contentPage, - "Multi-methods (CLJS)", - "Vector addition (arbitrary length)", - [ - codeBlock, - ` + ], + "~1.8x faster, but brittle solution", + ], + + [ + contentPage, + "Multi-methods (CLJS)", + "Vector addition (arbitrary length)", + [ + codeBlock, + ` (defmethod vadd :default [a b] (mapv (fn [aa bb] (+ aa bb)) a b)) (time (dotimes [i 1e7] (add [1 2 3 4 5 6 7 8] [1 2 3 4 5 6 7 8]))) ; "Elapsed time: 36108.277508 msecs"`, - ], - ], - - [ - contentPage, - "Multi-methods (CLJS)", - "Using manual loop & transients", - [ - codeBlock, - ` + ], + ], + + [ + contentPage, + "Multi-methods (CLJS)", + "Using manual loop & transients", + [ + codeBlock, + ` (defmethod add :default [a b] (loop [acc (transient[]), i 0, n (count a)] @@ -739,17 +739,17 @@ end;`, (time (dotimes [i 1e7] (add [1 2 3 4 5 6 7 8] [1 2 3 4 5 6 7 8]))) ; "Elapsed time: 15168.490184 msecs"`, - ], - "2.4x faster, but hardly idiomatic", - ], - - [ - contentPage, - "Multi-methods (ES6)", - "Vector addition", - [ - codeBlock, - ` + ], + "2.4x faster, but hardly idiomatic", + ], + + [ + contentPage, + "Multi-methods (ES6)", + "Vector addition", + [ + codeBlock, + ` vadd = defmulti((a, b) => a.length); @@ -757,63 +757,63 @@ vadd.add(2, (a, b) => [a[0] + b[0], a[1] + b[1]]); b.bench(() => vadd([1, 2], [10, 20]), 1e7); // 544ms`, - ], - ], - - [ - contentPage, - "Multi-methods (ES6)", - "Vector addition", - [ - codeBlock, - ` + ], + ], + + [ + contentPage, + "Multi-methods (ES6)", + "Vector addition", + [ + codeBlock, + ` vadd.add(3, (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]]) bench(() => vadd([1, 2, 3], [10, 20, 30]), 1e7) // 584ms`, - ], - "6x faster", - ], - - [ - contentPage, - "Multi-methods (ES6)", - "Vector addition (arbitrary length)", - [ - codeBlock, - ` + ], + "6x faster", + ], + + [ + contentPage, + "Multi-methods (ES6)", + "Vector addition (arbitrary length)", + [ + codeBlock, + ` vadd.setDefault((a, b) => a.map((a, i) => a + b[i])) bench(() => add([1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7, 8]), 1e7) // 698ms`, - ], - ["div", "only ~100ms more than Vec3"], - ["div", "50x faster than idiomatic CLJS"], - ], + ], + ["div", "only ~100ms more than Vec3"], + ["div", "50x faster than idiomatic CLJS"], + ], - [titlePage, "", [link, "http://thi.ng/umbrella", "thi.ng/umbrella"]], + [titlePage, "", [link, "http://thi.ng/umbrella", "thi.ng/umbrella"]], - [ - quotePage, - [ - `"Society created institutions in order to serve society. + [ + quotePage, + [ + `"Society created institutions in order to serve society. [...] these institutions have all become counterproductive to their original intent because they now exist to benefit themselves rather than the betterment of society."`, - ], - "Ivan Illich", - ], - - [ - titlePage, - "bg-black white", - "Thanks :)", - [twitterLink, "toxi"], - ["br"], - [twitterLink, "thing_umbrella"], - ["br"], - [link, "https://medium.com/@thi.ng", "medium.com/@thi.ng"], - ["br"], - ["p.blue", "media.thi.ng/2018/talks/clojurex/"], - ], + ], + "Ivan Illich", + ], + + [ + titlePage, + "bg-black white", + "Thanks :)", + [twitterLink, "toxi"], + ["br"], + [twitterLink, "thing_umbrella"], + ["br"], + [link, "https://medium.com/@thi.ng", "medium.com/@thi.ng"], + ["br"], + ["p.blue", "media.thi.ng/2018/talks/clojurex/"], + ], ]; diff --git a/examples/talk-slides/tsconfig.json b/examples/talk-slides/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/talk-slides/tsconfig.json +++ b/examples/talk-slides/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/text-canvas-image/package.json b/examples/text-canvas-image/package.json index 792745fd8a..e31f1dd8a9 100644 --- a/examples/text-canvas-image/package.json +++ b/examples/text-canvas-image/package.json @@ -1,32 +1,32 @@ { - "name": "@example/text-canvas-image", - "private": true, - "version": "0.0.1", - "description": "Textmode image warping w/ 16bit color output", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - }, - "dependencies": { - "@thi.ng/pixel": "workspace:^", - "@thi.ng/text-canvas": "workspace:^", - "@thi.ng/text-format": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true, - "screenshot": "examples/text-canvas-image.png" - } + "name": "@example/text-canvas-image", + "private": true, + "version": "0.0.1", + "description": "Textmode image warping w/ 16bit color output", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + }, + "dependencies": { + "@thi.ng/pixel": "workspace:^", + "@thi.ng/text-canvas": "workspace:^", + "@thi.ng/text-format": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true, + "screenshot": "examples/text-canvas-image.png" + } } diff --git a/examples/text-canvas-image/src/index.ts b/examples/text-canvas-image/src/index.ts index 33fb057ce6..b677837c26 100644 --- a/examples/text-canvas-image/src/index.ts +++ b/examples/text-canvas-image/src/index.ts @@ -11,39 +11,39 @@ const W = 64; const H = 48; (async () => { - const el = document.getElementById("app")!; - const img = await imagePromise(IMG); - const iw = img.width; - const ih = img.height; - // create 16bit color buffer from image - const buf = intBufferFromImage(img, RGB565); - // create text canvas - const c = canvas(W, H, 0xffff); - // define 16bit formatter - const fmt = FMT_HTML565(); - // precalc charcode for each pixel - const char = "▓".charCodeAt(0); + const el = document.getElementById("app")!; + const img = await imagePromise(IMG); + const iw = img.width; + const ih = img.height; + // create 16bit color buffer from image + const buf = intBufferFromImage(img, RGB565); + // create text canvas + const c = canvas(W, H, 0xffff); + // define 16bit formatter + const fmt = FMT_HTML565(); + // precalc charcode for each pixel + const char = "▓".charCodeAt(0); - let start = 0; + let start = 0; - const update = (time: number) => { - if (!start) start = time; - const t = (time - start) * 0.001; - for (let y = 0; y < H; y++) { - const v = y / H; - for (let x = 0; x < W; x++) { - // compute texture coordinates - const u = x / W; - const uu = (u + 0.1 * Math.cos(u + 20 * v - 8 * t)) * iw; - const vv = (v + 0.1 * Math.sin(v + 20 * u + 8 * t)) * ih; - // set pixel w/ color from image - setAt(c, x, y, char, buf.getAt(uu, vv) || 0xffff); - } - } - // format text canvas as HTML spans - el.innerHTML = formatCanvas(c, fmt); - requestAnimationFrame(update); - }; + const update = (time: number) => { + if (!start) start = time; + const t = (time - start) * 0.001; + for (let y = 0; y < H; y++) { + const v = y / H; + for (let x = 0; x < W; x++) { + // compute texture coordinates + const u = x / W; + const uu = (u + 0.1 * Math.cos(u + 20 * v - 8 * t)) * iw; + const vv = (v + 0.1 * Math.sin(v + 20 * u + 8 * t)) * ih; + // set pixel w/ color from image + setAt(c, x, y, char, buf.getAt(uu, vv) || 0xffff); + } + } + // format text canvas as HTML spans + el.innerHTML = formatCanvas(c, fmt); + requestAnimationFrame(update); + }; - requestAnimationFrame(update); + requestAnimationFrame(update); })(); diff --git a/examples/text-canvas-image/tsconfig.json b/examples/text-canvas-image/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/text-canvas-image/tsconfig.json +++ b/examples/text-canvas-image/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/text-canvas/package.json b/examples/text-canvas/package.json index 955d3917c6..d7d65c6b29 100644 --- a/examples/text-canvas/package.json +++ b/examples/text-canvas/package.json @@ -1,41 +1,41 @@ { - "name": "@example/text-canvas", - "private": true, - "version": "0.0.1", - "description": "3D wireframe textmode demo", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/dsp": "workspace:^", - "@thi.ng/geom": "workspace:^", - "@thi.ng/matrices": "workspace:^", - "@thi.ng/text-canvas": "workspace:^", - "@thi.ng/text-format": "workspace:^", - "@thi.ng/vectors": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "geom", - "matrices", - "text-canvas", - "text-format", - "vectors" - ], - "screenshot": "examples/text-canvas.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/text-canvas", + "private": true, + "version": "0.0.1", + "description": "3D wireframe textmode demo", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/dsp": "workspace:^", + "@thi.ng/geom": "workspace:^", + "@thi.ng/matrices": "workspace:^", + "@thi.ng/text-canvas": "workspace:^", + "@thi.ng/text-format": "workspace:^", + "@thi.ng/vectors": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "geom", + "matrices", + "text-canvas", + "text-format", + "vectors" + ], + "screenshot": "examples/text-canvas.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/text-canvas/src/index.ts b/examples/text-canvas/src/index.ts index 0e9e10b5d6..83e31ffcbc 100644 --- a/examples/text-canvas/src/index.ts +++ b/examples/text-canvas/src/index.ts @@ -19,11 +19,11 @@ import { formatCanvas } from "@thi.ng/text-canvas/format"; import { line } from "@thi.ng/text-canvas/line"; import { clear } from "@thi.ng/text-canvas/rect"; import { - BG_GREEN, - BG_LIGHT_MAGENTA, - FG_CYAN, - FG_WHITE, - FG_YELLOW, + BG_GREEN, + BG_LIGHT_MAGENTA, + FG_CYAN, + FG_WHITE, + FG_YELLOW, } from "@thi.ng/text-format/api"; import { FMT_HTML_TACHYONS } from "@thi.ng/text-format/html"; import { add3 } from "@thi.ng/vectors/add"; @@ -37,18 +37,18 @@ const canvas = new Canvas(W, H); const cube = vertices(center(aabb(1))!); // edge list (vertex indices) const edges = [ - [0, 1], - [1, 2], - [2, 3], - [3, 0], - [4, 5], - [5, 6], - [6, 7], - [7, 4], - [0, 4], - [1, 5], - [2, 6], - [3, 7], + [0, 1], + [1, 2], + [2, 3], + [3, 0], + [4, 5], + [5, 6], + [6, 7], + [7, 4], + [0, 4], + [1, 5], + [2, 6], + [3, 7], ]; // animated parameters @@ -64,65 +64,65 @@ const viewp = viewport([], 0, W, H, 0); // cube instance position offsets const instances = [ - [-1, 0, 0], - [1, 0, 0], - [0, -1, 0], - [0, 1, 0], - [0, 0, -1], - [0, 0, 1], + [-1, 0, 0], + [1, 0, 0], + [0, -1, 0], + [0, 1, 0], + [0, 0, -1], + [0, 0, 1], ]; const root = document.getElementById("app"); requestAnimationFrame(function update() { - clear(canvas); - // draw background rings - for (let i = 7, phase = ringPhase.next(); --i >= 1; ) { - const id = - i & 1 - ? ((FG_CYAN | BG_GREEN) << 16) | 0x2f - : ((FG_YELLOW | BG_GREEN) << 16) | 0x2e; - // stroke only - // circle(canvas, W / 2, H / 2, (i + phase) * 8, id); - // filled disc - circle(canvas, W / 2, H / 2, (i + phase) * 8, id, true); - } - // animated center clip rectangle for 3D cube layer - const { pos, size } = center(rect(clipSize.next()), [W / 2, H / 2])!; - beginClip(canvas, pos[0], pos[1], size[0], size[1]); - // model rotation matrix - const model = concat( - [], - rotationX44([], rotx.next()), - rotationY44([], roty.next()) - ); - // combined model-view-projection matrix - const mvp = concat([], proj, view, model); - // draw cube instances - for (let pos of instances) { - // project 3D points to 2D viewport (canvas coords) - const pts = cube.map( - (p) => project3([], mvp, viewp, add3([], p, pos))! - ); - // draw cube edges - for (let e of edges) { - const a = pts[e[0]]; - const b = pts[e[1]]; - line( - canvas, - a[0], - a[1], - b[0], - b[1], - "#", - FG_WHITE | BG_LIGHT_MAGENTA - ); - } - } - // remove clip rect - endClip(canvas); + clear(canvas); + // draw background rings + for (let i = 7, phase = ringPhase.next(); --i >= 1; ) { + const id = + i & 1 + ? ((FG_CYAN | BG_GREEN) << 16) | 0x2f + : ((FG_YELLOW | BG_GREEN) << 16) | 0x2e; + // stroke only + // circle(canvas, W / 2, H / 2, (i + phase) * 8, id); + // filled disc + circle(canvas, W / 2, H / 2, (i + phase) * 8, id, true); + } + // animated center clip rectangle for 3D cube layer + const { pos, size } = center(rect(clipSize.next()), [W / 2, H / 2])!; + beginClip(canvas, pos[0], pos[1], size[0], size[1]); + // model rotation matrix + const model = concat( + [], + rotationX44([], rotx.next()), + rotationY44([], roty.next()) + ); + // combined model-view-projection matrix + const mvp = concat([], proj, view, model); + // draw cube instances + for (let pos of instances) { + // project 3D points to 2D viewport (canvas coords) + const pts = cube.map( + (p) => project3([], mvp, viewp, add3([], p, pos))! + ); + // draw cube edges + for (let e of edges) { + const a = pts[e[0]]; + const b = pts[e[1]]; + line( + canvas, + a[0], + a[1], + b[0], + b[1], + "#", + FG_WHITE | BG_LIGHT_MAGENTA + ); + } + } + // remove clip rect + endClip(canvas); - // draw canvas - root!.innerHTML = formatCanvas(canvas, FMT_HTML_TACHYONS); - requestAnimationFrame(update); + // draw canvas + root!.innerHTML = formatCanvas(canvas, FMT_HTML_TACHYONS); + requestAnimationFrame(update); }); diff --git a/examples/text-canvas/tsconfig.json b/examples/text-canvas/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/text-canvas/tsconfig.json +++ b/examples/text-canvas/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/todo-list/package.json b/examples/todo-list/package.json index eb9e3cb594..2338dc3443 100644 --- a/examples/todo-list/package.json +++ b/examples/todo-list/package.json @@ -1,37 +1,37 @@ { - "name": "@example/todo-list", - "private": true, - "description": "Obligatory to-do list example with undo/redo", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "hdom", - "paths" - ], - "screenshot": "examples/todo-list.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/todo-list", + "private": true, + "description": "Obligatory to-do list example with undo/redo", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "hdom", + "paths" + ], + "screenshot": "examples/todo-list.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/todo-list/src/index.ts b/examples/todo-list/src/index.ts index f89360d2fa..d2ad0a6881 100644 --- a/examples/todo-list/src/index.ts +++ b/examples/todo-list/src/index.ts @@ -8,15 +8,15 @@ import { map } from "@thi.ng/transducers/map"; import { pairs } from "@thi.ng/transducers/pairs"; interface Task { - done: boolean; - body: string; + done: boolean; + body: string; } type Tasks = IObjectOf; interface State { - tasks: Tasks; - nextID: number; + tasks: Tasks; + nextID: number; } // central app state (immutable) @@ -27,85 +27,85 @@ const tasks = defHistory(defCursor(db, ["tasks"]), 100); const nextID = defCursor(db, ["nextID"]); // create derived view of tasks transformed into components const items = defView(db, ["tasks"], (tasks: Tasks) => [ - ...map(([id, t]) => taskItem(id, t), pairs(tasks)), + ...map(([id, t]) => taskItem(id, t), pairs(tasks)), ]); // state updaters // each applies its updates via the history atom wrapper // the `atom.setter` calls produce an immutable update function for given paths const addNewTask = () => - tasks.resetIn([nextID.swap((id) => id + 1)], { body: "", done: false }); + tasks.resetIn([nextID.swap((id) => id + 1)], { body: "", done: false }); const toggleTask = (id: string) => - tasks.swapIn([id, "done"], (done) => !done); + tasks.swapIn([id, "done"], (done) => !done); const updateTask = (id: string, body: string) => - tasks.resetIn([id, "body"], body); + tasks.resetIn([id, "body"], body); // single task component const taskItem = (id: string, task: Task): any[] => { - const checkAttribs = { - type: "checkbox", - checked: task.done, - onclick: () => toggleTask(id), - }; - const textAttribs = { - type: "text", - placeholder: "todo...", - value: task.body, - onkeydown: (e: any) => e.key === "Enter" && e.target.blur(), - onblur: (e: any) => updateTask(id, (e.target).value), - }; - return [ - "div", - { class: "task" + (task.done ? " done" : "") }, - ["input", checkAttribs], - ["input", textAttribs], - ]; + const checkAttribs = { + type: "checkbox", + checked: task.done, + onclick: () => toggleTask(id), + }; + const textAttribs = { + type: "text", + placeholder: "todo...", + value: task.body, + onkeydown: (e: any) => e.key === "Enter" && e.target.blur(), + onblur: (e: any) => updateTask(id, (e.target).value), + }; + return [ + "div", + { class: "task" + (task.done ? " done" : "") }, + ["input", checkAttribs], + ["input", textAttribs], + ]; }; // complete task list // uses transducer to transform all tasks using above component function const taskList = () => { - const _items = items.deref()!; - return _items.length - ? ["div#tasks", _items] - : ["div", "nothing todo, get busy..."]; + const _items = items.deref()!; + return _items.length + ? ["div#tasks", _items] + : ["div", "nothing todo, get busy..."]; }; const button = - (onclick: EventListener, body: string) => (_: any, disabled: boolean) => - ["button", { onclick, disabled }, body]; + (onclick: EventListener, body: string) => (_: any, disabled: boolean) => + ["button", { onclick, disabled }, body]; const toolbar = () => { - const btAdd = button(() => addNewTask(), "+ Add"); - const btUndo = button(() => tasks.undo(), "Undo"); - const btRedo = button(() => tasks.redo(), "Redo"); - return () => [ - "div#toolbar", - [btAdd], - [btUndo, !tasks.canUndo()], - [btRedo, !tasks.canRedo()], - ]; + const btAdd = button(() => addNewTask(), "+ Add"); + const btUndo = button(() => tasks.undo(), "Undo"); + const btRedo = button(() => tasks.redo(), "Redo"); + return () => [ + "div#toolbar", + [btAdd], + [btUndo, !tasks.canUndo()], + [btRedo, !tasks.canRedo()], + ]; }; // static header component (simple array) const header = [ - "h1", - "My tasks", - [ - "small", - "made with \u2764 ", - [ - "a", - { - href: "https://github.com/thi-ng/umbrella/tree/develop/packages/hdom", - }, - "@thi.ng/hdom", - ], - ], + "h1", + "My tasks", + [ + "small", + "made with \u2764 ", + [ + "a", + { + href: "https://github.com/thi-ng/umbrella/tree/develop/packages/hdom", + }, + "@thi.ng/hdom", + ], + ], ]; const app = () => { - return ["div", header, toolbar(), taskList]; + return ["div", header, toolbar(), taskList]; }; // kick off UI w/ root component function diff --git a/examples/todo-list/tsconfig.json b/examples/todo-list/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/todo-list/tsconfig.json +++ b/examples/todo-list/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/transducers-hdom/package.json b/examples/transducers-hdom/package.json index 6bd4b29a87..2c918d1c4e 100644 --- a/examples/transducers-hdom/package.json +++ b/examples/transducers-hdom/package.json @@ -1,34 +1,34 @@ { - "name": "@example/transducers-hdom", - "private": true, - "description": "Transducer & rstream based hdom UI updates", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "rstream", - "transducers-hdom" - ] - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/transducers-hdom", + "private": true, + "description": "Transducer & rstream based hdom UI updates", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "rstream", + "transducers-hdom" + ] + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/transducers-hdom/src/index.ts b/examples/transducers-hdom/src/index.ts index 11d1a0aa15..6dcc5be343 100644 --- a/examples/transducers-hdom/src/index.ts +++ b/examples/transducers-hdom/src/index.ts @@ -9,29 +9,29 @@ import { scan } from "@thi.ng/transducers/scan"; // root component function // (using Tachyons CSS classes for styling) const app = ({ ticks, clicks }: any) => [ - "div.vh-100.dt.w-100.bg-dark-pink.sans-serif", - [ - "div.dtc.v-mid.tc.white.ph3.ph4-l", - [ - "h1.f6.f2-m.f-subheadline-l.fw1.tc", - `${ticks} ticks & `, - [ - "a.link.white.bb.bw1", - { href: "#", onclick: () => clickStream.next(0) }, - `${clicks} clicks`, - ], - ], - [ - "div", - [ - "a.link.white", - { - href: "https://github.com/thi-ng/umbrella/tree/develop/examples/transducers-hdom/", - }, - "Source code", - ], - ], - ], + "div.vh-100.dt.w-100.bg-dark-pink.sans-serif", + [ + "div.dtc.v-mid.tc.white.ph3.ph4-l", + [ + "h1.f6.f2-m.f-subheadline-l.fw1.tc", + `${ticks} ticks & `, + [ + "a.link.white.bb.bw1", + { href: "#", onclick: () => clickStream.next(0) }, + `${clicks} clicks`, + ], + ], + [ + "div", + [ + "a.link.white", + { + href: "https://github.com/thi-ng/umbrella/tree/develop/examples/transducers-hdom/", + }, + "Source code", + ], + ], + ], ]; // click stream (click counter) @@ -41,18 +41,18 @@ const clickStream = stream().transform(scan(count(-1))); // waits until all inputs have produced at least one value, // then updates whenever any input has changed sync({ - // streams to synchronize - src: { - ticks: fromInterval(1000), - clicks: clickStream, - }, - // only synchronize at first (default) - reset: false, + // streams to synchronize + src: { + ticks: fromInterval(1000), + clicks: clickStream, + }, + // only synchronize at first (default) + reset: false, }).transform( - // transform into hdom component - map(app), - // apply hdom tree to real DOM - updateDOM() + // transform into hdom component + map(app), + // apply hdom tree to real DOM + updateDOM() ); // kick off diff --git a/examples/transducers-hdom/tsconfig.json b/examples/transducers-hdom/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/transducers-hdom/tsconfig.json +++ b/examples/transducers-hdom/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/triple-query/package.json b/examples/triple-query/package.json index 01babea05e..10e6cf0688 100644 --- a/examples/triple-query/package.json +++ b/examples/triple-query/package.json @@ -1,49 +1,49 @@ { - "name": "@example/triple-query", - "private": true, - "description": "Triple store query results & sortable table", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/atom": "workspace:^", - "@thi.ng/checks": "workspace:^", - "@thi.ng/compare": "workspace:^", - "@thi.ng/expose": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/interceptors": "workspace:^", - "@thi.ng/paths": "workspace:^", - "@thi.ng/rstream-query": "workspace:^", - "@thi.ng/transducers": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "atom", - "compare", - "expose", - "hdom", - "hdom-components", - "paths", - "rstream-query", - "transducers" - ], - "screenshot": "examples/triple-query.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/triple-query", + "private": true, + "description": "Triple store query results & sortable table", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/atom": "workspace:^", + "@thi.ng/checks": "workspace:^", + "@thi.ng/compare": "workspace:^", + "@thi.ng/expose": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/interceptors": "workspace:^", + "@thi.ng/paths": "workspace:^", + "@thi.ng/rstream-query": "workspace:^", + "@thi.ng/transducers": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "atom", + "compare", + "expose", + "hdom", + "hdom-components", + "paths", + "rstream-query", + "transducers" + ], + "screenshot": "examples/triple-query.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/triple-query/src/api.ts b/examples/triple-query/src/api.ts index dc77a28de9..50502989c2 100644 --- a/examples/triple-query/src/api.ts +++ b/examples/triple-query/src/api.ts @@ -1,10 +1,10 @@ import type { Fn, IObjectOf, Path } from "@thi.ng/api"; import type { IView } from "@thi.ng/atom"; import type { - EffectDef, - EventBus, - EventDef, - InterceptorContext, + EffectDef, + EventBus, + EventDef, + InterceptorContext, } from "@thi.ng/interceptors"; import type { QuerySpec, TripleStore } from "@thi.ng/rstream-query"; @@ -23,38 +23,38 @@ export type ViewSpec = string | Path | [string | Path, Fn]; * See `src/config.ts`. */ export interface AppConfig { - events: IObjectOf; - effects: IObjectOf; - domRoot: string | Element; - initialState: any; - rootComponent: AppComponent; - ui: UIAttribs; - views: Partial>; - data: { - cities: string[][]; - countries: string[][]; - regions: string[]; - queries: IObjectOf; - }; + events: IObjectOf; + effects: IObjectOf; + domRoot: string | Element; + initialState: any; + rootComponent: AppComponent; + ui: UIAttribs; + views: Partial>; + data: { + cities: string[][]; + countries: string[][]; + regions: string[]; + queries: IObjectOf; + }; } export type AppViewIDs = - | "page" - | "pagedTriples" - | "cities" - | "countries" - | "sort"; + | "page" + | "pagedTriples" + | "cities" + | "countries" + | "sort"; /** * Base structure of derived views exposed by the base app. * Add more declarations here as needed. */ export interface AppViews extends Record> { - page: IView; - pagedTriples: IView; - cities: IView; - countries: IView; - sort: IView<[number, boolean]>; + page: IView; + pagedTriples: IView; + cities: IView; + countries: IView; + sort: IView<[number, boolean]>; } /** @@ -65,25 +65,25 @@ export interface AppViews extends Record> { * component functions. */ export interface UIAttribs { - button: any; - buttonDisabled: any; - buttongroup: any; - link: any; - root: any; - table: { root: any; head: any; headlink: any; row: any; cell: any }; - pager: { root: any; prev: any; pages: any; next: any }; + button: any; + buttonDisabled: any; + buttongroup: any; + link: any; + root: any; + table: { root: any; head: any; headlink: any; row: any; cell: any }; + pager: { root: any; prev: any; pages: any; next: any }; } /** * Structure of the context object passed to all component functions */ export interface AppContext { - bus: EventBus; - views: AppViews; - ui: UIAttribs; - store: TripleStore; + bus: EventBus; + views: AppViews; + ui: UIAttribs; + store: TripleStore; } export interface AppInterceptorContext extends InterceptorContext { - store: TripleStore; + store: TripleStore; } diff --git a/examples/triple-query/src/app.ts b/examples/triple-query/src/app.ts index beafc23122..3ca3451738 100644 --- a/examples/triple-query/src/app.ts +++ b/examples/triple-query/src/app.ts @@ -20,86 +20,86 @@ import * as ev from "./events"; * - start hdom render & event bus loop */ export class App { - config: AppConfig; - ctx: AppContext; - state: Atom; + config: AppConfig; + ctx: AppContext; + state: Atom; - constructor(config: AppConfig) { - this.config = config; - this.state = new Atom(config.initialState || {}); - this.ctx = { - bus: new EventBus(this.state, config.events, config.effects), - store: new TripleStore(), - views: {}, - ui: config.ui, - }; - this.addViews(this.config.views); - } + constructor(config: AppConfig) { + this.config = config; + this.state = new Atom(config.initialState || {}); + this.ctx = { + bus: new EventBus(this.state, config.events, config.effects), + store: new TripleStore(), + views: {}, + ui: config.ui, + }; + this.addViews(this.config.views); + } - /** - * Initializes given derived view specs and attaches them to app - * state atom. - * - * @param specs - - */ - addViews(specs: IObjectOf) { - const views: any = this.ctx.views; - for (let id in specs) { - const spec = specs[id]; - views[id] = isArray(spec) - ? defViewUnsafe(this.state, spec[0], >spec[1]) - : defViewUnsafe(this.state, spec); - } - } + /** + * Initializes given derived view specs and attaches them to app + * state atom. + * + * @param specs - + */ + addViews(specs: IObjectOf) { + const views: any = this.ctx.views; + for (let id in specs) { + const spec = specs[id]; + views[id] = isArray(spec) + ? defViewUnsafe(this.state, spec[0], >spec[1]) + : defViewUnsafe(this.state, spec); + } + } - /** - * Calls `init()` and kicks off hdom render loop, including batched - * event processing and fast fail check if DOM updates are necessary - * (assumes ALL state is held in the app state atom). So if there - * weren't any events causing a state change since last frame, - * re-rendering is skipped without even attempting to diff DOM - * tree). - */ - start() { - this.init(); - // assume main root component is a higher order function - // call it here to pre-initialize it - const root = this.config.rootComponent(this.ctx); - let firstFrame = true; - start( - () => { - if ( - this.ctx.bus.processQueue({ store: this.ctx.store }) || - firstFrame - ) { - firstFrame = false; - return root(); - } - }, - { root: this.config.domRoot, ctx: this.ctx } - ); - } + /** + * Calls `init()` and kicks off hdom render loop, including batched + * event processing and fast fail check if DOM updates are necessary + * (assumes ALL state is held in the app state atom). So if there + * weren't any events causing a state change since last frame, + * re-rendering is skipped without even attempting to diff DOM + * tree). + */ + start() { + this.init(); + // assume main root component is a higher order function + // call it here to pre-initialize it + const root = this.config.rootComponent(this.ctx); + let firstFrame = true; + start( + () => { + if ( + this.ctx.bus.processQueue({ store: this.ctx.store }) || + firstFrame + ) { + firstFrame = false; + return root(); + } + }, + { root: this.config.domRoot, ctx: this.ctx } + ); + } - /** - * User initialization hook. - * Automatically called from `start()` - */ - init() { - // ...add init tasks here - const conf = this.config.data; - const store = this.ctx.store; - conf.cities.forEach((x) => this.ctx.bus.dispatch([ev.ADD_CITY, x])); - conf.countries.forEach((x) => - this.ctx.bus.dispatch([ev.ADD_COUNTRY, x]) - ); - for (let q in conf.queries) { - store.addQueryFromSpec(conf.queries[q]).subscribe({ - next: (res) => - this.ctx.bus.dispatch([ - EV_SET_VALUE, - [["queries", q], res], - ]), - }); - } - } + /** + * User initialization hook. + * Automatically called from `start()` + */ + init() { + // ...add init tasks here + const conf = this.config.data; + const store = this.ctx.store; + conf.cities.forEach((x) => this.ctx.bus.dispatch([ev.ADD_CITY, x])); + conf.countries.forEach((x) => + this.ctx.bus.dispatch([ev.ADD_COUNTRY, x]) + ); + for (let q in conf.queries) { + store.addQueryFromSpec(conf.queries[q]).subscribe({ + next: (res) => + this.ctx.bus.dispatch([ + EV_SET_VALUE, + [["queries", q], res], + ]), + }); + } + } } diff --git a/examples/triple-query/src/components/button-group.ts b/examples/triple-query/src/components/button-group.ts index 446eed82d0..e20c3a2037 100644 --- a/examples/triple-query/src/components/button-group.ts +++ b/examples/triple-query/src/components/button-group.ts @@ -2,9 +2,9 @@ import type { AppContext } from "../api"; import { button } from "./button"; export function buttonGroup(ctx: AppContext, ...buttons: any[]) { - return [ - "section", - ctx.ui.buttongroup, - buttons.map((bt) => [button, ...bt]), - ]; + return [ + "section", + ctx.ui.buttongroup, + buttons.map((bt) => [button, ...bt]), + ]; } diff --git a/examples/triple-query/src/components/button.ts b/examples/triple-query/src/components/button.ts index ada9a8686f..9fc44f919a 100644 --- a/examples/triple-query/src/components/button.ts +++ b/examples/triple-query/src/components/button.ts @@ -2,12 +2,12 @@ import type { AppContext } from "../api"; import { eventLink } from "./event-link"; export function button( - ctx: AppContext, - event: Event, - label: string, - disabled = false + ctx: AppContext, + event: Event, + label: string, + disabled = false ) { - return disabled - ? ["span", ctx.ui.buttonDisabled, label] - : [eventLink, ctx.ui.button, event, label]; + return disabled + ? ["span", ctx.ui.buttonDisabled, label] + : [eventLink, ctx.ui.button, event, label]; } diff --git a/examples/triple-query/src/components/event-link.ts b/examples/triple-query/src/components/event-link.ts index e71b4e0ef9..f8e45c1b75 100644 --- a/examples/triple-query/src/components/event-link.ts +++ b/examples/triple-query/src/components/event-link.ts @@ -5,26 +5,26 @@ import type { AppContext } from "../api"; * Customizable hyperlink component emitting given event on event bus * when clicked. * - * @param ctx - + * @param ctx - * @param event - vent tuple of `[event-id, payload]` * @param attribs - lement attribs * @param body - ink body */ export function eventLink( - ctx: AppContext, - attribs: any, - event: Event, - body: any + ctx: AppContext, + attribs: any, + event: Event, + body: any ) { - return [ - "a", - { - ...attribs, - onclick: (e: any) => { - e.preventDefault(); - ctx.bus.dispatch(event); - }, - }, - body, - ]; + return [ + "a", + { + ...attribs, + onclick: (e: any) => { + e.preventDefault(); + ctx.bus.dispatch(event); + }, + }, + body, + ]; } diff --git a/examples/triple-query/src/components/link.ts b/examples/triple-query/src/components/link.ts index a9d250debb..b3783b2063 100644 --- a/examples/triple-query/src/components/link.ts +++ b/examples/triple-query/src/components/link.ts @@ -1,5 +1,5 @@ import type { AppContext } from "../api"; export function link(ctx: AppContext, href: string, ...body: any[]) { - return ["a", { ...ctx.ui.link, href }, ...body]; + return ["a", { ...ctx.ui.link, href }, ...body]; } diff --git a/examples/triple-query/src/components/main.ts b/examples/triple-query/src/components/main.ts index ac4e3a8254..4e67f71360 100644 --- a/examples/triple-query/src/components/main.ts +++ b/examples/triple-query/src/components/main.ts @@ -3,17 +3,17 @@ import { queryResults } from "./query-results"; import { tripleTable } from "./triple-table"; export function main(ctx: AppContext) { - const triples = tripleTable(); - return () => [ - "div", - ctx.ui.root, - [ - triples, - ctx.views.pagedTriples.deref(), - ctx.store.triples.length, - ctx.views.page.deref(), - ], - [queryResults, "Cities", ctx.views.cities.deref()], - [queryResults, "Countries", ctx.views.countries.deref()], - ]; + const triples = tripleTable(); + return () => [ + "div", + ctx.ui.root, + [ + triples, + ctx.views.pagedTriples.deref(), + ctx.store.triples.length, + ctx.views.page.deref(), + ], + [queryResults, "Cities", ctx.views.cities.deref()], + [queryResults, "Countries", ctx.views.countries.deref()], + ]; } diff --git a/examples/triple-query/src/components/query-results.ts b/examples/triple-query/src/components/query-results.ts index 8052f79fe4..56ae78e4e7 100644 --- a/examples/triple-query/src/components/query-results.ts +++ b/examples/triple-query/src/components/query-results.ts @@ -6,26 +6,26 @@ import { section } from "./section"; import { table } from "./table"; export const queryResults = ( - _: AppContext, - title: string, - results: Set + _: AppContext, + title: string, + results: Set ) => { - if (results) { - const [first] = results; - const keys = Object.keys(first).sort(); - return [ - section, - title, - ` (${results.size})`, - [ - table, - ["10%", ...repeat(`${(90 / keys.length) | 0}%`, keys.length)], - ["id", ...keys], - mapIndexed( - (i, x) => [i + 1, ...map((k: string) => x[k], keys)], - results - ), - ], - ]; - } + if (results) { + const [first] = results; + const keys = Object.keys(first).sort(); + return [ + section, + title, + ` (${results.size})`, + [ + table, + ["10%", ...repeat(`${(90 / keys.length) | 0}%`, keys.length)], + ["id", ...keys], + mapIndexed( + (i, x) => [i + 1, ...map((k: string) => x[k], keys)], + results + ), + ], + ]; + } }; diff --git a/examples/triple-query/src/components/section.ts b/examples/triple-query/src/components/section.ts index b9a2ec0fce..4200907520 100644 --- a/examples/triple-query/src/components/section.ts +++ b/examples/triple-query/src/components/section.ts @@ -3,8 +3,8 @@ import { title } from "@thi.ng/hdom-components/title"; const h1 = title({ subAttribs: { class: "moon-gray" } }); export const section = ( - _: any, - title: string, - subtitle: string, - ...body: any[] + _: any, + title: string, + subtitle: string, + ...body: any[] ) => ["section", [h1, title, subtitle], ...body]; diff --git a/examples/triple-query/src/components/table.ts b/examples/triple-query/src/components/table.ts index deef5e0d20..836d0b0d38 100644 --- a/examples/triple-query/src/components/table.ts +++ b/examples/triple-query/src/components/table.ts @@ -2,23 +2,23 @@ import { map } from "@thi.ng/transducers/map"; import type { AppContext } from "../api"; const row = (ctx: AppContext, body: Iterable) => [ - "tr", - ctx.ui.table.row, - ...body, + "tr", + ctx.ui.table.row, + ...body, ]; export const table = ( - ctx: AppContext, - layout: any[], - head: Iterable, - body: Iterable> + ctx: AppContext, + layout: any[], + head: Iterable, + body: Iterable> ) => [ - "table", - ctx.ui.table.root, - map((x) => ["col", { style: { width: x } }], layout || []), - [row, map((x) => ["th", ctx.ui.table.head, x], head)], - map( - (cols: any) => [row, map((x) => ["td", ctx.ui.table.cell, x], cols)], - body - ), + "table", + ctx.ui.table.root, + map((x) => ["col", { style: { width: x } }], layout || []), + [row, map((x) => ["th", ctx.ui.table.head, x], head)], + map( + (cols: any) => [row, map((x) => ["td", ctx.ui.table.cell, x], cols)], + body + ), ]; diff --git a/examples/triple-query/src/components/triple-table.ts b/examples/triple-query/src/components/triple-table.ts index eab4cef1ad..75d63b5fcc 100644 --- a/examples/triple-query/src/components/triple-table.ts +++ b/examples/triple-query/src/components/triple-table.ts @@ -8,40 +8,40 @@ import { section } from "./section"; import { table } from "./table"; export const tripleTable = () => { - const _pager = pager({ - root: (ctx, ...body) => ["div", ctx.ui.pager.root, ...body], - button: (i, _, __, label, disabled) => [ - button, - [SET_PAGE, i], - label, - disabled, - ], - groupPrev: (ctx, ...bts) => ["div", ctx.ui.pager.prev, ...bts], - groupNext: (ctx, ...bts) => ["div", ctx.ui.pager.next, ...bts], - groupPages: (ctx, bts) => ["div", ctx.ui.pager.pages, bts], - }); - return (ctx: AppContext, triples: any[], num: number, page: number) => { - const [sid, sdir] = ctx.views.sort.deref()!; - const icon = sdir ? "🔽" : "🔼"; - return [ - section, - "All triples", - ` (${ctx.store.triples.length})`, - [ - table, - ["10%", "30%", "30%", "30%"], - [ - "id", - ...["subject", "predicate", "object"].map((x, i) => [ - eventLink, - ctx.ui.table.headlink, - [SET_SORT, i], - `${x} ${sid === i ? icon : ""}`, - ]), - ], - triples, - ], - [_pager, page, num, PAGE_LEN, 5], - ]; - }; + const _pager = pager({ + root: (ctx, ...body) => ["div", ctx.ui.pager.root, ...body], + button: (i, _, __, label, disabled) => [ + button, + [SET_PAGE, i], + label, + disabled, + ], + groupPrev: (ctx, ...bts) => ["div", ctx.ui.pager.prev, ...bts], + groupNext: (ctx, ...bts) => ["div", ctx.ui.pager.next, ...bts], + groupPages: (ctx, bts) => ["div", ctx.ui.pager.pages, bts], + }); + return (ctx: AppContext, triples: any[], num: number, page: number) => { + const [sid, sdir] = ctx.views.sort.deref()!; + const icon = sdir ? "🔽" : "🔼"; + return [ + section, + "All triples", + ` (${ctx.store.triples.length})`, + [ + table, + ["10%", "30%", "30%", "30%"], + [ + "id", + ...["subject", "predicate", "object"].map((x, i) => [ + eventLink, + ctx.ui.table.headlink, + [SET_SORT, i], + `${x} ${sid === i ? icon : ""}`, + ]), + ], + triples, + ], + [_pager, page, num, PAGE_LEN, 5], + ]; + }; }; diff --git a/examples/triple-query/src/config.ts b/examples/triple-query/src/config.ts index df583c6347..40e5ceec30 100644 --- a/examples/triple-query/src/config.ts +++ b/examples/triple-query/src/config.ts @@ -4,143 +4,143 @@ import { EFFECTS, EVENTS } from "./handlers"; // main App configuration export const CONFIG: AppConfig = { - // event handlers events are queued and batch processed in app's RAF - // render loop event handlers can be single functions, interceptor - // objects with `pre`/`post` keys or arrays of either. + // event handlers events are queued and batch processed in app's RAF + // render loop event handlers can be single functions, interceptor + // objects with `pre`/`post` keys or arrays of either. - // the event handlers' only task is to transform the event into a - // number of side effects. event handlers should be pure functions - // and only side effect functions execute any "real" work. + // the event handlers' only task is to transform the event into a + // number of side effects. event handlers should be pure functions + // and only side effect functions execute any "real" work. - // Docs here: - // https://docs.thi.ng/umbrella/interceptors/#event-bus-interceptors-side-effects + // Docs here: + // https://docs.thi.ng/umbrella/interceptors/#event-bus-interceptors-side-effects - events: EVENTS, + events: EVENTS, - // custom side effects - effects: EFFECTS, + // custom side effects + effects: EFFECTS, - // DOM root element (or ID) - domRoot: "app", + // DOM root element (or ID) + domRoot: "app", - // root component function used by the app - rootComponent: main, + // root component function used by the app + rootComponent: main, - // initial app state - initialState: { - page: 0, - pagedTriples: [], - sort: [0, false], - }, + // initial app state + initialState: { + page: 0, + pagedTriples: [], + sort: [0, false], + }, - // derived view declarations - // each key specifies the name of the view and each value is - // a state path or `[path, transformer]` tuple - // docs here: - // https://github.com/thi-ng/umbrella/tree/develop/packages/atom#derived-views - // also see `app.ts` for view initialization - views: { - page: "page", - pagedTriples: "pagedTriples", - cities: "queries.cities", - countries: "queries.countries", - sort: "sort", - }, + // derived view declarations + // each key specifies the name of the view and each value is + // a state path or `[path, transformer]` tuple + // docs here: + // https://github.com/thi-ng/umbrella/tree/develop/packages/atom#derived-views + // also see `app.ts` for view initialization + views: { + page: "page", + pagedTriples: "pagedTriples", + cities: "queries.cities", + countries: "queries.countries", + sort: "sort", + }, - // component CSS class config using http://tachyons.io/ these - // attribs are made available to all components and allow for easy - // re-skinning of the whole app - ui: { - button: { - class: "pointer bg-black hover-bg-blue bg-animate white pa2 mr1 w-100 ttu b tracked-tight noselect", - }, - buttonDisabled: { - class: "bg-gray white pa2 mr1 w-100 ttu b tracked-tight noselect", - }, - buttongroup: { class: "flex mb2" }, - link: { class: "pointer link dim black b" }, - root: { class: "pa2 mw7-ns center f7 f6-m f5-ns" }, - table: { - root: { class: "w-100 collapse ba br2 b--black-10 pv2 ph3" }, - head: { class: "tl pv2 ph3 bg-black white" }, - headlink: { class: "pointer white" }, - row: { class: "striped--light-gray" }, - cell: { class: "pv2 ph3" }, - }, - pager: { - root: { class: "w-100 mt3 f7 tc" }, - prev: { class: "fl mr3" }, - next: { class: "fr ml3" }, - pages: { class: "dib" }, - }, - }, + // component CSS class config using http://tachyons.io/ these + // attribs are made available to all components and allow for easy + // re-skinning of the whole app + ui: { + button: { + class: "pointer bg-black hover-bg-blue bg-animate white pa2 mr1 w-100 ttu b tracked-tight noselect", + }, + buttonDisabled: { + class: "bg-gray white pa2 mr1 w-100 ttu b tracked-tight noselect", + }, + buttongroup: { class: "flex mb2" }, + link: { class: "pointer link dim black b" }, + root: { class: "pa2 mw7-ns center f7 f6-m f5-ns" }, + table: { + root: { class: "w-100 collapse ba br2 b--black-10 pv2 ph3" }, + head: { class: "tl pv2 ph3 bg-black white" }, + headlink: { class: "pointer white" }, + row: { class: "striped--light-gray" }, + cell: { class: "pv2 ph3" }, + }, + pager: { + root: { class: "w-100 mt3 f7 tc" }, + prev: { class: "fl mr3" }, + next: { class: "fr ml3" }, + pages: { class: "dib" }, + }, + }, - data: { - cities: [ - ["accra", "gh"], - ["amsterdam", "nl"], - ["berlin", "de"], - ["dublin", "ie"], - ["johannesburg", "za"], - ["london", "uk"], - ["new york", "us"], - ["san francisco", "us"], - ["são paulo", "br"], - ["shanghai", "cn"], - ["tokyo", "jp"], - ["toronto", "ca"], - ], - countries: [ - ["au", "australia", "oceania"], - ["br", "brasil", "south-america"], - ["ca", "canada", "north-america"], - ["cn", "china", "asia"], - ["de", "germany", "europe"], - ["gh", "ghana", "africa"], - ["ie", "ireland", "europe"], - ["nl", "netherlands", "europe"], - ["jp", "japan", "asia"], - ["za", "south africa", "africa"], - ["nz", "new zealand", "oceania"], - ["us", "united states", "north-america"], - ], - regions: [ - "africa", - "asia", - "central-america", - "europe", - "middle-east", - "north-america", - "oceania", - "south-america", - "caribbean", - ], - queries: { - cities: { - q: [ - { - where: [ - ["?city", "type", "city"], - ["?city", "locatedIn", "?cid"], - ["?cid", "type", "country"], - ["?cid", "name", "?country"], - ["?cid", "partOf", "?region"], - ], - }, - ], - select: ["city", "country", "region"], - }, - countries: { - q: [ - { - where: [ - ["?code", "type", "country"], - ["?code", "partOf", "?region"], - ["?region", "type", "region"], - ], - }, - ], - }, - }, - }, + data: { + cities: [ + ["accra", "gh"], + ["amsterdam", "nl"], + ["berlin", "de"], + ["dublin", "ie"], + ["johannesburg", "za"], + ["london", "uk"], + ["new york", "us"], + ["san francisco", "us"], + ["são paulo", "br"], + ["shanghai", "cn"], + ["tokyo", "jp"], + ["toronto", "ca"], + ], + countries: [ + ["au", "australia", "oceania"], + ["br", "brasil", "south-america"], + ["ca", "canada", "north-america"], + ["cn", "china", "asia"], + ["de", "germany", "europe"], + ["gh", "ghana", "africa"], + ["ie", "ireland", "europe"], + ["nl", "netherlands", "europe"], + ["jp", "japan", "asia"], + ["za", "south africa", "africa"], + ["nz", "new zealand", "oceania"], + ["us", "united states", "north-america"], + ], + regions: [ + "africa", + "asia", + "central-america", + "europe", + "middle-east", + "north-america", + "oceania", + "south-america", + "caribbean", + ], + queries: { + cities: { + q: [ + { + where: [ + ["?city", "type", "city"], + ["?city", "locatedIn", "?cid"], + ["?cid", "type", "country"], + ["?cid", "name", "?country"], + ["?cid", "partOf", "?region"], + ], + }, + ], + select: ["city", "country", "region"], + }, + countries: { + q: [ + { + where: [ + ["?code", "type", "country"], + ["?code", "partOf", "?region"], + ["?region", "type", "region"], + ], + }, + ], + }, + }, + }, }; diff --git a/examples/triple-query/src/handlers.ts b/examples/triple-query/src/handlers.ts index 5692c4bbbd..4b1abb44f9 100644 --- a/examples/triple-query/src/handlers.ts +++ b/examples/triple-query/src/handlers.ts @@ -1,12 +1,12 @@ import type { IObjectOf } from "@thi.ng/api"; import { compare } from "@thi.ng/compare/compare"; import { - dispatchNow, - type EffectDef, - type EventDef, - FX_DISPATCH_NOW, - FX_STATE, - valueSetter, + dispatchNow, + type EffectDef, + type EventDef, + FX_DISPATCH_NOW, + FX_STATE, + valueSetter, } from "@thi.ng/interceptors"; import { getIn } from "@thi.ng/paths/get-in"; import { setIn } from "@thi.ng/paths/set-in"; @@ -23,81 +23,81 @@ import * as ev from "./events"; export const PAGE_LEN = 5; export const EVENTS: IObjectOf = { - [ev.ADD_COUNTRY]: (_, [__, [id, name, region]]) => ({ - [fx.ADD_TRIPLE]: [ - [id, "type", "country"], - [id, "name", name], - [id, "partOf", region], - [region, "type", "region"], - ], - }), + [ev.ADD_COUNTRY]: (_, [__, [id, name, region]]) => ({ + [fx.ADD_TRIPLE]: [ + [id, "type", "country"], + [id, "name", name], + [id, "partOf", region], + [region, "type", "region"], + ], + }), - [ev.ADD_CITY]: (_, [__, [city, countryID]]) => ({ - [fx.ADD_TRIPLE]: [ - [city, "type", "city"], - [city, "locatedIn", countryID], - ], - }), + [ev.ADD_CITY]: (_, [__, [city, countryID]]) => ({ + [fx.ADD_TRIPLE]: [ + [city, "type", "city"], + [city, "locatedIn", countryID], + ], + }), - [ev.SET_SORT]: (state, [_, i]) => { - const sort = getIn(state, ["sort"]); - return { - [FX_STATE]: setIn( - state, - ["sort"], - [i, sort[0] === i ? !sort[1] : false] - ), - [FX_DISPATCH_NOW]: [ev.UPDATE_PAGE], - }; - }, + [ev.SET_SORT]: (state, [_, i]) => { + const sort = getIn(state, ["sort"]); + return { + [FX_STATE]: setIn( + state, + ["sort"], + [i, sort[0] === i ? !sort[1] : false] + ), + [FX_DISPATCH_NOW]: [ev.UPDATE_PAGE], + }; + }, - [ev.SET_PAGE]: [valueSetter("page"), dispatchNow([ev.UPDATE_PAGE])], + [ev.SET_PAGE]: [valueSetter("page"), dispatchNow([ev.UPDATE_PAGE])], - [ev.UPDATE_PAGE]: (state, _, __, ctx) => { - const maxPage = Math.floor( - Math.max(0, ctx.store.triples.length - 1) / PAGE_LEN - ); - let curr = getIn(state, ["page"]); - let sort = getIn(state, ["sort"]); - if (curr > maxPage) { - state = setIn(state, ["page"], (curr = maxPage)); - } - return { - [FX_STATE]: setIn( - state, - ["pagedTriples"], - [ - ...iterator( - comp( - page(curr, PAGE_LEN), - mapIndexed( - (i, x: Triple) => [i + 1, ...x], - curr * PAGE_LEN - ), - padLast(PAGE_LEN, [...repeat("\u00a0", 4)]) - ), - ctx.store.triples - .slice() - .sort(comparator.apply(null, sort)) - ), - ] - ), - }; - }, + [ev.UPDATE_PAGE]: (state, _, __, ctx) => { + const maxPage = Math.floor( + Math.max(0, ctx.store.triples.length - 1) / PAGE_LEN + ); + let curr = getIn(state, ["page"]); + let sort = getIn(state, ["sort"]); + if (curr > maxPage) { + state = setIn(state, ["page"], (curr = maxPage)); + } + return { + [FX_STATE]: setIn( + state, + ["pagedTriples"], + [ + ...iterator( + comp( + page(curr, PAGE_LEN), + mapIndexed( + (i, x: Triple) => [i + 1, ...x], + curr * PAGE_LEN + ), + padLast(PAGE_LEN, [...repeat("\u00a0", 4)]) + ), + ctx.store.triples + .slice() + .sort(comparator.apply(null, sort)) + ), + ] + ), + }; + }, }; export const EFFECTS: IObjectOf = { - [fx.ADD_TRIPLE]: (triple: Triple, bus, ctx) => { - ctx.store.add(triple); - bus.dispatch([ev.UPDATE_PAGE]); - }, - [fx.REMOVE_TRIPLE]: (triple: Triple, bus, ctx) => { - ctx.store.delete(triple); - bus.dispatch([ev.UPDATE_PAGE]); - }, + [fx.ADD_TRIPLE]: (triple: Triple, bus, ctx) => { + ctx.store.add(triple); + bus.dispatch([ev.UPDATE_PAGE]); + }, + [fx.REMOVE_TRIPLE]: (triple: Triple, bus, ctx) => { + ctx.store.delete(triple); + bus.dispatch([ev.UPDATE_PAGE]); + }, }; const comparator = (i: number, rev: boolean) => - rev - ? (a: Triple, b: Triple) => compare(b[i], a[i]) - : (a: Triple, b: Triple) => compare(a[i], b[i]); + rev + ? (a: Triple, b: Triple) => compare(b[i], a[i]) + : (a: Triple, b: Triple) => compare(a[i], b[i]); diff --git a/examples/triple-query/tsconfig.json b/examples/triple-query/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/triple-query/tsconfig.json +++ b/examples/triple-query/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/webgl-cube/package.json b/examples/webgl-cube/package.json index 4734dc400c..24908eb131 100644 --- a/examples/webgl-cube/package.json +++ b/examples/webgl-cube/package.json @@ -1,42 +1,42 @@ { - "name": "@example/webgl-cube", - "private": true, - "description": "WebGL multi-colored cube mesh", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/matrices": "workspace:^", - "@thi.ng/soa": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^", - "@thi.ng/webgl": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "matrices", - "soa", - "transducers", - "vectors", - "webgl" - ], - "screenshot": "examples/webgl-cube.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/webgl-cube", + "private": true, + "description": "WebGL multi-colored cube mesh", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/matrices": "workspace:^", + "@thi.ng/soa": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^", + "@thi.ng/webgl": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "matrices", + "soa", + "transducers", + "vectors", + "webgl" + ], + "screenshot": "examples/webgl-cube.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/webgl-cube/src/index.ts b/examples/webgl-cube/src/index.ts index c13942b4d0..b1e4ab5095 100644 --- a/examples/webgl-cube/src/index.ts +++ b/examples/webgl-cube/src/index.ts @@ -15,65 +15,65 @@ import { defShader } from "@thi.ng/webgl/shader"; import { LAMBERT } from "@thi.ng/webgl/shaders/lambert"; const cube = (): Partial => { - const soa = new SOA(36, { - pos: { size: 3 }, - normal: { size: 3 }, - col: { size: 3 }, - }); - const [a, b, c, d, e, f, g, h] = [ - ...permutations([-1, 1], [-1, 1], [-1, 1]), - ]; - [ - // tuples of: quad verts, normal, color - [a, b, c, d, [-1, 0, 0], [1, 0, 0]], - [f, e, h, g, [1, 0, 0], [0, 1, 0]], - [e, f, a, b, [0, -1, 0], [0, 0, 1]], - [c, d, g, h, [0, 1, 0], [1, 1, 0]], - [e, a, g, c, [0, 0, -1], [1, 0, 1]], - [b, f, d, h, [0, 0, 1], [0, 1, 1]], - ].forEach(([a, b, c, d, n, col], i: number) => { - i *= 6; - soa.setAttribValues("pos", [a, b, d, a, d, c], i); - soa.setAttribValues("normal", repeat(n, 6), i); - soa.setAttribValues("col", repeat(col, 6), i); - }); - return { - // THIS WILL BE SIMPLIFIED! - attribs: { - position: { data: soa.buffers.pos, size: 3 }, - normal: { data: soa.buffers.normal, size: 3 }, - col: { data: soa.buffers.col, size: 3 }, - }, - num: 36, - }; + const soa = new SOA(36, { + pos: { size: 3 }, + normal: { size: 3 }, + col: { size: 3 }, + }); + const [a, b, c, d, e, f, g, h] = [ + ...permutations([-1, 1], [-1, 1], [-1, 1]), + ]; + [ + // tuples of: quad verts, normal, color + [a, b, c, d, [-1, 0, 0], [1, 0, 0]], + [f, e, h, g, [1, 0, 0], [0, 1, 0]], + [e, f, a, b, [0, -1, 0], [0, 0, 1]], + [c, d, g, h, [0, 1, 0], [1, 1, 0]], + [e, a, g, c, [0, 0, -1], [1, 0, 1]], + [b, f, d, h, [0, 0, 1], [0, 1, 1]], + ].forEach(([a, b, c, d, n, col], i: number) => { + i *= 6; + soa.setAttribValues("pos", [a, b, d, a, d, c], i); + soa.setAttribValues("normal", repeat(n, 6), i); + soa.setAttribValues("col", repeat(col, 6), i); + }); + return { + // THIS WILL BE SIMPLIFIED! + attribs: { + position: { data: soa.buffers.pos, size: 3 }, + normal: { data: soa.buffers.normal, size: 3 }, + col: { data: soa.buffers.col, size: 3 }, + }, + num: 36, + }; }; const app = () => { - let model: ModelSpec; - const canvas = canvasWebGL({ - init(_, gl) { - model = compileModel(gl, { - shader: defShader(gl, LAMBERT({ color: "col" })), - uniforms: { - proj: perspective([], 60, 1, 0.1, 10), - view: lookAt([], [0, 0, 4], [0, 0, 0], [0, 1, 0]), - lightDir: normalize(null, [0.5, 0.75, 1]), - }, - ...cube(), - }); - }, - update(_, gl, __, time) { - if (!model) return; - time *= 0.001; - model.uniforms!.model = ( - concat([], rotationX44([], time), rotationY44([], time * 0.66)) - ); - gl.clearColor(0, 0, 0, 1); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - draw(model); - }, - }); - return [canvas, { width: 600, height: 600 }]; + let model: ModelSpec; + const canvas = canvasWebGL({ + init(_, gl) { + model = compileModel(gl, { + shader: defShader(gl, LAMBERT({ color: "col" })), + uniforms: { + proj: perspective([], 60, 1, 0.1, 10), + view: lookAt([], [0, 0, 4], [0, 0, 0], [0, 1, 0]), + lightDir: normalize(null, [0.5, 0.75, 1]), + }, + ...cube(), + }); + }, + update(_, gl, __, time) { + if (!model) return; + time *= 0.001; + model.uniforms!.model = ( + concat([], rotationX44([], time), rotationY44([], time * 0.66)) + ); + gl.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + draw(model); + }, + }); + return [canvas, { width: 600, height: 600 }]; }; start(app()); diff --git a/examples/webgl-cube/tsconfig.json b/examples/webgl-cube/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/webgl-cube/tsconfig.json +++ b/examples/webgl-cube/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/webgl-cubemap/package.json b/examples/webgl-cubemap/package.json index c9cf1aced2..cfd9f52247 100644 --- a/examples/webgl-cubemap/package.json +++ b/examples/webgl-cubemap/package.json @@ -1,45 +1,45 @@ { - "name": "@example/webgl-cubemap", - "private": true, - "description": "WebGL cube maps with async texture loading", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./' && cp -R img dist", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/adapt-dpi": "workspace:^", - "@thi.ng/dsp": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/matrices": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/webgl": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "adapt-dpi", - "dsp", - "hdom-components", - "matrices", - "rstream", - "shader-ast", - "webgl" - ], - "screenshot": "examples/webgl-cubemap.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/webgl-cubemap", + "private": true, + "description": "WebGL cube maps with async texture loading", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./' && cp -R img dist", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/adapt-dpi": "workspace:^", + "@thi.ng/dsp": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/matrices": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/webgl": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "adapt-dpi", + "dsp", + "hdom-components", + "matrices", + "rstream", + "shader-ast", + "webgl" + ], + "screenshot": "examples/webgl-cubemap.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/webgl-cubemap/src/index.ts b/examples/webgl-cubemap/src/index.ts index 039001ebe9..a3048cfc63 100644 --- a/examples/webgl-cubemap/src/index.ts +++ b/examples/webgl-cubemap/src/index.ts @@ -26,127 +26,142 @@ import { defShader } from "@thi.ng/webgl/shader"; import { defTextureCubeMap } from "@thi.ng/webgl/texture"; const CUBEMAP_SHADER: ShaderSpec = { - vs: (gl, unis, ins, outs) => [ - defMain(() => [ - assign(outs.vnormal, ins.position), - assign(gl.gl_Position, mul(unis.mvp, vec4(ins.position, 1))), - ]), - ], - fs: (_, unis, ins, outs) => [ - defMain(() => [ - assign(outs.fragColor, texture(unis.tex, normalize(ins.vnormal))), - ]), - ], - attribs: { - position: "vec3", - }, - varying: { - vnormal: "vec3", - }, - uniforms: { - mvp: "mat4", - tex: "samplerCube", - }, - state: { - depth: false, - blend: true, - blendFn: BLEND_ADD, - }, + vs: (gl, unis, ins, outs) => [ + defMain(() => [ + assign(outs.vnormal, ins.position), + assign(gl.gl_Position, mul(unis.mvp, vec4(ins.position, 1))), + ]), + ], + fs: (_, unis, ins, outs) => [ + defMain(() => [ + assign(outs.fragColor, texture(unis.tex, normalize(ins.vnormal))), + ]), + ], + attribs: { + position: "vec3", + }, + varying: { + vnormal: "vec3", + }, + uniforms: { + mvp: "mat4", + tex: "samplerCube", + }, + state: { + depth: false, + blend: true, + blendFn: BLEND_ADD, + }, }; const CUBE_MAPS = [ - ["langholmen2", "Langholmen"], - ["golden-gate", "Golden Gate"], - ["maskonaive2", "Maskonaive"], + ["langholmen2", "Langholmen"], + ["golden-gate", "Golden Gate"], + ["maskonaive2", "Maskonaive"], ]; const app = () => { - const selection = reactive(CUBE_MAPS[0][0]); - let model: ModelSpec; - const canvas = canvasWebGL({ - init: (_, gl) => { - selection - .subscribe( - metaStream((id: string) => - fromPromise(loadCubeMap(`./img/${id}/`)) - ) - ) - .subscribe({ - next(faces) { - try { - model = compileModel(gl, { - ...defCubeModel({ normal: false, uv: false }), - shader: defShader(gl, CUBEMAP_SHADER), - uniforms: {}, - textures: [ - defTextureCubeMap(gl, faces, { - filter: [ - TextureFilter.LINEAR_MIPMAP_LINEAR, - TextureFilter.LINEAR, - ], - mipmap: true, - }), - ], - }); - } catch (err) { - console.warn(err); - } - }, - error(e) { - console.warn(e); - return true; - }, - }); - }, - // prettier-ignore - update: (el, gl, __, time) => { - if (!model) return; - const bg = 0.01; - const p = perspective([], 45, gl.drawingBufferWidth/gl.drawingBufferHeight, 0.01, 10); - const v = lookAt([],[0, 0, sin(time, 0.00008, 1.99, 2)],[0, 0, 0], [0, 1, 0]); - const m = transform44([], [0, 0, 0], [sin(time, 0.0001, 0.7, 0.5), time * 0.0007,0], 1); - model.uniforms!.mvp = concat([], p, v, m); - adaptDPI(el, window.innerWidth, window.innerHeight); - gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); - gl.clearColor(bg, bg, bg, 1); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - draw(model); - }, - }); - return () => [ - "div.sans-serif", - [canvas, { width: window.innerWidth, height: window.innerHeight }], - [ - "div.fixed.top-0.left-0.z-1.ma3", - [ - dropdown, - { - onchange: (e: Event) => - selection.next((e.target).value), - }, - CUBE_MAPS, - selection.deref(), - ], - ], - ]; + const selection = reactive(CUBE_MAPS[0][0]); + let model: ModelSpec; + const canvas = canvasWebGL({ + init: (_, gl) => { + selection + .subscribe( + metaStream((id: string) => + fromPromise(loadCubeMap(`./img/${id}/`)) + ) + ) + .subscribe({ + next(faces) { + try { + model = compileModel(gl, { + ...defCubeModel({ normal: false, uv: false }), + shader: defShader(gl, CUBEMAP_SHADER), + uniforms: {}, + textures: [ + defTextureCubeMap(gl, faces, { + filter: [ + TextureFilter.LINEAR_MIPMAP_LINEAR, + TextureFilter.LINEAR, + ], + mipmap: true, + }), + ], + }); + } catch (err) { + console.warn(err); + } + }, + error(e) { + console.warn(e); + return true; + }, + }); + }, + update: (el, gl, __, time) => { + if (!model) return; + const bg = 0.01; + const p = perspective( + [], + 45, + gl.drawingBufferWidth / gl.drawingBufferHeight, + 0.01, + 10 + ); + const v = lookAt( + [], + [0, 0, sin(time, 0.00008, 1.99, 2)], + [0, 0, 0], + [0, 1, 0] + ); + const m = transform44( + [], + [0, 0, 0], + [sin(time, 0.0001, 0.7, 0.5), time * 0.0007, 0], + 1 + ); + model.uniforms!.mvp = concat([], p, v, m); + adaptDPI(el, window.innerWidth, window.innerHeight); + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + gl.clearColor(bg, bg, bg, 1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + draw(model); + }, + }); + return () => [ + "div.sans-serif", + [canvas, { width: window.innerWidth, height: window.innerHeight }], + [ + "div.fixed.top-0.left-0.z-1.ma3", + [ + dropdown, + { + onchange: (e: Event) => + selection.next((e.target).value), + }, + CUBE_MAPS, + selection.deref(), + ], + ], + ]; }; const imagePromise = (url: string) => - new Promise((resolve, fail) => { - const img = new Image(); - img.onload = () => resolve(img); - img.onerror = (e) => { - console.warn("error loading: " + url); - fail(e); - }; - img.src = url; - }); + new Promise((resolve, fail) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = (e) => { + console.warn("error loading: " + url); + fail(e); + }; + img.src = url; + }); const loadCubeMap = (base: string) => - Promise.all( - ["posx", "negx", "posy", "negy", "posz", "negz"].map((id) => - imagePromise(base + id + ".jpg") - ) - ); + Promise.all( + ["posx", "negx", "posy", "negy", "posz", "negz"].map((id) => + imagePromise(base + id + ".jpg") + ) + ); start(app()); diff --git a/examples/webgl-cubemap/tsconfig.json b/examples/webgl-cubemap/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/webgl-cubemap/tsconfig.json +++ b/examples/webgl-cubemap/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/webgl-grid/package.json b/examples/webgl-grid/package.json index 55d79008b8..1fbea0d276 100644 --- a/examples/webgl-grid/package.json +++ b/examples/webgl-grid/package.json @@ -1,44 +1,44 @@ { - "name": "@example/webgl-grid", - "private": true, - "description": "WebGL instancing, animated grid", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/adapt-dpi": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/matrices": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^", - "@thi.ng/webgl": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "adapt-dpi", - "matrices", - "shader-ast", - "transducers", - "vectors", - "webgl" - ], - "screenshot": "examples/webgl-grid.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/webgl-grid", + "private": true, + "description": "WebGL instancing, animated grid", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/adapt-dpi": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/matrices": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^", + "@thi.ng/webgl": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "adapt-dpi", + "matrices", + "shader-ast", + "transducers", + "vectors", + "webgl" + ], + "screenshot": "examples/webgl-grid.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/webgl-grid/src/index.ts b/examples/webgl-grid/src/index.ts index 47f71cd299..96ea8b887a 100644 --- a/examples/webgl-grid/src/index.ts +++ b/examples/webgl-grid/src/index.ts @@ -20,92 +20,103 @@ import { defTexture } from "@thi.ng/webgl/texture"; import { checkerboard } from "@thi.ng/webgl/textures/checkerboard"; const app = () => { - let model: ModelSpec; - const GRID = 16; - const canvas = canvasWebGL({ - init: (_, gl) => { - const C1 = [0.5, 0.5, 0.5]; - const C2 = [1, 1, 1]; - model = compileModel(gl, { - ...defCubeModel({ size: 0.9 }), - instances: { - attribs: { - offset: { - data: new Float32Array([ - ...mapcat( - ([x, z]) => [ - x * 2, - Math.sin(x * 0.4) + Math.sin(z * 0.4), - z * 2, - ], - range2d(-GRID + 1, GRID, -GRID + 1, GRID) - ), - ]), - }, - icol: { - data: new Float32Array([ - ...mapcat( - () => (Math.random() < 0.5 ? C1 : C2), - range2d(-GRID + 1, GRID, -GRID + 1, GRID) - ), - ]), - }, - }, - num: (GRID * 2 - 1) ** 2, - }, - shader: defShader( - gl, - LAMBERT({ - uv: "uv", - instancePos: "offset", - instanceColor: "icol", - state: { cull: true }, - }) - ), - uniforms: {}, - textures: [ - defTexture(gl, { - image: checkerboard({ - size: 8, - col1: 0xff808080, - col2: 0xffffffff, - corners: true, - }), - filter: TextureFilter.NEAREST, - wrap: TextureRepeat.CLAMP, - }), - ], - }); - }, - update: (el, gl, __, time) => { - adaptDPI(el, window.innerWidth, window.innerHeight); - const cam = [0, 2, 5]; - const eye = rotateY( - null, - [cam[0] + Math.sin(time * 0.0007) * 0, cam[1], cam[2]], - -PI / 4 + Math.sin(time * 0.0005) * 0.1 - ); - const aspect = gl.drawingBufferWidth / gl.drawingBufferHeight; - const zoom = Math.sin(time * 0.0005) * 0.4 + 0.5; - const scl = ((GRID * 2) / 3) * zoom; - // prettier-ignore - Object.assign(model.uniforms, { - proj: ortho([], -scl * aspect, scl * aspect, -scl, scl, -GRID * 2, GRID * 2), - view: lookAt([], eye, [0, 0, 0], [0, 1, 0]), - model: scale44([], [1, Math.sin(time * 0.001) + 1, 1]), - lightDir: rotateY(null, normalize(null, [-0.25, 1, 1]), 0) - }); - const bg = 0.1; - gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); - gl.clearColor(bg, bg, bg, 1); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - draw(model); - }, - }); - return () => [ - canvas, - { width: window.innerWidth, height: window.innerHeight }, - ]; + let model: ModelSpec; + const GRID = 16; + const canvas = canvasWebGL({ + init: (_, gl) => { + const C1 = [0.5, 0.5, 0.5]; + const C2 = [1, 1, 1]; + model = compileModel(gl, { + ...defCubeModel({ size: 0.9 }), + instances: { + attribs: { + offset: { + data: new Float32Array([ + ...mapcat( + ([x, z]) => [ + x * 2, + Math.sin(x * 0.4) + Math.sin(z * 0.4), + z * 2, + ], + range2d(-GRID + 1, GRID, -GRID + 1, GRID) + ), + ]), + }, + icol: { + data: new Float32Array([ + ...mapcat( + () => (Math.random() < 0.5 ? C1 : C2), + range2d(-GRID + 1, GRID, -GRID + 1, GRID) + ), + ]), + }, + }, + num: (GRID * 2 - 1) ** 2, + }, + shader: defShader( + gl, + LAMBERT({ + uv: "uv", + instancePos: "offset", + instanceColor: "icol", + state: { cull: true }, + }) + ), + uniforms: {}, + textures: [ + defTexture(gl, { + image: checkerboard({ + size: 8, + col1: 0xff808080, + col2: 0xffffffff, + corners: true, + }), + filter: TextureFilter.NEAREST, + wrap: TextureRepeat.CLAMP, + }), + ], + }); + }, + update: (el, gl, __, time) => { + adaptDPI(el, window.innerWidth, window.innerHeight); + const cam = [0, 2, 5]; + const eye = rotateY( + null, + [cam[0] + Math.sin(time * 0.0007) * 0, cam[1], cam[2]], + -PI / 4 + Math.sin(time * 0.0005) * 0.1 + ); + const aspect = gl.drawingBufferWidth / gl.drawingBufferHeight; + const zoom = Math.sin(time * 0.0005) * 0.4 + 0.5; + const scl = ((GRID * 2) / 3) * zoom; + Object.assign(model.uniforms!, { + proj: ( + ortho( + [], + -scl * aspect, + scl * aspect, + -scl, + scl, + -GRID * 2, + GRID * 2 + ) + ), + view: lookAt([], eye, [0, 0, 0], [0, 1, 0]), + model: scale44([], [1, Math.sin(time * 0.001) + 1, 1]), + lightDir: ( + rotateY(null, normalize(null, [-0.25, 1, 1]), 0) + ), + }); + const bg = 0.1; + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + gl.clearColor(bg, bg, bg, 1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + draw(model); + }, + }); + return () => [ + canvas, + { width: window.innerWidth, height: window.innerHeight }, + ]; }; start(app()); diff --git a/examples/webgl-grid/tsconfig.json b/examples/webgl-grid/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/webgl-grid/tsconfig.json +++ b/examples/webgl-grid/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/webgl-msdf/package.json b/examples/webgl-msdf/package.json index 97a6334a7f..4cd107ffb5 100644 --- a/examples/webgl-msdf/package.json +++ b/examples/webgl-msdf/package.json @@ -1,51 +1,51 @@ { - "name": "@example/webgl-msdf", - "private": true, - "description": "WebGL MSDF text rendering & particle system", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/adapt-dpi": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/math": "workspace:^", - "@thi.ng/matrices": "workspace:^", - "@thi.ng/random": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vector-pools": "workspace:^", - "@thi.ng/vectors": "workspace:^", - "@thi.ng/webgl": "workspace:^", - "@thi.ng/webgl-msdf": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "adapt-dpi", - "matrices", - "shader-ast", - "transducers", - "vectors", - "vector-pools", - "webgl", - "webgl-msdf" - ], - "screenshot": "examples/webgl-msdf.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/webgl-msdf", + "private": true, + "description": "WebGL MSDF text rendering & particle system", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/adapt-dpi": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/math": "workspace:^", + "@thi.ng/matrices": "workspace:^", + "@thi.ng/random": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vector-pools": "workspace:^", + "@thi.ng/vectors": "workspace:^", + "@thi.ng/webgl": "workspace:^", + "@thi.ng/webgl-msdf": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "adapt-dpi", + "matrices", + "shader-ast", + "transducers", + "vectors", + "vector-pools", + "webgl", + "webgl-msdf" + ], + "screenshot": "examples/webgl-msdf.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/webgl-msdf/src/index.ts b/examples/webgl-msdf/src/index.ts index 50a4896afa..1610be71a2 100644 --- a/examples/webgl-msdf/src/index.ts +++ b/examples/webgl-msdf/src/index.ts @@ -64,208 +64,207 @@ Rage, rage against the dying of the light. Dylan Thomas`; const createText = ( - gl: WebGLRenderingContext, - font: MSDFFont, - img: HTMLImageElement, - txt: string, - col = [1, 0.8, 0, 1] + gl: WebGLRenderingContext, + font: MSDFFont, + img: HTMLImageElement, + txt: string, + col = [1, 0.8, 0, 1] ) => { - const model = { - ...text(font, txt, { - align: alignCenter, - leading: 1.4, - spacing: 1, - dirY: -1, - useColor: true, - }), - shader: defShader(gl, msdfShader({ color: true })), - textures: [ - defTexture(gl, { - image: img, - filter: TextureFilter.LINEAR, - wrap: TextureRepeat.CLAMP, - }), - ], - uniforms: { - bg: [0, 0, 0, 1], - thresh: -0.2, - }, - }; - // update bottom vertex colors of each character - for (let i = 2; i < model.attribPool!.capacity; i += 4) { - model.attribPool!.setAttribValues("color", [col, col], i); - } - return compileModel(gl, model); + const model = { + ...text(font, txt, { + align: alignCenter, + leading: 1.4, + spacing: 1, + dirY: -1, + useColor: true, + }), + shader: defShader(gl, msdfShader({ color: true })), + textures: [ + defTexture(gl, { + image: img, + filter: TextureFilter.LINEAR, + wrap: TextureRepeat.CLAMP, + }), + ], + uniforms: { + bg: [0, 0, 0, 1], + thresh: -0.2, + }, + }; + // update bottom vertex colors of each character + for (let i = 2; i < model.attribPool!.capacity; i += 4) { + model.attribPool!.setAttribValues("color", [col, col], i); + } + return compileModel(gl, model); }; const createStarField = (gl: WebGLRenderingContext, num = 1000) => { - const pool = new AttribPool({ - attribs: { - position: { type: "f32", size: 3, byteOffset: 0 }, - dir: { type: "f32", size: 3, byteOffset: 12 }, - id: { type: "f32", size: 1, byteOffset: 24 }, - }, - mem: { - size: num * 28 + 8 /* FIXME */ + 40, - }, - num, - }); - for (let i = 0, r = SYSTEM; i < num; i++) { - const pos = [r.minmax(-15, 15), r.minmax(0, 10), 0]; - pool.setAttribValue("position", i, pos); - pool.setAttribValue( - "dir", - i, - mixN([], mulN([], pos, 0.1), [0, -1, r.minmax(2, 5)], 0.8) - ); - pool.setAttribValue("id", i, i); - } - return compileModel(gl, { - attribs: {}, - attribPool: pool, - uniforms: {}, - shader: defShader(gl, { - vs: (gl, unis, ins, outs) => [ - defMain(() => [ - assign( - outs.valpha, - add( - mul(sin(mul(ins.id, float(37.13829))), float(0.3)), - float(0.7) - ) - ), - assign( - gl.gl_Position, - mul( - mul(unis.proj, unis.modelview), - vec4( - sub( - mod( - add( - ins.position, - mul(ins.dir, unis.time) - ), - float(10) - ), - float(5) - ), - 1 - ) - ) - ), - assign(gl.gl_PointSize, div(float(20), $w(gl.gl_Position))), - ]), - ], - fs: (gl, _, ins, outs) => [ - defMain(() => [ - assign( - outs.fragColor, - vec4( - vec3(ins.valpha), - sub( - float(1), - smoothstep( - float(0.1), - float(0.5), - length(sub(gl.gl_PointCoord, float(0.5))) - ) - ) - ) - ), - ]), - ], - attribs: { - position: "vec3", - dir: "vec3", - id: "float", - }, - varying: { - valpha: "float", - }, - uniforms: { - modelview: "mat4", - proj: "mat4", - time: "float", - }, - state: { - blend: true, - blendFn: BLEND_NORMAL, - }, - }), - mode: DrawMode.POINTS, - num, - }); + const pool = new AttribPool({ + attribs: { + position: { type: "f32", size: 3, byteOffset: 0 }, + dir: { type: "f32", size: 3, byteOffset: 12 }, + id: { type: "f32", size: 1, byteOffset: 24 }, + }, + mem: { + size: num * 28 + 8 /* FIXME */ + 40, + }, + num, + }); + for (let i = 0, r = SYSTEM; i < num; i++) { + const pos = [r.minmax(-15, 15), r.minmax(0, 10), 0]; + pool.setAttribValue("position", i, pos); + pool.setAttribValue( + "dir", + i, + mixN([], mulN([], pos, 0.1), [0, -1, r.minmax(2, 5)], 0.8) + ); + pool.setAttribValue("id", i, i); + } + return compileModel(gl, { + attribs: {}, + attribPool: pool, + uniforms: {}, + shader: defShader(gl, { + vs: (gl, unis, ins, outs) => [ + defMain(() => [ + assign( + outs.valpha, + add( + mul(sin(mul(ins.id, float(37.13829))), float(0.3)), + float(0.7) + ) + ), + assign( + gl.gl_Position, + mul( + mul(unis.proj, unis.modelview), + vec4( + sub( + mod( + add( + ins.position, + mul(ins.dir, unis.time) + ), + float(10) + ), + float(5) + ), + 1 + ) + ) + ), + assign(gl.gl_PointSize, div(float(20), $w(gl.gl_Position))), + ]), + ], + fs: (gl, _, ins, outs) => [ + defMain(() => [ + assign( + outs.fragColor, + vec4( + vec3(ins.valpha), + sub( + float(1), + smoothstep( + float(0.1), + float(0.5), + length(sub(gl.gl_PointCoord, float(0.5))) + ) + ) + ) + ), + ]), + ], + attribs: { + position: "vec3", + dir: "vec3", + id: "float", + }, + varying: { + valpha: "float", + }, + uniforms: { + modelview: "mat4", + proj: "mat4", + time: "float", + }, + state: { + blend: true, + blendFn: BLEND_NORMAL, + }, + }), + mode: DrawMode.POINTS, + num, + }); }; const app = () => { - // If using MSDF font def from https://msdf-bmfont.donmccurdy.com/ - // const glyphs = convertGlyphs(GLYPHS); - const glyphs = GLYPHS; - let stars: ModelSpec; - let body: ModelSpec; - let mouse: ISubscription; - let bg = 0; - const canvas = canvasWebGL({ - init: async (el, gl) => { - const img = new Image(); - img.src = GLYPH_TEX; - await img.decode(); - body = createText(gl, glyphs, img, TEXT); - stars = createStarField(gl); - body.uniforms!.proj = stars.uniforms!.proj = ( - perspective( - [], - 60, - gl.drawingBufferWidth / gl.drawingBufferHeight, - 0.1, - 20 - ) - ); - mouse = fromDOMEvent(el, "mousemove").transform( - map((e) => - fit3( - [], - [e.clientX, e.clientY, 0], - ZERO3, - [window.innerWidth, window.innerHeight, 0], - [-1, -1, 0], - [1, 1, 0] - ) - ) - ); - }, - update: (el, gl, __, time) => { - if (!body) return; - adaptDPI(el, window.innerWidth, window.innerHeight); - // prettier-ignore - const eye = madd3( - [], - mouse.deref() || ZERO3, - [2, 0.5, 0], - [0, -4, 5] - ); - const view = lookAt([], eye, ZERO3, Y3); - body.uniforms!.modelview = ( - concat( - [], - view, - transform44( - [], - [0, fitClamped(time % 70000, 0, 70000, -3.5, 20), 0], - ZERO3, - 0.005 - ) - ) - ); - stars.uniforms!.modelview = view; - stars.uniforms!.time = 10 + time * 0.001; - gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); - gl.clearColor(bg, bg, bg, 1); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - draw([stars, body]); - }, - }); - return [canvas, { width: window.innerWidth, height: window.innerHeight }]; + // If using MSDF font def from https://msdf-bmfont.donmccurdy.com/ + // const glyphs = convertGlyphs(GLYPHS); + const glyphs = GLYPHS; + let stars: ModelSpec; + let body: ModelSpec; + let mouse: ISubscription; + let bg = 0; + const canvas = canvasWebGL({ + init: async (el, gl) => { + const img = new Image(); + img.src = GLYPH_TEX; + await img.decode(); + body = createText(gl, glyphs, img, TEXT); + stars = createStarField(gl); + body.uniforms!.proj = stars.uniforms!.proj = ( + perspective( + [], + 60, + gl.drawingBufferWidth / gl.drawingBufferHeight, + 0.1, + 20 + ) + ); + mouse = fromDOMEvent(el, "mousemove").transform( + map((e) => + fit3( + [], + [e.clientX, e.clientY, 0], + ZERO3, + [window.innerWidth, window.innerHeight, 0], + [-1, -1, 0], + [1, 1, 0] + ) + ) + ); + }, + update: (el, gl, __, time) => { + if (!body) return; + adaptDPI(el, window.innerWidth, window.innerHeight); + const eye = madd3( + [], + mouse.deref() || ZERO3, + [2, 0.5, 0], + [0, -4, 5] + ); + const view = lookAt([], eye, ZERO3, Y3); + body.uniforms!.modelview = ( + concat( + [], + view, + transform44( + [], + [0, fitClamped(time % 70000, 0, 70000, -3.5, 20), 0], + ZERO3, + 0.005 + ) + ) + ); + stars.uniforms!.modelview = view; + stars.uniforms!.time = 10 + time * 0.001; + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + gl.clearColor(bg, bg, bg, 1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + draw([stars, body]); + }, + }); + return [canvas, { width: window.innerWidth, height: window.innerHeight }]; }; start(app()); diff --git a/examples/webgl-msdf/tsconfig.json b/examples/webgl-msdf/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/webgl-msdf/tsconfig.json +++ b/examples/webgl-msdf/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/webgl-multipass/package.json b/examples/webgl-multipass/package.json index c44af5e7dc..877c0276e2 100644 --- a/examples/webgl-multipass/package.json +++ b/examples/webgl-multipass/package.json @@ -1,32 +1,32 @@ { - "name": "@example/webgl-multipass", - "private": true, - "description": "Minimal multi-pass / GPGPU example", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/pixel": "workspace:^", - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/shader-ast-stdlib": "workspace:^", - "@thi.ng/webgl": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/webgl-multipass", + "private": true, + "description": "Minimal multi-pass / GPGPU example", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/pixel": "workspace:^", + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/shader-ast-stdlib": "workspace:^", + "@thi.ng/webgl": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/webgl-multipass/src/index.ts b/examples/webgl-multipass/src/index.ts index c9c777101f..8573fa4e48 100644 --- a/examples/webgl-multipass/src/index.ts +++ b/examples/webgl-multipass/src/index.ts @@ -20,97 +20,97 @@ import { readTexture } from "@thi.ng/webgl/readpixels"; // create WebGL canvas const canvas = glCanvas({ - // width: window.innerWidth, - // height: window.innerHeight, - width: 512, - height: 512, - parent: document.body, - version: 2, + // width: window.innerWidth, + // height: window.innerHeight, + width: 512, + height: 512, + parent: document.body, + version: 2, }); // init multipass shader pipeline const toy = defMultiPass({ - gl: canvas.gl, - width: 32, - height: 32, - textures: { - foo: { format: TextureFormat.RGBA32F }, - bar: { format: TextureFormat.R32F }, - }, - passes: [ - { - fs: (gl, unis, _, outs) => [ - defMain(() => { - let uv: Vec2Sym; - return [ - (uv = sym(fragUV(gl.gl_FragCoord, unis.resolution))), - assign( - outs.output0, - vec4(vec3(uv, fract(unis.time)), 1) - ), - assign( - outs.output1, - vec4(clamp01(length(sub(uv, 0.5)))) - ), - ]; - }), - ], - inputs: [], - outputs: ["foo", "bar"], - uniforms: { - time: "float", - resolution: "vec2", - }, - uniformVals: { - // foo: () => Math.random() - }, - }, - { - fs: (gl, unis, _, outs) => [ - defMain(() => { - let uv: Vec2Sym; - return [ - (uv = sym(fragUV(gl.gl_FragCoord, unis.resolution))), - assign( - outs.fragColor, - vec4( - mul( - $xyz(texture(unis.input0, uv)), - pow( - $x(texture(unis.input1, uv)), - fit1101(sin(unis.time)) - ) - ), - 1 - ) - ), - ]; - }), - ], - inputs: ["foo", "bar"], - outputs: [], - uniforms: { - resolution: "vec2", - time: "float", - }, - }, - ], + gl: canvas.gl, + width: 32, + height: 32, + textures: { + foo: { format: TextureFormat.RGBA32F }, + bar: { format: TextureFormat.R32F }, + }, + passes: [ + { + fs: (gl, unis, _, outs) => [ + defMain(() => { + let uv: Vec2Sym; + return [ + (uv = sym(fragUV(gl.gl_FragCoord, unis.resolution))), + assign( + outs.output0, + vec4(vec3(uv, fract(unis.time)), 1) + ), + assign( + outs.output1, + vec4(clamp01(length(sub(uv, 0.5)))) + ), + ]; + }), + ], + inputs: [], + outputs: ["foo", "bar"], + uniforms: { + time: "float", + resolution: "vec2", + }, + uniformVals: { + // foo: () => Math.random() + }, + }, + { + fs: (gl, unis, _, outs) => [ + defMain(() => { + let uv: Vec2Sym; + return [ + (uv = sym(fragUV(gl.gl_FragCoord, unis.resolution))), + assign( + outs.fragColor, + vec4( + mul( + $xyz(texture(unis.input0, uv)), + pow( + $x(texture(unis.input1, uv)), + fit1101(sin(unis.time)) + ) + ), + 1 + ) + ), + ]; + }), + ], + inputs: ["foo", "bar"], + outputs: [], + uniforms: { + resolution: "vec2", + time: "float", + }, + }, + ], }); toy.update(0); const canv = canvas2d(32, 32, document.body); intBuffer( - 32, - 32, - GRAY8, - readTexture( - canvas.gl, - toy.textures.bar, - TextureFormat.RED, - TextureType.UNSIGNED_BYTE, - new Uint8Array(32 * 32) - ) + 32, + 32, + GRAY8, + readTexture( + canvas.gl, + toy.textures.bar, + TextureFormat.RED, + TextureType.UNSIGNED_BYTE, + new Uint8Array(32 * 32) + ) ).blitCanvas(canv.canvas); toy.start(); diff --git a/examples/webgl-multipass/tsconfig.json b/examples/webgl-multipass/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/webgl-multipass/tsconfig.json +++ b/examples/webgl-multipass/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/webgl-shadertoy/package.json b/examples/webgl-shadertoy/package.json index 2c11947ae3..67278a2c34 100644 --- a/examples/webgl-shadertoy/package.json +++ b/examples/webgl-shadertoy/package.json @@ -1,33 +1,33 @@ { - "name": "@example/webgl-shadertoy", - "private": true, - "description": "Shadertoy-like WebGL setup", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/shader-ast-stdlib": "workspace:^", - "@thi.ng/webgl": "workspace:^", - "@thi.ng/webgl-shadertoy": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": true, - "screenshot": "examples/webgl-shadertoy.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/webgl-shadertoy", + "private": true, + "description": "Shadertoy-like WebGL setup", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/shader-ast-stdlib": "workspace:^", + "@thi.ng/webgl": "workspace:^", + "@thi.ng/webgl-shadertoy": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": true, + "screenshot": "examples/webgl-shadertoy.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/webgl-shadertoy/src/index.ts b/examples/webgl-shadertoy/src/index.ts index 72fddb2bbe..39b8a72bee 100644 --- a/examples/webgl-shadertoy/src/index.ts +++ b/examples/webgl-shadertoy/src/index.ts @@ -7,21 +7,21 @@ import { add, eq, mul, neg } from "@thi.ng/shader-ast/ast/ops"; import { $xy } from "@thi.ng/shader-ast/ast/swizzle"; import { sym } from "@thi.ng/shader-ast/ast/sym"; import { - distance, - fract, - min, - mix, - sin, + distance, + fract, + min, + mix, + sin, } from "@thi.ng/shader-ast/builtin/math"; import { - type MainImageFn, - shaderToy, - type ShaderToyUniforms, + type MainImageFn, + shaderToy, + type ShaderToyUniforms, } from "@thi.ng/webgl-shadertoy"; import { glCanvas } from "@thi.ng/webgl/canvas"; interface DemoUniforms extends ShaderToyUniforms { - bright: FloatSym; + bright: FloatSym; } // main shader function, supplied to `shaderToy` below @@ -31,58 +31,58 @@ interface DemoUniforms extends ShaderToyUniforms { // - https://docs.thi.ng/umbrella/shader-ast-glsl/interfaces/GLSLTarget.html // - https://docs.thi.ng/umbrella/webgl-shadertoy/modules.html#MainImageFn const mainImage: MainImageFn = (gl, unis) => { - // predeclare local vars / symbols - let uv: Vec2Sym; - let mp: Vec2Sym; - let d1: FloatSym; - let d2: FloatSym; - let col: Vec3Sym; + // predeclare local vars / symbols + let uv: Vec2Sym; + let mp: Vec2Sym; + let d1: FloatSym; + let d2: FloatSym; + let col: Vec3Sym; - // Inline function to create ring pattern with center at `p` - const rings = (p: Vec2Term, speed = 0.25, freq = 50) => - sin(mul(add(distance(uv, p), fract(mul(unis.time, speed))), freq)); + // Inline function to create ring pattern with center at `p` + const rings = (p: Vec2Term, speed = 0.25, freq = 50) => + sin(mul(add(distance(uv, p), fract(mul(unis.time, speed))), freq)); - return [ - // let's work in [-1..+1] range (based on vertical resolution) - (uv = sym(aspectCorrectedUV($xy(gl.gl_FragCoord), unis.resolution))), - (mp = sym(aspectCorrectedUV(unis.mouse, unis.resolution))), - // compute ring colors - (d1 = sym(rings(mp))), - (d2 = sym(rings(neg(mp)))), - // combine rings and multiply with target color based on - // mouse button state - (col = sym( - mul( - vec3(fit1101(min(d1, d2))), - mix( - vec3(1), - mul(vec3(d1, 0, d2), unis.bright), - float(eq(unis.mouseButtons, int(1))) - ) - ) - )), - // return as vec4 (mandatory) - ret(vec4(col, 1)), - ]; + return [ + // let's work in [-1..+1] range (based on vertical resolution) + (uv = sym(aspectCorrectedUV($xy(gl.gl_FragCoord), unis.resolution))), + (mp = sym(aspectCorrectedUV(unis.mouse, unis.resolution))), + // compute ring colors + (d1 = sym(rings(mp))), + (d2 = sym(rings(neg(mp)))), + // combine rings and multiply with target color based on + // mouse button state + (col = sym( + mul( + vec3(fit1101(min(d1, d2))), + mix( + vec3(1), + mul(vec3(d1, 0, d2), unis.bright), + float(eq(unis.mouseButtons, int(1))) + ) + ) + )), + // return as vec4 (mandatory) + ret(vec4(col, 1)), + ]; }; // create WebGL canvas const canvas = glCanvas({ - width: window.innerWidth, - height: window.innerHeight, - parent: document.body, - version: 1, + width: window.innerWidth, + height: window.innerHeight, + parent: document.body, + version: 1, }); // init shader toy with canvas & shader fn const toy = shaderToy({ - canvas: canvas.canvas, - gl: canvas.gl, - main: mainImage, - uniforms: { - // brightness factor for colored rings - bright: ["float", 1], - }, + canvas: canvas.canvas, + gl: canvas.gl, + main: mainImage, + uniforms: { + // brightness factor for colored rings + bright: ["float", 1], + }, }); toy.start(); diff --git a/examples/webgl-shadertoy/tsconfig.json b/examples/webgl-shadertoy/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/webgl-shadertoy/tsconfig.json +++ b/examples/webgl-shadertoy/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/webgl-ssao/package.json b/examples/webgl-ssao/package.json index 2bdd2006d7..b7cb96bb18 100644 --- a/examples/webgl-ssao/package.json +++ b/examples/webgl-ssao/package.json @@ -1,46 +1,46 @@ { - "name": "@example/webgl-ssao", - "private": true, - "description": "WebGL screenspace ambient occlusion", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/api": "workspace:^", - "@thi.ng/associative": "workspace:^", - "@thi.ng/dsp": "workspace:^", - "@thi.ng/hdom": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/matrices": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/shader-ast": "workspace:^", - "@thi.ng/shader-ast-stdlib": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/vectors": "workspace:^", - "@thi.ng/webgl": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "dsp", - "shader-ast", - "shader-ast-stdlib", - "webgl" - ], - "screenshot": "examples/webgl-ssao.jpg" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/webgl-ssao", + "private": true, + "description": "WebGL screenspace ambient occlusion", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/api": "workspace:^", + "@thi.ng/associative": "workspace:^", + "@thi.ng/dsp": "workspace:^", + "@thi.ng/hdom": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/matrices": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/shader-ast": "workspace:^", + "@thi.ng/shader-ast-stdlib": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/vectors": "workspace:^", + "@thi.ng/webgl": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "dsp", + "shader-ast", + "shader-ast-stdlib", + "webgl" + ], + "screenshot": "examples/webgl-ssao.jpg" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/webgl-ssao/src/index.ts b/examples/webgl-ssao/src/index.ts index c44cf64ad6..530672cc8b 100644 --- a/examples/webgl-ssao/src/index.ts +++ b/examples/webgl-ssao/src/index.ts @@ -14,10 +14,10 @@ import { repeatedly } from "@thi.ng/transducers/repeatedly"; import { rotateY } from "@thi.ng/vectors/rotate"; import type { GLMat4, GLVec3, ModelSpec } from "@thi.ng/webgl"; import { - TextureFilter, - TextureFormat, - type TextureOpts, - TextureRepeat, + TextureFilter, + TextureFormat, + type TextureOpts, + TextureRepeat, } from "@thi.ng/webgl/api/texture"; import { compileModel } from "@thi.ng/webgl/buffer"; import { draw } from "@thi.ng/webgl/draw"; @@ -41,168 +41,168 @@ const Z_FAR = 20; // noise texture data for SSAO shader const NOISE = new Float32Array([ - ...repeatedly(() => Math.random() * 2 - 1, W * H * 2), + ...repeatedly(() => Math.random() * 2 - 1, W * H * 2), ]); // instance position data for animated cubes const instancePositions = (o: number) => - // prettier-ignore - new Float32Array([ + // prettier-ignore + new Float32Array([ -o, 0, 0, o, 0, 0, 0, -2 * o, 0, 0, 2 * o, 0, 0, 0, -2 * o, 0, 0, 2 * o ]); const app = () => { - let model: ModelSpec; - let ssaoQuad: ModelSpec; - let finalQuad: ModelSpec; - let fboGeo: FBO; - let fboSSAO: FBO; - const raf = fromRAF(); - const fps = raf.transform( - benchmark(), - movingAverage(100), - map((x: number) => (1000 / x).toFixed(2)) - ); - const canvas = canvasWebGL2({ - init(_, gl: WebGL2RenderingContext) { - if (!gl.getExtension("EXT_color_buffer_float")) { - alert("EXT_color_buffer_float not available"); - return; - } - const [colorTex, posTex, normTex, noiseTex, ssaoTex] = [ - {}, - { format: TextureFormat.RGBA16F }, - { format: TextureFormat.RGBA16F }, - { - image: NOISE, - format: TextureFormat.RG16F, - }, - {}, - ].map((opts: Partial) => - defTexture(gl, { - width: W, - height: H, - image: null, - filter: TextureFilter.NEAREST, - wrap: TextureRepeat.CLAMP, - ...opts, - }) - ); - fboGeo = defFBO(gl, { - tex: [colorTex, posTex, normTex], - depth: defRBO(gl, { width: W, height: H }), - }); - fboSSAO = defFBO(gl, { - tex: [ssaoTex], - }); - model = compileModel(gl, { - ...defCubeModel({ uv: true }), - shader: defShader(gl, LIGHT_SHADER), - instances: { - attribs: { - offset: { - data: instancePositions(1.05), - }, - }, - num: 6, - }, - uniforms: { - eyePos: >( - tweenNumber( - PARAMS.eyeDist, - PARAM_DEFS.eyeDist[2], - 0.05, - 1e-3, - raf - ).transform(map((z) => [0, 0, z])) - ), - lightPos: >( - tweenNumber( - PARAMS.lightTheta, - PARAM_DEFS.lightTheta[2], - 0.05, - 1e-3, - raf - ).transform( - map( - (theta) => rotateY([], LIGHT_POS, theta) - ) - ) - ), - specular: >PARAMS.specular, - }, - textures: [ - defTexture(gl, { - image: checkerboard({ - size: 16, - col1: 0xffc0c0c0, - col2: 0xffe0e0e0, - corners: true, - }), - filter: TextureFilter.NEAREST, - wrap: TextureRepeat.CLAMP, - }), - ], - }); - ssaoQuad = compileModel(gl, { - ...defQuadModel({ uv: false }), - shader: defShader(gl, SSAO_SHADER), - textures: [posTex, normTex, noiseTex], - uniforms: { - sampleRadius: >PARAMS.radius, - bias: >PARAMS.bias, - attenuate: >PARAMS.baseAttenuation, - attenuateDist: >PARAMS.distAttenuation, - depthRange: [Z_NEAR, Z_FAR], - }, - }); - finalQuad = compileModel(gl, { - ...defQuadModel(), - shader: defShader(gl, FINAL_SHADER), - textures: [colorTex, ssaoTex], - uniforms: { - amp: >PARAMS.amp, - }, - }); - }, - update(_, gl, __, time, frame) { - if (frame < 1 || !model) return; - const bg = 0.1; - const eye = (>model.uniforms!.eyePos).deref(); - const p = perspective([], 45, W / H, Z_NEAR, Z_FAR); - const v = lookAt([], eye, [0, 0, 0], [0, 1, 0]); - const m = transform44( - [], - [0, 0, 0], - [sin(time, 0.00005, 1, 0), time * 0.0003, 0], - 1 - ); - model.instances!.attribs.offset.buffer!.set( - instancePositions(sin(time, 0.0004, 0.14, 1.15)) - ); - model.uniforms!.model = m; - model.uniforms!.view = v; - model.uniforms!.proj = p; - gl.viewport(0, 0, W, H); - fboGeo.bind(); - gl.clearColor(bg, bg, bg, 1); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - draw(model); - fboSSAO.bind(); - draw(ssaoQuad); - fboSSAO.unbind(); - gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); - draw(finalQuad); - }, - }); - return () => [ - "div", - [canvas, { width: W, height: H }], - ["div.fixed.top-0.left-0.z-1.ma3.pa3", fps, " fps"], - ["div.mt3", ...CONTROLS], - ]; + let model: ModelSpec; + let ssaoQuad: ModelSpec; + let finalQuad: ModelSpec; + let fboGeo: FBO; + let fboSSAO: FBO; + const raf = fromRAF(); + const fps = raf.transform( + benchmark(), + movingAverage(100), + map((x: number) => (1000 / x).toFixed(2)) + ); + const canvas = canvasWebGL2({ + init(_, gl: WebGL2RenderingContext) { + if (!gl.getExtension("EXT_color_buffer_float")) { + alert("EXT_color_buffer_float not available"); + return; + } + const [colorTex, posTex, normTex, noiseTex, ssaoTex] = [ + {}, + { format: TextureFormat.RGBA16F }, + { format: TextureFormat.RGBA16F }, + { + image: NOISE, + format: TextureFormat.RG16F, + }, + {}, + ].map((opts: Partial) => + defTexture(gl, { + width: W, + height: H, + image: null, + filter: TextureFilter.NEAREST, + wrap: TextureRepeat.CLAMP, + ...opts, + }) + ); + fboGeo = defFBO(gl, { + tex: [colorTex, posTex, normTex], + depth: defRBO(gl, { width: W, height: H }), + }); + fboSSAO = defFBO(gl, { + tex: [ssaoTex], + }); + model = compileModel(gl, { + ...defCubeModel({ uv: true }), + shader: defShader(gl, LIGHT_SHADER), + instances: { + attribs: { + offset: { + data: instancePositions(1.05), + }, + }, + num: 6, + }, + uniforms: { + eyePos: >( + tweenNumber( + PARAMS.eyeDist, + PARAM_DEFS.eyeDist[2], + 0.05, + 1e-3, + raf + ).transform(map((z) => [0, 0, z])) + ), + lightPos: >( + tweenNumber( + PARAMS.lightTheta, + PARAM_DEFS.lightTheta[2], + 0.05, + 1e-3, + raf + ).transform( + map( + (theta) => rotateY([], LIGHT_POS, theta) + ) + ) + ), + specular: >PARAMS.specular, + }, + textures: [ + defTexture(gl, { + image: checkerboard({ + size: 16, + col1: 0xffc0c0c0, + col2: 0xffe0e0e0, + corners: true, + }), + filter: TextureFilter.NEAREST, + wrap: TextureRepeat.CLAMP, + }), + ], + }); + ssaoQuad = compileModel(gl, { + ...defQuadModel({ uv: false }), + shader: defShader(gl, SSAO_SHADER), + textures: [posTex, normTex, noiseTex], + uniforms: { + sampleRadius: >PARAMS.radius, + bias: >PARAMS.bias, + attenuate: >PARAMS.baseAttenuation, + attenuateDist: >PARAMS.distAttenuation, + depthRange: [Z_NEAR, Z_FAR], + }, + }); + finalQuad = compileModel(gl, { + ...defQuadModel(), + shader: defShader(gl, FINAL_SHADER), + textures: [colorTex, ssaoTex], + uniforms: { + amp: >PARAMS.amp, + }, + }); + }, + update(_, gl, __, time, frame) { + if (frame < 1 || !model) return; + const bg = 0.1; + const eye = (>model.uniforms!.eyePos).deref(); + const p = perspective([], 45, W / H, Z_NEAR, Z_FAR); + const v = lookAt([], eye, [0, 0, 0], [0, 1, 0]); + const m = transform44( + [], + [0, 0, 0], + [sin(time, 0.00005, 1, 0), time * 0.0003, 0], + 1 + ); + model.instances!.attribs.offset.buffer!.set( + instancePositions(sin(time, 0.0004, 0.14, 1.15)) + ); + model.uniforms!.model = m; + model.uniforms!.view = v; + model.uniforms!.proj = p; + gl.viewport(0, 0, W, H); + fboGeo.bind(); + gl.clearColor(bg, bg, bg, 1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + draw(model); + fboSSAO.bind(); + draw(ssaoQuad); + fboSSAO.unbind(); + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + draw(finalQuad); + }, + }); + return () => [ + "div", + [canvas, { width: W, height: H }], + ["div.fixed.top-0.left-0.z-1.ma3.pa3", fps, " fps"], + ["div.mt3", ...CONTROLS], + ]; }; start(app()); diff --git a/examples/webgl-ssao/src/params.ts b/examples/webgl-ssao/src/params.ts index 2029ff86b6..4f59c354a5 100644 --- a/examples/webgl-ssao/src/params.ts +++ b/examples/webgl-ssao/src/params.ts @@ -7,21 +7,21 @@ import { push } from "@thi.ng/transducers/push"; import { transduce } from "@thi.ng/transducers/transduce"; const slider = (label: string, attribs: any, stream: Stream) => () => - [ - "div.mb2", - ["span.dib.w4", label], - [ - "input.w5", - { - ...attribs, - type: "range", - value: stream.deref(), - oninput: (e: Event) => - stream.next(parseFloat((e.target).value)), - }, - ], - ["span.ml3", stream.deref()], - ]; + [ + "div.mb2", + ["span.dib.w4", label], + [ + "input.w5", + { + ...attribs, + type: "range", + value: stream.deref(), + oninput: (e: Event) => + stream.next(parseFloat((e.target).value)), + }, + ], + ["span.ml3", stream.deref()], + ]; type ParamDef = [string, any, number]; @@ -38,16 +38,16 @@ export const PARAM_DEFS: IObjectOf = { }; export const PARAMS = transduce( - map<[string, ParamDef], [string, Stream]>(([id, spec]) => [ - id, - reactive(spec[2]), - ]), - assocObj(), - pairs(PARAM_DEFS) + map<[string, ParamDef], [string, Stream]>(([id, spec]) => [ + id, + reactive(spec[2]), + ]), + assocObj(), + pairs(PARAM_DEFS) ); export const CONTROLS = transduce( - map(([id, [label, attribs]]) => slider(label, attribs, PARAMS[id])), - push(), - pairs(PARAM_DEFS) + map(([id, [label, attribs]]) => slider(label, attribs, PARAMS[id])), + push(), + pairs(PARAM_DEFS) ); diff --git a/examples/webgl-ssao/src/shaders.ts b/examples/webgl-ssao/src/shaders.ts index b735fc9545..5205b98ed9 100644 --- a/examples/webgl-ssao/src/shaders.ts +++ b/examples/webgl-ssao/src/shaders.ts @@ -8,12 +8,12 @@ import { $x, $xyz } from "@thi.ng/shader-ast/ast/swizzle"; import { texture } from "@thi.ng/shader-ast/builtin/texture"; import type { ShaderFn, ShaderSpec } from "@thi.ng/webgl"; import { - FX_SHADER_SPEC, - FX_SHADER_SPEC_UV, + FX_SHADER_SPEC, + FX_SHADER_SPEC_UV, } from "@thi.ng/webgl/shaders/pipeline"; export const LIGHT_SHADER: ShaderSpec = { - vs: `void main() { + vs: `void main() { v_position = model * vec4(position + offset, 1.0); v_normal = model * vec4(normal, 0.0); v_uv = uv; @@ -21,7 +21,7 @@ export const LIGHT_SHADER: ShaderSpec = { v_viewNormal = view * v_normal; gl_Position = proj * v_viewPos; }`, - fs: `void main() { + fs: `void main() { vec3 position = v_position.xyz; vec3 normal = normalize(v_normal.xyz); vec3 baseColor = texture(tex, v_uv).xyz; @@ -34,44 +34,44 @@ export const LIGHT_SHADER: ShaderSpec = { o_viewPos = v_viewPos; o_viewNormal = v_viewNormal; }`, - attribs: { - position: "vec3", - normal: "vec3", - offset: "vec3", - uv: "vec2", - }, - varying: { - v_position: "vec4", - v_normal: "vec4", - v_uv: "vec2", - v_viewPos: "vec4", - v_viewNormal: "vec4", - }, - uniforms: { - model: "mat4", - view: "mat4", - proj: "mat4", - eyePos: "vec3", - lightPos: "vec3", - shininess: ["float", 250], - specular: "float", - ambient: ["float", 0.15], - tex: "sampler2D", - }, - outputs: { - o_color: ["vec4", 0], - o_viewPos: ["vec4", 1], - o_viewNormal: ["vec4", 2], - }, - state: { - depth: true, - cull: true, - }, + attribs: { + position: "vec3", + normal: "vec3", + offset: "vec3", + uv: "vec2", + }, + varying: { + v_position: "vec4", + v_normal: "vec4", + v_uv: "vec2", + v_viewPos: "vec4", + v_viewNormal: "vec4", + }, + uniforms: { + model: "mat4", + view: "mat4", + proj: "mat4", + eyePos: "vec3", + lightPos: "vec3", + shininess: ["float", 250], + specular: "float", + ambient: ["float", 0.15], + tex: "sampler2D", + }, + outputs: { + o_color: ["vec4", 0], + o_viewPos: ["vec4", 1], + o_viewNormal: ["vec4", 2], + }, + state: { + depth: true, + cull: true, + }, }; export const SSAO_SHADER: ShaderSpec = { - ...FX_SHADER_SPEC, - fs: `const vec2 kernel[4] = vec2[]( + ...FX_SHADER_SPEC, + fs: `const vec2 kernel[4] = vec2[]( vec2(-K, 0.0), vec2(K, 0.0), vec2(0.0, -K), vec2(0.0, K) ); @@ -103,43 +103,43 @@ void main() { o_occlusion = clamp(sum / 16.0, 0.0, 1.0); }`, - pre: "#define K (0.707107)", - uniforms: { - positionTex: ["sampler2D", 0], - normalTex: ["sampler2D", 1], - noiseTex: ["sampler2D", 2], - sampleRadius: ["float", 32], - bias: ["float", 0.09], - attenuate: ["float", 1], - attenuateDist: ["float", 1], - depthRange: ["vec2", [0.1, 10]], - }, - outputs: { - o_occlusion: "float", - }, + pre: "#define K (0.707107)", + uniforms: { + positionTex: ["sampler2D", 0], + normalTex: ["sampler2D", 1], + noiseTex: ["sampler2D", 2], + sampleRadius: ["float", 32], + bias: ["float", 0.09], + attenuate: ["float", 1], + attenuateDist: ["float", 1], + depthRange: ["vec2", [0.1, 10]], + }, + outputs: { + o_occlusion: "float", + }, }; export const FINAL_SHADER: ShaderSpec = mergeDeepObj(FX_SHADER_SPEC_UV, { - fs: ( - ((_, unis, ins, outs) => [ - defMain(() => [ - assign( - outs.fragColor, - vec4( - clamp01( - sub( - $xyz(texture(unis.tex, ins.v_uv)), - mul($x(texture(unis.tex2, ins.v_uv)), unis.amp) - ) - ), - 1 - ) - ), - ]), - ]) - ), - uniforms: { - tex2: ["sampler2D", 1], - amp: ["float", 1], - }, + fs: ( + ((_, unis, ins, outs) => [ + defMain(() => [ + assign( + outs.fragColor, + vec4( + clamp01( + sub( + $xyz(texture(unis.tex, ins.v_uv)), + mul($x(texture(unis.tex2, ins.v_uv)), unis.amp) + ) + ), + 1 + ) + ), + ]), + ]) + ), + uniforms: { + tex2: ["sampler2D", 1], + amp: ["float", 1], + }, }); diff --git a/examples/webgl-ssao/tsconfig.json b/examples/webgl-ssao/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/webgl-ssao/tsconfig.json +++ b/examples/webgl-ssao/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/wolfram/package.json b/examples/wolfram/package.json index 577c61d380..867c01d786 100644 --- a/examples/wolfram/package.json +++ b/examples/wolfram/package.json @@ -1,39 +1,39 @@ { - "name": "@example/wolfram", - "private": true, - "description": "1D Wolfram automata with OBJ point cloud export", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/dl-asset": "workspace:^", - "@thi.ng/hdom-components": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-binary": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "dl-asset", - "transducers", - "transducers-binary" - ], - "screenshot": "examples/wolfram.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/wolfram", + "private": true, + "description": "1D Wolfram automata with OBJ point cloud export", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/dl-asset": "workspace:^", + "@thi.ng/hdom-components": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-binary": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "dl-asset", + "transducers", + "transducers-binary" + ], + "screenshot": "examples/wolfram.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/wolfram/src/index.ts b/examples/wolfram/src/index.ts index 9d1f52493a..58074ee83d 100644 --- a/examples/wolfram/src/index.ts +++ b/examples/wolfram/src/index.ts @@ -31,111 +31,111 @@ const HEIGHT = 32; const resetCA = () => [...randomBits(0.25, WIDTH)]; const evolveCA = (src: number[], { kernel, rule, reset }: any) => - reset - ? resetCA() - : [ - ...iterator1( - comp( - convolve1d({ - src, - kernel, - width: src.length, - wrap: true, - }), - map(lookup1d(rule)) - ), - range(src.length) - ), - ]; + reset + ? resetCA() + : [ + ...iterator1( + comp( + convolve1d({ + src, + kernel, + width: src.length, + wrap: true, + }), + map(lookup1d(rule)) + ), + range(src.length) + ), + ]; const triggerReset = () => - wolfram.add(fromIterable([true, false], { delay: 16 }), "reset"); + wolfram.add(fromIterable([true, false], { delay: 16 }), "reset"); const triggerOBJExport = () => objExport.next(1); const setRule = (e: Event) => { - rule.next(parseInt((e.target).value)); - triggerReset(); + rule.next(parseInt((e.target).value)); + triggerReset(); }; const setKernel = (e: Event) => - kernel.next(parseInt((e.target).value)); + kernel.next(parseInt((e.target).value)); const app = ({ id, ksize, sim }: any) => [ - "div.sans-serif.ma3", - [ - "div", - "Rule:", - [ - "input.w4.h2.mh3.pa2", - { - type: "number", - value: id, - oninput: setRule, - }, - ], - "Kernel:", - [ - dropdown, - { class: "h2 pa2 mh3", onchange: setKernel }, - [ - [3, "3"], - [5, "5"], - ], - ksize, - ], - [ - "button.mr3.pa2", - { - onclick: triggerReset, - }, - "Reset", - ], - [ - "button.mr3.pa2", - { - onclick: triggerOBJExport, - }, - "Export OBJ", - ], - [ - "a.link.blue", - { - href: "https://en.wikipedia.org/wiki/Elementary_cellular_automaton#Random_initial_state", - }, - "Wikipedia", - ], - ], - ["pre.f7.code", sim], + "div.sans-serif.ma3", + [ + "div", + "Rule:", + [ + "input.w4.h2.mh3.pa2", + { + type: "number", + value: id, + oninput: setRule, + }, + ], + "Kernel:", + [ + dropdown, + { class: "h2 pa2 mh3", onchange: setKernel }, + [ + [3, "3"], + [5, "5"], + ], + ksize, + ], + [ + "button.mr3.pa2", + { + onclick: triggerReset, + }, + "Reset", + ], + [ + "button.mr3.pa2", + { + onclick: triggerOBJExport, + }, + "Export OBJ", + ], + [ + "a.link.blue", + { + href: "https://en.wikipedia.org/wiki/Elementary_cellular_automaton#Random_initial_state", + }, + "Wikipedia", + ], + ], + ["pre.f7.code", sim], ]; const rule = reactive(105); const kernel = reactive(3); const objExport = metaStream(() => - fromIterable([true, false], { delay: 17 }) + fromIterable([true, false], { delay: 17 }) ); const wolfram = sync({ - src: { - rule: rule.transform(map((x) => [...bits(32, false, [x])])), - kernel: kernel.transform( - map((x) => buildKernel1d([1, 2, 4, 8, 16], x)) - ), - _: fromRAF(), - }, - xform: scan(reducer(resetCA, evolveCA)), + src: { + rule: rule.transform(map((x) => [...bits(32, false, [x])])), + kernel: kernel.transform( + map((x) => buildKernel1d([1, 2, 4, 8, 16], x)) + ), + _: fromRAF(), + }, + xform: scan(reducer(resetCA, evolveCA)), }); sync({ - src: { - id: rule, - ksize: kernel, - sim: wolfram.transform( - map((gen) => gen.map((x: number) => " █"[x]).join("")), - slidingWindow(HEIGHT), - map((win: string[]) => win.join("\n")) - ), - }, + src: { + id: rule, + ksize: kernel, + sim: wolfram.transform( + map((gen) => gen.map((x: number) => " █"[x]).join("")), + slidingWindow(HEIGHT), + map((win: string[]) => win.join("\n")) + ), + }, }).transform(map(app), updateDOM()); // Wavefront OBJ 3D pointcloud export @@ -143,27 +143,27 @@ sync({ // uses `objExport` metastream as toggle switch to produce OBJ file // and trigger download wolfram - // always collect new generations - // history length same as WIDTH to export square area - .transform(slidingWindow(WIDTH)) - // sidechainToggle is only letting new values through if enabled by - // objExport stream - .subscribe(sidechainToggle(objExport, { initial: false })) - // actual OBJ conversion & export - .transform( - map((grid) => - transduce( - comp( - filter((t) => !!t[1]), - map(([[x, y]]) => `v ${x} ${y} 0`) - ), - str("\n"), - zip(range2d(WIDTH, WIDTH), flatten(grid)) - ) - ), - map((obj: string) => - downloadWithMime(`ca-${rule.deref()}.obj`, obj, { - mime: "model/obj", - }) - ) - ); + // always collect new generations + // history length same as WIDTH to export square area + .transform(slidingWindow(WIDTH)) + // sidechainToggle is only letting new values through if enabled by + // objExport stream + .subscribe(sidechainToggle(objExport, { initial: false })) + // actual OBJ conversion & export + .transform( + map((grid) => + transduce( + comp( + filter((t) => !!t[1]), + map(([[x, y]]) => `v ${x} ${y} 0`) + ), + str("\n"), + zip(range2d(WIDTH, WIDTH), flatten(grid)) + ) + ), + map((obj: string) => + downloadWithMime(`ca-${rule.deref()}.obj`, obj, { + mime: "model/obj", + }) + ) + ); diff --git a/examples/wolfram/tsconfig.json b/examples/wolfram/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/wolfram/tsconfig.json +++ b/examples/wolfram/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/examples/xml-converter/package.json b/examples/xml-converter/package.json index 4f47786180..eaf38159b8 100644 --- a/examples/xml-converter/package.json +++ b/examples/xml-converter/package.json @@ -1,45 +1,45 @@ { - "name": "@example/xml-converter", - "private": true, - "description": "XML/HTML/SVG to hiccup/JS conversion", - "version": "0.0.1", - "repository": "https://github.com/thi-ng/umbrella", - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "type": "module", - "scripts": { - "start": "vite --open", - "build": "tsc && vite build --base='./'", - "build:cli": "tsc -p tsconfig-cli.json", - "preview": "vite preview --host --open" - }, - "dependencies": { - "@thi.ng/args": "workspace:^", - "@thi.ng/arrays": "workspace:^", - "@thi.ng/checks": "workspace:^", - "@thi.ng/defmulti": "workspace:^", - "@thi.ng/rstream": "workspace:^", - "@thi.ng/sax": "workspace:^", - "@thi.ng/strings": "workspace:^", - "@thi.ng/transducers": "workspace:^", - "@thi.ng/transducers-hdom": "workspace:^" - }, - "browser": { - "process": false, - "setTimeout": false, - "util": false - }, - "thi.ng": { - "readme": [ - "defmulti", - "sax", - "strings", - "transducers" - ], - "screenshot": "examples/xml-converter.png" - }, - "devDependencies": { - "typescript": "^4.7.4", - "vite": "^3.0.0" - } + "name": "@example/xml-converter", + "private": true, + "description": "XML/HTML/SVG to hiccup/JS conversion", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "type": "module", + "scripts": { + "start": "vite --open", + "build": "tsc && vite build --base='./'", + "build:cli": "tsc -p tsconfig-cli.json", + "preview": "vite preview --host --open" + }, + "dependencies": { + "@thi.ng/args": "workspace:^", + "@thi.ng/arrays": "workspace:^", + "@thi.ng/checks": "workspace:^", + "@thi.ng/defmulti": "workspace:^", + "@thi.ng/rstream": "workspace:^", + "@thi.ng/sax": "workspace:^", + "@thi.ng/strings": "workspace:^", + "@thi.ng/transducers": "workspace:^", + "@thi.ng/transducers-hdom": "workspace:^" + }, + "browser": { + "process": false, + "setTimeout": false, + "util": false + }, + "thi.ng": { + "readme": [ + "defmulti", + "sax", + "strings", + "transducers" + ], + "screenshot": "examples/xml-converter.png" + }, + "devDependencies": { + "typescript": "^4.7.4", + "vite": "^3.0.0" + } } diff --git a/examples/xml-converter/src/cli.ts b/examples/xml-converter/src/cli.ts index 9065902af6..acff20a317 100644 --- a/examples/xml-converter/src/cli.ts +++ b/examples/xml-converter/src/cli.ts @@ -6,57 +6,57 @@ import { COMPACT_FORMAT, DEFAULT_FORMAT } from "./format.js"; import { varName } from "./utils.js"; interface CLIOpts { - tags?: string[]; - attribs?: string[]; - var?: string; - singleQuote: boolean; - pretty: boolean; + tags?: string[]; + attribs?: string[]; + var?: string; + singleQuote: boolean; + pretty: boolean; } const res = parse( - { - tags: strings({ - alias: "t", - hint: "NAMES", - desc: "remove tags from tree", - delim: ",", - }), - attribs: strings({ - alias: "a", - hint: "NAMES", - desc: "remove attribs from tree", - delim: ",", - }), - var: string({ - alias: "v", - hint: "NAME", - desc: "generate TS export var decl", - }), - singleQuote: flag({ alias: "s", desc: "use single quotes" }), - pretty: flag({ alias: "p", desc: "enable pretty printing" }), - }, - process.argv + { + tags: strings({ + alias: "t", + hint: "NAMES", + desc: "remove tags from tree", + delim: ",", + }), + attribs: strings({ + alias: "a", + hint: "NAMES", + desc: "remove attribs from tree", + delim: ",", + }), + var: string({ + alias: "v", + hint: "NAME", + desc: "generate TS export var decl", + }), + singleQuote: flag({ alias: "s", desc: "use single quotes" }), + pretty: flag({ alias: "p", desc: "enable pretty printing" }), + }, + process.argv ); if (!res || res.done) { - console.log("Please run: hiccup --help"); - process.exit(1); + console.log("Please run: hiccup --help"); + process.exit(1); } const opts = res.result; const xmlFile = resolve(res.rest[0]); const quote = opts.singleQuote ? `'` : `"`; const copts = { - format: opts.pretty - ? { ...DEFAULT_FORMAT, quote, indent: 4 } - : { ...COMPACT_FORMAT, quote }, - removeAttribs: new Set(opts.attribs || []), - removeTags: new Set(opts.tags || []), + format: opts.pretty + ? { ...DEFAULT_FORMAT, quote, indent: 4 } + : { ...COMPACT_FORMAT, quote }, + removeAttribs: new Set(opts.attribs || []), + removeTags: new Set(opts.tags || []), }; const xml = readFileSync(xmlFile).toString(); const hiccup = convertXML(xml, copts); console.log( - opts.var ? `export const ${varName(opts.var)} =\n${hiccup};` : hiccup + opts.var ? `export const ${varName(opts.var)} =\n${hiccup};` : hiccup ); diff --git a/examples/xml-converter/src/convert.ts b/examples/xml-converter/src/convert.ts index be2547a6da..2af05bc7c7 100644 --- a/examples/xml-converter/src/convert.ts +++ b/examples/xml-converter/src/convert.ts @@ -11,24 +11,24 @@ import { transduce } from "@thi.ng/transducers/transduce"; import { DEFAULT_FORMAT, format, type FormatOpts } from "./format.js"; export interface ConversionOpts { - format: FormatOpts; - removeTags: Set; - removeAttribs: Set; + format: FormatOpts; + removeTags: Set; + removeAttribs: Set; } export const DEFAULT_OPTS: ConversionOpts = { - format: DEFAULT_FORMAT, - removeAttribs: new Set(), - removeTags: new Set(), + format: DEFAULT_FORMAT, + removeAttribs: new Set(), + removeTags: new Set(), }; // converts given XMLish string into formatted hiccup export const convertXML = (src: string, opts: Partial = {}) => { - let tree = transformTree(parseXML(src), { - ...DEFAULT_OPTS, - ...opts, - }); - return format({ ...DEFAULT_FORMAT, ...opts.format }, "", tree); + let tree = transformTree(parseXML(src), { + ...DEFAULT_OPTS, + ...opts, + }); + return format({ ...DEFAULT_FORMAT, ...opts.format }, "", tree); }; // parses given XMLish string using @thi.ng/sax transducer into a @@ -36,98 +36,98 @@ export const convertXML = (src: string, opts: Partial = {}) => { // event, which will be related to the final close tag and contains the // entire tree const parseXML = (src: string) => - transduce( - comp( - parse({ trim: true, boolean: true, entities: true }), - filter((e) => e.type === Type.ELEM_END || e.type === Type.ERROR) - ), - last(), - src - ); + transduce( + comp( + parse({ trim: true, boolean: true, entities: true }), + filter((e) => e.type === Type.ELEM_END || e.type === Type.ERROR) + ), + last(), + src + ); // transforms string of CSS properties into a plain object const transformCSS = (css: string) => - css.split(";").reduce((acc: any, p) => { - const [k, v] = p.split(":"); - v != null && (acc[k.trim()] = parseAttrib([k, v.trim()])[1]); - return acc; - }, {}); + css.split(";").reduce((acc: any, p) => { + const [k, v] = p.split(":"); + v != null && (acc[k.trim()] = parseAttrib([k, v.trim()])[1]); + return acc; + }, {}); // takes attrib key-value pair and attempts to coerce / transform its // value. returns updated pair. const parseAttrib = (attrib: string[]) => { - let [k, v] = attrib; - if (isString(v)) { - v = v.replace(/[\n\r]+\s*/g, " "); - return k === "style" - ? [k, transformCSS(v)] - : v === "true" - ? [k, true] - : v === "false" - ? [k, false] - : [k, /^[0-9.e+-]+$/.test(v) ? parseFloat(v) : v]; - } - return attrib; + let [k, v] = attrib; + if (isString(v)) { + v = v.replace(/[\n\r]+\s*/g, " "); + return k === "style" + ? [k, transformCSS(v)] + : v === "true" + ? [k, true] + : v === "false" + ? [k, false] + : [k, /^[0-9.e+-]+$/.test(v) ? parseFloat(v) : v]; + } + return attrib; }; // transforms an entire object of attributes const transformAttribs = (attribs: any, remove: Set = new Set()) => - transduce( - comp( - filter((a) => !remove.has(a[0])), - map(parseAttrib) - ), - assocObj(), - {}, - pairs(attribs) - ); + transduce( + comp( + filter((a) => !remove.has(a[0])), + map(parseAttrib) + ), + assocObj(), + {}, + pairs(attribs) + ); // transforms element name by attempting to form Emmet-like tags const transformTag = (tag: string, attribs: any) => { - if (attribs.id) { - tag += "#" + attribs.id; - delete attribs.id; - } - if (isString(attribs.class)) { - const classes = attribs.class.replace(/\s+/g, "."); - classes.length && (tag += "." + classes); - delete attribs.class; - } - return tag; + if (attribs.id) { + tag += "#" + attribs.id; + delete attribs.id; + } + if (isString(attribs.class)) { + const classes = attribs.class.replace(/\s+/g, "."); + classes.length && (tag += "." + classes); + delete attribs.class; + } + return tag; }; // recursively transforms entire parse tree const transformTree = ( - tree: ParseEvent | ParseElement, - opts: ConversionOpts + tree: ParseEvent | ParseElement, + opts: ConversionOpts ) => { - if (tree == null) { - return ""; - } - if ((tree).type === Type.ERROR) { - return ["error", tree.body]; - } - if (opts.removeTags.has(tree.tag!)) { - return; - } - const attribs = transformAttribs(tree.attribs, opts.removeAttribs); - const res: any[] = [transformTag(tree.tag!, attribs)]; - if (Object.keys(attribs).length) { - res.push(attribs); - } - if (tree.body) { - res.push(tree.body); - } - if (tree.children && tree.children.length) { - transduce( - comp( - map((t: any) => transformTree(t, opts)), - filter((t) => !!t) - ), - push(), - res, - tree.children - ); - } - return res; + if (tree == null) { + return ""; + } + if ((tree).type === Type.ERROR) { + return ["error", tree.body]; + } + if (opts.removeTags.has(tree.tag!)) { + return; + } + const attribs = transformAttribs(tree.attribs, opts.removeAttribs); + const res: any[] = [transformTag(tree.tag!, attribs)]; + if (Object.keys(attribs).length) { + res.push(attribs); + } + if (tree.body) { + res.push(tree.body); + } + if (tree.children && tree.children.length) { + transduce( + comp( + map((t: any) => transformTree(t, opts)), + filter((t) => !!t) + ), + push(), + res, + tree.children + ); + } + return res; }; diff --git a/examples/xml-converter/src/format.ts b/examples/xml-converter/src/format.ts index 569f21faa6..e770d82992 100644 --- a/examples/xml-converter/src/format.ts +++ b/examples/xml-converter/src/format.ts @@ -7,33 +7,33 @@ import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti"; import { repeat } from "@thi.ng/strings/repeat"; export interface FormatOpts { - indent: number; - tabSize: number; - lineSep: string; - prefix?: string; - quote: string; - ws: string; - trailingComma: boolean; + indent: number; + tabSize: number; + lineSep: string; + prefix?: string; + quote: string; + ws: string; + trailingComma: boolean; } export const DEFAULT_FORMAT: FormatOpts = { - indent: 0, - tabSize: 4, - trailingComma: true, - lineSep: "\n", - prefix: "", - quote: `"`, - ws: " ", + indent: 0, + tabSize: 4, + trailingComma: true, + lineSep: "\n", + prefix: "", + quote: `"`, + ws: " ", }; export const COMPACT_FORMAT: FormatOpts = { - indent: 0, - tabSize: 0, - trailingComma: false, - lineSep: "", - prefix: "", - quote: `"`, - ws: "", + indent: 0, + tabSize: 0, + trailingComma: false, + lineSep: "", + prefix: "", + quote: `"`, + ws: "", }; // memoized indentations @@ -41,119 +41,119 @@ export const spaces = (n: number) => repeat(" ", n); // creates new state with deeper indentation const indentState = (opts: FormatOpts): FormatOpts => ({ - ...opts, - indent: opts.indent + opts.tabSize, + ...opts, + indent: opts.indent + opts.tabSize, }); // dispatch helper function for the `format` defmulti below const classify = (x: any) => - isArray(x) ? "array" : isPlainObject(x) ? "obj" : DEFAULT; + isArray(x) ? "array" : isPlainObject(x) ? "obj" : DEFAULT; // wraps attrib name in quotes if needed const formatAttrib = (opts: FormatOpts, x: string) => - /^[a-z]+$/i.test(x) ? x : opts.quote + x + opts.quote; + /^[a-z]+$/i.test(x) ? x : opts.quote + x + opts.quote; // attrib or body value formatter const formatVal = (opts: FormatOpts, x: any, indent = true) => - isNumber(x) || isBoolean(x) - ? x - : isPlainObject(x) - ? format(indent ? indentState(opts) : opts, "", x) - : opts.quote + escape(opts, x) + opts.quote; + isNumber(x) || isBoolean(x) + ? x + : isPlainObject(x) + ? format(indent ? indentState(opts) : opts, "", x) + : opts.quote + escape(opts, x) + opts.quote; // attrib key-value pair formatter w/ indentation const formatPair = (opts: FormatOpts, x: any, k: string) => - spaces(opts.indent) + - formatAttrib(opts, k) + - ":" + - opts.ws + - formatVal(opts, x[k], k !== "style"); + spaces(opts.indent) + + formatAttrib(opts, k) + + ":" + + opts.ws + + formatVal(opts, x[k], k !== "style"); const escape = (opts: FormatOpts, x: any) => - opts.quote === '"' - ? String(x).replace(/"/g, '\\"') - : String(x).replace(/'/g, "\\'"); + opts.quote === '"' + ? String(x).replace(/"/g, '\\"') + : String(x).replace(/'/g, "\\'"); // multiple-dispatch function to format the transformed tree (hiccup // structure) into a more compact & legible format than produced by // standard: `JSON.stringify(tree, null, 4)` export const format = defmulti((_, __, x) => - classify(x) + classify(x) ); // implementation for array values format.add("array", (opts, res, x) => { - const hasAttribs = isPlainObject(x[1]); - let attribs = hasAttribs ? Object.keys(x[1]) : []; - res += spaces(opts.indent) + "[" + opts.quote + x[0] + opts.quote; - if (hasAttribs) { - res += "," + opts.ws; - res = format( - { - ...indentState(opts), - prefix: `${opts.lineSep}${spaces(opts.indent + opts.tabSize)}`, - }, - res, - x[1] - ); - } - // single line if none or only single child - // and if no `style` attrib - if ( - x.length === (hasAttribs ? 3 : 2) && - attribs.length < 2 && - attribs[0] !== "style" && - classify(peek(x)) === DEFAULT - ) { - return ( - format({ ...opts, indent: 0 }, (res += "," + opts.ws), peek(x)) + - "]" - ); - } - // default format if more children - for (let i = hasAttribs ? 2 : 1; i < x.length; i++) { - res += "," + opts.lineSep; - res = format(indentState(opts), res, x[i]); - } - res += "]"; - return res; + const hasAttribs = isPlainObject(x[1]); + let attribs = hasAttribs ? Object.keys(x[1]) : []; + res += spaces(opts.indent) + "[" + opts.quote + x[0] + opts.quote; + if (hasAttribs) { + res += "," + opts.ws; + res = format( + { + ...indentState(opts), + prefix: `${opts.lineSep}${spaces(opts.indent + opts.tabSize)}`, + }, + res, + x[1] + ); + } + // single line if none or only single child + // and if no `style` attrib + if ( + x.length === (hasAttribs ? 3 : 2) && + attribs.length < 2 && + attribs[0] !== "style" && + classify(peek(x)) === DEFAULT + ) { + return ( + format({ ...opts, indent: 0 }, (res += "," + opts.ws), peek(x)) + + "]" + ); + } + // default format if more children + for (let i = hasAttribs ? 2 : 1; i < x.length; i++) { + res += "," + opts.lineSep; + res = format(indentState(opts), res, x[i]); + } + res += "]"; + return res; }); // implementation for object values (i.e. attributes in this case) format.add("obj", (opts, res, x) => { - const keys = Object.keys(x); - if (keys.length === 0) { - res += `{}`; - } else if ( - keys.length === 1 && - (keys[0] !== "style" || Object.keys(x.style).length < 2) - ) { - res += - "{" + - opts.ws + - formatPair({ ...opts, indent: 0 }, x, keys[0]) + - opts.ws + - "}"; - } else { - const outer = spaces(opts.indent); - res += opts.prefix + "{" + opts.lineSep; - for (let i = keys.length; i-- > 0; ) { - const k = keys[i]; - res += formatPair( - k === "style" - ? { ...indentState(opts), prefix: "" } - : indentState(opts), - x, - k - ); - res += (opts.trailingComma || i > 0 ? "," : "") + opts.lineSep; - } - res += outer + "}"; - } - return res; + const keys = Object.keys(x); + if (keys.length === 0) { + res += `{}`; + } else if ( + keys.length === 1 && + (keys[0] !== "style" || Object.keys(x.style).length < 2) + ) { + res += + "{" + + opts.ws + + formatPair({ ...opts, indent: 0 }, x, keys[0]) + + opts.ws + + "}"; + } else { + const outer = spaces(opts.indent); + res += opts.prefix + "{" + opts.lineSep; + for (let i = keys.length; i-- > 0; ) { + const k = keys[i]; + res += formatPair( + k === "style" + ? { ...indentState(opts), prefix: "" } + : indentState(opts), + x, + k + ); + res += (opts.trailingComma || i > 0 ? "," : "") + opts.lineSep; + } + res += outer + "}"; + } + return res; }); // implementation for other values format.setDefault( - (opts, res, x) => (res += spaces(opts.indent) + formatVal(opts, x)) + (opts, res, x) => (res += spaces(opts.indent) + formatVal(opts, x)) ); diff --git a/examples/xml-converter/src/index.ts b/examples/xml-converter/src/index.ts index 9a43def27e..b946454f0e 100644 --- a/examples/xml-converter/src/index.ts +++ b/examples/xml-converter/src/index.ts @@ -9,7 +9,7 @@ import { xformAsSet } from "./utils.js"; // input streams (reactive state values) const inputs = { - xml: reactive(` + xml: reactive(` foo @@ -21,52 +21,52 @@ const inputs = { `), - prettyPrint: reactive(true), - doubleQuote: reactive(true), - trailingComma: reactive(true), - removeAttribs: reactive("id,class"), - removeTags: reactive("head"), - copyButton: reactive(false), + prettyPrint: reactive(true), + doubleQuote: reactive(true), + trailingComma: reactive(true), + removeAttribs: reactive("id,class"), + removeTags: reactive("head"), + copyButton: reactive(false), }; // stream combinator to assemble formatter options const formatOpts = sync({ - src: { - trailingComma: inputs.trailingComma, - doubleQuote: inputs.doubleQuote, - prettyPrint: inputs.prettyPrint, - }, - xform: map((opts: any) => ({ - ...(opts.prettyPrint ? DEFAULT_FORMAT : COMPACT_FORMAT), - trailingComma: opts.trailingComma, - quote: opts.doubleQuote ? `"` : `'`, - })), + src: { + trailingComma: inputs.trailingComma, + doubleQuote: inputs.doubleQuote, + prettyPrint: inputs.prettyPrint, + }, + xform: map((opts: any) => ({ + ...(opts.prettyPrint ? DEFAULT_FORMAT : COMPACT_FORMAT), + trailingComma: opts.trailingComma, + quote: opts.doubleQuote ? `"` : `'`, + })), }); // stream combinator to assemble conversion options const opts = sync({ - src: { - format: formatOpts, - removeAttribs: inputs.removeAttribs.transform(xformAsSet), - removeTags: inputs.removeTags.transform(xformAsSet), - }, + src: { + format: formatOpts, + removeAttribs: inputs.removeAttribs.transform(xformAsSet), + removeTags: inputs.removeTags.transform(xformAsSet), + }, }); // main stream combinator to create & update UI sync({ - src: { - src: inputs.xml, - copy: inputs.copyButton, - opts, - }, + src: { + src: inputs.xml, + copy: inputs.copyButton, + opts, + }, }).transform( - // convert xml -> hiccup - map((state: any) => ({ - ...state, - hiccup: convertXML(state.src, state.opts), - })), - // transform into hdom tree - map(app(UI.main, inputs)), - // apply to real DOM - updateDOM({ ctx: UI }) + // convert xml -> hiccup + map((state: any) => ({ + ...state, + hiccup: convertXML(state.src, state.opts), + })), + // transform into hdom tree + map(app(UI.main, inputs)), + // apply to real DOM + updateDOM({ ctx: UI }) ); diff --git a/examples/xml-converter/src/ui.ts b/examples/xml-converter/src/ui.ts index e88e7acdb4..b84bdfd98d 100644 --- a/examples/xml-converter/src/ui.ts +++ b/examples/xml-converter/src/ui.ts @@ -5,68 +5,68 @@ import { handleTab } from "./utils.js"; // converted from: // https://github.com/IBM/carbon-icons/blob/develop/src/svg/copy.svg const ICON_COPY = [ - "svg.mr2", - { - viewBox: "0 0 16 16", - width: "0.7rem", - height: "0.7rem", - stroke: "white", - }, - [ - "path", - { d: "M1,10H0V2c0-1.1,0.9-2,2-2l8,0l0,1L2,1C1.4,1,1,1.5,1,2L1,10z" }, - ], - [ - "path", - { - d: "M11,4.2V8h3.8L11,4.2z M15,9h-4c-0.6,0-1-0.4-1-1V4H4.5C4.2,4,4,4.2,4,4.5v10C4,14.8,4.2,15,4.5,15h10 c0.3,0,0.5-0.2,0.5-0.5V9z M11,3c0.1,0,0.3,0.1,0.4,0.1l4.5,4.5C15.9,7.7,16,7.9,16,8v6.5c0,0.8-0.7,1.5-1.5,1.5h-10 C3.7,16,3,15.3,3,14.5v-10C3,3.7,3.7,3,4.5,3H11z", - }, - ], + "svg.mr2", + { + viewBox: "0 0 16 16", + width: "0.7rem", + height: "0.7rem", + stroke: "white", + }, + [ + "path", + { d: "M1,10H0V2c0-1.1,0.9-2,2-2l8,0l0,1L2,1C1.4,1,1,1.5,1,2L1,10z" }, + ], + [ + "path", + { + d: "M11,4.2V8h3.8L11,4.2z M15,9h-4c-0.6,0-1-0.4-1-1V4H4.5C4.2,4,4,4.2,4,4.5v10C4,14.8,4.2,15,4.5,15h10 c0.3,0,0.5-0.2,0.5-0.5V9z M11,3c0.1,0,0.3,0.1,0.4,0.1l4.5,4.5C15.9,7.7,16,7.9,16,8v6.5c0,0.8-0.7,1.5-1.5,1.5h-10 C3.7,16,3,15.3,3,14.5v-10C3,3.7,3.7,3,4.5,3H11z", + }, + ], ]; // component styles: // this object will be provided as hdom user context to all // component functions export const UI = { - editor: { - root: { class: "w-50-ns pa3" }, - title: { class: "ma0 mb2" }, - textarea: { class: "w-100.pa2.bn.f7.code.lh-copy" }, - stats: { class: "f7" }, - }, - input: { - root: { class: "mb2" }, - label: { class: "dib w-100 w-25-l pv2" }, - input: { class: "w-100 w-75-l pa1 bg-silver white bn" }, - }, - button: { - href: "#", - class: "dib.link.white.pv1.ph2.w4.bn.hover-bg-blue.bg-animate", - }, - copyButton: { - style: { - position: "relative", - top: "-3rem", - left: "0.5rem", - }, - }, - main: { - small: { class: "fw1 ml2 dn dib-l" }, - src: { - class: "bg-washed-green", - autofocus: true, - }, - result: { - success: { - disabled: true, - class: "bg-light-gray", - }, - error: { - disabled: true, - class: "bg-washed-red", - }, - }, - }, + editor: { + root: { class: "w-50-ns pa3" }, + title: { class: "ma0 mb2" }, + textarea: { class: "w-100.pa2.bn.f7.code.lh-copy" }, + stats: { class: "f7" }, + }, + input: { + root: { class: "mb2" }, + label: { class: "dib w-100 w-25-l pv2" }, + input: { class: "w-100 w-75-l pa1 bg-silver white bn" }, + }, + button: { + href: "#", + class: "dib.link.white.pv1.ph2.w4.bn.hover-bg-blue.bg-animate", + }, + copyButton: { + style: { + position: "relative", + top: "-3rem", + left: "0.5rem", + }, + }, + main: { + small: { class: "fw1 ml2 dn dib-l" }, + src: { + class: "bg-washed-green", + autofocus: true, + }, + result: { + success: { + disabled: true, + class: "bg-light-gray", + }, + error: { + disabled: true, + class: "bg-washed-red", + }, + }, + }, }; // hdom UI root component. this function will be used as stream @@ -74,133 +74,133 @@ export const UI = { // defined as closure to avoid using global vars. the `ctx` is the above // `UI.main` and `inputs` are defined in `index.ts`. export const app = - (ctx: any, inputs: any) => - ({ src, hiccup }: any) => - [ - "div.flex-ns", - [ - editPane, - [ - "XML/HTML source", - ["small", ctx.small, "(must be well formed!)"], - ], - { - ...ctx.src, - onkeydown: handleTab(inputs.xml), - // emitting a new value to the stream will - // re-trigger conversion & UI update - oninput: (e: any) => inputs.xml.next(e.target.value), - }, - src, - ], - [ - editPane, - ["Transformed Hiccup / JSON"], - hiccup.indexOf("error") < 0 - ? ctx.result.success - : ctx.result.error, - hiccup, - [ - copyButton, - { - class: - hiccup.indexOf("error") < 0 - ? "bg-green" - : "bg-gray", - }, - inputs.copyButton, - hiccup, - ], - [transformOpts, inputs], - ], - ]; + (ctx: any, inputs: any) => + ({ src, hiccup }: any) => + [ + "div.flex-ns", + [ + editPane, + [ + "XML/HTML source", + ["small", ctx.small, "(must be well formed!)"], + ], + { + ...ctx.src, + onkeydown: handleTab(inputs.xml), + // emitting a new value to the stream will + // re-trigger conversion & UI update + oninput: (e: any) => inputs.xml.next(e.target.value), + }, + src, + ], + [ + editPane, + ["Transformed Hiccup / JSON"], + hiccup.indexOf("error") < 0 + ? ctx.result.success + : ctx.result.error, + hiccup, + [ + copyButton, + { + class: + hiccup.indexOf("error") < 0 + ? "bg-green" + : "bg-gray", + }, + inputs.copyButton, + hiccup, + ], + [transformOpts, inputs], + ], + ]; // configurable editor panel UI component // (uses Tachyons CSS classes for styling) const editPane = ( - { editor }: any, - title: string, - attribs: any, - value: string, - ...extra: any[] + { editor }: any, + title: string, + attribs: any, + value: string, + ...extra: any[] ) => [ - "div", - editor.root, - ["h3", editor.title, ...title], - [`textarea.${editor.textarea.class}`, { rows: 16, value, ...attribs }], - ["div", editor.stats, `${value.length} chars`], - ...extra, + "div", + editor.root, + ["h3", editor.title, ...title], + [`textarea.${editor.textarea.class}`, { rows: 16, value, ...attribs }], + ["div", editor.stats, `${value.length} chars`], + ...extra, ]; // configurable input UI component const input = ({ input }: any, label: string, attribs: any) => [ - "div", - input.root, - ["label", { ...input.label, for: attribs.id }, label], - ["input", { ...input.input, ...attribs }], + "div", + input.root, + ["label", { ...input.label, for: attribs.id }, label], + ["input", { ...input.input, ...attribs }], ]; const iconButton = ( - { button }: any, - attribs: any, - icon: any, - label: string + { button }: any, + attribs: any, + icon: any, + label: string ) => [`a.${button.class}`, { ...button, ...attribs }, icon, label]; // button which, when clicked, copies given `text` to clipboard and // emits true on `stream`. resets stream back to false after given // `delay` time. const copyButton = ( - { copyButton }: any, - attribs: any, - stream: ISubscriber, - text: string, - delay = 500 + { copyButton }: any, + attribs: any, + stream: ISubscriber, + text: string, + delay = 500 ) => [ - iconButton, - { - ...copyButton, - ...attribs, - onclick: (e: any) => { - e.preventDefault(); - e.target.blur(); - (navigator).clipboard.writeText(text).then( - () => { - setTimeout(() => stream.next(false), delay); - stream.next(true); - }, - () => alert("Couldn't copy to clipboard") - ); - }, - }, - ICON_COPY, - stream.deref() ? "Copied" : "Copy", + iconButton, + { + ...copyButton, + ...attribs, + onclick: (e: any) => { + e.preventDefault(); + e.target.blur(); + (navigator).clipboard.writeText(text).then( + () => { + setTimeout(() => stream.next(false), delay); + stream.next(true); + }, + () => alert("Couldn't copy to clipboard") + ); + }, + }, + ICON_COPY, + stream.deref() ? "Copied" : "Copy", ]; // combined transform options input components const transformOpts = (_: any, inputs: any) => [ - "div", - ["h3", "Options"], - mapIndexed( - (i, [label, type, stream]) => { - let v = type === "checkbox" ? "checked" : "value"; - return [ - input, - label, - { - id: "opt" + i, - type, - [v]: stream.deref(), - oninput: (e: any) => stream.next(e.target[v]), - }, - ]; - }, - [ - ["Remove tags", "text", inputs.removeTags], - ["Remove attributes", "text", inputs.removeAttribs], - ["Pretty print", "checkbox", inputs.prettyPrint], - ["Double quotes", "checkbox", inputs.doubleQuote], - ["Trailing commas", "checkbox", inputs.trailingComma], - ] - ), + "div", + ["h3", "Options"], + mapIndexed( + (i, [label, type, stream]) => { + let v = type === "checkbox" ? "checked" : "value"; + return [ + input, + label, + { + id: "opt" + i, + type, + [v]: stream.deref(), + oninput: (e: any) => stream.next(e.target[v]), + }, + ]; + }, + [ + ["Remove tags", "text", inputs.removeTags], + ["Remove attributes", "text", inputs.removeAttribs], + ["Pretty print", "checkbox", inputs.prettyPrint], + ["Double quotes", "checkbox", inputs.doubleQuote], + ["Trailing commas", "checkbox", inputs.trailingComma], + ] + ), ]; diff --git a/examples/xml-converter/src/utils.ts b/examples/xml-converter/src/utils.ts index a43fe9aef9..0caadf0345 100644 --- a/examples/xml-converter/src/utils.ts +++ b/examples/xml-converter/src/utils.ts @@ -10,18 +10,18 @@ export const xformAsSet = map(asSet); // key event handler for textareas to override Tab key behavior and // insert spaces at cursor position instead of changing keyboard focus export const handleTab = - (stream: ISubscriber) => (e: KeyboardEvent) => { - // override tab to insert spaces at edit pos - if (e.key === "Tab") { - e.preventDefault(); - stream.next( - splice( - (e.target).value, - " ", - (e.target).selectionStart - ) - ); - } - }; + (stream: ISubscriber) => (e: KeyboardEvent) => { + // override tab to insert spaces at edit pos + if (e.key === "Tab") { + e.preventDefault(); + stream.next( + splice( + (e.target).value, + " ", + (e.target).selectionStart + ) + ); + } + }; export const varName = (name: string) => name.replace(/\-+/g, "_"); diff --git a/examples/xml-converter/tsconfig-cli.json b/examples/xml-converter/tsconfig-cli.json index a002cfe4ad..2a7f44e9f6 100644 --- a/examples/xml-converter/tsconfig-cli.json +++ b/examples/xml-converter/tsconfig-cli.json @@ -1,12 +1,12 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": ".", - "target": "es2020", - "module": "es2020", - "declaration": false, - "declarationMap": false, - "sourceMap": false - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "target": "es2020", + "module": "es2020", + "declaration": false, + "declarationMap": false, + "sourceMap": false + }, + "include": ["./src/**/*.ts"] } diff --git a/examples/xml-converter/tsconfig.json b/examples/xml-converter/tsconfig.json index dacbce09bb..3616cb2d30 100644 --- a/examples/xml-converter/tsconfig.json +++ b/examples/xml-converter/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": {} } diff --git a/packages/adapt-dpi/api-extractor.json b/packages/adapt-dpi/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/adapt-dpi/api-extractor.json +++ b/packages/adapt-dpi/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/adapt-dpi/package.json b/packages/adapt-dpi/package.json index 75054a8652..5134fbcaf8 100644 --- a/packages/adapt-dpi/package.json +++ b/packages/adapt-dpi/package.json @@ -1,68 +1,68 @@ { - "name": "@thi.ng/adapt-dpi", - "version": "2.1.8", - "description": "HDPI canvas adapter / styling utility", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/adapt-dpi#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "browser", - "canvas", - "dpi", - "hdpi", - "retina", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": {}, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "year": 2015 - } + "name": "@thi.ng/adapt-dpi", + "version": "2.1.8", + "description": "HDPI canvas adapter / styling utility", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/adapt-dpi#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "browser", + "canvas", + "dpi", + "hdpi", + "retina", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": {}, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "year": 2015 + } } diff --git a/packages/adapt-dpi/src/index.ts b/packages/adapt-dpi/src/index.ts index c480dac192..49de9409e0 100644 --- a/packages/adapt-dpi/src/index.ts +++ b/packages/adapt-dpi/src/index.ts @@ -10,18 +10,18 @@ * @param height - uncompensated pixel height */ export const adaptDPI = ( - canvas: HTMLCanvasElement, - width: number, - height: number + canvas: HTMLCanvasElement, + width: number, + height: number ) => { - const dpr = window.devicePixelRatio || 1; - if (dpr !== 1) { - canvas.style.width = `${width}px`; - canvas.style.height = `${height}px`; - } - canvas.width = width * dpr; - canvas.height = height * dpr; - return dpr; + const dpr = window.devicePixelRatio || 1; + if (dpr !== 1) { + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + } + canvas.width = width * dpr; + canvas.height = height * dpr; + return dpr; }; /** diff --git a/packages/adapt-dpi/tsconfig.json b/packages/adapt-dpi/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/adapt-dpi/tsconfig.json +++ b/packages/adapt-dpi/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/adjacency/api-extractor.json b/packages/adjacency/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/adjacency/api-extractor.json +++ b/packages/adjacency/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/adjacency/package.json b/packages/adjacency/package.json index 81ab4f8213..03e36df462 100644 --- a/packages/adjacency/package.json +++ b/packages/adjacency/package.json @@ -1,122 +1,122 @@ { - "name": "@thi.ng/adjacency", - "version": "2.2.0", - "description": "Sparse & bitwise adjacency matrices and related functions for directed & undirected graphs", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/adjacency#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/arrays": "^2.3.1", - "@thi.ng/bitfield": "^2.2.0", - "@thi.ng/dcons": "^3.2.7", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/sparse": "^0.3.12" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "@thi.ng/vectors": "^7.5.8", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "adjacency", - "binary", - "bitwise", - "datastructure", - "degree", - "directed", - "disjointset", - "graph", - "laplacian", - "list", - "matrix", - "neighbors", - "path", - "search", - "span", - "sparse", - "tree", - "typescript", - "undirected", - "unionfind", - "valence", - "vertex" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./bfs": { - "default": "./bfs.js" - }, - "./binary": { - "default": "./binary.js" - }, - "./dfs": { - "default": "./dfs.js" - }, - "./disjoint-set": { - "default": "./disjoint-set.js" - }, - "./list": { - "default": "./list.js" - }, - "./mst": { - "default": "./mst.js" - }, - "./sparse": { - "default": "./sparse.js" - } - }, - "thi.ng": { - "related": [ - "dgraph" - ], - "year": 2018 - } + "name": "@thi.ng/adjacency", + "version": "2.2.0", + "description": "Sparse & bitwise adjacency matrices and related functions for directed & undirected graphs", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/adjacency#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/arrays": "^2.3.1", + "@thi.ng/bitfield": "^2.2.0", + "@thi.ng/dcons": "^3.2.7", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/sparse": "^0.3.12" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "@thi.ng/vectors": "^7.5.8", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "adjacency", + "binary", + "bitwise", + "datastructure", + "degree", + "directed", + "disjointset", + "graph", + "laplacian", + "list", + "matrix", + "neighbors", + "path", + "search", + "span", + "sparse", + "tree", + "typescript", + "undirected", + "unionfind", + "valence", + "vertex" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./bfs": { + "default": "./bfs.js" + }, + "./binary": { + "default": "./binary.js" + }, + "./dfs": { + "default": "./dfs.js" + }, + "./disjoint-set": { + "default": "./disjoint-set.js" + }, + "./list": { + "default": "./list.js" + }, + "./mst": { + "default": "./mst.js" + }, + "./sparse": { + "default": "./sparse.js" + } + }, + "thi.ng": { + "related": [ + "dgraph" + ], + "year": 2018 + } } diff --git a/packages/adjacency/src/api.ts b/packages/adjacency/src/api.ts index e82e04a438..9373c11471 100644 --- a/packages/adjacency/src/api.ts +++ b/packages/adjacency/src/api.ts @@ -6,65 +6,65 @@ export type DegreeType = "in" | "out" | "inout"; * @typeParam T - vertex type (default: `number`) */ export interface IGraph { - numEdges(): number; - numVertices(): number; + numEdges(): number; + numVertices(): number; - /** - * Returns iterator of all edges in the graph. Each edge is a tuple of - * `[from, to]`. For undirected graphs each edge will only be emitted once. - */ - edges(): IterableIterator>; - /** - * Attempts to add an edge between given vertex IDs. Depending on the actual - * implementation, only max. 1 edge per unique vertex pair is supported - * (currently only {@link AdjacencyList} supports multi-edges). If the graph - * is undirected, a symmetric edge might be created automatically. Returns - * true, if the edge was created. - * - * @param from - - * @param to - - */ - addEdge(from: T, to: T): boolean; - /** - * Attempts to remove an edge for given vertex pair. Returns true, if - * successful. - * - * @param from - - * @param to - - */ - removeEdge(from: T, to: T): boolean; - /** - * Returns true if an edge exists for the given vertex pair. For undirected - * graphs, the vertex order is irrelevant. - * - * @param from - - * @param to - - */ - hasEdge(from: T, to: T): boolean; - /** - * Returns number of edges for given vertex. By default only outgoing edges - * are counted, but can be customized via given {@link DegreeType}. Note: In - * undirected graphs the `type` has no relevance and essentially is always - * `"inout"`. - * - * @param id - - * @param type - - */ - degree(id: T, type?: DegreeType): number; - /** - * Returns neighbor IDs for given vertex, i.e. those vertices connected via - * edges starting *from* given vertex (or, in undirected graphs, the other - * vertices of edges which the given vertex is part of). - * - * @param id - - */ - neighbors(id: T): Iterable; - /** - * Only useful for directed graphs. Returns a new graph in which the - * direction of edges is inverted/flipped. I.e. an edge `a -> b` becomes `b - * -> a`. - */ - invert(): IGraph; + /** + * Returns iterator of all edges in the graph. Each edge is a tuple of + * `[from, to]`. For undirected graphs each edge will only be emitted once. + */ + edges(): IterableIterator>; + /** + * Attempts to add an edge between given vertex IDs. Depending on the actual + * implementation, only max. 1 edge per unique vertex pair is supported + * (currently only {@link AdjacencyList} supports multi-edges). If the graph + * is undirected, a symmetric edge might be created automatically. Returns + * true, if the edge was created. + * + * @param from - + * @param to - + */ + addEdge(from: T, to: T): boolean; + /** + * Attempts to remove an edge for given vertex pair. Returns true, if + * successful. + * + * @param from - + * @param to - + */ + removeEdge(from: T, to: T): boolean; + /** + * Returns true if an edge exists for the given vertex pair. For undirected + * graphs, the vertex order is irrelevant. + * + * @param from - + * @param to - + */ + hasEdge(from: T, to: T): boolean; + /** + * Returns number of edges for given vertex. By default only outgoing edges + * are counted, but can be customized via given {@link DegreeType}. Note: In + * undirected graphs the `type` has no relevance and essentially is always + * `"inout"`. + * + * @param id - + * @param type - + */ + degree(id: T, type?: DegreeType): number; + /** + * Returns neighbor IDs for given vertex, i.e. those vertices connected via + * edges starting *from* given vertex (or, in undirected graphs, the other + * vertices of edges which the given vertex is part of). + * + * @param id - + */ + neighbors(id: T): Iterable; + /** + * Only useful for directed graphs. Returns a new graph in which the + * direction of edges is inverted/flipped. I.e. an edge `a -> b` becomes `b + * -> a`. + */ + invert(): IGraph; } export type Edge = Pair; diff --git a/packages/adjacency/src/bfs.ts b/packages/adjacency/src/bfs.ts index aae1b93c3a..a46a1aa207 100644 --- a/packages/adjacency/src/bfs.ts +++ b/packages/adjacency/src/bfs.ts @@ -3,55 +3,55 @@ import { DCons } from "@thi.ng/dcons/dcons"; import type { CostFn, IGraph } from "./api.js"; export class BFS { - graph: IGraph; - marked: BitField; - edges: Uint32Array; - dist: Uint32Array; + graph: IGraph; + marked: BitField; + edges: Uint32Array; + dist: Uint32Array; - constructor(graph: IGraph, src: number, cost: CostFn = () => 1) { - this.graph = graph; - const numV = graph.numVertices(); - this.edges = new Uint32Array(numV); - this.dist = new Uint32Array(numV); - this.marked = new BitField(numV); - this.search(src, cost); - } + constructor(graph: IGraph, src: number, cost: CostFn = () => 1) { + this.graph = graph; + const numV = graph.numVertices(); + this.edges = new Uint32Array(numV); + this.dist = new Uint32Array(numV); + this.marked = new BitField(numV); + this.search(src, cost); + } - protected search(id: number, cost: CostFn) { - const queue = new DCons(); - queue.prepend(id); - const { dist, edges, graph, marked } = this; - dist.fill(0xffffffff); - dist[id] = 0; - marked.setAt(id); - while (queue.length) { - const v = queue.drop()!; - for (let n of graph.neighbors(v)) { - const c = dist[v] + cost(v, n); - if (c < dist[n] || !marked.at(n)) { - edges[n] = v; - dist[n] = c; - marked.setAt(n); - queue.push(n); - } - } - } - } + protected search(id: number, cost: CostFn) { + const queue = new DCons(); + queue.prepend(id); + const { dist, edges, graph, marked } = this; + dist.fill(0xffffffff); + dist[id] = 0; + marked.setAt(id); + while (queue.length) { + const v = queue.drop()!; + for (let n of graph.neighbors(v)) { + const c = dist[v] + cost(v, n); + if (c < dist[n] || !marked.at(n)) { + edges[n] = v; + dist[n] = c; + marked.setAt(n); + queue.push(n); + } + } + } + } - hasPathTo(id: number) { - return this.marked.at(id) !== 0; - } + hasPathTo(id: number) { + return this.marked.at(id) !== 0; + } - pathTo(id: number): Iterable | undefined { - if (!this.marked.at(id)) return; - const { dist, edges } = this; - const path = new DCons(); - for (; dist[id] > 0; id = edges[id]) { - path.prepend(id); - } - path.prepend(id); - return path; - } + pathTo(id: number): Iterable | undefined { + if (!this.marked.at(id)) return; + const { dist, edges } = this; + const path = new DCons(); + for (; dist[id] > 0; id = edges[id]) { + path.prepend(id); + } + path.prepend(id); + return path; + } } /** @@ -77,4 +77,4 @@ export class BFS { * @param cost - */ export const bfs = (graph: IGraph, src: number, dest: number, cost?: CostFn) => - new BFS(graph, src, cost).pathTo(dest); + new BFS(graph, src, cost).pathTo(dest); diff --git a/packages/adjacency/src/binary.ts b/packages/adjacency/src/binary.ts index 949e27e83d..1565f6792c 100644 --- a/packages/adjacency/src/binary.ts +++ b/packages/adjacency/src/binary.ts @@ -9,110 +9,110 @@ import { __into, __invert, __toDot } from "./utils.js"; * storing 16384 directed edges in just 2KB of memory (128 * 128 / 8 = 2048). */ export class AdjacencyBitMatrix implements IGraph { - mat: BitMatrix; - protected undirected: boolean; - protected numE: number; + mat: BitMatrix; + protected undirected: boolean; + protected numE: number; - constructor(n: number, edges?: Iterable, undirected = false) { - this.mat = new BitMatrix(n); - this.undirected = undirected; - this.numE = 0; - edges && __into(this, edges); - } + constructor(n: number, edges?: Iterable, undirected = false) { + this.mat = new BitMatrix(n); + this.undirected = undirected; + this.numE = 0; + edges && __into(this, edges); + } - *edges() { - const directed = !this.undirected; - for (let i = this.mat.n; i-- > 0; ) { - for (let n of this.neighbors(i)) { - if (directed || n > i) { - yield [i, n]; - } - } - } - } + *edges() { + const directed = !this.undirected; + for (let i = this.mat.n; i-- > 0; ) { + for (let n of this.neighbors(i)) { + if (directed || n > i) { + yield [i, n]; + } + } + } + } - numEdges(): number { - return this.numE; - } + numEdges(): number { + return this.numE; + } - numVertices(): number { - return this.mat.n; - } + numVertices(): number { + return this.mat.n; + } - /** - * Resizes matrix to new size given. - * - * @param n - new max vertices - */ - resize(n: number) { - this.mat.resize(n); - return this; - } + /** + * Resizes matrix to new size given. + * + * @param n - new max vertices + */ + resize(n: number) { + this.mat.resize(n); + return this; + } - addEdge(from: number, to: number) { - if (!this.mat.setAt(from, to, true)) { - this.numE++; - this.undirected && this.mat.setAt(to, from, true); - return true; - } - return false; - } + addEdge(from: number, to: number) { + if (!this.mat.setAt(from, to, true)) { + this.numE++; + this.undirected && this.mat.setAt(to, from, true); + return true; + } + return false; + } - removeEdge(from: number, to: number) { - if (this.mat.setAt(from, to, false)) { - this.numE--; - this.undirected && this.mat.setAt(to, from, false); - return true; - } - return false; - } + removeEdge(from: number, to: number) { + if (this.mat.setAt(from, to, false)) { + this.numE--; + this.undirected && this.mat.setAt(to, from, false); + return true; + } + return false; + } - hasEdge(from: number, to: number) { - return this.mat.at(from, to) !== 0; - } + hasEdge(from: number, to: number) { + return this.mat.at(from, to) !== 0; + } - degree(id: number, type: DegreeType = "out") { - let degree = 0; - if (this.undirected || type !== "in") - degree += this.mat.popCountRow(id); - if (!this.undirected && type !== "out") - degree += this.mat.popCountColumn(id); - return degree; - } + degree(id: number, type: DegreeType = "out") { + let degree = 0; + if (this.undirected || type !== "in") + degree += this.mat.popCountRow(id); + if (!this.undirected && type !== "out") + degree += this.mat.popCountColumn(id); + return degree; + } - neighbors(id: number) { - const res: number[] = []; - const { data, stride } = this.mat; - id *= stride; - for ( - let i = this.mat.n - 1, j = id + stride - 1; - i >= 0; - i -= 32, j-- - ) { - const v = data[j]; - if (v !== 0) { - for (let k = 31 - Math.clz32(v); k >= 0; k--) { - (v & (1 << k)) !== 0 && res.push(i - k); - } - } - } - return res; - } + neighbors(id: number) { + const res: number[] = []; + const { data, stride } = this.mat; + id *= stride; + for ( + let i = this.mat.n - 1, j = id + stride - 1; + i >= 0; + i -= 32, j-- + ) { + const v = data[j]; + if (v !== 0) { + for (let k = 31 - Math.clz32(v); k >= 0; k--) { + (v & (1 << k)) !== 0 && res.push(i - k); + } + } + } + return res; + } - invert(): AdjacencyBitMatrix { - return __invert( - new AdjacencyBitMatrix(this.mat.n, undefined, this.undirected), - this.edges() - ); - } + invert(): AdjacencyBitMatrix { + return __invert( + new AdjacencyBitMatrix(this.mat.n, undefined, this.undirected), + this.edges() + ); + } - toString() { - return this.mat.toString(); - } + toString() { + return this.mat.toString(); + } - toDot(ids?: string[]) { - return __toDot(this.edges(), this.undirected, ids); - } + toDot(ids?: string[]) { + return __toDot(this.edges(), this.undirected, ids); + } } /** @@ -126,7 +126,7 @@ export class AdjacencyBitMatrix implements IGraph { * @param undirected -true, if undirected */ export const defAdjBitMatrix = ( - n: number, - edges?: Iterable, - undirected?: boolean + n: number, + edges?: Iterable, + undirected?: boolean ) => new AdjacencyBitMatrix(n, edges, undirected); diff --git a/packages/adjacency/src/dfs.ts b/packages/adjacency/src/dfs.ts index 9d0628ea51..e694996232 100644 --- a/packages/adjacency/src/dfs.ts +++ b/packages/adjacency/src/dfs.ts @@ -3,45 +3,45 @@ import { DCons } from "@thi.ng/dcons/dcons"; import type { IGraph } from "./api.js"; export class DFS { - graph: IGraph; - marked: BitField; - edges: Uint32Array; - src: number; + graph: IGraph; + marked: BitField; + edges: Uint32Array; + src: number; - constructor(graph: IGraph, src: number) { - this.graph = graph; - this.src = src; - const numV = graph.numVertices(); - this.edges = new Uint32Array(numV); - this.marked = new BitField(numV); - this.search(src); - } + constructor(graph: IGraph, src: number) { + this.graph = graph; + this.src = src; + const numV = graph.numVertices(); + this.edges = new Uint32Array(numV); + this.marked = new BitField(numV); + this.search(src); + } - search(id: number) { - const { edges, marked } = this; - marked.setAt(id); - for (let n of this.graph.neighbors(id)) { - if (!marked.at(n)) { - edges[n] = id; - this.search(n); - } - } - } + search(id: number) { + const { edges, marked } = this; + marked.setAt(id); + for (let n of this.graph.neighbors(id)) { + if (!marked.at(n)) { + edges[n] = id; + this.search(n); + } + } + } - hasPathTo(id: number) { - return this.marked.at(id) !== 0; - } + hasPathTo(id: number) { + return this.marked.at(id) !== 0; + } - pathTo(id: number): Iterable | undefined { - if (!this.marked.at(id)) return; - const { edges, src } = this; - const path = new DCons(); - for (; id !== src; id = edges[id]) { - path.prepend(id); - } - path.prepend(id); - return path; - } + pathTo(id: number): Iterable | undefined { + if (!this.marked.at(id)) return; + const { edges, src } = this; + const path = new DCons(); + for (; id !== src; id = edges[id]) { + path.prepend(id); + } + path.prepend(id); + return path; + } } /** @@ -54,4 +54,4 @@ export class DFS { * @param dest - */ export const dfs = (graph: IGraph, src: number, dest: number) => - new DFS(graph, src).pathTo(dest); + new DFS(graph, src).pathTo(dest); diff --git a/packages/adjacency/src/disjoint-set.ts b/packages/adjacency/src/disjoint-set.ts index 700b8362ba..f645b5f426 100644 --- a/packages/adjacency/src/disjoint-set.ts +++ b/packages/adjacency/src/disjoint-set.ts @@ -9,99 +9,99 @@ import { fillRange } from "@thi.ng/arrays/fill-range"; * - {@link https://algs4.cs.princeton.edu/lectures/15UnionFind-2x2.pdf} */ export class DisjointSet { - roots: Uint32Array; - ranks: Uint8Array; - count: number; + roots: Uint32Array; + ranks: Uint8Array; + count: number; - /** - * Creates new instance with `n` initial singular subsets. - * - * @param n - initial capacity, ID range [0..n) - */ - constructor(n: number) { - this.roots = fillRange(new Uint32Array(n)); - this.ranks = new Uint8Array(n); - this.count = n; - } + /** + * Creates new instance with `n` initial singular subsets. + * + * @param n - initial capacity, ID range [0..n) + */ + constructor(n: number) { + this.roots = fillRange(new Uint32Array(n)); + this.ranks = new Uint8Array(n); + this.count = n; + } - /** - * Returns canonical ID (tree root) for given `id`. Unless `id` - * already is unified with some other ID, this will always return - * `id` itself (since each node is initially its own root). - * - * @param id - node ID - */ - canonical(id: number) { - const roots = this.roots; - while (id !== roots[id]) { - id = roots[id] = roots[roots[id]]; - } - return id; - } + /** + * Returns canonical ID (tree root) for given `id`. Unless `id` + * already is unified with some other ID, this will always return + * `id` itself (since each node is initially its own root). + * + * @param id - node ID + */ + canonical(id: number) { + const roots = this.roots; + while (id !== roots[id]) { + id = roots[id] = roots[roots[id]]; + } + return id; + } - /** - * Connects combines the trees of the given two node IDs and returns - * the new resulting canonical tree root ID. - * - * @param a - node ID - * @param b - node ID - */ - union(a: number, b: number) { - const rootA = this.canonical(a); - const rootB = this.canonical(b); - if (rootA === rootB) { - return rootA; - } - this.count--; - const ranks = this.ranks; - const ra = ranks[rootA]; - const rb = ranks[rootB]; - if (ra < rb) { - return (this.roots[rootA] = rootB); - } - ra === rb && ranks[rootA]++; - return (this.roots[rootB] = rootA); - } + /** + * Connects combines the trees of the given two node IDs and returns + * the new resulting canonical tree root ID. + * + * @param a - node ID + * @param b - node ID + */ + union(a: number, b: number) { + const rootA = this.canonical(a); + const rootB = this.canonical(b); + if (rootA === rootB) { + return rootA; + } + this.count--; + const ranks = this.ranks; + const ra = ranks[rootA]; + const rb = ranks[rootB]; + if (ra < rb) { + return (this.roots[rootA] = rootB); + } + ra === rb && ranks[rootA]++; + return (this.roots[rootB] = rootA); + } - /** - * Returns true, if the given two nodes belong to the same tree / - * subset. - * - * @param a - node ID - * @param b - node ID - */ - unified(a: number, b: number) { - return this.canonical(a) === this.canonical(b); - } + /** + * Returns true, if the given two nodes belong to the same tree / + * subset. + * + * @param a - node ID + * @param b - node ID + */ + unified(a: number, b: number) { + return this.canonical(a) === this.canonical(b); + } - /** - * Returns a `Map` of all subsets (connected components) with their - * canonical tree root IDs as keys and arrays of node IDs as values. - * - * @remarks - * If only the number of subsets is required, use the `count` - * property of this class instance instead (O(1), updated with each - * call to {@link DisjointSet.union}). - */ - subsets() { - const sets: Map = new Map(); - const roots = this.roots; - for (let i = roots.length; i-- > 0; ) { - const id = this.canonical(i); - const s = sets.get(id); - if (s) { - s.push(i); - } else { - sets.set(id, [i]); - } - } - return sets; - } + /** + * Returns a `Map` of all subsets (connected components) with their + * canonical tree root IDs as keys and arrays of node IDs as values. + * + * @remarks + * If only the number of subsets is required, use the `count` + * property of this class instance instead (O(1), updated with each + * call to {@link DisjointSet.union}). + */ + subsets() { + const sets: Map = new Map(); + const roots = this.roots; + for (let i = roots.length; i-- > 0; ) { + const id = this.canonical(i); + const s = sets.get(id); + if (s) { + s.push(i); + } else { + sets.set(id, [i]); + } + } + return sets; + } } /** * Creates a new {@link DisjointSet} with capacity `n`. * - * @param n - + * @param n - */ export const defDisjointSet = (n: number) => new DisjointSet(n); diff --git a/packages/adjacency/src/list.ts b/packages/adjacency/src/list.ts index b6653d1abc..610d18c462 100644 --- a/packages/adjacency/src/list.ts +++ b/packages/adjacency/src/list.ts @@ -3,145 +3,145 @@ import type { DegreeType, Edge, IGraph } from "./api.js"; import { __into, __invert, __toDot } from "./utils.js"; export class AdjacencyList implements IGraph { - adjacency: number[][] = []; - indegree: number[] = []; - protected numE = 0; - protected numV = 0; - - constructor(edges?: Iterable) { - edges && __into(this, edges); - } - - numEdges(): number { - return this.numE; - } - - numVertices(): number { - return this.numV; - } - - *vertices() { - const { adjacency } = this; - for (let i = 0, n = adjacency.length; i < n; i++) { - if (adjacency[i]) yield i; - } - } - - *edges() { - const { adjacency } = this; - for (let i = 0, n = adjacency.length; i < n; i++) { - const vertex = adjacency[i]; - if (!vertex) continue; - for (let j of vertex) yield [i, j]; - } - } - - addVertex(id: number) { - this.ensureVertexData(id); - } - - removeVertex(id: number) { - const { adjacency, indegree } = this; - const vertex = adjacency[id]; - if (!vertex) return false; - // remove outgoing - while (vertex.length) { - indegree[vertex.pop()!]--; - this.numE--; - } - delete adjacency[id]; - // remove incoming - for (let i = 0, n = adjacency.length; i < n && indegree[id] > 0; i++) { - const vertex = adjacency[i]; - if (!vertex) continue; - while (vertex.includes(id)) this.removeEdge(i, id); - } - this.numV--; - return true; - } - - addEdge(from: number, to: number) { - const vertex = this.ensureVertexData(from); - this.ensureVertexData(to); - vertex.push(to); - this.indegree[to]++; - this.numE++; - return true; - } - - removeEdge(from: number, to: number) { - const vertex = this.adjacency[from]; - if (vertex) { - const dest = vertex.indexOf(to); - if (dest >= 0) { - vertex.splice(dest, 1); - this.numE--; - this.indegree[to]--; - return true; - } - } - return false; - } - - hasEdge(from: number, to: number) { - const vertex = this.adjacency[from]; - return vertex ? vertex.includes(to) : false; - } - - degree(id: number, type: DegreeType = "out") { - let degree = 0; - const vertex = this.adjacency[id]; - if (vertex) { - if (type !== "in") degree += vertex.length; - if (type !== "out") degree += this.indegree[id]; - } - return degree; - } - - neighbors(id: number): Iterable { - return [...(this.adjacency[id] || [])]; - } - - invert(): AdjacencyList { - return __invert(new AdjacencyList(), this.edges()); - } - - toDot(ids?: string[]) { - return __toDot(this.edges(), false, ids); - } - - toString() { - const { adjacency } = this; - const res: string[] = []; - for (let i = 0, n = adjacency.length; i < n; i++) { - if (adjacency[i]) { - res.push( - `${i}: [${[...adjacency[i]!] - .sort((a, b) => a - b) - .join(", ")}]` - ); - } - } - return res.join("\n"); - } - - protected ensureVertexData(id: number) { - const vertex = this.adjacency[id]; - if (vertex) return vertex; - this.numV++; - this.indegree[id] = 0; - return (this.adjacency[id] = []); - } + adjacency: number[][] = []; + indegree: number[] = []; + protected numE = 0; + protected numV = 0; + + constructor(edges?: Iterable) { + edges && __into(this, edges); + } + + numEdges(): number { + return this.numE; + } + + numVertices(): number { + return this.numV; + } + + *vertices() { + const { adjacency } = this; + for (let i = 0, n = adjacency.length; i < n; i++) { + if (adjacency[i]) yield i; + } + } + + *edges() { + const { adjacency } = this; + for (let i = 0, n = adjacency.length; i < n; i++) { + const vertex = adjacency[i]; + if (!vertex) continue; + for (let j of vertex) yield [i, j]; + } + } + + addVertex(id: number) { + this.ensureVertexData(id); + } + + removeVertex(id: number) { + const { adjacency, indegree } = this; + const vertex = adjacency[id]; + if (!vertex) return false; + // remove outgoing + while (vertex.length) { + indegree[vertex.pop()!]--; + this.numE--; + } + delete adjacency[id]; + // remove incoming + for (let i = 0, n = adjacency.length; i < n && indegree[id] > 0; i++) { + const vertex = adjacency[i]; + if (!vertex) continue; + while (vertex.includes(id)) this.removeEdge(i, id); + } + this.numV--; + return true; + } + + addEdge(from: number, to: number) { + const vertex = this.ensureVertexData(from); + this.ensureVertexData(to); + vertex.push(to); + this.indegree[to]++; + this.numE++; + return true; + } + + removeEdge(from: number, to: number) { + const vertex = this.adjacency[from]; + if (vertex) { + const dest = vertex.indexOf(to); + if (dest >= 0) { + vertex.splice(dest, 1); + this.numE--; + this.indegree[to]--; + return true; + } + } + return false; + } + + hasEdge(from: number, to: number) { + const vertex = this.adjacency[from]; + return vertex ? vertex.includes(to) : false; + } + + degree(id: number, type: DegreeType = "out") { + let degree = 0; + const vertex = this.adjacency[id]; + if (vertex) { + if (type !== "in") degree += vertex.length; + if (type !== "out") degree += this.indegree[id]; + } + return degree; + } + + neighbors(id: number): Iterable { + return [...(this.adjacency[id] || [])]; + } + + invert(): AdjacencyList { + return __invert(new AdjacencyList(), this.edges()); + } + + toDot(ids?: string[]) { + return __toDot(this.edges(), false, ids); + } + + toString() { + const { adjacency } = this; + const res: string[] = []; + for (let i = 0, n = adjacency.length; i < n; i++) { + if (adjacency[i]) { + res.push( + `${i}: [${[...adjacency[i]!] + .sort((a, b) => a - b) + .join(", ")}]` + ); + } + } + return res.join("\n"); + } + + protected ensureVertexData(id: number) { + const vertex = this.adjacency[id]; + if (vertex) return vertex; + this.numV++; + this.indegree[id] = 0; + return (this.adjacency[id] = []); + } } export const defAdjList = (edges?: Iterable) => new AdjacencyList(edges); export const adjListFromAdjacency = (src: Nullable[]) => { - const res = new AdjacencyList(); - for (let i = 0, n = src.length; i < n; i++) { - const v = src[i]; - if (!v) continue; - for (let w of v) res.addEdge(i, w); - } - return res; + const res = new AdjacencyList(); + for (let i = 0, n = src.length; i < n; i++) { + const v = src[i]; + if (!v) continue; + for (let w of v) res.addEdge(i, w); + } + return res; }; diff --git a/packages/adjacency/src/mst.ts b/packages/adjacency/src/mst.ts index 834835736d..4ed8b63d02 100644 --- a/packages/adjacency/src/mst.ts +++ b/packages/adjacency/src/mst.ts @@ -51,19 +51,19 @@ import { DisjointSet } from "./disjoint-set.js"; * @typeParam T - edge type */ export const mst = ( - edges: T[], - maxID: number, - cost: Fn, - verts: Fn + edges: T[], + maxID: number, + cost: Fn, + verts: Fn ) => { - const graph = new DisjointSet(maxID + 1); - const res: T[] = []; - for (let e of sortByCachedKey(edges, cost)) { - const v = verts(e); - if (!graph.unified(v[0], v[1])) { - graph.union(v[0], v[1]); - res.push(e); - } - } - return res; + const graph = new DisjointSet(maxID + 1); + const res: T[] = []; + for (let e of sortByCachedKey(edges, cost)) { + const v = verts(e); + if (!graph.unified(v[0], v[1])) { + graph.union(v[0], v[1]); + res.push(e); + } + } + return res; }; diff --git a/packages/adjacency/src/sparse.ts b/packages/adjacency/src/sparse.ts index bf83fbb091..6aec1680c3 100644 --- a/packages/adjacency/src/sparse.ts +++ b/packages/adjacency/src/sparse.ts @@ -4,172 +4,172 @@ import type { DegreeType, Edge, IGraph } from "./api.js"; import { __into, __invert, __toDot } from "./utils.js"; export class AdjacencyMatrix extends CSR implements IGraph { - undirected: boolean; - - constructor( - n: number, - data: number[], - rows: number[], - cols: number[], - undirected = false - ) { - super(n, n, data, rows, cols); - this.undirected = undirected; - } - - *edges() { - const { cols, rows } = this; - const directed = !this.undirected; - for (let i = 0; i < this.m; i++) { - const jj = rows[i + 1]; - for (let j = rows[i]; j < jj; j++) { - const k = cols[j]; - if (directed || i <= k) { - yield [i, k]; - } - } - } - } - - addEdge(from: number, to: number) { - if (!this.at(from, to)) { - this.setAt(from, to, 1, false); - this.undirected && this.setAt(to, from, 1, false); - return true; - } - return false; - } - - removeEdge(from: number, to: number) { - if (this.at(from, to)) { - this.setAt(from, to, 0, false); - this.undirected && this.setAt(to, from, 0, false); - return true; - } - return false; - } - - hasEdge(from: number, to: number) { - return this.at(from, to) !== 0; - } - - numEdges() { - const n = this.data.length; - return this.undirected ? n / 2 : n; - } - - numVertices() { - return this.m; - } - - degree(id: number, type: DegreeType = "out") { - let degree = 0; - ensureIndex2(id, id, this.m, this.n); - if (this.undirected || type !== "in") degree += this.nnzRow(id); - if (!this.undirected && type !== "out") degree += this.nnzCol(id); - return degree; - } - - neighbors(id: number): number[] { - return this.nzRowCols(id); - } - - invert(): AdjacencyMatrix { - return __invert( - defAdjMatrix(this.m, undefined, this.undirected), - this.edges() - ); - } - - /** - * Returns a diagonal sparse matrix {@link @thi.ng/sparse#CSR} containing - * information about the degree of each vertex, i.e. the number of edges - * attached to each vertex. - * - * @remarks - * Reference: https://en.wikipedia.org/wiki/Degree_matrix - * - * @param deg - degree type - */ - degreeMat(deg: DegreeType = "out") { - const res = CSR.empty(this.m); - switch (deg) { - case "out": - default: - for (let i = this.m; i-- > 0; ) { - res.setAt(i, i, this.nnzRow(i)); - } - break; - case "in": - for (let i = this.m; i-- > 0; ) { - res.setAt(i, i, this.nnzCol(i)); - } - break; - case "inout": - for (let i = this.m; i-- > 0; ) { - res.setAt(i, i, this.nnzRow(i) + this.nnzCol(i)); - } - break; - } - return res; - } - - /** - * Returns this graph's Laplacian matrix: `L = D - A` Where `D` is - * the degree matrix and `A` this adjacency matrix. - * - * @remarks - * - {@link https://en.wikipedia.org/wiki/Laplacian_matrix} - * - {@link https://en.wikipedia.org/wiki/Discrete_Laplace_operator} - * - * @param deg - degree type for {@link AdjacencyMatrix.degreeMat} - */ - laplacianMat(deg?: CSR) { - return (deg || this.degreeMat()).sub(this); - } - - normalizedLaplacian(deg?: CSR) { - deg = deg || this.degreeMat(); - const m = this.m; - const res = CSR.empty(m); - for (let i = 0; i < m; i++) { - for (let j = 0; j < m; j++) { - if (i === j && deg.at(i, i) > 0) { - res.setAt(i, j, 1); - } else if (i !== j && this.at(i, j) > 0) { - res.setAt( - i, - j, - -1 / Math.sqrt(deg.at(i, i) * deg.at(j, j)) - ); - } - } - } - return res; - } - - /** - * Computes: `I - nA + n^2 * (D - I)`, where `I` is the identity matrix, - * `A` the adjacency matrix, `D` the degree matrix, and `n` is a - * (complex-valued) number. - * - * @remarks - * See {@link AdjacencyMatrix.degreeMat}. - * - * @param n - scale factor - * @param deg - degree matrix - */ - deformedLaplacian(n: number, deg?: CSR) { - deg = deg ? deg.copy() : this.degreeMat(); - const I = CSR.identity(this.m); - return I.copy() - .sub(this.copy().mulN(n)) - .add(deg.sub(I).mulN(n * n)); - } - - toDot(ids?: string[]) { - return __toDot(this.edges(), this.undirected, ids); - } + undirected: boolean; + + constructor( + n: number, + data: number[], + rows: number[], + cols: number[], + undirected = false + ) { + super(n, n, data, rows, cols); + this.undirected = undirected; + } + + *edges() { + const { cols, rows } = this; + const directed = !this.undirected; + for (let i = 0; i < this.m; i++) { + const jj = rows[i + 1]; + for (let j = rows[i]; j < jj; j++) { + const k = cols[j]; + if (directed || i <= k) { + yield [i, k]; + } + } + } + } + + addEdge(from: number, to: number) { + if (!this.at(from, to)) { + this.setAt(from, to, 1, false); + this.undirected && this.setAt(to, from, 1, false); + return true; + } + return false; + } + + removeEdge(from: number, to: number) { + if (this.at(from, to)) { + this.setAt(from, to, 0, false); + this.undirected && this.setAt(to, from, 0, false); + return true; + } + return false; + } + + hasEdge(from: number, to: number) { + return this.at(from, to) !== 0; + } + + numEdges() { + const n = this.data.length; + return this.undirected ? n / 2 : n; + } + + numVertices() { + return this.m; + } + + degree(id: number, type: DegreeType = "out") { + let degree = 0; + ensureIndex2(id, id, this.m, this.n); + if (this.undirected || type !== "in") degree += this.nnzRow(id); + if (!this.undirected && type !== "out") degree += this.nnzCol(id); + return degree; + } + + neighbors(id: number): number[] { + return this.nzRowCols(id); + } + + invert(): AdjacencyMatrix { + return __invert( + defAdjMatrix(this.m, undefined, this.undirected), + this.edges() + ); + } + + /** + * Returns a diagonal sparse matrix {@link @thi.ng/sparse#CSR} containing + * information about the degree of each vertex, i.e. the number of edges + * attached to each vertex. + * + * @remarks + * Reference: https://en.wikipedia.org/wiki/Degree_matrix + * + * @param deg - degree type + */ + degreeMat(deg: DegreeType = "out") { + const res = CSR.empty(this.m); + switch (deg) { + case "out": + default: + for (let i = this.m; i-- > 0; ) { + res.setAt(i, i, this.nnzRow(i)); + } + break; + case "in": + for (let i = this.m; i-- > 0; ) { + res.setAt(i, i, this.nnzCol(i)); + } + break; + case "inout": + for (let i = this.m; i-- > 0; ) { + res.setAt(i, i, this.nnzRow(i) + this.nnzCol(i)); + } + break; + } + return res; + } + + /** + * Returns this graph's Laplacian matrix: `L = D - A` Where `D` is + * the degree matrix and `A` this adjacency matrix. + * + * @remarks + * - {@link https://en.wikipedia.org/wiki/Laplacian_matrix} + * - {@link https://en.wikipedia.org/wiki/Discrete_Laplace_operator} + * + * @param deg - degree type for {@link AdjacencyMatrix.degreeMat} + */ + laplacianMat(deg?: CSR) { + return (deg || this.degreeMat()).sub(this); + } + + normalizedLaplacian(deg?: CSR) { + deg = deg || this.degreeMat(); + const m = this.m; + const res = CSR.empty(m); + for (let i = 0; i < m; i++) { + for (let j = 0; j < m; j++) { + if (i === j && deg.at(i, i) > 0) { + res.setAt(i, j, 1); + } else if (i !== j && this.at(i, j) > 0) { + res.setAt( + i, + j, + -1 / Math.sqrt(deg.at(i, i) * deg.at(j, j)) + ); + } + } + } + return res; + } + + /** + * Computes: `I - nA + n^2 * (D - I)`, where `I` is the identity matrix, + * `A` the adjacency matrix, `D` the degree matrix, and `n` is a + * (complex-valued) number. + * + * @remarks + * See {@link AdjacencyMatrix.degreeMat}. + * + * @param n - scale factor + * @param deg - degree matrix + */ + deformedLaplacian(n: number, deg?: CSR) { + deg = deg ? deg.copy() : this.degreeMat(); + const I = CSR.identity(this.m); + return I.copy() + .sub(this.copy().mulN(n)) + .add(deg.sub(I).mulN(n * n)); + } + + toDot(ids?: string[]) { + return __toDot(this.edges(), this.undirected, ids); + } } /** @@ -183,18 +183,18 @@ export class AdjacencyMatrix extends CSR implements IGraph { * @param undirected - true, if undirected */ export const defAdjMatrix = ( - n: number, - edges?: Iterable, - undirected = false + n: number, + edges?: Iterable, + undirected = false ) => { - const raw = CSR.empty(n); - const mat = new AdjacencyMatrix( - n, - raw.data, - raw.rows, - raw.cols, - undirected - ); - edges && __into(mat, edges); - return mat; + const raw = CSR.empty(n); + const mat = new AdjacencyMatrix( + n, + raw.data, + raw.rows, + raw.cols, + undirected + ); + edges && __into(mat, edges); + return mat; }; diff --git a/packages/adjacency/src/utils.ts b/packages/adjacency/src/utils.ts index 65b8d8774d..5d63c9054a 100644 --- a/packages/adjacency/src/utils.ts +++ b/packages/adjacency/src/utils.ts @@ -4,34 +4,34 @@ import type { Edge, IGraph } from "./api.js"; /** @internal */ export const __toDot = ( - edges: Iterable>, - undirected: boolean, - ids?: string[] + edges: Iterable>, + undirected: boolean, + ids?: string[] ) => { - const [type, sep] = undirected ? ["graph", "--"] : ["digraph", "->"]; - const res = [`${type} g {`]; - for (let e of edges) { - res.push( - ids - ? `"${ids[e[0]]}"${sep}"${ids[e[1]]}";` - : `"${e[0]}"${sep}"${e[1]}";` - ); - } - res.push(`}`); - return res.join("\n"); + const [type, sep] = undirected ? ["graph", "--"] : ["digraph", "->"]; + const res = [`${type} g {`]; + for (let e of edges) { + res.push( + ids + ? `"${ids[e[0]]}"${sep}"${ids[e[1]]}";` + : `"${e[0]}"${sep}"${e[1]}";` + ); + } + res.push(`}`); + return res.join("\n"); }; /** @internal */ export const __into = (graph: IGraph, edges: Iterable) => { - for (let e of edges) { - graph.addEdge(e[0], e[1]); - } + for (let e of edges) { + graph.addEdge(e[0], e[1]); + } }; /** @internal */ export const __invert = (graph: T, edges: Iterable) => { - for (let e of edges) { - graph.addEdge(e[1], e[0]); - } - return graph; + for (let e of edges) { + graph.addEdge(e[1], e[0]); + } + return graph; }; diff --git a/packages/adjacency/test/binary.ts b/packages/adjacency/test/binary.ts index a18b43ecd7..a6da3ce274 100644 --- a/packages/adjacency/test/binary.ts +++ b/packages/adjacency/test/binary.ts @@ -1,46 +1,46 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { defAdjBitMatrix, Edge } from "../src/index.js" +import { defAdjBitMatrix, Edge } from "../src/index.js"; const edges: Edge[] = [ - [2, 3], - [0, 1], - [5, 4], - [2, 0], + [2, 3], + [0, 1], + [5, 4], + [2, 0], ]; group("adjacency (bitmatrix)", { - directed: () => { - const m = defAdjBitMatrix(4, [[1, 2]], false); - assert.ok(m.hasEdge(1, 2)); - assert.deepStrictEqual(m.neighbors(1), [2]); - assert.deepStrictEqual(m.neighbors(2), []); - assert.strictEqual(m.degree(1), 1); - assert.strictEqual(m.degree(2), 0); - assert.deepStrictEqual([...m.edges()], [[1, 2]]); - // console.log(m.toString()); - }, + directed: () => { + const m = defAdjBitMatrix(4, [[1, 2]], false); + assert.ok(m.hasEdge(1, 2)); + assert.deepStrictEqual(m.neighbors(1), [2]); + assert.deepStrictEqual(m.neighbors(2), []); + assert.strictEqual(m.degree(1), 1); + assert.strictEqual(m.degree(2), 0); + assert.deepStrictEqual([...m.edges()], [[1, 2]]); + // console.log(m.toString()); + }, - undirected: () => { - const m = defAdjBitMatrix(6, edges, true); - assert.deepStrictEqual( - [...m.mat.data.slice(0, 6)], - [ - 1610612736, 2147483648, 2415919104, 536870912, 67108864, - 134217728, - ], - "data" - ); - assert.strictEqual(m.numEdges(), 4, "numEdges"); - assert.deepStrictEqual( - [...m.edges()], - [ - [4, 5], - [2, 3], - [0, 1], - [0, 2], - ], - "edges" - ); - }, + undirected: () => { + const m = defAdjBitMatrix(6, edges, true); + assert.deepStrictEqual( + [...m.mat.data.slice(0, 6)], + [ + 1610612736, 2147483648, 2415919104, 536870912, 67108864, + 134217728, + ], + "data" + ); + assert.strictEqual(m.numEdges(), 4, "numEdges"); + assert.deepStrictEqual( + [...m.edges()], + [ + [4, 5], + [2, 3], + [0, 1], + [0, 2], + ], + "edges" + ); + }, }); diff --git a/packages/adjacency/test/list.ts b/packages/adjacency/test/list.ts index bc1336d9f4..6f13abd6eb 100644 --- a/packages/adjacency/test/list.ts +++ b/packages/adjacency/test/list.ts @@ -1,27 +1,27 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { defAdjList } from "../src/index.js" +import { defAdjList } from "../src/index.js"; group("adjacency (list)", { - directed: () => { - const m = defAdjList([ - [1, 2], - [2, 0], - ]); - assert.ok(m.hasEdge(1, 2)); - assert.ok(m.hasEdge(2, 0)); - assert.ok(!m.hasEdge(2, 1)); - assert.ok(!m.hasEdge(0, 2)); - assert.deepStrictEqual(m.neighbors(1), [2]); - assert.deepStrictEqual(m.neighbors(2), [0]); - assert.strictEqual(m.degree(1), 1); - assert.deepStrictEqual( - [...m.edges()], - [ - [1, 2], - [2, 0], - ] - ); - console.log(m.toString()); - }, + directed: () => { + const m = defAdjList([ + [1, 2], + [2, 0], + ]); + assert.ok(m.hasEdge(1, 2)); + assert.ok(m.hasEdge(2, 0)); + assert.ok(!m.hasEdge(2, 1)); + assert.ok(!m.hasEdge(0, 2)); + assert.deepStrictEqual(m.neighbors(1), [2]); + assert.deepStrictEqual(m.neighbors(2), [0]); + assert.strictEqual(m.degree(1), 1); + assert.deepStrictEqual( + [...m.edges()], + [ + [1, 2], + [2, 0], + ] + ); + console.log(m.toString()); + }, }); diff --git a/packages/adjacency/test/mst.ts b/packages/adjacency/test/mst.ts index 07ca065724..2781ca8623 100644 --- a/packages/adjacency/test/mst.ts +++ b/packages/adjacency/test/mst.ts @@ -1,54 +1,54 @@ import { comparator2, distSq } from "@thi.ng/vectors"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { mst } from "../src/index.js" +import { mst } from "../src/index.js"; group("unionfind", { - mst: () => { - const verts = [ - [0, 0], // 0 - [0, 1], // 1 - [1, 1], // 2 - [1, 2], // 3 - [2, 1], // 4 - [3, 1], // 5 - [3, 3], // 6 - [5, 2], // 7 - [5, 3], // 8 - ]; - const edges = [ - [0, 1], - [1, 2], - [0, 4], - [0, 5], - [2, 3], - [2, 4], - [4, 5], - [3, 7], - [5, 7], - [5, 6], - [6, 7], - [6, 8], - [7, 8], - ]; + mst: () => { + const verts = [ + [0, 0], // 0 + [0, 1], // 1 + [1, 1], // 2 + [1, 2], // 3 + [2, 1], // 4 + [3, 1], // 5 + [3, 3], // 6 + [5, 2], // 7 + [5, 3], // 8 + ]; + const edges = [ + [0, 1], + [1, 2], + [0, 4], + [0, 5], + [2, 3], + [2, 4], + [4, 5], + [3, 7], + [5, 7], + [5, 6], + [6, 7], + [6, 8], + [7, 8], + ]; - assert.deepStrictEqual( - mst( - edges, - 10, - ([a, b]) => distSq(verts[a], verts[b]), - ([a, b]) => [a, b] - ).sort(comparator2(0, 1)), - [ - [0, 1], - [1, 2], - [2, 3], - [2, 4], - [4, 5], - [5, 6], - [6, 8], - [7, 8], - ] - ); - }, + assert.deepStrictEqual( + mst( + edges, + 10, + ([a, b]) => distSq(verts[a], verts[b]), + ([a, b]) => [a, b] + ).sort(comparator2(0, 1)), + [ + [0, 1], + [1, 2], + [2, 3], + [2, 4], + [4, 5], + [5, 6], + [6, 8], + [7, 8], + ] + ); + }, }); diff --git a/packages/adjacency/test/sparse.ts b/packages/adjacency/test/sparse.ts index f927fd85b5..a298479508 100644 --- a/packages/adjacency/test/sparse.ts +++ b/packages/adjacency/test/sparse.ts @@ -1,40 +1,40 @@ import type { Pair } from "@thi.ng/api"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { defAdjMatrix } from "../src/index.js" +import { defAdjMatrix } from "../src/index.js"; const edges: Pair[] = [ - [2, 3], - [0, 1], - [5, 4], - [2, 0], + [2, 3], + [0, 1], + [5, 4], + [2, 0], ]; group("adjacency (sparse)", { - "edges directed": () => { - const m = defAdjMatrix(4, [], false); - m.addEdge(1, 2); - assert.ok(m.hasEdge(1, 2)); - assert.deepStrictEqual(m.neighbors(1), [2]); - assert.deepStrictEqual(m.neighbors(2), []); - assert.strictEqual(m.degree(1), 1); - assert.deepStrictEqual([...m.edges()], [[1, 2]]); - }, + "edges directed": () => { + const m = defAdjMatrix(4, [], false); + m.addEdge(1, 2); + assert.ok(m.hasEdge(1, 2)); + assert.deepStrictEqual(m.neighbors(1), [2]); + assert.deepStrictEqual(m.neighbors(2), []); + assert.strictEqual(m.degree(1), 1); + assert.deepStrictEqual([...m.edges()], [[1, 2]]); + }, - "fromEdges, undirected": () => { - const m = defAdjMatrix(6, edges, true); - assert.deepStrictEqual(m.rows, [0, 2, 3, 5, 6, 7, 8], "rows"); - assert.deepStrictEqual(m.cols, [1, 2, 0, 0, 3, 2, 5, 4], "cols"); - assert.strictEqual(m.numEdges(), 4, "numEdges"); - assert.deepStrictEqual( - [...m.edges()], - [ - [0, 1], - [0, 2], - [2, 3], - [4, 5], - ], - "edges" - ); - }, + "fromEdges, undirected": () => { + const m = defAdjMatrix(6, edges, true); + assert.deepStrictEqual(m.rows, [0, 2, 3, 5, 6, 7, 8], "rows"); + assert.deepStrictEqual(m.cols, [1, 2, 0, 0, 3, 2, 5, 4], "cols"); + assert.strictEqual(m.numEdges(), 4, "numEdges"); + assert.deepStrictEqual( + [...m.edges()], + [ + [0, 1], + [0, 2], + [2, 3], + [4, 5], + ], + "edges" + ); + }, }); diff --git a/packages/adjacency/tsconfig.json b/packages/adjacency/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/adjacency/tsconfig.json +++ b/packages/adjacency/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/api/api-extractor.json b/packages/api/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/api/api-extractor.json +++ b/packages/api/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/api/package.json b/packages/api/package.json index 6178a09e9c..05013773e5 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,225 +1,225 @@ { - "name": "@thi.ng/api", - "version": "8.3.8", - "description": "Common, generic types, interfaces & mixins", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/api#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc api decorators mixins", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "assert", - "constants", - "decorators", - "generic", - "interface", - "mixin", - "type", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "decorators", - "mixins" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./assoc": { - "default": "./assoc.js" - }, - "./bind": { - "default": "./bind.js" - }, - "./buffered": { - "default": "./buffered.js" - }, - "./clear": { - "default": "./clear.js" - }, - "./compare": { - "default": "./compare.js" - }, - "./contains": { - "default": "./contains.js" - }, - "./copy": { - "default": "./copy.js" - }, - "./decorators/configurable": { - "default": "./decorators/configurable.js" - }, - "./decorators/deprecated": { - "default": "./decorators/deprecated.js" - }, - "./decorators/nomixin": { - "default": "./decorators/nomixin.js" - }, - "./decorators/sealed": { - "default": "./decorators/sealed.js" - }, - "./deref": { - "default": "./deref.js" - }, - "./dissoc": { - "default": "./dissoc.js" - }, - "./empty": { - "default": "./empty.js" - }, - "./enable": { - "default": "./enable.js" - }, - "./equiv": { - "default": "./equiv.js" - }, - "./event": { - "default": "./event.js" - }, - "./fn": { - "default": "./fn.js" - }, - "./get": { - "default": "./get.js" - }, - "./grid": { - "default": "./grid.js" - }, - "./hash": { - "default": "./hash.js" - }, - "./hiccup": { - "default": "./hiccup.js" - }, - "./id": { - "default": "./id.js" - }, - "./indexed": { - "default": "./indexed.js" - }, - "./into": { - "default": "./into.js" - }, - "./keyval": { - "default": "./keyval.js" - }, - "./length": { - "default": "./length.js" - }, - "./meta": { - "default": "./meta.js" - }, - "./mixin": { - "default": "./mixin.js" - }, - "./mixins/ienable": { - "default": "./mixins/ienable.js" - }, - "./mixins/igrid": { - "default": "./mixins/igrid.js" - }, - "./mixins/inotify": { - "default": "./mixins/inotify.js" - }, - "./mixins/iterable": { - "default": "./mixins/iterable.js" - }, - "./mixins/iwatch": { - "default": "./mixins/iwatch.js" - }, - "./null": { - "default": "./null.js" - }, - "./object": { - "default": "./object.js" - }, - "./path": { - "default": "./path.js" - }, - "./predicate": { - "default": "./predicate.js" - }, - "./prim": { - "default": "./prim.js" - }, - "./range": { - "default": "./range.js" - }, - "./release": { - "default": "./release.js" - }, - "./reset": { - "default": "./reset.js" - }, - "./select": { - "default": "./select.js" - }, - "./seq": { - "default": "./seq.js" - }, - "./set": { - "default": "./set.js" - }, - "./stack": { - "default": "./stack.js" - }, - "./tuple": { - "default": "./tuple.js" - }, - "./typedarray": { - "default": "./typedarray.js" - }, - "./watch": { - "default": "./watch.js" - } - } + "name": "@thi.ng/api", + "version": "8.3.8", + "description": "Common, generic types, interfaces & mixins", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/api#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc api decorators mixins", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "assert", + "constants", + "decorators", + "generic", + "interface", + "mixin", + "type", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "decorators", + "mixins" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./assoc": { + "default": "./assoc.js" + }, + "./bind": { + "default": "./bind.js" + }, + "./buffered": { + "default": "./buffered.js" + }, + "./clear": { + "default": "./clear.js" + }, + "./compare": { + "default": "./compare.js" + }, + "./contains": { + "default": "./contains.js" + }, + "./copy": { + "default": "./copy.js" + }, + "./decorators/configurable": { + "default": "./decorators/configurable.js" + }, + "./decorators/deprecated": { + "default": "./decorators/deprecated.js" + }, + "./decorators/nomixin": { + "default": "./decorators/nomixin.js" + }, + "./decorators/sealed": { + "default": "./decorators/sealed.js" + }, + "./deref": { + "default": "./deref.js" + }, + "./dissoc": { + "default": "./dissoc.js" + }, + "./empty": { + "default": "./empty.js" + }, + "./enable": { + "default": "./enable.js" + }, + "./equiv": { + "default": "./equiv.js" + }, + "./event": { + "default": "./event.js" + }, + "./fn": { + "default": "./fn.js" + }, + "./get": { + "default": "./get.js" + }, + "./grid": { + "default": "./grid.js" + }, + "./hash": { + "default": "./hash.js" + }, + "./hiccup": { + "default": "./hiccup.js" + }, + "./id": { + "default": "./id.js" + }, + "./indexed": { + "default": "./indexed.js" + }, + "./into": { + "default": "./into.js" + }, + "./keyval": { + "default": "./keyval.js" + }, + "./length": { + "default": "./length.js" + }, + "./meta": { + "default": "./meta.js" + }, + "./mixin": { + "default": "./mixin.js" + }, + "./mixins/ienable": { + "default": "./mixins/ienable.js" + }, + "./mixins/igrid": { + "default": "./mixins/igrid.js" + }, + "./mixins/inotify": { + "default": "./mixins/inotify.js" + }, + "./mixins/iterable": { + "default": "./mixins/iterable.js" + }, + "./mixins/iwatch": { + "default": "./mixins/iwatch.js" + }, + "./null": { + "default": "./null.js" + }, + "./object": { + "default": "./object.js" + }, + "./path": { + "default": "./path.js" + }, + "./predicate": { + "default": "./predicate.js" + }, + "./prim": { + "default": "./prim.js" + }, + "./range": { + "default": "./range.js" + }, + "./release": { + "default": "./release.js" + }, + "./reset": { + "default": "./reset.js" + }, + "./select": { + "default": "./select.js" + }, + "./seq": { + "default": "./seq.js" + }, + "./set": { + "default": "./set.js" + }, + "./stack": { + "default": "./stack.js" + }, + "./tuple": { + "default": "./tuple.js" + }, + "./typedarray": { + "default": "./typedarray.js" + }, + "./watch": { + "default": "./watch.js" + } + } } diff --git a/packages/api/src/assoc.ts b/packages/api/src/assoc.ts index 546a7b24b5..bc0d9549e3 100644 --- a/packages/api/src/assoc.ts +++ b/packages/api/src/assoc.ts @@ -11,8 +11,8 @@ export type Pair = [K, V]; * @param T - return type */ export interface IAssoc { - assoc(key: K, val: V): T; - update(key: K, f: Fn): T; + assoc(key: K, val: V): T; + update(key: K, f: Fn): T; } /** @@ -21,6 +21,6 @@ export interface IAssoc { * @param T - return type */ export interface IAssocIn { - assocIn(key: K[], val: V): T; - updateIn(key: K[], f: Fn): T; + assocIn(key: K[], val: V): T; + updateIn(key: K[], f: Fn): T; } diff --git a/packages/api/src/bind.ts b/packages/api/src/bind.ts index 1b9369b6c1..d798095373 100644 --- a/packages/api/src/bind.ts +++ b/packages/api/src/bind.ts @@ -2,12 +2,12 @@ * Generic resource binding methods. */ export interface IBind { - /** - * @returns true, if successful - */ - bind(opt: T): boolean; - /** - * @returns true, if successful - */ - unbind(opt: T): boolean; + /** + * @returns true, if successful + */ + bind(opt: T): boolean; + /** + * @returns true, if successful + */ + unbind(opt: T): boolean; } diff --git a/packages/api/src/buffered.ts b/packages/api/src/buffered.ts index 5f8b14ec19..022aae583e 100644 --- a/packages/api/src/buffered.ts +++ b/packages/api/src/buffered.ts @@ -2,13 +2,13 @@ * Generic interface for types with binary backing buffers. */ export interface IBuffered { - /** - * An implementation's publicly accessible backing array / - * ArrayBuffer (usually a typed array instance). - */ - buffer: T; - /** - * Returns an Uint8Array view of backing array. - */ - bytes?(): Uint8Array; + /** + * An implementation's publicly accessible backing array / + * ArrayBuffer (usually a typed array instance). + */ + buffer: T; + /** + * Returns an Uint8Array view of backing array. + */ + bytes?(): Uint8Array; } diff --git a/packages/api/src/clear.ts b/packages/api/src/clear.ts index 4302d1aaaf..4ca941d0b2 100644 --- a/packages/api/src/clear.ts +++ b/packages/api/src/clear.ts @@ -6,5 +6,5 @@ * Also see {@link IEmpty} and {@link IRelease} for related operations. */ export interface IClear { - clear(): void; + clear(): void; } diff --git a/packages/api/src/compare.ts b/packages/api/src/compare.ts index ea3a4269af..39a8dbe354 100644 --- a/packages/api/src/compare.ts +++ b/packages/api/src/compare.ts @@ -16,17 +16,17 @@ export type Comparator = Fn2; * Generic interface to compare value types. */ export interface ICompare { - /** - * Compares this value with given value `x`. MUST follow same - * contract as {@link Comparator}. - * - * @remarks - * MUST return 0 if the type also implements `IEquiv` and `equiv` - * returns true for same `x`. - * - * Also see {@link IHash}. - * - * @param x - compare value - */ - compare(x: T): number; + /** + * Compares this value with given value `x`. MUST follow same + * contract as {@link Comparator}. + * + * @remarks + * MUST return 0 if the type also implements `IEquiv` and `equiv` + * returns true for same `x`. + * + * Also see {@link IHash}. + * + * @param x - compare value + */ + compare(x: T): number; } diff --git a/packages/api/src/contains.ts b/packages/api/src/contains.ts index 266fc22c10..673bd73c62 100644 --- a/packages/api/src/contains.ts +++ b/packages/api/src/contains.ts @@ -3,10 +3,10 @@ * part of the collection. */ export interface IContains { - /** - * Returns `true` if `x` is part of collection. - * - * @param x - value to check for - */ - contains(x: T): boolean; + /** + * Returns `true` if `x` is part of collection. + * + * @param x - value to check for + */ + contains(x: T): boolean; } diff --git a/packages/api/src/copy.ts b/packages/api/src/copy.ts index 9a70c4f8a9..a2d9ae201f 100644 --- a/packages/api/src/copy.ts +++ b/packages/api/src/copy.ts @@ -2,9 +2,9 @@ * Generic interface for clonable types. */ export interface ICopy { - /** - * Returns a copy of this instance. Shallow or deep copies are - * implementation specific. - */ - copy(): T; + /** + * Returns a copy of this instance. Shallow or deep copies are + * implementation specific. + */ + copy(): T; } diff --git a/packages/api/src/decorators/configurable.ts b/packages/api/src/decorators/configurable.ts index 1d5c876d95..785248712c 100644 --- a/packages/api/src/decorators/configurable.ts +++ b/packages/api/src/decorators/configurable.ts @@ -5,6 +5,6 @@ * @param state - true, if propoerty is configurable */ export const configurable = (state: boolean): MethodDecorator => - function (_: any, __: string | symbol, descriptor: PropertyDescriptor) { - descriptor.configurable = state; - }; + function (_: any, __: string | symbol, descriptor: PropertyDescriptor) { + descriptor.configurable = state; + }; diff --git a/packages/api/src/decorators/deprecated.ts b/packages/api/src/decorators/deprecated.ts index 371571ced6..3977da9b96 100644 --- a/packages/api/src/decorators/deprecated.ts +++ b/packages/api/src/decorators/deprecated.ts @@ -7,19 +7,19 @@ * @param msg - deprecation message */ export const deprecated = (msg?: string, log = console.log): MethodDecorator => - function ( - target: any, - prop: string | symbol, - descriptor: PropertyDescriptor - ) { - const signature = `${target.constructor.name}#${prop.toString()}`; - const fn = descriptor.value; - if (typeof fn !== "function") { - throw new Error(`${signature} is not a function`); - } - descriptor.value = function () { - log(`DEPRECATED ${signature}: ${msg || "will be removed soon"}`); - return fn.apply(this, arguments); - }; - return descriptor; - }; + function ( + target: any, + prop: string | symbol, + descriptor: PropertyDescriptor + ) { + const signature = `${target.constructor.name}#${prop.toString()}`; + const fn = descriptor.value; + if (typeof fn !== "function") { + throw new Error(`${signature} is not a function`); + } + descriptor.value = function () { + log(`DEPRECATED ${signature}: ${msg || "will be removed soon"}`); + return fn.apply(this, arguments); + }; + return descriptor; + }; diff --git a/packages/api/src/decorators/nomixin.ts b/packages/api/src/decorators/nomixin.ts index af850ab12c..715a510416 100644 --- a/packages/api/src/decorators/nomixin.ts +++ b/packages/api/src/decorators/nomixin.ts @@ -6,5 +6,5 @@ * avoid them being overidden by mixed-in behaviour. */ export const nomixin = (_: any, __: string, descriptor: PropertyDescriptor) => { - descriptor.configurable = false; + descriptor.configurable = false; }; diff --git a/packages/api/src/decorators/sealed.ts b/packages/api/src/decorators/sealed.ts index e39356f2ef..1876fbab0d 100644 --- a/packages/api/src/decorators/sealed.ts +++ b/packages/api/src/decorators/sealed.ts @@ -4,6 +4,6 @@ * @param constructor - class ctor to seal */ export const sealed = (constructor: Function) => { - Object.seal(constructor); - Object.seal(constructor.prototype); + Object.seal(constructor); + Object.seal(constructor.prototype); }; diff --git a/packages/api/src/deref.ts b/packages/api/src/deref.ts index 0f7b044c07..b1e32b6817 100644 --- a/packages/api/src/deref.ts +++ b/packages/api/src/deref.ts @@ -4,10 +4,10 @@ import type { IObjectOf } from "./object.js"; * Generic interface for reference types (value wrappers). */ export interface IDeref { - /** - * Returns wrapped value. - */ - deref(): T; + /** + * Returns wrapped value. + */ + deref(): T; } export type MaybeDeref = IDeref | T; @@ -37,19 +37,19 @@ export type Derefed = T extends IDeref ? ReturnType : T; * ``` */ export type DerefedKeys< - T extends IObjectOf, - K extends keyof T = keyof T + T extends IObjectOf, + K extends keyof T = keyof T > = { - [P in K]: Derefed; + [P in K]: Derefed; }; /** * Returns true iff `x` implements {@link IDeref}. * - * @param x - + * @param x - */ export const isDeref = (x: any): x is IDeref => - x != null && typeof x["deref"] === "function"; + x != null && typeof x["deref"] === "function"; /** * If `x` implements {@link IDeref}, returns its wrapped value, else diff --git a/packages/api/src/dissoc.ts b/packages/api/src/dissoc.ts index 2e221c4489..d1a4bd868d 100644 --- a/packages/api/src/dissoc.ts +++ b/packages/api/src/dissoc.ts @@ -8,7 +8,7 @@ import type { IAssoc, IAssocIn } from "./assoc.js"; * @param T - return type */ export interface IDissoc extends IAssoc { - dissoc(key: K): T; + dissoc(key: K): T; } /** @@ -19,5 +19,5 @@ export interface IDissoc extends IAssoc { * @param T - return type */ export interface IDissocIn extends IAssocIn { - dissocIn(key: K[]): T; + dissocIn(key: K[]): T; } diff --git a/packages/api/src/empty.ts b/packages/api/src/empty.ts index ed5db397f1..4c540f67ba 100644 --- a/packages/api/src/empty.ts +++ b/packages/api/src/empty.ts @@ -1,7 +1,7 @@ export interface IEmpty { - /** - * Returns an empty/blank instance of same type (with possibly same - * config, if any). - */ - empty(): T; + /** + * Returns an empty/blank instance of same type (with possibly same + * config, if any). + */ + empty(): T; } diff --git a/packages/api/src/enable.ts b/packages/api/src/enable.ts index 15a0e8dd4e..292594ecb8 100644 --- a/packages/api/src/enable.ts +++ b/packages/api/src/enable.ts @@ -5,16 +5,16 @@ * @param T - type for enable/disable option arg */ export interface IEnable { - isEnabled(): boolean; - /** - * Disables this entity. - * @param opts - optional implementation specific arg - */ - disable(opts?: T): any; - /** - * Enables this entity. - * @param opts - optional implementation specific arg - */ - enable(opts?: T): any; - toggle?(): boolean; + isEnabled(): boolean; + /** + * Disables this entity. + * @param opts - optional implementation specific arg + */ + disable(opts?: T): any; + /** + * Enables this entity. + * @param opts - optional implementation specific arg + */ + enable(opts?: T): any; + toggle?(): boolean; } diff --git a/packages/api/src/equiv.ts b/packages/api/src/equiv.ts index ef6fed53c5..a43a0ae57c 100644 --- a/packages/api/src/equiv.ts +++ b/packages/api/src/equiv.ts @@ -1,23 +1,23 @@ export interface IEquiv { - /** - * Returns `true` if this *value* is equivalent to `o`. Also see - * {@link ICompare.compare} and {@link IHash.hash}. - * - * @param o - value to check for equivalence - */ - equiv(o: any): boolean; + /** + * Returns `true` if this *value* is equivalent to `o`. Also see + * {@link ICompare.compare} and {@link IHash.hash}. + * + * @param o - value to check for equivalence + */ + equiv(o: any): boolean; } /** * @param T - value type */ export interface IEqualsDelta { - /** - * Returns `true` if this value equals `o` with optional allowance - * for given tolerance `eps`. - * - * @param o - 2nd value to test - * @param eps - tolerance (usually defaults to `DEFAULT_EPS`) - */ - eqDelta(o: T, eps?: number): boolean; + /** + * Returns `true` if this value equals `o` with optional allowance + * for given tolerance `eps`. + * + * @param o - 2nd value to test + * @param eps - tolerance (usually defaults to `DEFAULT_EPS`) + */ + eqDelta(o: T, eps?: number): boolean; } diff --git a/packages/api/src/event.ts b/packages/api/src/event.ts index c07558543c..4cb3a38f74 100644 --- a/packages/api/src/event.ts +++ b/packages/api/src/event.ts @@ -7,9 +7,9 @@ import type { IID } from "./id.js"; export type Listener = Fn; export interface Event extends IID { - target?: any; - canceled?: boolean; - value?: any; + target?: any; + canceled?: boolean; + value?: any; } /** @@ -17,7 +17,7 @@ export interface Event extends IID { * {@link INotifyMixin} decorator mixin. */ export interface INotify { - addListener(id: string, fn: Listener, scope?: any): boolean; - removeListener(id: string, fn: Listener, scope?: any): boolean; - notify(event: Event): void; + addListener(id: string, fn: Listener, scope?: any): boolean; + removeListener(id: string, fn: Listener, scope?: any): boolean; + notify(event: Event): void; } diff --git a/packages/api/src/fn.ts b/packages/api/src/fn.ts index d3015fa24a..df97b7b38d 100644 --- a/packages/api/src/fn.ts +++ b/packages/api/src/fn.ts @@ -32,70 +32,70 @@ export type Fn5 = (a: A, b: B, c: C, d: D, e: E) => F; * A 6-arg function from A,B,C,D,E,F to G. */ export type Fn6 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F + a: A, + b: B, + c: C, + d: D, + e: E, + f: F ) => G; /** * A 7-arg function from A,B,C,D,E,F,G to H. */ export type Fn7 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G ) => H; /** * A 8-arg function from A,B,C,D,E,F,G,H to I. */ export type Fn8 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H ) => I; /** * A 9-arg function from A,B,C,D,E,F,G,H,I to J. */ export type Fn9 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i: I + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I ) => J; /** * A 10-arg function from A,B,C,D,E,F,G,H,I,J to K. */ export type Fn10 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i: I, - j: J + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J ) => K; export type FnO = (a: A, ...xs: any[]) => B; @@ -107,72 +107,72 @@ export type FnO3 = (a: A, b: B, c: C, ...xs: any[]) => D; export type FnO4 = (a: A, b: B, c: C, d: D, ...xs: any[]) => E; export type FnO5 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - ...xs: any[] + a: A, + b: B, + c: C, + d: D, + e: E, + ...xs: any[] ) => F; export type FnO6 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - ...xs: any[] + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + ...xs: any[] ) => G; export type FnO7 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - ...xs: any[] + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + ...xs: any[] ) => H; export type FnO8 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - ...xs: any[] + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + ...xs: any[] ) => I; export type FnO9 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i: I, - ...xs: any[] + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + ...xs: any[] ) => J; export type FnO10 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i: I, - j: J, - ...xs: any[] + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J, + ...xs: any[] ) => K; /** diff --git a/packages/api/src/get.ts b/packages/api/src/get.ts index 72945a0c7e..8ef0d2acbe 100644 --- a/packages/api/src/get.ts +++ b/packages/api/src/get.ts @@ -3,7 +3,7 @@ * @param V - value type */ export interface IGet { - get(key: K, notfound?: V): V | undefined; + get(key: K, notfound?: V): V | undefined; } /** @@ -11,5 +11,5 @@ export interface IGet { * @param V - value type */ export interface IGetIn { - getIn(key: K[], notfound?: V): V | undefined; + getIn(key: K[], notfound?: V): V | undefined; } diff --git a/packages/api/src/grid.ts b/packages/api/src/grid.ts index b408979f86..8d8e5ed456 100644 --- a/packages/api/src/grid.ts +++ b/packages/api/src/grid.ts @@ -1,14 +1,14 @@ import type { NumericArray, TypedArray } from "./typedarray.js"; export interface INDBase { - size: NumericArray; - stride: NumericArray; - offset: number; - data: BUF; + size: NumericArray; + stride: NumericArray; + offset: number; + data: BUF; - readonly dim: number; + readonly dim: number; - order(): number[]; + order(): number[]; } /** @@ -16,100 +16,100 @@ export interface INDBase { * {@link IGrid1D} - {@link IGrid4D}. */ export interface IGridND - extends INDBase { - /** - * Returns true if given position is valid (i.e. within grid bounds). - * - * @param pos - - */ - includes( - ...pos: - | [number] - | [number, number] - | [number, number, number] - | [number, number, number, number] - ): boolean; + extends INDBase { + /** + * Returns true if given position is valid (i.e. within grid bounds). + * + * @param pos - + */ + includes( + ...pos: + | [number] + | [number, number] + | [number, number, number] + | [number, number, number, number] + ): boolean; - /** - * Returns index for given position. Returns negative value if outside the - * grid's defined region. - * - * @param pos - - */ - indexAt( - ...pos: - | [number] - | [number, number] - | [number, number, number] - | [number, number, number, number] - ): number; - /** - * Non-boundschecked version of {@link IGridND.indexAt}. Assumes given - * position is valid. - * - * @param pos - - */ - indexAtUnsafe( - ...pos: - | [number] - | [number, number] - | [number, number, number] - | [number, number, number, number] - ): number; + /** + * Returns index for given position. Returns negative value if outside the + * grid's defined region. + * + * @param pos - + */ + indexAt( + ...pos: + | [number] + | [number, number] + | [number, number, number] + | [number, number, number, number] + ): number; + /** + * Non-boundschecked version of {@link IGridND.indexAt}. Assumes given + * position is valid. + * + * @param pos - + */ + indexAtUnsafe( + ...pos: + | [number] + | [number, number] + | [number, number, number] + | [number, number, number, number] + ): number; - /** - * Returns value at given position. If outside the grid's defined region, - * returns a suitable zero value. - * - * @param pos - - */ - getAt( - ...pos: - | [number] - | [number, number] - | [number, number, number] - | [number, number, number, number] - ): T; - /** - * Non-boundschecked version of {@link IGridND.getAt}. Assumes given - * position is valid. - * - * @param pos - - */ - getAtUnsafe( - ...pos: - | [number] - | [number, number] - | [number, number, number] - | [number, number, number, number] - ): T; + /** + * Returns value at given position. If outside the grid's defined region, + * returns a suitable zero value. + * + * @param pos - + */ + getAt( + ...pos: + | [number] + | [number, number] + | [number, number, number] + | [number, number, number, number] + ): T; + /** + * Non-boundschecked version of {@link IGridND.getAt}. Assumes given + * position is valid. + * + * @param pos - + */ + getAtUnsafe( + ...pos: + | [number] + | [number, number] + | [number, number, number] + | [number, number, number, number] + ): T; - /** - * Writes value at given position. Has no effect if outside of the defined - * region. Returns true, if succeeded. - * - * @param args - - */ - setAt( - ...args: - | [number, T] - | [number, number, T] - | [number, number, number, T] - | [number, number, number, number, T] - ): boolean; - /** - * Non-boundschecked version of {@link IGridND.setAt}. Assumes given - * position is valid. Returns true, if succeeded. - * - * @param args - - */ - setAtUnsafe( - ...args: - | [number, T] - | [number, number, T] - | [number, number, number, T] - | [number, number, number, number, T] - ): boolean; + /** + * Writes value at given position. Has no effect if outside of the defined + * region. Returns true, if succeeded. + * + * @param args - + */ + setAt( + ...args: + | [number, T] + | [number, number, T] + | [number, number, number, T] + | [number, number, number, number, T] + ): boolean; + /** + * Non-boundschecked version of {@link IGridND.setAt}. Assumes given + * position is valid. Returns true, if succeeded. + * + * @param args - + */ + setAtUnsafe( + ...args: + | [number, T] + | [number, number, T] + | [number, number, number, T] + | [number, number, number, number, T] + ): boolean; } /** @@ -119,62 +119,62 @@ export interface IGridND * See {@link IGrid1DMixin} for mixin implementation. */ export interface IGrid1D - extends INDBase { - readonly dim: 1; + extends INDBase { + readonly dim: 1; - /** - * Returns true if given position is valid (i.e. within grid bounds). - * - * @param d0 - - */ - includes(d0: number): boolean; + /** + * Returns true if given position is valid (i.e. within grid bounds). + * + * @param d0 - + */ + includes(d0: number): boolean; - /** - * Returns index for given position. Returns negative value if outside the - * grid's defined region. - * - * @param d0 - - */ - indexAt(d0: number): number; - /** - * Non-boundschecked version of {@link IGrid1D.indexAt}. Assumes given - * position is valid. - * - * @param d0 - - */ - indexAtUnsafe(d0: number): number; + /** + * Returns index for given position. Returns negative value if outside the + * grid's defined region. + * + * @param d0 - + */ + indexAt(d0: number): number; + /** + * Non-boundschecked version of {@link IGrid1D.indexAt}. Assumes given + * position is valid. + * + * @param d0 - + */ + indexAtUnsafe(d0: number): number; - /** - * Returns value at given position. If outside the grid's defined region, - * returns a suitable zero value. - * - * @param d0 - - */ - getAt(d0: number): T; - /** - * Non-boundschecked version of {@link IGrid1D.getAt}. Assumes given - * position is valid. - * - * @param d0 - - */ - getAtUnsafe(d0: number): T; + /** + * Returns value at given position. If outside the grid's defined region, + * returns a suitable zero value. + * + * @param d0 - + */ + getAt(d0: number): T; + /** + * Non-boundschecked version of {@link IGrid1D.getAt}. Assumes given + * position is valid. + * + * @param d0 - + */ + getAtUnsafe(d0: number): T; - /** - * Writes value at given position. Has no effect if outside of the defined - * region. Returns true, if succeeded. - * - * @param d0 - - * @param value - - */ - setAt(d0: number, value: T): boolean; - /** - * Non-boundschecked version of {@link IGrid1D.setAt}. Assumes given - * position is valid. Returns true, if succeeded. - * - * @param d0 - - * @param value - - */ - setAtUnsafe(d0: number, value: T): boolean; + /** + * Writes value at given position. Has no effect if outside of the defined + * region. Returns true, if succeeded. + * + * @param d0 - + * @param value - + */ + setAt(d0: number, value: T): boolean; + /** + * Non-boundschecked version of {@link IGrid1D.setAt}. Assumes given + * position is valid. Returns true, if succeeded. + * + * @param d0 - + * @param value - + */ + setAtUnsafe(d0: number, value: T): boolean; } /** @@ -184,72 +184,72 @@ export interface IGrid1D * See {@link IGrid2DMixin} for mixin implementation. */ export interface IGrid2D - extends INDBase { - readonly dim: 2; + extends INDBase { + readonly dim: 2; - /** - * Returns true if given position is valid (i.e. within grid bounds). - * - * @param d0 - - * @param d1 - - */ - includes(d0: number, d1: number): boolean; + /** + * Returns true if given position is valid (i.e. within grid bounds). + * + * @param d0 - + * @param d1 - + */ + includes(d0: number, d1: number): boolean; - /** - * Returns index for given position (stated in same order as - * {@link IGrid2D.size} and {@link IGrid2D.stride}). Returns negative value - * if outside the grid's defined region. - * - * @param d0 - - * @param d1 - - */ - indexAt(d0: number, d1: number): number; - /** - * Non-boundschecked version of {@link IGrid2D.indexAt}. Assumes given - * position is valid. - * - * @param d0 - - * @param d1 - - */ - indexAtUnsafe(d0: number, d1: number): number; + /** + * Returns index for given position (stated in same order as + * {@link IGrid2D.size} and {@link IGrid2D.stride}). Returns negative value + * if outside the grid's defined region. + * + * @param d0 - + * @param d1 - + */ + indexAt(d0: number, d1: number): number; + /** + * Non-boundschecked version of {@link IGrid2D.indexAt}. Assumes given + * position is valid. + * + * @param d0 - + * @param d1 - + */ + indexAtUnsafe(d0: number, d1: number): number; - /** - * Returns value at given position (given in same order as - * {@link IGrid2D.size} and {@link IGrid2D.stride}). If outside the grid's - * defined region, returns a suitable zero value. - * - * @param d0 - - * @param d1 - - */ - getAt(d0: number, d1: number): T; - /** - * Non-boundschecked version of {@link IGrid2D.getAt}. Assumes given - * position is valid. - * - * @param d0 - - * @param d1 - - */ - getAtUnsafe(d0: number, d1: number): T; + /** + * Returns value at given position (given in same order as + * {@link IGrid2D.size} and {@link IGrid2D.stride}). If outside the grid's + * defined region, returns a suitable zero value. + * + * @param d0 - + * @param d1 - + */ + getAt(d0: number, d1: number): T; + /** + * Non-boundschecked version of {@link IGrid2D.getAt}. Assumes given + * position is valid. + * + * @param d0 - + * @param d1 - + */ + getAtUnsafe(d0: number, d1: number): T; - /** - * Writes value at given position (given in same order as - * {@link IGrid2D.size} and {@link IGrid2D.stride}). Has no effect if - * outside of the defined region. Returns true, if succeeded. - * - * @param d0 - - * @param d1 - - * @param value - - */ - setAt(d0: number, d1: number, value: T): boolean; - /** - * Non-boundschecked version of {@link IGrid2D.setAt}. Assumes given - * position is valid. Returns true, if succeeded. - * - * @param d0 - - * @param d1 - - * @param value - - */ - setAtUnsafe(d0: number, d1: number, value: T): boolean; + /** + * Writes value at given position (given in same order as + * {@link IGrid2D.size} and {@link IGrid2D.stride}). Has no effect if + * outside of the defined region. Returns true, if succeeded. + * + * @param d0 - + * @param d1 - + * @param value - + */ + setAt(d0: number, d1: number, value: T): boolean; + /** + * Non-boundschecked version of {@link IGrid2D.setAt}. Assumes given + * position is valid. Returns true, if succeeded. + * + * @param d0 - + * @param d1 - + * @param value - + */ + setAtUnsafe(d0: number, d1: number, value: T): boolean; } /** @@ -259,79 +259,79 @@ export interface IGrid2D * See {@link IGrid3DMixin} for mixin implementation. */ export interface IGrid3D - extends INDBase { - readonly dim: 3; + extends INDBase { + readonly dim: 3; - /** - * Returns true if given position is valid (i.e. within grid bounds). - * - * @param d0 - - * @param d1 - - * @param d2 - - */ - includes(d0: number, d1: number, d2: number): boolean; + /** + * Returns true if given position is valid (i.e. within grid bounds). + * + * @param d0 - + * @param d1 - + * @param d2 - + */ + includes(d0: number, d1: number, d2: number): boolean; - /** - * Returns index for given position (stated in same order as - * {@link IGrid3D.size} and {@link IGrid3D.stride}). Returns negative value - * if outside the grid's defined region. - * - * @param d0 - - * @param d1 - - * @param d2 - - */ - indexAt(d0: number, d1: number, d2: number): number; - /** - * Non-boundschecked version of {@link IGrid3D.indexAt}. Assumes given - * position is valid. - * - * @param d0 - - * @param d1 - - * @param d2 - - */ - indexAtUnsafe(d0: number, d1: number, d2: number): number; + /** + * Returns index for given position (stated in same order as + * {@link IGrid3D.size} and {@link IGrid3D.stride}). Returns negative value + * if outside the grid's defined region. + * + * @param d0 - + * @param d1 - + * @param d2 - + */ + indexAt(d0: number, d1: number, d2: number): number; + /** + * Non-boundschecked version of {@link IGrid3D.indexAt}. Assumes given + * position is valid. + * + * @param d0 - + * @param d1 - + * @param d2 - + */ + indexAtUnsafe(d0: number, d1: number, d2: number): number; - /** - * Returns value at given position (given in same order as - * {@link IGrid3D.size} and {@link IGrid3D.stride}). If outside the grid's - * defined region, returns a suitable zero value. - * - * @param d0 - - * @param d1 - - * @param d2 - - */ - getAt(d0: number, d1: number, d2: number): T; - /** - * Non-boundschecked version of {@link IGrid3D.getAt}. Assumes given - * position is valid. - * - * @param d0 - - * @param d1 - - * @param d2 - - */ - getAtUnsafe(d0: number, d1: number, d2: number): T; + /** + * Returns value at given position (given in same order as + * {@link IGrid3D.size} and {@link IGrid3D.stride}). If outside the grid's + * defined region, returns a suitable zero value. + * + * @param d0 - + * @param d1 - + * @param d2 - + */ + getAt(d0: number, d1: number, d2: number): T; + /** + * Non-boundschecked version of {@link IGrid3D.getAt}. Assumes given + * position is valid. + * + * @param d0 - + * @param d1 - + * @param d2 - + */ + getAtUnsafe(d0: number, d1: number, d2: number): T; - /** - * Writes value at given position (given in same order as - * {@link IGrid3D.size} and {@link IGrid3D.stride}). Has no effect if - * outside of the defined region. Returns true, if succeeded. - * - * @param d0 - - * @param d1 - - * @param d2 - - * @param value - - */ - setAt(d0: number, d1: number, d2: number, value: T): boolean; - /** - * Non-boundschecked version of {@link IGrid3D.setAt}. Assumes given - * position is valid. Returns true, if succeeded. - * - * @param d0 - - * @param d1 - - * @param d2 - - * @param value - - */ - setAtUnsafe(d0: number, d1: number, d2: number, value: T): boolean; + /** + * Writes value at given position (given in same order as + * {@link IGrid3D.size} and {@link IGrid3D.stride}). Has no effect if + * outside of the defined region. Returns true, if succeeded. + * + * @param d0 - + * @param d1 - + * @param d2 - + * @param value - + */ + setAt(d0: number, d1: number, d2: number, value: T): boolean; + /** + * Non-boundschecked version of {@link IGrid3D.setAt}. Assumes given + * position is valid. Returns true, if succeeded. + * + * @param d0 - + * @param d1 - + * @param d2 - + * @param value - + */ + setAtUnsafe(d0: number, d1: number, d2: number, value: T): boolean; } /** @@ -341,90 +341,90 @@ export interface IGrid3D * See {@link IGrid4DMixin} for mixin implementation. */ export interface IGrid4D - extends INDBase { - readonly dim: 4; + extends INDBase { + readonly dim: 4; - /** - * Returns true if given position is valid (i.e. within grid bounds). - * - * @param d0 - - * @param d1 - - * @param d2 - - * @param d3 - - */ - includes(d0: number, d1: number, d2: number, d3: number): boolean; + /** + * Returns true if given position is valid (i.e. within grid bounds). + * + * @param d0 - + * @param d1 - + * @param d2 - + * @param d3 - + */ + includes(d0: number, d1: number, d2: number, d3: number): boolean; - /** - * Returns index for given position (stated in same order as - * {@link IGrid4D.size} and {@link IGrid4D.stride}). Returns negative value - * if outside the grid's defined region. - * - * @param d0 - - * @param d1 - - * @param d2 - - * @param d3 - - */ - indexAt(d0: number, d1: number, d2: number, d3: number): number; - /** - * Non-boundschecked version of {@link IGrid4D.indexAt}. Assumes given - * position is valid. - * - * @param d0 - - * @param d1 - - * @param d2 - - * @param d3 - - */ - indexAtUnsafe(d0: number, d1: number, d2: number, d3: number): number; + /** + * Returns index for given position (stated in same order as + * {@link IGrid4D.size} and {@link IGrid4D.stride}). Returns negative value + * if outside the grid's defined region. + * + * @param d0 - + * @param d1 - + * @param d2 - + * @param d3 - + */ + indexAt(d0: number, d1: number, d2: number, d3: number): number; + /** + * Non-boundschecked version of {@link IGrid4D.indexAt}. Assumes given + * position is valid. + * + * @param d0 - + * @param d1 - + * @param d2 - + * @param d3 - + */ + indexAtUnsafe(d0: number, d1: number, d2: number, d3: number): number; - /** - * Returns value at given position (given in same order as - * {@link IGrid4D.size} and {@link IGrid4D.stride}). If outside the grid's - * defined region, returns a suitable zero value. - * - * @param d0 - - * @param d1 - - * @param d2 - - * @param d3 - - */ - getAt(d0: number, d1: number, d2: number, d3: number): T; - /** - * Non-boundschecked version of {@link IGrid4D.getAt}. Assumes given - * position is valid. - * - * @param d0 - - * @param d1 - - * @param d2 - - * @param d3 - - */ - getAtUnsafe(d0: number, d1: number, d2: number, d3: number): T; + /** + * Returns value at given position (given in same order as + * {@link IGrid4D.size} and {@link IGrid4D.stride}). If outside the grid's + * defined region, returns a suitable zero value. + * + * @param d0 - + * @param d1 - + * @param d2 - + * @param d3 - + */ + getAt(d0: number, d1: number, d2: number, d3: number): T; + /** + * Non-boundschecked version of {@link IGrid4D.getAt}. Assumes given + * position is valid. + * + * @param d0 - + * @param d1 - + * @param d2 - + * @param d3 - + */ + getAtUnsafe(d0: number, d1: number, d2: number, d3: number): T; - /** - * Writes value at given position (given in same order as - * {@link IGrid4D.size} and {@link IGrid4D.stride}). Has no effect if - * outside of the defined region. Returns true, if succeeded. - * - * @param d0 - - * @param d1 - - * @param d2 - - * @param d3 - - * @param value - - */ - setAt(d0: number, d1: number, d2: number, d3: number, value: T): boolean; - /** - * Non-boundschecked version of {@link IGrid4D.setAt}. Assumes given - * position is valid. Returns true, if succeeded. - * - * @param d0 - - * @param d1 - - * @param d2 - - * @param d3 - - * @param value - - */ - setAtUnsafe( - d0: number, - d1: number, - d2: number, - d3: number, - value: T - ): boolean; + /** + * Writes value at given position (given in same order as + * {@link IGrid4D.size} and {@link IGrid4D.stride}). Has no effect if + * outside of the defined region. Returns true, if succeeded. + * + * @param d0 - + * @param d1 - + * @param d2 - + * @param d3 - + * @param value - + */ + setAt(d0: number, d1: number, d2: number, d3: number, value: T): boolean; + /** + * Non-boundschecked version of {@link IGrid4D.setAt}. Assumes given + * position is valid. Returns true, if succeeded. + * + * @param d0 - + * @param d1 - + * @param d2 - + * @param d3 - + * @param value - + */ + setAtUnsafe( + d0: number, + d1: number, + d2: number, + d3: number, + value: T + ): boolean; } diff --git a/packages/api/src/hash.ts b/packages/api/src/hash.ts index 7047d120f4..da8858a87e 100644 --- a/packages/api/src/hash.ts +++ b/packages/api/src/hash.ts @@ -2,10 +2,10 @@ * Interface for hashable types. */ export interface IHash { - /** - * Returns a value's hash code. The contract of this function is: If - * `IEquiv.equiv` returns `true` for two values, their hash codes - * MUST also be equal. - */ - hash(): T; + /** + * Returns a value's hash code. The contract of this function is: If + * `IEquiv.equiv` returns `true` for two values, their hash codes + * MUST also be equal. + */ + hash(): T; } diff --git a/packages/api/src/hiccup.ts b/packages/api/src/hiccup.ts index 3834d52d26..134f02f022 100644 --- a/packages/api/src/hiccup.ts +++ b/packages/api/src/hiccup.ts @@ -1,13 +1,13 @@ export interface IToHiccup { - /** - * Returns a {@link @thi.ng/hiccup# | @thi.ng/hiccup} compatible representation. The optional - * `ctx` arg is an arbitrary user context object passed to all - * hiccup components during serialization (or during DOM creation / - * update if used with {@link @thi.ng/hdom# | @thi.ng/hdom}) - * - * @param ctx - user context object - * @param attribs - user attribs - * @param xs - additional args - */ - toHiccup(ctx?: any, attribs?: any, ...xs: any[]): any; + /** + * Returns a {@link @thi.ng/hiccup# | @thi.ng/hiccup} compatible representation. The optional + * `ctx` arg is an arbitrary user context object passed to all + * hiccup components during serialization (or during DOM creation / + * update if used with {@link @thi.ng/hdom# | @thi.ng/hdom}) + * + * @param ctx - user context object + * @param attribs - user attribs + * @param xs - additional args + */ + toHiccup(ctx?: any, attribs?: any, ...xs: any[]): any; } diff --git a/packages/api/src/id.ts b/packages/api/src/id.ts index a04ab98410..4187882493 100644 --- a/packages/api/src/id.ts +++ b/packages/api/src/id.ts @@ -2,5 +2,5 @@ * `id` property declaration. */ export interface IID { - readonly id: T; + readonly id: T; } diff --git a/packages/api/src/indexed.ts b/packages/api/src/indexed.ts index 502a7be167..7cd091c3c5 100644 --- a/packages/api/src/indexed.ts +++ b/packages/api/src/indexed.ts @@ -3,5 +3,5 @@ * index. */ export interface IIndexed { - nth(i: number, notfound: T): T; + nth(i: number, notfound: T): T; } diff --git a/packages/api/src/into.ts b/packages/api/src/into.ts index cc2399d534..19e88b478a 100644 --- a/packages/api/src/into.ts +++ b/packages/api/src/into.ts @@ -3,5 +3,5 @@ * values. */ export interface IInto { - into(coll: Iterable): T; + into(coll: Iterable): T; } diff --git a/packages/api/src/keyval.ts b/packages/api/src/keyval.ts index f727133a9f..fd00dc492e 100644 --- a/packages/api/src/keyval.ts +++ b/packages/api/src/keyval.ts @@ -4,7 +4,7 @@ import type { Head, Tail } from "./tuple.js"; * Extracts from A all keys which have values assignable to type B. */ export type TypedKeys = { - [P in Keys]: B extends A[P] ? P : never; + [P in Keys]: B extends A[P] ? P : never; }[keyof A]; export type NumericKeys = TypedKeys; @@ -12,7 +12,7 @@ export type NumericKeys = TypedKeys; export type StringKeys = TypedKeys; export type DeepPartial = Partial<{ - [k in keyof T]: DeepPartial; + [k in keyof T]: DeepPartial; }>; /* @@ -21,59 +21,59 @@ export type DeepPartial = Partial<{ export type Keys = keyof Required; export type Keys1> = Keys[A]>; export type Keys2, B extends Keys1> = Keys1< - Required[A], - B + Required[A], + B >; export type Keys3< - T, - A extends Keys, - B extends Keys1, - C extends Keys2 + T, + A extends Keys, + B extends Keys1, + C extends Keys2 > = Keys2[A], B, C>; export type Keys4< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3 > = Keys3[A], B, C, D>; export type Keys5< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4 > = Keys4[A], B, C, D, E>; export type Keys6< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5 > = Keys5[A], B, C, D, E, F>; export type Keys7< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5, - G extends Keys6 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5, + G extends Keys6 > = Keys6[A], B, C, D, E, F, G>; export type Keys8< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5, - G extends Keys6, - H extends Keys7 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5, + G extends Keys6, + H extends Keys7 > = Keys7[A], B, C, D, E, F, G, H>; /** @@ -86,18 +86,18 @@ export type Keys8< * @param R - Remaining values. */ type KeysNReducer = L extends keyof T - ? { - 0: keyof Required[L]; - 1: KeysNReducer[L], Head, Tail>; - }[R extends [] ? 0 : 1] - : never; + ? { + 0: keyof Required[L]; + 1: KeysNReducer[L], Head, Tail>; + }[R extends [] ? 0 : 1] + : never; /** * Generalised version of Keys0 - Keys7. */ export type KeysN = L extends [] - ? Keys - : KeysNReducer, Tail>; + ? Keys + : KeysNReducer, Tail>; /* * Utilities for extracting value types from nested objects. @@ -105,55 +105,55 @@ export type KeysN = L extends [] export type Val1> = T[A]; export type Val2, B extends Keys1> = ValN; export type Val3< - T, - A extends Keys, - B extends Keys1, - C extends Keys2 + T, + A extends Keys, + B extends Keys1, + C extends Keys2 > = ValN; export type Val4< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3 > = ValN; export type Val5< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4 > = ValN; export type Val6< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5 > = ValN; export type Val7< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5, - G extends Keys6 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5, + G extends Keys6 > = ValN; export type Val8< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5, - G extends Keys6, - H extends Keys7 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5, + G extends Keys6, + H extends Keys7 > = ValN; /** @@ -166,77 +166,77 @@ export type Val8< * @param R - he remaining keys */ type ValNReducer = C extends keyof T - ? { - 0: T[C]; - 1: ValNReducer[C], Head, Tail>; - }[R extends [] ? 0 : 1] - : never; + ? { + 0: T[C]; + 1: ValNReducer[C], Head, Tail>; + }[R extends [] ? 0 : 1] + : never; /** * Generalised version of Val1-Val7 */ export type ValN = L extends [] - ? T - : ValNReducer, Tail>; + ? T + : ValNReducer, Tail>; /** * Utilities for constructing types with nested keys removed. */ export type Without> = Omit; export type Without2, B extends Keys1> = Without< - T, - A + T, + A > & { [id in A]: Without, B> }; export type Without3< - T, - A extends Keys, - B extends Keys1, - C extends Keys2 + T, + A extends Keys, + B extends Keys1, + C extends Keys2 > = Without & { [id in A]: Without2, B, C> }; export type Without4< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3 > = Without & { [id in A]: Without3, B, C, D> }; export type Without5< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4 > = Without & { [id in A]: Without4, B, C, D, E> }; export type Without6< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5 > = Without & { [id in A]: Without5, B, C, D, E, F> }; export type Without7< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5, - G extends Keys6 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5, + G extends Keys6 > = Without & { [id in A]: Without6, B, C, D, E, F, G> }; export type Without8< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5, - G extends Keys6, - H extends Keys7 + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5, + G extends Keys6, + H extends Keys7 > = Without & { [id in A]: Without7, B, C, D, E, F, G, H> }; /** @@ -249,19 +249,20 @@ export type Without8< * @param R - he remaining keys. */ type WithoutNReducer = C extends keyof T - ? { - 0: Without; - 1: Without & Record, Tail>>; - }[R extends [] ? 0 : 1] - : never; + ? { + 0: Without; + 1: Without & + Record, Tail>>; + }[R extends [] ? 0 : 1] + : never; /** * Generalised version of Without0-Without8. */ export type WithoutN = WithoutNReducer< - T, - Head

, - Tail

+ T, + Head

, + Tail

>; /** @@ -269,65 +270,65 @@ export type WithoutN = WithoutNReducer< */ export type Replace, V> = Without & { [id in A]: V }; export type Replace2, B extends Keys1, V> = Without< - T, - A + T, + A > & { [id in A]: Replace, B, V> }; export type Replace3< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - V + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + V > = Without & { [id in A]: Replace2, B, C, V> }; export type Replace4< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - V + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + V > = Without & { [id in A]: Replace3, B, C, D, V> }; export type Replace5< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - V + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + V > = Without & { [id in A]: Replace4, B, C, D, E, V> }; export type Replace6< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5, - V + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5, + V > = Without & { [id in A]: Replace5, B, C, D, E, F, V> }; export type Replace7< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5, - G extends Keys6, - V + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5, + G extends Keys6, + V > = Without & { [id in A]: Replace6, B, C, D, E, F, G, V> }; export type Replace8< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4, - F extends Keys5, - G extends Keys6, - H extends Keys7, - V + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4, + F extends Keys5, + G extends Keys6, + H extends Keys7, + V > = Without & { [id in A]: Replace7, B, C, D, E, F, G, H, V> }; /** @@ -341,19 +342,19 @@ export type Replace8< * @param V - he type to use for the replacement. */ type ReplaceNReducer = C extends keyof T - ? { - 0: Replace; - 1: Without & - Record, Tail, V>>; - }[R extends [] ? 0 : 1] - : never; + ? { + 0: Replace; + 1: Without & + Record, Tail, V>>; + }[R extends [] ? 0 : 1] + : never; /** * Generalised version of Replace0-Replace8. */ export type ReplaceN = ReplaceNReducer< - T, - Head

, - Tail

, - V + T, + Head

, + Tail

, + V >; diff --git a/packages/api/src/length.ts b/packages/api/src/length.ts index 5529f858d3..3072c26230 100644 --- a/packages/api/src/length.ts +++ b/packages/api/src/length.ts @@ -3,5 +3,5 @@ * count. */ export interface ILength { - readonly length: number; + readonly length: number; } diff --git a/packages/api/src/meta.ts b/packages/api/src/meta.ts index ae1a2055dc..281314b857 100644 --- a/packages/api/src/meta.ts +++ b/packages/api/src/meta.ts @@ -3,12 +3,12 @@ * exclude metadata from any comparisons, equality checks & hashing. */ export interface IMeta { - meta(): any; - /** - * Returns a copy of the original value with given metadata - * attached. - * - * @param meta - meta data - */ - withMeta(meta: any): T; + meta(): any; + /** + * Returns a copy of the original value with given metadata + * attached. + * + * @param meta - meta data + */ + withMeta(meta: any): T; } diff --git a/packages/api/src/mixin.ts b/packages/api/src/mixin.ts index 6b0e10115a..9e4d68b2c5 100644 --- a/packages/api/src/mixin.ts +++ b/packages/api/src/mixin.ts @@ -11,39 +11,39 @@ * @returns decorator function */ export const mixin = (behaviour: any, sharedBehaviour: any = {}) => { - const instanceKeys = Reflect.ownKeys(behaviour); - const sharedKeys = Reflect.ownKeys(sharedBehaviour); - const typeTag = Symbol("isa"); + const instanceKeys = Reflect.ownKeys(behaviour); + const sharedKeys = Reflect.ownKeys(sharedBehaviour); + const typeTag = Symbol("isa"); - function _mixin(clazz: any) { - for (let key of instanceKeys) { - const existing = Object.getOwnPropertyDescriptor( - clazz.prototype, - key - ); - if (!existing || existing.configurable) { - Object.defineProperty(clazz.prototype, key, { - value: behaviour[key], - writable: true, - }); - } else { - // console.log(`not patching: ${clazz.name}.${key.toString()}`); - } - } - Object.defineProperty(clazz.prototype, typeTag, { value: true }); - return clazz; - } + function _mixin(clazz: any) { + for (let key of instanceKeys) { + const existing = Object.getOwnPropertyDescriptor( + clazz.prototype, + key + ); + if (!existing || existing.configurable) { + Object.defineProperty(clazz.prototype, key, { + value: behaviour[key], + writable: true, + }); + } else { + // console.log(`not patching: ${clazz.name}.${key.toString()}`); + } + } + Object.defineProperty(clazz.prototype, typeTag, { value: true }); + return clazz; + } - for (let key of sharedKeys) { - Object.defineProperty(_mixin, key, { - value: sharedBehaviour[key], - enumerable: sharedBehaviour.propertyIsEnumerable(key), - }); - } + for (let key of sharedKeys) { + Object.defineProperty(_mixin, key, { + value: sharedBehaviour[key], + enumerable: sharedBehaviour.propertyIsEnumerable(key), + }); + } - Object.defineProperty(_mixin, Symbol.hasInstance, { - value: (x: any) => !!x[typeTag], - }); + Object.defineProperty(_mixin, Symbol.hasInstance, { + value: (x: any) => !!x[typeTag], + }); - return _mixin; + return _mixin; }; diff --git a/packages/api/src/mixins/ienable.ts b/packages/api/src/mixins/ienable.ts index 1b0f479d57..16871c9433 100644 --- a/packages/api/src/mixins/ienable.ts +++ b/packages/api/src/mixins/ienable.ts @@ -4,8 +4,8 @@ import type { Event } from "../event.js"; import { mixin } from "../mixin.js"; interface _IEnable extends IEnable { - _enabled: boolean; - notify?(e: Event): void; + _enabled: boolean; + notify?(e: Event): void; } /** @@ -16,29 +16,29 @@ interface _IEnable extends IEnable { * events. */ export const IEnableMixin = mixin(>{ - _enabled: true, + _enabled: true, - isEnabled(this: _IEnable) { - return this._enabled; - }, + isEnabled(this: _IEnable) { + return this._enabled; + }, - enable(this: _IEnable) { - $enable(this, true, EVENT_ENABLE); - }, + enable(this: _IEnable) { + $enable(this, true, EVENT_ENABLE); + }, - disable(this: _IEnable) { - $enable(this, false, EVENT_DISABLE); - }, + disable(this: _IEnable) { + $enable(this, false, EVENT_DISABLE); + }, - toggle(this: _IEnable) { - this._enabled ? this.disable() : this.enable(); - return this._enabled; - }, + toggle(this: _IEnable) { + this._enabled ? this.disable() : this.enable(); + return this._enabled; + }, }); const $enable = (target: _IEnable, state: boolean, id: string) => { - target._enabled = state; - if (target.notify) { - target.notify({ id, target }); - } + target._enabled = state; + if (target.notify) { + target.notify({ id, target }); + } }; diff --git a/packages/api/src/mixins/igrid.ts b/packages/api/src/mixins/igrid.ts index 9c5bb1adae..e9640db749 100644 --- a/packages/api/src/mixins/igrid.ts +++ b/packages/api/src/mixins/igrid.ts @@ -6,204 +6,204 @@ import type { NumericArray } from "../typedarray.js"; * Default implementation for {@link IGrid1D} methods. */ export const IGrid1DMixin = mixin(>{ - order() { - return [0]; - }, - - includes(x: number) { - return x >= 0 && x < this.size[0]; - }, - - indexAt(x: number) { - return this.includes(x) ? this.indexAtUnsafe(x) : -1; - }, - - indexAtUnsafe(x: number) { - return this.offset + (x | 0) * this.stride[0]; - }, - - getAt(x: number) { - return this.includes(x) ? this.data[this.indexAtUnsafe(x)] : 0; - }, - - getAtUnsafe(x: number) { - return this.data[this.indexAtUnsafe(x)]; - }, - - setAt(x: number, val: any) { - return this.includes(x) - ? ((this.data[this.indexAtUnsafe(x)] = val), true) - : false; - }, - - setAtUnsafe(x: number, val: any) { - this.data[this.indexAtUnsafe(x)] = val; - return true; - }, + order() { + return [0]; + }, + + includes(x: number) { + return x >= 0 && x < this.size[0]; + }, + + indexAt(x: number) { + return this.includes(x) ? this.indexAtUnsafe(x) : -1; + }, + + indexAtUnsafe(x: number) { + return this.offset + (x | 0) * this.stride[0]; + }, + + getAt(x: number) { + return this.includes(x) ? this.data[this.indexAtUnsafe(x)] : 0; + }, + + getAtUnsafe(x: number) { + return this.data[this.indexAtUnsafe(x)]; + }, + + setAt(x: number, val: any) { + return this.includes(x) + ? ((this.data[this.indexAtUnsafe(x)] = val), true) + : false; + }, + + setAtUnsafe(x: number, val: any) { + this.data[this.indexAtUnsafe(x)] = val; + return true; + }, }); /** * Default implementation for {@link IGrid2D} methods. */ export const IGrid2DMixin = mixin(>{ - order() { - return Math.abs(this.stride[1]) > Math.abs(this.stride[0]) - ? [1, 0] - : [0, 1]; - }, - - includes(x: number, y: number) { - const size = this.size; - return x >= 0 && x < size[0] && y >= 0 && y < size[1]; - }, - - indexAt(x: number, y: number) { - return this.includes(x, y) ? this.indexAtUnsafe(x, y) : -1; - }, - - indexAtUnsafe(x: number, y: number) { - return ( - this.offset + (x | 0) * this.stride[0] + (y | 0) * this.stride[1] - ); - }, - - getAt(x: number, y: number) { - return this.includes(x, y) ? this.data[this.indexAtUnsafe(x, y)] : 0; - }, - - getAtUnsafe(x: number, y: number) { - return this.data[this.indexAtUnsafe(x, y)]; - }, - - setAt(x: number, y: number, val: any) { - return this.includes(x, y) - ? ((this.data[this.indexAtUnsafe(x, y)] = val), true) - : false; - }, - - setAtUnsafe(x: number, y: number, val: any) { - this.data[this.indexAtUnsafe(x, y)] = val; - return true; - }, + order() { + return Math.abs(this.stride[1]) > Math.abs(this.stride[0]) + ? [1, 0] + : [0, 1]; + }, + + includes(x: number, y: number) { + const size = this.size; + return x >= 0 && x < size[0] && y >= 0 && y < size[1]; + }, + + indexAt(x: number, y: number) { + return this.includes(x, y) ? this.indexAtUnsafe(x, y) : -1; + }, + + indexAtUnsafe(x: number, y: number) { + return ( + this.offset + (x | 0) * this.stride[0] + (y | 0) * this.stride[1] + ); + }, + + getAt(x: number, y: number) { + return this.includes(x, y) ? this.data[this.indexAtUnsafe(x, y)] : 0; + }, + + getAtUnsafe(x: number, y: number) { + return this.data[this.indexAtUnsafe(x, y)]; + }, + + setAt(x: number, y: number, val: any) { + return this.includes(x, y) + ? ((this.data[this.indexAtUnsafe(x, y)] = val), true) + : false; + }, + + setAtUnsafe(x: number, y: number, val: any) { + this.data[this.indexAtUnsafe(x, y)] = val; + return true; + }, }); /** * Default implementation for {@link IGrid3D} methods. */ export const IGrid3DMixin = mixin(>{ - order() { - return strideOrder(this.stride); - }, - - includes(x: number, y: number, z: number) { - const size = this.size; - return ( - x >= 0 && - x < size[0] && - y >= 0 && - y < size[1] && - z >= 0 && - z < size[2] - ); - }, - - indexAt(x: number, y: number, z: number) { - return this.includes(x, y, z) ? this.indexAtUnsafe(x, y, z) : -1; - }, - - indexAtUnsafe(x: number, y: number, z: number) { - const stride = this.stride; - return ( - this.offset + - (x | 0) * stride[0] + - (y | 0) * stride[1] + - (z | 0) * stride[2] - ); - }, - - getAt(x: number, y: number, z: number) { - return this.includes(x, y, z) - ? this.data[this.indexAtUnsafe(x, y, z)] - : 0; - }, - - getAtUnsafe(x: number, y: number, z: number) { - return this.data[this.indexAtUnsafe(x, y, z)]; - }, - - setAt(x: number, y: number, z: number, val: any) { - return this.includes(x, y, z) - ? ((this.data[this.indexAtUnsafe(x, y, z)] = val), true) - : false; - }, - - setAtUnsafe(x: number, y: number, z: number, val: any) { - this.data[this.indexAtUnsafe(x, y, z)] = val; - return true; - }, + order() { + return strideOrder(this.stride); + }, + + includes(x: number, y: number, z: number) { + const size = this.size; + return ( + x >= 0 && + x < size[0] && + y >= 0 && + y < size[1] && + z >= 0 && + z < size[2] + ); + }, + + indexAt(x: number, y: number, z: number) { + return this.includes(x, y, z) ? this.indexAtUnsafe(x, y, z) : -1; + }, + + indexAtUnsafe(x: number, y: number, z: number) { + const stride = this.stride; + return ( + this.offset + + (x | 0) * stride[0] + + (y | 0) * stride[1] + + (z | 0) * stride[2] + ); + }, + + getAt(x: number, y: number, z: number) { + return this.includes(x, y, z) + ? this.data[this.indexAtUnsafe(x, y, z)] + : 0; + }, + + getAtUnsafe(x: number, y: number, z: number) { + return this.data[this.indexAtUnsafe(x, y, z)]; + }, + + setAt(x: number, y: number, z: number, val: any) { + return this.includes(x, y, z) + ? ((this.data[this.indexAtUnsafe(x, y, z)] = val), true) + : false; + }, + + setAtUnsafe(x: number, y: number, z: number, val: any) { + this.data[this.indexAtUnsafe(x, y, z)] = val; + return true; + }, }); /** * Default implementation for {@link IGrid4D} methods. */ export const IGrid4DMixin = mixin(>{ - order() { - return strideOrder(this.stride); - }, - - includes(x: number, y: number, z: number, w: number) { - const size = this.size; - return ( - x >= 0 && - x < size[0] && - y >= 0 && - y < size[1] && - z >= 0 && - z < size[2] && - w >= 0 && - w < size[3] - ); - }, - - indexAt(x: number, y: number, z: number, w: number) { - return this.includes(x, y, z, w) ? this.indexAtUnsafe(x, y, z, w) : -1; - }, - - indexAtUnsafe(x: number, y: number, z: number, w: number) { - const stride = this.stride; - return ( - this.offset + - (x | 0) * stride[0] + - (y | 0) * stride[1] + - (z | 0) * stride[2] + - (w | 0) * stride[3] - ); - }, - - getAt(x: number, y: number, z: number, w: number) { - return this.includes(x, y, z, w) - ? this.data[this.indexAtUnsafe(x, y, z, w)] - : 0; - }, - - getAtUnsafe(x: number, y: number, z: number, w: number) { - return this.data[this.indexAtUnsafe(x, y, z, w)]; - }, - - setAt(x: number, y: number, z: number, w: number, val: any) { - return this.includes(x, y, z, w) - ? ((this.data[this.indexAtUnsafe(x, y, z, w)] = val), true) - : false; - }, - - setAtUnsafe(x: number, y: number, z: number, w: number, val: any) { - this.data[this.indexAtUnsafe(x, y, z, w)] = val; - return true; - }, + order() { + return strideOrder(this.stride); + }, + + includes(x: number, y: number, z: number, w: number) { + const size = this.size; + return ( + x >= 0 && + x < size[0] && + y >= 0 && + y < size[1] && + z >= 0 && + z < size[2] && + w >= 0 && + w < size[3] + ); + }, + + indexAt(x: number, y: number, z: number, w: number) { + return this.includes(x, y, z, w) ? this.indexAtUnsafe(x, y, z, w) : -1; + }, + + indexAtUnsafe(x: number, y: number, z: number, w: number) { + const stride = this.stride; + return ( + this.offset + + (x | 0) * stride[0] + + (y | 0) * stride[1] + + (z | 0) * stride[2] + + (w | 0) * stride[3] + ); + }, + + getAt(x: number, y: number, z: number, w: number) { + return this.includes(x, y, z, w) + ? this.data[this.indexAtUnsafe(x, y, z, w)] + : 0; + }, + + getAtUnsafe(x: number, y: number, z: number, w: number) { + return this.data[this.indexAtUnsafe(x, y, z, w)]; + }, + + setAt(x: number, y: number, z: number, w: number, val: any) { + return this.includes(x, y, z, w) + ? ((this.data[this.indexAtUnsafe(x, y, z, w)] = val), true) + : false; + }, + + setAtUnsafe(x: number, y: number, z: number, w: number, val: any) { + this.data[this.indexAtUnsafe(x, y, z, w)] = val; + return true; + }, }); const strideOrder = (strides: NumericArray) => - [...strides] - .map((x, i) => [x, i]) - .sort((a, b) => Math.abs(b[0]) - Math.abs(a[0])) - .map((x) => x[1]); + [...strides] + .map((x, i) => [x, i]) + .sort((a, b) => Math.abs(b[0]) - Math.abs(a[0])) + .map((x) => x[1]); diff --git a/packages/api/src/mixins/inotify.ts b/packages/api/src/mixins/inotify.ts index 02ef107435..a1e7f75da9 100644 --- a/packages/api/src/mixins/inotify.ts +++ b/packages/api/src/mixins/inotify.ts @@ -4,19 +4,19 @@ import { mixin } from "../mixin.js"; import type { IObjectOf } from "../object.js"; interface _INotify extends INotify { - _listeners: IObjectOf<[Listener, any][]>; - __listener(listeners: any[][], f: Listener, scope: any): number; + _listeners: IObjectOf<[Listener, any][]>; + __listener(listeners: any[][], f: Listener, scope: any): number; } export const inotify_dispatch = (listeners: any[][], e: Event) => { - if (!listeners) return; - for (let i = 0, n = listeners.length, l; i < n; i++) { - l = listeners[i]; - l[0].call(l[1], e); - if (e.canceled) { - return; - } - } + if (!listeners) return; + for (let i = 0, n = listeners.length, l; i < n; i++) { + l = listeners[i]; + l[0].call(l[1], e); + if (e.canceled) { + return; + } + } }; /** @@ -25,47 +25,47 @@ export const inotify_dispatch = (listeners: any[][], e: Event) => { * registered listeners. */ export const INotifyMixin = mixin({ - addListener(this: _INotify, id: string, fn: Listener, scope?: any) { - let l = (this._listeners = this._listeners || {})[id]; - !l && (l = this._listeners[id] = []); - if (this.__listener(l, fn, scope) === -1) { - l.push([fn, scope]); - return true; - } - return false; - }, + addListener(this: _INotify, id: string, fn: Listener, scope?: any) { + let l = (this._listeners = this._listeners || {})[id]; + !l && (l = this._listeners[id] = []); + if (this.__listener(l, fn, scope) === -1) { + l.push([fn, scope]); + return true; + } + return false; + }, - removeListener(this: _INotify, id: string, fn: Listener, scope?: any) { - let listeners: IObjectOf<[Listener, any][]>; - if (!(listeners = this._listeners)) return false; - const l = listeners[id]; - if (l) { - const idx = this.__listener(l, fn, scope); - if (idx !== -1) { - l.splice(idx, 1); - !l.length && delete listeners[id]; - return true; - } - } - return false; - }, + removeListener(this: _INotify, id: string, fn: Listener, scope?: any) { + let listeners: IObjectOf<[Listener, any][]>; + if (!(listeners = this._listeners)) return false; + const l = listeners[id]; + if (l) { + const idx = this.__listener(l, fn, scope); + if (idx !== -1) { + l.splice(idx, 1); + !l.length && delete listeners[id]; + return true; + } + } + return false; + }, - notify(this: _INotify, e: Event) { - let listeners: IObjectOf<[Listener, any][]>; - if (!(listeners = this._listeners)) return false; - e.target === undefined && (e.target = this); - inotify_dispatch(listeners[e.id], e); - inotify_dispatch(listeners[EVENT_ALL], e); - }, + notify(this: _INotify, e: Event) { + let listeners: IObjectOf<[Listener, any][]>; + if (!(listeners = this._listeners)) return false; + e.target === undefined && (e.target = this); + inotify_dispatch(listeners[e.id], e); + inotify_dispatch(listeners[EVENT_ALL], e); + }, - __listener(listeners: [Listener, any][], f: Listener, scope: any) { - let i = listeners.length; - while (i-- > 0) { - const l = listeners[i]; - if (l[0] === f && l[1] === scope) { - break; - } - } - return i; - }, + __listener(listeners: [Listener, any][], f: Listener, scope: any) { + let i = listeners.length; + while (i-- > 0) { + const l = listeners[i]; + if (l[0] === f && l[1] === scope) { + break; + } + } + return i; + }, }); diff --git a/packages/api/src/mixins/iterable.ts b/packages/api/src/mixins/iterable.ts index 70adeec3da..da4a1c60f6 100644 --- a/packages/api/src/mixins/iterable.ts +++ b/packages/api/src/mixins/iterable.ts @@ -1,8 +1,8 @@ import { mixin } from "../mixin.js"; export const iterable = (prop: PropertyKey) => - mixin({ - *[Symbol.iterator]() { - yield* this[prop]; - }, - }); + mixin({ + *[Symbol.iterator]() { + yield* this[prop]; + }, + }); diff --git a/packages/api/src/mixins/iwatch.ts b/packages/api/src/mixins/iwatch.ts index 3c311b361f..31209a18a1 100644 --- a/packages/api/src/mixins/iwatch.ts +++ b/packages/api/src/mixins/iwatch.ts @@ -3,33 +3,33 @@ import type { IObjectOf } from "../object.js"; import type { IWatch, Watch } from "../watch.js"; interface _IWatch extends IWatch { - _watches: IObjectOf>; + _watches: IObjectOf>; } export const IWatchMixin = mixin(>{ - addWatch(this: _IWatch, id: string, fn: Watch) { - this._watches = this._watches || {}; - if (this._watches[id]) { - return false; - } - this._watches[id] = fn; - return true; - }, + addWatch(this: _IWatch, id: string, fn: Watch) { + this._watches = this._watches || {}; + if (this._watches[id]) { + return false; + } + this._watches[id] = fn; + return true; + }, - removeWatch(this: _IWatch, id: string) { - if (!this._watches) return; - if (this._watches[id]) { - delete this._watches[id]; - return true; - } - return false; - }, + removeWatch(this: _IWatch, id: string) { + if (!this._watches) return; + if (this._watches[id]) { + delete this._watches[id]; + return true; + } + return false; + }, - notifyWatches(this: _IWatch, oldState: any, newState: any) { - if (!this._watches) return; - const w = this._watches; - for (let id in w) { - w[id](id, oldState, newState); - } - }, + notifyWatches(this: _IWatch, oldState: any, newState: any) { + if (!this._watches) return; + const w = this._watches; + for (let id in w) { + w[id](id, oldState, newState); + } + }, }); diff --git a/packages/api/src/object.ts b/packages/api/src/object.ts index 46ab13d481..3640306077 100644 --- a/packages/api/src/object.ts +++ b/packages/api/src/object.ts @@ -2,5 +2,5 @@ * Generic plain object with all key values of given type. */ export interface IObjectOf { - [id: string]: T; + [id: string]: T; } diff --git a/packages/api/src/path.ts b/packages/api/src/path.ts index e3602992ce..2d972380a2 100644 --- a/packages/api/src/path.ts +++ b/packages/api/src/path.ts @@ -1,14 +1,14 @@ import type { NumOrString } from "./prim.js"; import type { - Keys, - Keys1, - Keys2, - Keys3, - Keys4, - Keys5, - Keys6, - Keys7, - ValN, + Keys, + Keys1, + Keys2, + Keys3, + Keys4, + Keys5, + Keys6, + Keys7, + ValN, } from "./keyval.js"; import type { Head, Tail, IsEmpty } from "./tuple.js"; @@ -31,106 +31,106 @@ export type Path1 = A extends Keys ? readonly [A] : never; * Type checked lookup path (depth 2) */ export type Path2 = A extends Keys - ? B extends Keys1 - ? readonly [A, B] - : never - : never; + ? B extends Keys1 + ? readonly [A, B] + : never + : never; /** * Type checked lookup path (depth 3) */ export type Path3 = A extends Keys - ? B extends Keys1 - ? C extends Keys2 - ? readonly [A, B, C] - : never - : never - : never; + ? B extends Keys1 + ? C extends Keys2 + ? readonly [A, B, C] + : never + : never + : never; /** * Type checked lookup path (depth 4) */ export type Path4 = A extends Keys - ? B extends Keys1 - ? C extends Keys2 - ? D extends Keys3 - ? readonly [A, B, C, D] - : never - : never - : never - : never; + ? B extends Keys1 + ? C extends Keys2 + ? D extends Keys3 + ? readonly [A, B, C, D] + : never + : never + : never + : never; /** * Type checked lookup path (depth 5) */ export type Path5 = A extends Keys - ? B extends Keys1 - ? C extends Keys2 - ? D extends Keys3 - ? E extends Keys4 - ? readonly [A, B, C, D, E] - : never - : never - : never - : never - : never; + ? B extends Keys1 + ? C extends Keys2 + ? D extends Keys3 + ? E extends Keys4 + ? readonly [A, B, C, D, E] + : never + : never + : never + : never + : never; /** * Type checked lookup path (depth 6) */ export type Path6 = A extends Keys - ? B extends Keys1 - ? C extends Keys2 - ? D extends Keys3 - ? E extends Keys4 - ? F extends Keys5 - ? readonly [A, B, C, D, E, F] - : never - : never - : never - : never - : never - : never; + ? B extends Keys1 + ? C extends Keys2 + ? D extends Keys3 + ? E extends Keys4 + ? F extends Keys5 + ? readonly [A, B, C, D, E, F] + : never + : never + : never + : never + : never + : never; /** * Type checked lookup path (depth 7) */ export type Path7 = A extends Keys - ? B extends Keys1 - ? C extends Keys2 - ? D extends Keys3 - ? E extends Keys4 - ? F extends Keys5 - ? G extends Keys6 - ? readonly [A, B, C, D, E, F, G] - : never - : never - : never - : never - : never - : never - : never; + ? B extends Keys1 + ? C extends Keys2 + ? D extends Keys3 + ? E extends Keys4 + ? F extends Keys5 + ? G extends Keys6 + ? readonly [A, B, C, D, E, F, G] + : never + : never + : never + : never + : never + : never + : never; /** * Type checked lookup path (depth 8) */ export type Path8 = A extends Keys - ? B extends Keys1 - ? C extends Keys2 - ? D extends Keys3 - ? E extends Keys4 - ? F extends Keys5 - ? G extends Keys6 - ? H extends Keys7 - ? readonly [A, B, C, D, E, F, G, H] - : never - : never - : never - : never - : never - : never - : never - : never; + ? B extends Keys1 + ? C extends Keys2 + ? D extends Keys3 + ? E extends Keys4 + ? F extends Keys5 + ? G extends Keys6 + ? H extends Keys7 + ? readonly [A, B, C, D, E, F, G, H] + : never + : never + : never + : never + : never + : never + : never + : never; /** * Semi-typechecked lookup path (depth > 8). Only the first 8 levels are @@ -169,13 +169,13 @@ export type IsOpt = T extends undefined ? true : never; * Internal recursive helper type for {@link IsOptPath}. */ type IsOptR = K extends Keys - ? [true] extends [IsOpt] - ? true - : { - 0: IsOptR[K], Head

, Tail

>; - 1: never; - }[IsEmpty

] - : never; + ? [true] extends [IsOpt] + ? true + : { + 0: IsOptR[K], Head

, Tail

>; + 1: never; + }[IsEmpty

] + : never; /** * Returns true if given path contains any intermediate properties @@ -185,8 +185,8 @@ type IsOptR = K extends Keys * https://stackoverflow.com/q/60869412/294515 */ export type IsOptPath = P extends [] - ? never - : IsOptR, Tail

>; + ? never + : IsOptR, Tail

>; /** * Similar to {@link PathVal}, but also takes into account if given path @@ -197,8 +197,8 @@ export type IsOptPath = P extends [] * Context & reference: https://stackoverflow.com/q/60869412/294515 */ export type OptPathVal = OptVal< - IsOptPath, - ValN + IsOptPath, + ValN >; /** diff --git a/packages/api/src/range.ts b/packages/api/src/range.ts index c306b18555..93c56c63c9 100644 --- a/packages/api/src/range.ts +++ b/packages/api/src/range.ts @@ -59,68 +59,68 @@ export type Range32_63 = Range32_47 | Range48_63; * ``` */ export interface RangeValueMap { - 1: 0; - 2: Range0_1; - 3: Range0_1 | 2; - 4: Range0_3; - 5: Range0_3 | 4; - 6: Range0_3 | 4 | 5; - 7: Range0_3 | 4 | 5 | 6; - 8: Range0_7; - 9: Range0_7 | 8; - 10: Range0_7 | 8 | 9; - 11: Range0_7 | 8 | 9 | 10; - 12: Range0_7 | 8 | 9 | 10 | 11; - 13: Range0_7 | 8 | 9 | 10 | 11 | 12; - 14: Range0_7 | 8 | 9 | 10 | 11 | 12 | 13; - 15: Range0_7 | 8 | 9 | 10 | 11 | 12 | 13 | 14; - 16: Range0_15; - 17: Range0_15 | 16; - 18: Range0_15 | 16 | 17; - 19: Range0_15 | 16 | 17 | 18; - 20: Range0_15 | 16 | 17 | 18 | 19; - 21: Range0_15 | 16 | 17 | 18 | 19 | 20; - 22: Range0_15 | 16 | 17 | 18 | 19 | 20 | 21; - 23: Range0_15 | 16 | 17 | 18 | 19 | 20 | 21 | 22; - 24: Range0_15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23; - 25: Exclude; - 26: Exclude; - 27: Exclude; - 28: Exclude; - 29: Exclude; - 30: Exclude; - 31: Exclude; - 32: Range0_31; - 33: Range0_31 | 32; - 34: Range0_31 | 32 | 33; - 35: Range0_31 | 32 | 33 | 34; - 36: Range0_31 | 32 | 33 | 34 | 35; - 37: Range0_31 | 32 | 33 | 34 | 35 | 36; - 38: Range0_31 | 32 | 33 | 34 | 35 | 36 | 37; - 39: Range0_31 | 32 | 33 | 34 | 35 | 36 | 37 | 38; - 40: Range0_31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39; - 41: Exclude; - 42: Exclude; - 43: Exclude; - 44: Exclude; - 45: Exclude; - 46: Exclude; - 47: Exclude; - 48: Range0_47; - 49: Range0_47 | 48; - 50: Range0_47 | 48 | 49; - 51: Range0_47 | 48 | 49 | 50; - 52: Range0_47 | 48 | 49 | 50 | 51; - 53: Range0_47 | 48 | 49 | 50 | 51 | 52; - 54: Range0_47 | 48 | 49 | 50 | 51 | 52 | 53; - 55: Range0_47 | 48 | 49 | 50 | 51 | 52 | 53 | 54; - 56: Range0_47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55; - 57: Exclude; - 58: Exclude; - 59: Exclude; - 60: Exclude; - 61: Exclude; - 62: Exclude; - 63: Exclude; - 64: Range0_63; + 1: 0; + 2: Range0_1; + 3: Range0_1 | 2; + 4: Range0_3; + 5: Range0_3 | 4; + 6: Range0_3 | 4 | 5; + 7: Range0_3 | 4 | 5 | 6; + 8: Range0_7; + 9: Range0_7 | 8; + 10: Range0_7 | 8 | 9; + 11: Range0_7 | 8 | 9 | 10; + 12: Range0_7 | 8 | 9 | 10 | 11; + 13: Range0_7 | 8 | 9 | 10 | 11 | 12; + 14: Range0_7 | 8 | 9 | 10 | 11 | 12 | 13; + 15: Range0_7 | 8 | 9 | 10 | 11 | 12 | 13 | 14; + 16: Range0_15; + 17: Range0_15 | 16; + 18: Range0_15 | 16 | 17; + 19: Range0_15 | 16 | 17 | 18; + 20: Range0_15 | 16 | 17 | 18 | 19; + 21: Range0_15 | 16 | 17 | 18 | 19 | 20; + 22: Range0_15 | 16 | 17 | 18 | 19 | 20 | 21; + 23: Range0_15 | 16 | 17 | 18 | 19 | 20 | 21 | 22; + 24: Range0_15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23; + 25: Exclude; + 26: Exclude; + 27: Exclude; + 28: Exclude; + 29: Exclude; + 30: Exclude; + 31: Exclude; + 32: Range0_31; + 33: Range0_31 | 32; + 34: Range0_31 | 32 | 33; + 35: Range0_31 | 32 | 33 | 34; + 36: Range0_31 | 32 | 33 | 34 | 35; + 37: Range0_31 | 32 | 33 | 34 | 35 | 36; + 38: Range0_31 | 32 | 33 | 34 | 35 | 36 | 37; + 39: Range0_31 | 32 | 33 | 34 | 35 | 36 | 37 | 38; + 40: Range0_31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39; + 41: Exclude; + 42: Exclude; + 43: Exclude; + 44: Exclude; + 45: Exclude; + 46: Exclude; + 47: Exclude; + 48: Range0_47; + 49: Range0_47 | 48; + 50: Range0_47 | 48 | 49; + 51: Range0_47 | 48 | 49 | 50; + 52: Range0_47 | 48 | 49 | 50 | 51; + 53: Range0_47 | 48 | 49 | 50 | 51 | 52; + 54: Range0_47 | 48 | 49 | 50 | 51 | 52 | 53; + 55: Range0_47 | 48 | 49 | 50 | 51 | 52 | 53 | 54; + 56: Range0_47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55; + 57: Exclude; + 58: Exclude; + 59: Exclude; + 60: Exclude; + 61: Exclude; + 62: Exclude; + 63: Exclude; + 64: Range0_63; } diff --git a/packages/api/src/release.ts b/packages/api/src/release.ts index d763ea3277..b788a584d1 100644 --- a/packages/api/src/release.ts +++ b/packages/api/src/release.ts @@ -2,5 +2,5 @@ * Interface for types supported the release of internal resources. */ export interface IRelease { - release(opt?: any): boolean; + release(opt?: any): boolean; } diff --git a/packages/api/src/reset.ts b/packages/api/src/reset.ts index e980d22e99..fa5257f327 100644 --- a/packages/api/src/reset.ts +++ b/packages/api/src/reset.ts @@ -2,5 +2,5 @@ * Interface for types supporting some form of internal state reset. */ export interface IReset { - reset(): this; + reset(): this; } diff --git a/packages/api/src/select.ts b/packages/api/src/select.ts index f2b2e8ea6e..f6c8cf6bcb 100644 --- a/packages/api/src/select.ts +++ b/packages/api/src/select.ts @@ -1,15 +1,15 @@ export type Select2 = T extends Q ? A : B; export type Select3 = T extends Q1 - ? A - : T extends Q2 - ? B - : C; + ? A + : T extends Q2 + ? B + : C; export type Select4 = T extends Q1 - ? A - : T extends Q2 - ? B - : T extends Q3 - ? C - : D; + ? A + : T extends Q2 + ? B + : T extends Q3 + ? C + : D; diff --git a/packages/api/src/seq.ts b/packages/api/src/seq.ts index 7ebec3c0c4..1d57484f0a 100644 --- a/packages/api/src/seq.ts +++ b/packages/api/src/seq.ts @@ -8,36 +8,36 @@ * value (if any) from the sequence. */ export interface ISeq { - /** - * Returns the sequence's first value or `undefined` if there're no - * further values. - * - * @remarks - * If the sequence is guaranteed to not include `undefined` values, - * a simple check for `seq.first() === undefined` is sufficient to - * determine the end. If the sequence DOES contain `undefined` - * values, the check should use `seq.next()`. - */ - first(): T | undefined; - /** - * Returns a new sequence of the remaining elements or `undefined` - * if there're no further values. - * - * @remarks - * In general, implementations of this interface MUST always return - * a new sequence instance and not mutate some internal cursor. I.e. - * `seq.next() !== seq` - */ - next(): ISeq | undefined; + /** + * Returns the sequence's first value or `undefined` if there're no + * further values. + * + * @remarks + * If the sequence is guaranteed to not include `undefined` values, + * a simple check for `seq.first() === undefined` is sufficient to + * determine the end. If the sequence DOES contain `undefined` + * values, the check should use `seq.next()`. + */ + first(): T | undefined; + /** + * Returns a new sequence of the remaining elements or `undefined` + * if there're no further values. + * + * @remarks + * In general, implementations of this interface MUST always return + * a new sequence instance and not mutate some internal cursor. I.e. + * `seq.next() !== seq` + */ + next(): ISeq | undefined; } /** * Interface for data types providing an {@link ISeq} abstraction. */ export interface ISeqable { - /** - * Returns an {@link ISeq} of the type's data or `undefined` if - * there're no values available. See {@link ISeq.next} - */ - seq(): ISeq | undefined; + /** + * Returns an {@link ISeq} of the type's data or `undefined` if + * there're no values available. See {@link ISeq.next} + */ + seq(): ISeq | undefined; } diff --git a/packages/api/src/set.ts b/packages/api/src/set.ts index 37b1a33863..f49701184f 100644 --- a/packages/api/src/set.ts +++ b/packages/api/src/set.ts @@ -7,16 +7,16 @@ import type { IInto } from "./into.js"; * @param T - return type */ export interface ISet extends IInto { - /** - * Conjoins/adds value `x` to set. - * - * @param x - value to add - */ - conj(x: V): T; - /** - * Disjoins/removes value `x` from set. - * - * @param x - value to remove - */ - disj(x: V): T; + /** + * Conjoins/adds value `x` to set. + * + * @param x - value to add + */ + conj(x: V): T; + /** + * Disjoins/removes value `x` from set. + * + * @param x - value to remove + */ + disj(x: V): T; } diff --git a/packages/api/src/stack.ts b/packages/api/src/stack.ts index bc0ad55737..1cb069eb2d 100644 --- a/packages/api/src/stack.ts +++ b/packages/api/src/stack.ts @@ -7,13 +7,13 @@ * @param S - return type for push() */ export interface IStack { - /** - * Returns top-of-stack item. - */ - peek(): V | undefined; - /** - * Removes top-of-stack item and returns type P. - */ - pop(): P | undefined; - push(x: V): S; + /** + * Returns top-of-stack item. + */ + peek(): V | undefined; + /** + * Removes top-of-stack item and returns type P. + */ + pop(): P | undefined; + push(x: V): S; } diff --git a/packages/api/src/tuple.ts b/packages/api/src/tuple.ts index 67ea113af9..6208b8c4ab 100644 --- a/packages/api/src/tuple.ts +++ b/packages/api/src/tuple.ts @@ -13,17 +13,17 @@ export type ArrayValue = T[0]; * elements will be considered). */ export type DeepArrayValue = T extends unknown[] - ? DeepArrayValue - : T extends TypedArray - ? number - : T; + ? DeepArrayValue + : T extends TypedArray + ? number + : T; /** * Defines a fixed sized, iterable tuple with elements of type `T` and * length `N`. */ export type Tuple = [T, ...T[]] & { - length: N; + length: N; } & Iterable; /** @@ -40,15 +40,15 @@ export type IsEmpty = T extends [] ? 1 : 0; * Extracts the first element of a tuple. */ export type Head = T extends [infer A, ...unknown[]] - ? A - : never; + ? A + : never; /** * Extracts everything except the first element from a tuple. */ export type Tail = T extends [unknown, ...infer A] - ? A - : never; + ? A + : never; /** * Adds an element at the start of an tuple. @@ -60,13 +60,13 @@ export type Prepend = [T, ...U]; * the accumulated value. */ type ReverseReducer = { - // case when we got 0 elements - 0: C; - // case when we got 1 element - 1: Prepend, C>; - // case when we got more than 1 element - 2: ReverseReducer, Prepend, C>>; - // switch between cases + // case when we got 0 elements + 0: C; + // case when we got 1 element + 1: Prepend, C>; + // case when we got more than 1 element + 2: ReverseReducer, Prepend, C>>; + // switch between cases }[Tail extends [] ? (Head extends never ? 0 : 1) : 2]; /** @@ -78,14 +78,14 @@ export type Reverse = ReverseReducer; * Extracts the last element from a tuple. */ export type Last = { - 0: Last>; - 1: Head; + 0: Last>; + 1: Head; }[IsEmpty>]; /** * Extracts everything except the last element from a tuple. */ export type ButLast = { - 0: ButLast, Prepend, C>>; - 1: Reverse; + 0: ButLast, Prepend, C>>; + 1: Reverse; }[IsEmpty>]; diff --git a/packages/api/src/typedarray.ts b/packages/api/src/typedarray.ts index ee75549421..23ba7219ea 100644 --- a/packages/api/src/typedarray.ts +++ b/packages/api/src/typedarray.ts @@ -3,45 +3,45 @@ export type ArrayLikeIterable = ArrayLike & Iterable; export type NumericArray = number[] | TypedArray; export type TypedArray = - | Float32Array - | Float64Array - | Int8Array - | Int16Array - | Int32Array - | Uint8Array - | Uint8ClampedArray - | Uint16Array - | Uint32Array; + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array; export type FloatArray = Float32Array | Float64Array; export type IntArray = Int8Array | Int16Array | Int32Array; export type UIntArray = - | Uint8Array - | Uint8ClampedArray - | Uint16Array - | Uint32Array; + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array; export type FloatArrayConstructor = - | Float32ArrayConstructor - | Float64ArrayConstructor; + | Float32ArrayConstructor + | Float64ArrayConstructor; export type IntArrayConstructor = - | Int8ArrayConstructor - | Int16ArrayConstructor - | Int32ArrayConstructor; + | Int8ArrayConstructor + | Int16ArrayConstructor + | Int32ArrayConstructor; export type UIntArrayConstructor = - | Uint8ArrayConstructor - | Uint8ClampedArrayConstructor - | Uint16ArrayConstructor - | Uint32ArrayConstructor; + | Uint8ArrayConstructor + | Uint8ClampedArrayConstructor + | Uint16ArrayConstructor + | Uint32ArrayConstructor; export type TypedArrayConstructor = - | FloatArrayConstructor - | IntArrayConstructor - | UIntArrayConstructor; + | FloatArrayConstructor + | IntArrayConstructor + | UIntArrayConstructor; /** * Type IDs for typed array backed buffers and generally describing binary data @@ -50,15 +50,15 @@ export type TypedArrayConstructor = * {@link GLType} {@link GL2TYPE} {@link TYPE2GL} */ export type Type = - | "u8" - | "u8c" - | "i8" - | "u16" - | "i16" - | "u32" - | "i32" - | "f32" - | "f64"; + | "u8" + | "u8c" + | "i8" + | "u16" + | "i16" + | "u32" + | "i32" + | "f32" + | "f64"; export type UintType = "u8" | "u8c" | "u16" | "u32"; @@ -74,26 +74,26 @@ export type FloatType = "f32" | "f64"; * {@link TYPE2GL} */ export enum GLType { - I8 = 0x1400, - U8 = 0x1401, - I16 = 0x1402, - U16 = 0x1403, - I32 = 0x1404, - U32 = 0x1405, - F32 = 0x1406, + I8 = 0x1400, + U8 = 0x1401, + I16 = 0x1402, + U16 = 0x1403, + I32 = 0x1404, + U32 = 0x1405, + F32 = 0x1406, } /** * Conversion from {@link GLType} to {@link Type} enums. */ export const GL2TYPE: Record = { - [GLType.I8]: "i8", - [GLType.U8]: "u8", - [GLType.I16]: "i16", - [GLType.U16]: "u16", - [GLType.I32]: "i32", - [GLType.U32]: "u32", - [GLType.F32]: "f32", + [GLType.I8]: "i8", + [GLType.U8]: "u8", + [GLType.I16]: "i16", + [GLType.U16]: "u16", + [GLType.I32]: "i32", + [GLType.U32]: "u32", + [GLType.F32]: "f32", }; /** @@ -105,73 +105,73 @@ export const GL2TYPE: Record = { * - `U8C` maps to "u8" */ export const TYPE2GL: Record = { - i8: GLType.I8, - u8: GLType.U8, - u8c: GLType.U8, - i16: GLType.I16, - u16: GLType.U16, - i32: GLType.I32, - u32: GLType.U32, - f32: GLType.F32, - f64: undefined, + i8: GLType.I8, + u8: GLType.U8, + u8c: GLType.U8, + i16: GLType.I16, + u16: GLType.U16, + i32: GLType.I32, + u32: GLType.U32, + f32: GLType.F32, + f64: undefined, }; /** * Size information (in bytes) for {@link Type}. Also see {@link sizeOf}. */ export const SIZEOF = { - u8: 1, - u8c: 1, - i8: 1, - u16: 2, - i16: 2, - u32: 4, - i32: 4, - f32: 4, - f64: 8, + u8: 1, + u8c: 1, + i8: 1, + u16: 2, + i16: 2, + u32: 4, + i32: 4, + f32: 4, + f64: 8, }; export const FLOAT_ARRAY_CTORS: Record = { - f32: Float32Array, - f64: Float64Array, + f32: Float32Array, + f64: Float64Array, }; export const INT_ARRAY_CTORS: Record = { - i8: Int8Array, - i16: Int16Array, - i32: Int32Array, + i8: Int8Array, + i16: Int16Array, + i32: Int32Array, }; export const UINT_ARRAY_CTORS: Record = { - u8: Uint8Array, - u8c: Uint8ClampedArray, - u16: Uint16Array, - u32: Uint32Array, + u8: Uint8Array, + u8c: Uint8ClampedArray, + u16: Uint16Array, + u32: Uint32Array, }; export const TYPEDARRAY_CTORS: Record = { - ...FLOAT_ARRAY_CTORS, - ...INT_ARRAY_CTORS, - ...UINT_ARRAY_CTORS, + ...FLOAT_ARRAY_CTORS, + ...INT_ARRAY_CTORS, + ...UINT_ARRAY_CTORS, }; export interface TypedArrayTypeMap extends Record { - u8: Uint8Array; - u8c: Uint8ClampedArray; - i8: Int8Array; - u16: Uint16Array; - i16: Int16Array; - u32: Uint32Array; - i32: Int32Array; - f32: Float32Array; - f64: Float64Array; - [GLType.U8]: Uint8Array; - [GLType.I8]: Int8Array; - [GLType.U16]: Uint16Array; - [GLType.I16]: Int16Array; - [GLType.U32]: Uint32Array; - [GLType.I32]: Int32Array; - [GLType.F32]: Float32Array; + u8: Uint8Array; + u8c: Uint8ClampedArray; + i8: Int8Array; + u16: Uint16Array; + i16: Int16Array; + u32: Uint32Array; + i32: Int32Array; + f32: Float32Array; + f64: Float64Array; + [GLType.U8]: Uint8Array; + [GLType.I8]: Int8Array; + [GLType.U16]: Uint16Array; + [GLType.I16]: Int16Array; + [GLType.U32]: Uint32Array; + [GLType.I32]: Int32Array; + [GLType.F32]: Float32Array; } /** @@ -187,8 +187,8 @@ export interface TypedArrayTypeMap extends Record { * @param type - */ export const asNativeType = (type: GLType | Type): Type => { - const t = (GL2TYPE)[type]; - return t !== undefined ? t : type; + const t = (GL2TYPE)[type]; + return t !== undefined ? t : type; }; /** @@ -203,8 +203,8 @@ export const asNativeType = (type: GLType | Type): Type => { * @param type - */ export const asGLType = (type: GLType | Type): GLType => { - const t = (TYPE2GL)[type]; - return t !== undefined ? t : type; + const t = (TYPE2GL)[type]; + return t !== undefined ? t : type; }; /** @@ -215,7 +215,7 @@ export const asInt = (...args: number[]) => args.map((x) => x | 0); /** * Returns byte size for given {@link Type} ID or {@link GLType} enum. * - * @param type - + * @param type - */ export const sizeOf = (type: GLType | Type) => SIZEOF[asNativeType(type)]; @@ -232,21 +232,21 @@ export function typedArray(type: T, src: ArrayLike(type: T, buf: ArrayBufferLike, byteOffset: number, length?: number): TypedArrayTypeMap[T]; export function typedArray(type: T, ...xs: any[]) { - return new (TYPEDARRAY_CTORS[asNativeType(type)])(...xs); + return new (TYPEDARRAY_CTORS[asNativeType(type)])(...xs); } /** * Takes an {@link NumericArray} and returns its corresponding {@link Type} ID. * Standard JS arrays will default to {@link "f64"}. * - * @param x - + * @param x - */ export const typedArrayType = (x: NumericArray) => { - if (Array.isArray(x)) return "f64"; - for (let id in TYPEDARRAY_CTORS) { - if (x instanceof (TYPEDARRAY_CTORS)[id]) return id; - } - return "f64"; + if (Array.isArray(x)) return "f64"; + for (let id in TYPEDARRAY_CTORS) { + if (x instanceof (TYPEDARRAY_CTORS)[id]) return id; + } + return "f64"; }; /** @@ -256,7 +256,7 @@ export const typedArrayType = (x: NumericArray) => { * @param x - value to classify */ export const uintTypeForSize = (x: number): UintType => - x <= 0x100 ? "u8" : x <= 0x10000 ? "u16" : "u32"; + x <= 0x100 ? "u8" : x <= 0x10000 ? "u16" : "u32"; /** * Returns the smallest possible *signed* int type enum for given `x`. @@ -265,20 +265,20 @@ export const uintTypeForSize = (x: number): UintType => * @param x - value to classify */ export const intTypeForSize = (x: number): IntType => - x >= -0x80 && x < 0x80 ? "i8" : x >= -0x8000 && x < 0x8000 ? "i16" : "i32"; + x >= -0x80 && x < 0x80 ? "i8" : x >= -0x8000 && x < 0x8000 ? "i16" : "i32"; /** * Returns suitable {@link UintType} for given bit size (`[0,32]` range) * - * @param x - + * @param x - */ export const uintTypeForBits = (x: number): UintType => - x > 16 ? "u32" : x > 8 ? "u16" : "u8"; + x > 16 ? "u32" : x > 8 ? "u16" : "u8"; /** * Returns suitable {@link IntType} for given bit size (`[0,32]` range) * - * @param x - + * @param x - */ export const intTypeForBits = (x: number): IntType => - x > 16 ? "i32" : x > 8 ? "i16" : "i8"; + x > 16 ? "i32" : x > 8 ? "i16" : "i8"; diff --git a/packages/api/src/watch.ts b/packages/api/src/watch.ts index 047247c4c7..18f7c49882 100644 --- a/packages/api/src/watch.ts +++ b/packages/api/src/watch.ts @@ -8,7 +8,7 @@ export type Watch = (id: string, oldState: T, newState: T) => void; * Also see `@IWatch` decorator mixin. */ export interface IWatch { - addWatch(id: string, fn: Watch): boolean; - removeWatch(id: string): boolean; - notifyWatches(oldState: T, newState: T): void; + addWatch(id: string, fn: Watch): boolean; + removeWatch(id: string): boolean; + notifyWatches(oldState: T, newState: T): void; } diff --git a/packages/api/test/mixins.ts b/packages/api/test/mixins.ts index e821aaf92e..b53055f87f 100644 --- a/packages/api/test/mixins.ts +++ b/packages/api/test/mixins.ts @@ -1,39 +1,45 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { Event, EVENT_ALL, INotify, INotifyMixin, Listener } from "../src/index.js"; +import { + Event, + EVENT_ALL, + INotify, + INotifyMixin, + Listener, +} from "../src/index.js"; group("mixins", { - INotify: () => { - @INotifyMixin - class Foo implements INotify { - addListener(_: string, __: Listener, ___?: any): boolean { - throw new Error(); - } - removeListener(_: string, __: Listener, ___?: any): boolean { - throw new Error(); - } - notify(_: Event) { - throw new Error(); - } - } + INotify: () => { + @INotifyMixin + class Foo implements INotify { + addListener(_: string, __: Listener, ___?: any): boolean { + throw new Error(); + } + removeListener(_: string, __: Listener, ___?: any): boolean { + throw new Error(); + } + notify(_: Event) { + throw new Error(); + } + } - const res: any = {}; - const foo = new Foo(); - const l = (e: Event) => (res[e.id] = e.value); - const lall = (e: Event) => (res[EVENT_ALL] = e.value); - assert.ok(foo.addListener("x", l)); - assert.ok(foo.addListener(EVENT_ALL, lall)); - foo.notify({ id: "x", value: 1 }); - assert.deepStrictEqual(res, { x: 1, [EVENT_ALL]: 1 }); - assert.ok(foo.removeListener("x", l)); - assert.ok((foo)._listeners.x === undefined); - assert.ok(!foo.removeListener("x", l)); - foo.notify({ id: "x", value: 2 }); - assert.deepStrictEqual(res, { x: 1, [EVENT_ALL]: 2 }); - assert.ok(foo.removeListener(EVENT_ALL, lall)); - assert.ok((foo)._listeners[EVENT_ALL] === undefined); - assert.deepStrictEqual((foo)._listeners, {}); - foo.notify({ id: "x", value: 3 }); - assert.deepStrictEqual(res, { x: 1, [EVENT_ALL]: 2 }); - }, + const res: any = {}; + const foo = new Foo(); + const l = (e: Event) => (res[e.id] = e.value); + const lall = (e: Event) => (res[EVENT_ALL] = e.value); + assert.ok(foo.addListener("x", l)); + assert.ok(foo.addListener(EVENT_ALL, lall)); + foo.notify({ id: "x", value: 1 }); + assert.deepStrictEqual(res, { x: 1, [EVENT_ALL]: 1 }); + assert.ok(foo.removeListener("x", l)); + assert.ok((foo)._listeners.x === undefined); + assert.ok(!foo.removeListener("x", l)); + foo.notify({ id: "x", value: 2 }); + assert.deepStrictEqual(res, { x: 1, [EVENT_ALL]: 2 }); + assert.ok(foo.removeListener(EVENT_ALL, lall)); + assert.ok((foo)._listeners[EVENT_ALL] === undefined); + assert.deepStrictEqual((foo)._listeners, {}); + foo.notify({ id: "x", value: 3 }); + assert.deepStrictEqual(res, { x: 1, [EVENT_ALL]: 2 }); + }, }); diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/args/api-extractor.json b/packages/args/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/args/api-extractor.json +++ b/packages/args/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/args/package.json b/packages/args/package.json index f2807a4000..31f24a9546 100644 --- a/packages/args/package.json +++ b/packages/args/package.json @@ -1,99 +1,99 @@ { - "name": "@thi.ng/args", - "version": "2.1.10", - "description": "Declarative, functional & typechecked CLI argument/options parser, value coercions etc.", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/args#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/strings": "^3.3.6" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "argument", - "ansi", - "cli", - "coerce", - "color", - "conversion", - "declarative", - "functional", - "hex", - "node", - "parser", - "tuple", - "typescript", - "validate" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./args": { - "default": "./args.js" - }, - "./coerce": { - "default": "./coerce.js" - }, - "./parse": { - "default": "./parse.js" - }, - "./usage": { - "default": "./usage.js" - } - }, - "thi.ng": { - "year": 2018 - } + "name": "@thi.ng/args", + "version": "2.1.10", + "description": "Declarative, functional & typechecked CLI argument/options parser, value coercions etc.", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/args#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/strings": "^3.3.6" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "argument", + "ansi", + "cli", + "coerce", + "color", + "conversion", + "declarative", + "functional", + "hex", + "node", + "parser", + "tuple", + "typescript", + "validate" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./args": { + "default": "./args.js" + }, + "./coerce": { + "default": "./coerce.js" + }, + "./parse": { + "default": "./parse.js" + }, + "./usage": { + "default": "./usage.js" + } + }, + "thi.ng": { + "year": 2018 + } } diff --git a/packages/args/src/api.ts b/packages/args/src/api.ts index 3276e5831c..ca5a928f82 100644 --- a/packages/args/src/api.ts +++ b/packages/args/src/api.ts @@ -1,168 +1,168 @@ import type { Fn, IDeref, IObjectOf } from "@thi.ng/api"; export interface ArgSpecBase { - /** - * Shorthand for given arg/option - */ - alias?: string; - /** - * Description (for usage only) - */ - desc?: string; - /** - * Type hint (for usage only) - */ - hint?: string; - /** - * Custom string representation of default value (e.g. a hex value) - */ - defaultHint?: string; - /** - * Predicate function called when this arg is being parsed. Function MUST - * return true for parsing to continue. - */ - fn?: Fn; - /** - * Group ID this arg belongs to. By default args belong to a "main" group. - * {@link flag} args are associated with the "flags" group (by default). - * - * See {@link UsageOpts.groups} for more details. - */ - group?: string; + /** + * Shorthand for given arg/option + */ + alias?: string; + /** + * Description (for usage only) + */ + desc?: string; + /** + * Type hint (for usage only) + */ + hint?: string; + /** + * Custom string representation of default value (e.g. a hex value) + */ + defaultHint?: string; + /** + * Predicate function called when this arg is being parsed. Function MUST + * return true for parsing to continue. + */ + fn?: Fn; + /** + * Group ID this arg belongs to. By default args belong to a "main" group. + * {@link flag} args are associated with the "flags" group (by default). + * + * See {@link UsageOpts.groups} for more details. + */ + group?: string; } export type ArgSpecRestrict = undefined extends T - ? {} - : { optional: false } | { default: T }; + ? {} + : { optional: false } | { default: T }; export type ArgSpec = ArgSpecBase & ArgSpecRestrict; export type ArgSpecExt = ArgSpec & { - coerce?: Fn; - delim?: string; - default?: any; - flag?: boolean; - fn?: Fn; - multi?: boolean; - optional?: any; + coerce?: Fn; + delim?: string; + default?: any; + flag?: boolean; + fn?: Fn; + multi?: boolean; + optional?: any; }; export type Args> = { - [id in keyof T]: boolean extends T[id] - ? ArgSpec & { flag: true } - : any[] extends T[id] - ? ArgSpec & { - coerce: Fn>; - multi: true; - delim?: string; - } - : KVDict extends T[id] - ? ArgSpec & { - coerce: Fn>; - multi: true; - } - : KVMultiDict extends T[id] - ? ArgSpec & { - coerce: Fn>; - multi: true; - } - : ArgSpec & { - coerce: Fn>; - }; + [id in keyof T]: boolean extends T[id] + ? ArgSpec & { flag: true } + : any[] extends T[id] + ? ArgSpec & { + coerce: Fn>; + multi: true; + delim?: string; + } + : KVDict extends T[id] + ? ArgSpec & { + coerce: Fn>; + multi: true; + } + : KVMultiDict extends T[id] + ? ArgSpec & { + coerce: Fn>; + multi: true; + } + : ArgSpec & { + coerce: Fn>; + }; }; export type KVDict = IObjectOf; export type KVMultiDict = IObjectOf; export interface ParseResult { - result: T; - index: number; - done: boolean; - rest: string[]; + result: T; + index: number; + done: boolean; + rest: string[]; } export interface ParseOpts { - /** - * Start index in `process.argv` to start parsing arguments from. - * - * @defaultValue 2 - */ - start: number; - /** - * True, if {@link usage} should be called automatically when a parse error - * occurs. Note: Errors will always be thrown, regardless of this setting. - * - * @defaultValue true - */ - showUsage: boolean; - /** - * Only used if `showUsage` is enabled. - * - * @see {@link UsageOpts} - */ - usageOpts: Partial; - /** - * Usage command option(s) - * - * @defaultValue ["--help"] - */ - help: string[]; + /** + * Start index in `process.argv` to start parsing arguments from. + * + * @defaultValue 2 + */ + start: number; + /** + * True, if {@link usage} should be called automatically when a parse error + * occurs. Note: Errors will always be thrown, regardless of this setting. + * + * @defaultValue true + */ + showUsage: boolean; + /** + * Only used if `showUsage` is enabled. + * + * @see {@link UsageOpts} + */ + usageOpts: Partial; + /** + * Usage command option(s) + * + * @defaultValue ["--help"] + */ + help: string[]; } export interface UsageOpts { - /** - * Line width for usage information (used for word wrapping). - * - * @defaultValue 80 - */ - lineWidth: number; - /** - * Column width for params - * - * @defaultValue 32 - */ - paramWidth: number; - /** - * If false, ANSI colors will be stripped from output. - * - * @defaultValue true - */ - color: Partial | false; - /** - * If true (default), display argument default values. Nullish or false - * default values will never be shown. - * - * @defaultValue true - */ - showDefaults: boolean; - /** - * If true, displays group names as sub headings. - * - * @defaultValue false - */ - showGroupNames: boolean; - /** - * Prefix content to show before list of options. Can contain ANSI control - * seqs and will be automatically word wrapped to given `lineWidth`. - * - * @default empty string - */ - prefix: string; - /** - * Suffix content to show after list of options. Can contain ANSI control - * seqs and will be automatically word wrapped to given `lineWidth`. - * - * @default empty string - */ - suffix: string; - /** - * Defines output order of arg groups. By default args belong to either - * "main" or "flag" groups. Each group's args are output in alphabetical - * order. Groups are separated by an empty line. - * - * @defaultValue `["flags", "main"]` - */ - groups: string[]; + /** + * Line width for usage information (used for word wrapping). + * + * @defaultValue 80 + */ + lineWidth: number; + /** + * Column width for params + * + * @defaultValue 32 + */ + paramWidth: number; + /** + * If false, ANSI colors will be stripped from output. + * + * @defaultValue true + */ + color: Partial | false; + /** + * If true (default), display argument default values. Nullish or false + * default values will never be shown. + * + * @defaultValue true + */ + showDefaults: boolean; + /** + * If true, displays group names as sub headings. + * + * @defaultValue false + */ + showGroupNames: boolean; + /** + * Prefix content to show before list of options. Can contain ANSI control + * seqs and will be automatically word wrapped to given `lineWidth`. + * + * @default empty string + */ + prefix: string; + /** + * Suffix content to show after list of options. Can contain ANSI control + * seqs and will be automatically word wrapped to given `lineWidth`. + * + * @default empty string + */ + suffix: string; + /** + * Defines output order of arg groups. By default args belong to either + * "main" or "flag" groups. Each group's args are output in alphabetical + * order. Groups are separated by an empty line. + * + * @defaultValue `["flags", "main"]` + */ + groups: string[]; } /** @@ -171,28 +171,28 @@ export interface UsageOpts { * https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit */ export interface ColorTheme { - default: number; - hint: number; - multi: number; - param: number; - required: number; + default: number; + hint: number; + multi: number; + param: number; + required: number; } export const DEFAULT_THEME: ColorTheme = { - default: 95, - hint: 90, - multi: 90, - param: 96, - required: 33, + default: 95, + hint: 90, + multi: 90, + param: 96, + required: 33, }; /** * Wrapper for fixed size tuple values produced by {@link tuple}. */ export class Tuple implements IDeref { - constructor(public value: T[]) {} + constructor(public value: T[]) {} - deref() { - return this.value; - } + deref() { + return this.value; + } } diff --git a/packages/args/src/args.ts b/packages/args/src/args.ts index e77642bef1..87db8876bc 100644 --- a/packages/args/src/args.ts +++ b/packages/args/src/args.ts @@ -2,68 +2,68 @@ import type { Fn } from "@thi.ng/api"; import { repeat } from "@thi.ng/strings/repeat"; import type { ArgSpec, KVDict, KVMultiDict, Tuple } from "./api.js"; import { - coerceFloat, - coerceFloats, - coerceHexInt, - coerceHexInts, - coerceInt, - coerceInts, - coerceJson, - coerceKV, - coerceOneOf, - coerceTuple, + coerceFloat, + coerceFloats, + coerceHexInt, + coerceHexInts, + coerceInt, + coerceInts, + coerceJson, + coerceKV, + coerceOneOf, + coerceTuple, } from "./coerce.js"; const $single = - (coerce: Fn, hint: string) => - >>( - spec: S - ): S & { coerce: Fn; hint: string; group: string } => ({ - coerce, - hint, - group: "main", - ...spec, - }); + (coerce: Fn, hint: string) => + >>( + spec: S + ): S & { coerce: Fn; hint: string; group: string } => ({ + coerce, + hint, + group: "main", + ...spec, + }); const $multi = - (coerce: Fn, hint: string) => - & { delim: string }>>( - spec: S - ): S & { - coerce: Fn; - hint: string; - multi: true; - group: string; - } => ({ - hint: $hint(hint, spec.delim), - multi: true, - coerce, - group: "main", - ...spec, - }); + (coerce: Fn, hint: string) => + & { delim: string }>>( + spec: S + ): S & { + coerce: Fn; + hint: string; + multi: true; + group: string; + } => ({ + hint: $hint(hint, spec.delim), + multi: true, + coerce, + group: "main", + ...spec, + }); const $hint = (hint: string, delim?: string) => - hint + (delim ? `[${delim}..]` : ""); + hint + (delim ? `[${delim}..]` : ""); /** * Returns a full {@link ArgSpec} for a boolean flag. The mere presence of this * arg will enable the flag. * - * @param spec - + * @param spec - */ export const flag = >>( - spec: S + spec: S ): S & { flag: true; default: boolean; group: string } => ({ - flag: true, - default: false, - group: "flags", - ...spec, + flag: true, + default: false, + group: "flags", + ...spec, }); /** * Returns a full {@link ArgSpec} for a string value arg. * - * @param spec - + * @param spec - */ export const string = $single((x) => x, "STR"); @@ -72,7 +72,7 @@ export const string = $single((x) => x, "STR"); * multi string value arg. This argument can be provided mutiple times with * values collected into an array. * - * @param spec - + * @param spec - */ export const strings = $multi((x) => x, "STR"); @@ -80,7 +80,7 @@ export const strings = $multi((x) => x, "STR"); * Returns a full {@link ArgSpec} for a floating point value arg. The value * will be autoatically coerced into a number using {@link coerceFloat}. * - * @param spec - + * @param spec - */ export const float = $single(coerceFloat, "NUM"); @@ -88,7 +88,7 @@ export const float = $single(coerceFloat, "NUM"); * Returns a full {@link ArgSpec} for a single hex integer value arg. The value * will be autoatically coerced into a number using {@link coerceHexInt}. * - * @param spec - + * @param spec - */ export const hex = $single(coerceHexInt, "HEX"); @@ -96,7 +96,7 @@ export const hex = $single(coerceHexInt, "HEX"); * Returns a full {@link ArgSpec} for a single integer value arg. The value * will be autoatically coerced into a number using {@link coerceInt}. * - * @param spec - + * @param spec - */ export const int = $single(coerceInt, "INT"); @@ -105,7 +105,7 @@ export const int = $single(coerceInt, "INT"); * multi floating point value arg. This argument can be provided mutiple times * with values being coerced into numbers and collected into an array. * - * @param spec - + * @param spec - */ export const floats = $multi(coerceFloats, "NUM"); @@ -114,7 +114,7 @@ export const floats = $multi(coerceFloats, "NUM"); * hex integer value arg. This argument can be provided mutiple times with * values being coerced into numbers and collected into an array. * - * @param spec - + * @param spec - */ export const hexes = $multi(coerceHexInts, "HEX"); @@ -123,7 +123,7 @@ export const hexes = $multi(coerceHexInts, "HEX"); * integer value arg. This argument can be provided mutiple times with values * being coerced into numbers and collected into an array. * - * @param spec - + * @param spec - */ export const ints = $multi(coerceInts, "INT"); @@ -131,38 +131,38 @@ export const ints = $multi(coerceInts, "INT"); * Returns full {@link ArgSpec} for a JSON value arg. The raw CLI value string * will be automcatically coerced using {@link coerceJson}. * - * @param spec - + * @param spec - */ export const json = >>( - spec: S + spec: S ): S & { coerce: Fn; hint: string; group: string } => ({ - coerce: coerceJson, - hint: "JSON", - group: "main", - ...spec, + coerce: coerceJson, + hint: "JSON", + group: "main", + ...spec, }); const $desc = (opts: readonly string[], prefix?: string) => - `${prefix ? prefix + ": " : ""}${opts.map((x) => `"${x}"`).join(", ")}`; + `${prefix ? prefix + ": " : ""}${opts.map((x) => `"${x}"`).join(", ")}`; /** * Returns full {@link ArgSpec} for an enum-like string value arg. The raw CLI * value string will be automcatically validated using {@link coerceOneOf}. * - * @param opts - - * @param spec - + * @param opts - + * @param spec - */ export const oneOf = >>( - opts: readonly K[], - spec: S + opts: readonly K[], + spec: S ): S & { coerce: Fn; hint: string; group: string } & { - desc: string; + desc: string; } => ({ - coerce: coerceOneOf(opts), - hint: "ID", - group: "main", - ...spec, - desc: $desc(opts, spec.desc), + coerce: coerceOneOf(opts), + hint: "ID", + group: "main", + ...spec, + desc: $desc(opts, spec.desc), }); /** @@ -170,27 +170,27 @@ export const oneOf = >>( * enum-like string value args. The raw CLI value strings will be automcatically * validated using {@link coerceOneOf} and collected into an array. * - * @param opts - - * @param spec - + * @param opts - + * @param spec - */ export const oneOfMulti = < - K extends string, - S extends Partial & { delim: string }> + K extends string, + S extends Partial & { delim: string }> >( - opts: readonly K[], - spec: S + opts: readonly K[], + spec: S ): S & { - coerce: Fn; - hint: string; - multi: true; - group: string; + coerce: Fn; + hint: string; + multi: true; + group: string; } & { desc: string } => ({ - coerce: (xs) => xs.map(coerceOneOf(opts)), - hint: $hint("ID", spec.delim), - multi: true, - group: "main", - ...spec, - desc: $desc(opts, spec.desc), + coerce: (xs) => xs.map(coerceOneOf(opts)), + hint: $hint("ID", spec.delim), + multi: true, + group: "main", + ...spec, + desc: $desc(opts, spec.desc), }); /** @@ -202,24 +202,24 @@ export const oneOfMulti = < * are allowed and will receive a `"true"` as their value. However, if `strict` * is true, only full KV pairs are allowed. * - * @param spec - - * @param delim - + * @param spec - + * @param delim - */ export const kvPairs = >>( - spec: S, - delim = "=", - strict?: boolean + spec: S, + delim = "=", + strict?: boolean ): S & { - coerce: Fn; - hint: string; - multi: true; - group: string; + coerce: Fn; + hint: string; + multi: true; + group: string; } => ({ - coerce: coerceKV(delim, strict), - hint: `key${delim}val`, - multi: true, - group: "main", - ...spec, + coerce: coerceKV(delim, strict), + hint: `key${delim}val`, + multi: true, + group: "main", + ...spec, }); /** @@ -227,25 +227,25 @@ export const kvPairs = >>( * which supports multiple values per given key (each key's values are collected * into arrays). * - * @param spec - - * @param delim - - * @param strict - + * @param spec - + * @param delim - + * @param strict - */ export const kvPairsMulti = >>( - spec: S, - delim = "=", - strict?: boolean + spec: S, + delim = "=", + strict?: boolean ): S & { - coerce: Fn; - hint: string; - multi: true; - group: string; + coerce: Fn; + hint: string; + multi: true; + group: string; } => ({ - coerce: coerceKV(delim, strict, true), - hint: `key${delim}val(s)`, - multi: true, - group: "main", - ...spec, + coerce: coerceKV(delim, strict, true), + hint: `key${delim}val(s)`, + multi: true, + group: "main", + ...spec, }); /** @@ -269,45 +269,45 @@ export const kvPairsMulti = >>( * // } * ``` * - * @param coerce - - * @param size - - * @param spec - - * @param delim - + * @param coerce - + * @param size - + * @param spec - + * @param delim - */ export const tuple = >>>( - coerce: Fn, - size: number, - spec: S, - delim = "," + coerce: Fn, + size: number, + spec: S, + delim = "," ): S & { coerce: Fn>; hint: string; group: string } => ({ - coerce: coerceTuple(coerce, size, delim), - hint: [...repeat("N", size)].join(delim), - group: "main", - ...spec, + coerce: coerceTuple(coerce, size, delim), + hint: [...repeat("N", size)].join(delim), + group: "main", + ...spec, }); /** * Syntax sugar for `tuple(coerceInt, size, {...}, delim)`. * - * @param size - - * @param spec - - * @param delim - + * @param size - + * @param spec - + * @param delim - */ export const size = >>>( - size: number, - spec: S, - delim = "x" + size: number, + spec: S, + delim = "x" ) => tuple(coerceInt, size, spec, delim); /** * Syntax sugar for `tuple(coerceFloat, size, {...}, delim)`. * - * @param size - - * @param spec - - * @param delim - + * @param size - + * @param spec - + * @param delim - */ export const vec = >>>( - size: number, - spec: S, - delim = "," + size: number, + spec: S, + delim = "," ) => tuple(coerceFloat, size, spec, delim); diff --git a/packages/args/src/coerce.ts b/packages/args/src/coerce.ts index 99519f4bda..59b1dc95cf 100644 --- a/packages/args/src/coerce.ts +++ b/packages/args/src/coerce.ts @@ -7,68 +7,68 @@ import { KVDict, KVMultiDict, Tuple } from "./api.js"; export const coerceString = (x: string) => x; export const coerceFloat = (x: string) => - isNumericFloat(x) - ? parseFloat(x) - : illegalArgs(`not a numeric value: ${x}`); + isNumericFloat(x) + ? parseFloat(x) + : illegalArgs(`not a numeric value: ${x}`); export const coerceFloats = (xs: string[]) => xs.map(coerceFloat); export const coerceHexInt = (x: string) => - isHex(x) ? parseInt(x, 16) : illegalArgs(`not a hex value: ${x}`); + isHex(x) ? parseInt(x, 16) : illegalArgs(`not a hex value: ${x}`); export const coerceHexInts = (xs: string[]) => xs.map(coerceHexInt); export const coerceInt = (x: string) => - isNumericInt(x) ? parseInt(x) : illegalArgs(`not an integer: ${x}`); + isNumericInt(x) ? parseInt(x) : illegalArgs(`not an integer: ${x}`); export const coerceInts = (xs: string[]) => xs.map(coerceInt); export const coerceJson = (x: string): T => JSON.parse(x); export const coerceOneOf = - (xs: readonly K[]) => - (x: string) => - xs.includes(x) ? x : illegalArgs(`invalid option: ${x}`); + (xs: readonly K[]) => + (x: string) => + xs.includes(x) ? x : illegalArgs(`invalid option: ${x}`); export function coerceKV( - delim?: string, - strict?: boolean, - multi?: false + delim?: string, + strict?: boolean, + multi?: false ): Fn; export function coerceKV( - delim?: string, - strict?: boolean, - multi?: true + delim?: string, + strict?: boolean, + multi?: true ): Fn; export function coerceKV(delim = "=", strict = false, multi = false) { - return (pairs: string[]) => - pairs.reduce((acc, x) => { - const idx = x.indexOf(delim); - strict && - idx < 1 && - illegalArgs( - `got '${x}', but expected a 'key${delim}value' pair` - ); - if (idx > 0) { - const id = x.substring(0, idx); - const val = x.substring(idx + 1); - if (multi) { - acc[id] ? (acc[id]).push(val) : (acc[id] = [val]); - } else { - acc[id] = val; - } - } else { - acc[x] = multi ? ["true"] : "true"; - } - return acc; - }, {}); + return (pairs: string[]) => + pairs.reduce((acc, x) => { + const idx = x.indexOf(delim); + strict && + idx < 1 && + illegalArgs( + `got '${x}', but expected a 'key${delim}value' pair` + ); + if (idx > 0) { + const id = x.substring(0, idx); + const val = x.substring(idx + 1); + if (multi) { + acc[id] ? (acc[id]).push(val) : (acc[id] = [val]); + } else { + acc[id] = val; + } + } else { + acc[x] = multi ? ["true"] : "true"; + } + return acc; + }, {}); } export const coerceTuple = - (coerce: Fn, size: number, delim = ",") => - (src: string) => { - const parts = src.split(delim); - parts.length !== size && - illegalArgs(`got '${src}', but expected a tuple of ${size} values`); - return new Tuple(parts.map(coerce)); - }; + (coerce: Fn, size: number, delim = ",") => + (src: string) => { + const parts = src.split(delim); + parts.length !== size && + illegalArgs(`got '${src}', but expected a tuple of ${size} values`); + return new Tuple(parts.map(coerce)); + }; diff --git a/packages/args/src/parse.ts b/packages/args/src/parse.ts index 4fc2ff4625..2fb0e5b094 100644 --- a/packages/args/src/parse.ts +++ b/packages/args/src/parse.ts @@ -6,139 +6,139 @@ import type { Args, ArgSpecExt, ParseOpts, ParseResult } from "./api.js"; import { usage } from "./usage.js"; export const parse = >( - specs: Args, - argv: string[], - opts?: Partial + specs: Args, + argv: string[], + opts?: Partial ): ParseResult | undefined => { - opts = { start: 2, showUsage: true, help: ["--help", "-h"], ...opts }; - try { - return parseOpts(specs, argv, opts); - } catch (e) { - if (opts.showUsage) { - console.log( - (e).message + "\n\n" + usage(specs, opts.usageOpts) - ); - } - throw e; - } + opts = { start: 2, showUsage: true, help: ["--help", "-h"], ...opts }; + try { + return parseOpts(specs, argv, opts); + } catch (e) { + if (opts.showUsage) { + console.log( + (e).message + "\n\n" + usage(specs, opts.usageOpts) + ); + } + throw e; + } }; const parseOpts = >( - specs: Args, - argv: string[], - opts: Partial + specs: Args, + argv: string[], + opts: Partial ): ParseResult | undefined => { - const aliases = aliasIndex(specs); - const acc: any = {}; - let id: Nullable; - let spec: Nullable; - let i = opts.start!; - for (; i < argv.length; ) { - const a = argv[i]; - if (!id) { - if (opts.help!.includes(a)) { - console.log(usage(specs, opts.usageOpts)); - return; - } - const state = parseKey(specs, aliases, acc, a); - id = state.id; - spec = state.spec; - i = i + ~~(state.state < 2); - if (state.state) break; - } else { - if (parseValue(spec!, acc, id, a)) break; - id = null; - i++; - } - } - id && illegalArgs(`missing value for: --${id}`); - return { - result: processResults(specs, acc), - index: i, - rest: argv.slice(i), - done: i >= argv.length, - }; + const aliases = aliasIndex(specs); + const acc: any = {}; + let id: Nullable; + let spec: Nullable; + let i = opts.start!; + for (; i < argv.length; ) { + const a = argv[i]; + if (!id) { + if (opts.help!.includes(a)) { + console.log(usage(specs, opts.usageOpts)); + return; + } + const state = parseKey(specs, aliases, acc, a); + id = state.id; + spec = state.spec; + i = i + ~~(state.state < 2); + if (state.state) break; + } else { + if (parseValue(spec!, acc, id, a)) break; + id = null; + i++; + } + } + id && illegalArgs(`missing value for: --${id}`); + return { + result: processResults(specs, acc), + index: i, + rest: argv.slice(i), + done: i >= argv.length, + }; }; const aliasIndex = >(specs: Args) => - Object.entries(specs).reduce( - (acc, [k, v]) => (v.alias ? ((acc[v.alias] = k), acc) : acc), - >{} - ); + Object.entries(specs).reduce( + (acc, [k, v]) => (v.alias ? ((acc[v.alias] = k), acc) : acc), + >{} + ); interface ParseKeyResult { - state: number; - id?: string; - spec?: ArgSpecExt; + state: number; + id?: string; + spec?: ArgSpecExt; } const parseKey = >( - specs: Args, - aliases: IObjectOf, - acc: any, - a: string + specs: Args, + aliases: IObjectOf, + acc: any, + a: string ): ParseKeyResult => { - if (a[0] === "-") { - let id: string | undefined; - if (a[1] === "-") { - // terminator arg, stop parsing - if (a === "--") return { state: 1 }; - id = camel(a.substring(2)); - } else { - id = aliases[a.substring(1)]; - !id && illegalArgs(`unknown option: ${a}`); - } - const spec: ArgSpecExt = specs[id]; - !spec && illegalArgs(id); - if (spec.flag) { - acc[id] = true; - id = undefined; - // stop parsing if fn returns false - if (spec.fn && !spec.fn("true")) return { state: 1, spec }; - } - return { state: 0, id, spec }; - } - // no option arg, stop parsing - return { state: 2 }; + if (a[0] === "-") { + let id: string | undefined; + if (a[1] === "-") { + // terminator arg, stop parsing + if (a === "--") return { state: 1 }; + id = camel(a.substring(2)); + } else { + id = aliases[a.substring(1)]; + !id && illegalArgs(`unknown option: ${a}`); + } + const spec: ArgSpecExt = specs[id]; + !spec && illegalArgs(id); + if (spec.flag) { + acc[id] = true; + id = undefined; + // stop parsing if fn returns false + if (spec.fn && !spec.fn("true")) return { state: 1, spec }; + } + return { state: 0, id, spec }; + } + // no option arg, stop parsing + return { state: 2 }; }; const parseValue = (spec: ArgSpecExt, acc: any, id: string, a: string) => { - /^-[a-z]/i.test(a) && illegalArgs(`missing value for: --${id}`); - if (spec!.multi) { - isArray(acc[id!]) ? acc[id!].push(a) : (acc[id!] = [a]); - } else { - acc[id!] = a; - } - return spec!.fn && !spec!.fn(a); + /^-[a-z]/i.test(a) && illegalArgs(`missing value for: --${id}`); + if (spec!.multi) { + isArray(acc[id!]) ? acc[id!].push(a) : (acc[id!] = [a]); + } else { + acc[id!] = a; + } + return spec!.fn && !spec!.fn(a); }; const processResults = >(specs: Args, acc: any) => { - let spec: Nullable; - for (let id in specs) { - spec = specs[id]; - if (acc[id] === undefined) { - if (spec.default !== undefined) { - acc[id] = spec.default; - } else if (spec.optional === false) { - illegalArgs(`missing arg: --${id}`); - } - } else if (spec.coerce) { - coerceValue(spec, acc, id); - } - } - return acc; + let spec: Nullable; + for (let id in specs) { + spec = specs[id]; + if (acc[id] === undefined) { + if (spec.default !== undefined) { + acc[id] = spec.default; + } else if (spec.optional === false) { + illegalArgs(`missing arg: --${id}`); + } + } else if (spec.coerce) { + coerceValue(spec, acc, id); + } + } + return acc; }; const coerceValue = (spec: ArgSpecExt, acc: any, id: string) => { - try { - if (spec.multi && spec.delim) { - acc[id] = (acc[id]).reduce( - (acc, x) => (acc.push(...x.split(spec!.delim!)), acc), - [] - ); - } - acc[id] = spec.coerce!(acc[id]); - } catch (e) { - throw new Error(`arg --${id}: ${(e).message}`); - } + try { + if (spec.multi && spec.delim) { + acc[id] = (acc[id]).reduce( + (acc, x) => (acc.push(...x.split(spec!.delim!)), acc), + [] + ); + } + acc[id] = spec.coerce!(acc[id]); + } catch (e) { + throw new Error(`arg --${id}: ${(e).message}`); + } }; diff --git a/packages/args/src/usage.ts b/packages/args/src/usage.ts index 7cbfdd415f..9fb329b1f7 100644 --- a/packages/args/src/usage.ts +++ b/packages/args/src/usage.ts @@ -6,126 +6,126 @@ import { repeat } from "@thi.ng/strings/repeat"; import { stringify } from "@thi.ng/strings/stringify"; import { SPLIT_ANSI, wordWrapLines } from "@thi.ng/strings/word-wrap"; import { - Args, - ArgSpecExt, - ColorTheme, - DEFAULT_THEME, - UsageOpts, + Args, + ArgSpecExt, + ColorTheme, + DEFAULT_THEME, + UsageOpts, } from "./api.js"; export const usage = >( - specs: Args, - opts: Partial = {} + specs: Args, + opts: Partial = {} ) => { - opts = { - lineWidth: 80, - paramWidth: 32, - showDefaults: true, - prefix: "", - suffix: "", - groups: ["flags", "main"], - ...opts, - }; - const theme = - opts.color !== false - ? { ...DEFAULT_THEME, ...opts.color } - : {}; - const indent = repeat(" ", opts.paramWidth!); - const format = (ids: string[]) => - ids.map((id) => argUsage(id, specs[id], opts, theme, indent)); - const sortedIDs = Object.keys(specs).sort(); - const groups: Pair[] = opts.groups - ? opts.groups - .map( - (gid): Pair => [ - gid, - sortedIDs.filter((id) => specs[id].group === gid), - ] - ) - .filter((g) => !!g[1].length) - : [["options", sortedIDs]]; - return [ - ...wrap(opts.prefix, opts.lineWidth!), - ...groups.map(([gid, ids]) => - [ - ...(opts.showGroupNames ? [`${capitalize(gid)}:\n`] : []), - ...format(ids), - "", - ].join("\n") - ), - ...wrap(opts.suffix, opts.lineWidth!), - ].join("\n"); + opts = { + lineWidth: 80, + paramWidth: 32, + showDefaults: true, + prefix: "", + suffix: "", + groups: ["flags", "main"], + ...opts, + }; + const theme = + opts.color !== false + ? { ...DEFAULT_THEME, ...opts.color } + : {}; + const indent = repeat(" ", opts.paramWidth!); + const format = (ids: string[]) => + ids.map((id) => argUsage(id, specs[id], opts, theme, indent)); + const sortedIDs = Object.keys(specs).sort(); + const groups: Pair[] = opts.groups + ? opts.groups + .map( + (gid): Pair => [ + gid, + sortedIDs.filter((id) => specs[id].group === gid), + ] + ) + .filter((g) => !!g[1].length) + : [["options", sortedIDs]]; + return [ + ...wrap(opts.prefix, opts.lineWidth!), + ...groups.map(([gid, ids]) => + [ + ...(opts.showGroupNames ? [`${capitalize(gid)}:\n`] : []), + ...format(ids), + "", + ].join("\n") + ), + ...wrap(opts.suffix, opts.lineWidth!), + ].join("\n"); }; const argUsage = ( - id: string, - spec: ArgSpecExt, - opts: Partial, - theme: ColorTheme, - indent: string + id: string, + spec: ArgSpecExt, + opts: Partial, + theme: ColorTheme, + indent: string ) => { - const hint = argHint(spec, theme); - const alias = argAlias(spec, theme, hint); - const name = ansi(`--${kebab(id)}`, theme.param!); - const params = `${alias}${name}${hint}`; - const isRequired = spec.optional === false && spec.default === undefined; - const prefixes: string[] = []; - isRequired && prefixes.push("required"); - spec.multi && prefixes.push("multiple"); - const body = - argPrefix(prefixes, theme, isRequired) + - (spec.desc || "") + - argDefault(spec, opts, theme); - return ( - padRight(opts.paramWidth!)(params, lengthAnsi(params)) + - wrap(body, opts.lineWidth! - opts.paramWidth!) - .map((l, i) => (i > 0 ? indent + l : l)) - .join("\n") - ); + const hint = argHint(spec, theme); + const alias = argAlias(spec, theme, hint); + const name = ansi(`--${kebab(id)}`, theme.param!); + const params = `${alias}${name}${hint}`; + const isRequired = spec.optional === false && spec.default === undefined; + const prefixes: string[] = []; + isRequired && prefixes.push("required"); + spec.multi && prefixes.push("multiple"); + const body = + argPrefix(prefixes, theme, isRequired) + + (spec.desc || "") + + argDefault(spec, opts, theme); + return ( + padRight(opts.paramWidth!)(params, lengthAnsi(params)) + + wrap(body, opts.lineWidth! - opts.paramWidth!) + .map((l, i) => (i > 0 ? indent + l : l)) + .join("\n") + ); }; const argHint = (spec: ArgSpecExt, theme: ColorTheme) => - spec.hint ? ansi(" " + spec.hint, theme.hint!) : ""; + spec.hint ? ansi(" " + spec.hint, theme.hint!) : ""; const argAlias = (spec: ArgSpecExt, theme: ColorTheme, hint: string) => - spec.alias ? `${ansi("-" + spec.alias, theme.param!)}${hint}, ` : ""; + spec.alias ? `${ansi("-" + spec.alias, theme.param!)}${hint}, ` : ""; const argPrefix = ( - prefixes: string[], - theme: ColorTheme, - isRequired: boolean + prefixes: string[], + theme: ColorTheme, + isRequired: boolean ) => - prefixes.length - ? ansi( - `[${prefixes.join(", ")}] `, - isRequired ? theme.required! : theme.multi! - ) - : ""; + prefixes.length + ? ansi( + `[${prefixes.join(", ")}] `, + isRequired ? theme.required! : theme.multi! + ) + : ""; const argDefault = ( - spec: ArgSpecExt, - opts: Partial, - theme: ColorTheme + spec: ArgSpecExt, + opts: Partial, + theme: ColorTheme ) => - opts.showDefaults && spec.default != null && spec.default !== false - ? ansi( - ` (default: ${stringify(true)( - spec.defaultHint != undefined - ? spec.defaultHint - : spec.default - )})`, - theme.default - ) - : ""; + opts.showDefaults && spec.default != null && spec.default !== false + ? ansi( + ` (default: ${stringify(true)( + spec.defaultHint != undefined + ? spec.defaultHint + : spec.default + )})`, + theme.default + ) + : ""; const ansi = (x: string, col: number) => - col != null ? `\x1b[${col}m${x}\x1b[0m` : x; + col != null ? `\x1b[${col}m${x}\x1b[0m` : x; const wrap = (str: string | undefined, width: number) => - str - ? wordWrapLines(str, { - width, - splitter: SPLIT_ANSI, - hard: true, - }) - : []; + str + ? wordWrapLines(str, { + width, + splitter: SPLIT_ANSI, + hard: true, + }) + : []; diff --git a/packages/args/test/index.ts b/packages/args/test/index.ts index 8cd81edd77..b01158e026 100644 --- a/packages/args/test/index.ts +++ b/packages/args/test/index.ts @@ -1,277 +1,277 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; import { - coerceInt, - flag, - float, - hex, - int, - ints, - json, - KVDict, - KVMultiDict, - kvPairs, - kvPairsMulti, - oneOf, - parse, - size, - string, - tuple, - Tuple, -} from "../src/index.js" + coerceInt, + flag, + float, + hex, + int, + ints, + json, + KVDict, + KVMultiDict, + kvPairs, + kvPairsMulti, + oneOf, + parse, + size, + string, + tuple, + Tuple, +} from "../src/index.js"; group("args", { - "basic / string": () => { - assert.deepStrictEqual( - parse<{ a?: string }>({ a: string({}) }, ["--a", "a"], { - start: 0, - }), - { result: { a: "a" }, index: 2, done: true, rest: [] } - ); - assert.deepStrictEqual( - parse<{ a?: string }>({ a: string({ alias: "A" }) }, ["-A", "a"], { - start: 0, - }), - { result: { a: "a" }, index: 2, done: true, rest: [] } - ); - assert.deepStrictEqual( - parse<{ a?: string }>({ a: string({}) }, [], { - start: 0, - }), - { result: {}, index: 0, done: true, rest: [] } - ); - assert.deepStrictEqual( - parse<{ a: string }>({ a: string({ default: "a" }) }, [], { - start: 0, - }), - { result: { a: "a" }, index: 0, done: true, rest: [] } - ); - assert.throws(() => - parse<{ a: string }>({ a: string({ optional: false }) }, [], { - showUsage: false, - }) - ); - }, + "basic / string": () => { + assert.deepStrictEqual( + parse<{ a?: string }>({ a: string({}) }, ["--a", "a"], { + start: 0, + }), + { result: { a: "a" }, index: 2, done: true, rest: [] } + ); + assert.deepStrictEqual( + parse<{ a?: string }>({ a: string({ alias: "A" }) }, ["-A", "a"], { + start: 0, + }), + { result: { a: "a" }, index: 2, done: true, rest: [] } + ); + assert.deepStrictEqual( + parse<{ a?: string }>({ a: string({}) }, [], { + start: 0, + }), + { result: {}, index: 0, done: true, rest: [] } + ); + assert.deepStrictEqual( + parse<{ a: string }>({ a: string({ default: "a" }) }, [], { + start: 0, + }), + { result: { a: "a" }, index: 0, done: true, rest: [] } + ); + assert.throws(() => + parse<{ a: string }>({ a: string({ optional: false }) }, [], { + showUsage: false, + }) + ); + }, - flag: () => { - assert.deepStrictEqual( - parse<{ a?: boolean }>({ a: flag({}) }, ["--a"], { - start: 0, - }), - { result: { a: true }, index: 1, done: true, rest: [] } - ); - assert.deepStrictEqual( - parse<{ a: boolean }>({ a: flag({ default: false }) }, [], { - start: 0, - }), - { result: { a: false }, index: 0, done: true, rest: [] } - ); - }, + flag: () => { + assert.deepStrictEqual( + parse<{ a?: boolean }>({ a: flag({}) }, ["--a"], { + start: 0, + }), + { result: { a: true }, index: 1, done: true, rest: [] } + ); + assert.deepStrictEqual( + parse<{ a: boolean }>({ a: flag({ default: false }) }, [], { + start: 0, + }), + { result: { a: false }, index: 0, done: true, rest: [] } + ); + }, - number: () => { - assert.deepStrictEqual( - parse<{ a?: number }>({ a: float({}) }, ["--a", "1.23"], { - start: 0, - }), - { result: { a: 1.23 }, index: 2, done: true, rest: [] } - ); - assert.deepStrictEqual( - parse<{ a?: number }>({ a: int({}) }, ["--a", "123"], { - start: 0, - }), - { result: { a: 123 }, index: 2, done: true, rest: [] } - ); - assert.deepStrictEqual( - parse<{ a?: number }>({ a: hex({}) }, ["--a", "123"], { - start: 0, - }), - { result: { a: 0x123 }, index: 2, done: true, rest: [] } - ); - assert.throws(() => - parse<{ a?: number }>({ a: int({}) }, ["--a", "a"], { - start: 0, - showUsage: false, - }) - ); - }, + number: () => { + assert.deepStrictEqual( + parse<{ a?: number }>({ a: float({}) }, ["--a", "1.23"], { + start: 0, + }), + { result: { a: 1.23 }, index: 2, done: true, rest: [] } + ); + assert.deepStrictEqual( + parse<{ a?: number }>({ a: int({}) }, ["--a", "123"], { + start: 0, + }), + { result: { a: 123 }, index: 2, done: true, rest: [] } + ); + assert.deepStrictEqual( + parse<{ a?: number }>({ a: hex({}) }, ["--a", "123"], { + start: 0, + }), + { result: { a: 0x123 }, index: 2, done: true, rest: [] } + ); + assert.throws(() => + parse<{ a?: number }>({ a: int({}) }, ["--a", "a"], { + start: 0, + showUsage: false, + }) + ); + }, - enum: () => { - type E = "abc" | "xyz"; - const opts: E[] = ["abc", "xyz"]; - assert.deepStrictEqual( - parse<{ a?: E }>({ a: oneOf(opts, {}) }, ["--a", "abc"], { - start: 0, - }), - { result: { a: "abc" }, index: 2, done: true, rest: [] } - ); - assert.deepStrictEqual( - parse<{ a?: E }>({ a: oneOf(opts, { default: "xyz" }) }, []), - { result: { a: "xyz" }, index: 2, done: true, rest: [] } - ); - assert.throws(() => - parse<{ a?: E }>({ a: oneOf(opts, {}) }, ["--a", "def"], { - start: 0, - showUsage: false, - }) - ); - }, + enum: () => { + type E = "abc" | "xyz"; + const opts: E[] = ["abc", "xyz"]; + assert.deepStrictEqual( + parse<{ a?: E }>({ a: oneOf(opts, {}) }, ["--a", "abc"], { + start: 0, + }), + { result: { a: "abc" }, index: 2, done: true, rest: [] } + ); + assert.deepStrictEqual( + parse<{ a?: E }>({ a: oneOf(opts, { default: "xyz" }) }, []), + { result: { a: "xyz" }, index: 2, done: true, rest: [] } + ); + assert.throws(() => + parse<{ a?: E }>({ a: oneOf(opts, {}) }, ["--a", "def"], { + start: 0, + showUsage: false, + }) + ); + }, - kv: () => { - assert.deepStrictEqual( - parse<{ a?: KVDict }>( - { a: kvPairs({}) }, - ["--a", "foo=bar", "--a", "baz"], - { - start: 0, - } - ), - { - result: { a: { foo: "bar", baz: "true" } }, - index: 4, - done: true, - rest: [], - } - ); - assert.deepStrictEqual( - parse<{ a?: KVDict }>({ a: kvPairs({}, ":") }, ["--a", "foo:bar"], { - start: 0, - }), - { - result: { a: { foo: "bar" } }, - index: 2, - done: true, - rest: [], - } - ); - assert.throws(() => - parse<{ a?: KVDict }>( - { a: kvPairs({}, ":", true) }, - ["--a", "foo"], - { - start: 0, - showUsage: false, - } - ) - ); - }, + kv: () => { + assert.deepStrictEqual( + parse<{ a?: KVDict }>( + { a: kvPairs({}) }, + ["--a", "foo=bar", "--a", "baz"], + { + start: 0, + } + ), + { + result: { a: { foo: "bar", baz: "true" } }, + index: 4, + done: true, + rest: [], + } + ); + assert.deepStrictEqual( + parse<{ a?: KVDict }>({ a: kvPairs({}, ":") }, ["--a", "foo:bar"], { + start: 0, + }), + { + result: { a: { foo: "bar" } }, + index: 2, + done: true, + rest: [], + } + ); + assert.throws(() => + parse<{ a?: KVDict }>( + { a: kvPairs({}, ":", true) }, + ["--a", "foo"], + { + start: 0, + showUsage: false, + } + ) + ); + }, - kvMulti: () => { - assert.deepStrictEqual( - parse<{ a?: KVMultiDict }>( - { a: kvPairsMulti({}) }, - [ - "--a", - "foo=aa", - "--a", - "bar=bb", - "--a", - "foo=cc", - "--a", - "debug", - ], - { start: 0 } - ), - { - result: { - a: { foo: ["aa", "cc"], bar: ["bb"], debug: ["true"] }, - }, - index: 8, - done: true, - rest: [], - } - ); - }, + kvMulti: () => { + assert.deepStrictEqual( + parse<{ a?: KVMultiDict }>( + { a: kvPairsMulti({}) }, + [ + "--a", + "foo=aa", + "--a", + "bar=bb", + "--a", + "foo=cc", + "--a", + "debug", + ], + { start: 0 } + ), + { + result: { + a: { foo: ["aa", "cc"], bar: ["bb"], debug: ["true"] }, + }, + index: 8, + done: true, + rest: [], + } + ); + }, - json: () => { - assert.deepStrictEqual( - parse<{ a: any }>( - { a: json({}) }, - ["--a", '{"foo":[23]}'], - { - start: 0, - } - ), - { result: { a: { foo: [23] } }, index: 2, done: true, rest: [] } - ); - }, + json: () => { + assert.deepStrictEqual( + parse<{ a: any }>( + { a: json({}) }, + ["--a", '{"foo":[23]}'], + { + start: 0, + } + ), + { result: { a: { foo: [23] } }, index: 2, done: true, rest: [] } + ); + }, - "number[]": () => { - assert.deepStrictEqual( - parse<{ a?: number[] }>({ a: ints({}) }, ["--a", "1", "--a", "2"], { - start: 0, - }), - { result: { a: [1, 2] }, index: 4, done: true, rest: [] } - ); - assert.deepStrictEqual( - parse<{ a?: number[] }>( - { a: ints({ delim: "," }) }, - ["--a", "1,2", "--a", "3,4"], - { - start: 0, - } - ), - { result: { a: [1, 2, 3, 4] }, index: 4, done: true, rest: [] } - ); - }, + "number[]": () => { + assert.deepStrictEqual( + parse<{ a?: number[] }>({ a: ints({}) }, ["--a", "1", "--a", "2"], { + start: 0, + }), + { result: { a: [1, 2] }, index: 4, done: true, rest: [] } + ); + assert.deepStrictEqual( + parse<{ a?: number[] }>( + { a: ints({ delim: "," }) }, + ["--a", "1,2", "--a", "3,4"], + { + start: 0, + } + ), + { result: { a: [1, 2, 3, 4] }, index: 4, done: true, rest: [] } + ); + }, - tuple: () => { - const res = { - result: { a: new Tuple([1, 2, 3]) }, - index: 2, - done: true, - rest: [], - }; - assert.deepStrictEqual( - parse<{ a?: Tuple }>( - { a: tuple(coerceInt, 3, {}) }, - ["--a", "1,2,3"], - { - start: 0, - } - ), - res - ); - assert.deepStrictEqual( - parse<{ a?: Tuple }>( - { a: size(3, {}, "x") }, - ["--a", "1x2x3"], - { - start: 0, - } - ), - res - ); - }, + tuple: () => { + const res = { + result: { a: new Tuple([1, 2, 3]) }, + index: 2, + done: true, + rest: [], + }; + assert.deepStrictEqual( + parse<{ a?: Tuple }>( + { a: tuple(coerceInt, 3, {}) }, + ["--a", "1,2,3"], + { + start: 0, + } + ), + res + ); + assert.deepStrictEqual( + parse<{ a?: Tuple }>( + { a: size(3, {}, "x") }, + ["--a", "1x2x3"], + { + start: 0, + } + ), + res + ); + }, - "stop early": () => { - assert.deepStrictEqual( - parse<{ a?: number }>({ a: int({}) }, ["--a", "1", "foo"], { - start: 0, - }), - { result: { a: 1 }, index: 2, done: false, rest: ["foo"] } - ); - assert.deepStrictEqual( - parse<{ a?: number }>( - { a: int({}) }, - ["--a", "1", "--", "ignore"], - { - start: 0, - } - ), - { result: { a: 1 }, index: 3, done: false, rest: ["ignore"] } - ); - }, + "stop early": () => { + assert.deepStrictEqual( + parse<{ a?: number }>({ a: int({}) }, ["--a", "1", "foo"], { + start: 0, + }), + { result: { a: 1 }, index: 2, done: false, rest: ["foo"] } + ); + assert.deepStrictEqual( + parse<{ a?: number }>( + { a: int({}) }, + ["--a", "1", "--", "ignore"], + { + start: 0, + } + ), + { result: { a: 1 }, index: 3, done: false, rest: ["ignore"] } + ); + }, - "long alias": () => { - assert.deepStrictEqual( - parse<{ a?: string }>( - { a: string({ alias: "aaa" }) }, - ["-aaa", "a"], - { - start: 0, - } - ), - { result: { a: "a" }, index: 2, done: true, rest: [] } - ); - }, + "long alias": () => { + assert.deepStrictEqual( + parse<{ a?: string }>( + { a: string({ alias: "aaa" }) }, + ["-aaa", "a"], + { + start: 0, + } + ), + { result: { a: "a" }, index: 2, done: true, rest: [] } + ); + }, }); diff --git a/packages/args/tsconfig.json b/packages/args/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/args/tsconfig.json +++ b/packages/args/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/arrays/api-extractor.json b/packages/arrays/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/arrays/api-extractor.json +++ b/packages/arrays/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/arrays/package.json b/packages/arrays/package.json index 5b37bcd60e..851ca8a0e5 100644 --- a/packages/arrays/package.json +++ b/packages/arrays/package.json @@ -1,149 +1,149 @@ { - "name": "@thi.ng/arrays", - "version": "2.3.1", - "description": "Array / Arraylike utilities", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/arrays#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/compare": "^2.1.8", - "@thi.ng/equiv": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/random": "^3.3.3" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "aos", - "array", - "binary", - "fuzzy", - "search", - "shuffle", - "swizzle", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./arg-sort": { - "default": "./arg-sort.js" - }, - "./binary-search": { - "default": "./binary-search.js" - }, - "./bisect": { - "default": "./bisect.js" - }, - "./blit": { - "default": "./blit.js" - }, - "./ends-with": { - "default": "./ends-with.js" - }, - "./ensure-array": { - "default": "./ensure-array.js" - }, - "./ensure-iterable": { - "default": "./ensure-iterable.js" - }, - "./fill-range": { - "default": "./fill-range.js" - }, - "./find": { - "default": "./find.js" - }, - "./fuzzy-match": { - "default": "./fuzzy-match.js" - }, - "./insert": { - "default": "./insert.js" - }, - "./into": { - "default": "./into.js" - }, - "./is-sorted": { - "default": "./is-sorted.js" - }, - "./iterator": { - "default": "./iterator.js" - }, - "./levenshtein": { - "default": "./levenshtein.js" - }, - "./peek": { - "default": "./peek.js" - }, - "./quicksort": { - "default": "./quicksort.js" - }, - "./shuffle": { - "default": "./shuffle.js" - }, - "./sort-cached": { - "default": "./sort-cached.js" - }, - "./starts-with": { - "default": "./starts-with.js" - }, - "./swap": { - "default": "./swap.js" - }, - "./swizzle": { - "default": "./swizzle.js" - } - }, - "thi.ng": { - "year": 2018 - } + "name": "@thi.ng/arrays", + "version": "2.3.1", + "description": "Array / Arraylike utilities", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/arrays#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/compare": "^2.1.8", + "@thi.ng/equiv": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/random": "^3.3.3" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "aos", + "array", + "binary", + "fuzzy", + "search", + "shuffle", + "swizzle", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./arg-sort": { + "default": "./arg-sort.js" + }, + "./binary-search": { + "default": "./binary-search.js" + }, + "./bisect": { + "default": "./bisect.js" + }, + "./blit": { + "default": "./blit.js" + }, + "./ends-with": { + "default": "./ends-with.js" + }, + "./ensure-array": { + "default": "./ensure-array.js" + }, + "./ensure-iterable": { + "default": "./ensure-iterable.js" + }, + "./fill-range": { + "default": "./fill-range.js" + }, + "./find": { + "default": "./find.js" + }, + "./fuzzy-match": { + "default": "./fuzzy-match.js" + }, + "./insert": { + "default": "./insert.js" + }, + "./into": { + "default": "./into.js" + }, + "./is-sorted": { + "default": "./is-sorted.js" + }, + "./iterator": { + "default": "./iterator.js" + }, + "./levenshtein": { + "default": "./levenshtein.js" + }, + "./peek": { + "default": "./peek.js" + }, + "./quicksort": { + "default": "./quicksort.js" + }, + "./shuffle": { + "default": "./shuffle.js" + }, + "./sort-cached": { + "default": "./sort-cached.js" + }, + "./starts-with": { + "default": "./starts-with.js" + }, + "./swap": { + "default": "./swap.js" + }, + "./swizzle": { + "default": "./swizzle.js" + } + }, + "thi.ng": { + "year": 2018 + } } diff --git a/packages/arrays/src/arg-sort.ts b/packages/arrays/src/arg-sort.ts index ee175b2026..15818d4826 100644 --- a/packages/arrays/src/arg-sort.ts +++ b/packages/arrays/src/arg-sort.ts @@ -30,4 +30,4 @@ import { sortByCachedKey } from "./sort-cached.js"; * @param cmp - */ export const argSort = (src: T[], cmp = compare) => - sortByCachedKey(fillRange(new Array(src.length)), src.slice(), cmp); + sortByCachedKey(fillRange(new Array(src.length)), src.slice(), cmp); diff --git a/packages/arrays/src/binary-search.ts b/packages/arrays/src/binary-search.ts index a026016cbc..c7f53be8e7 100644 --- a/packages/arrays/src/binary-search.ts +++ b/packages/arrays/src/binary-search.ts @@ -33,26 +33,26 @@ import { compareNumAsc } from "@thi.ng/compare/numeric"; * @param high - max index */ export const binarySearch = ( - buf: ArrayLike, - x: A, - key: Fn = (x) => x, - cmp: Comparator = compare, - low = 0, - high = buf.length - 1 + buf: ArrayLike, + x: A, + key: Fn = (x) => x, + cmp: Comparator = compare, + low = 0, + high = buf.length - 1 ) => { - const kx = key(x); - while (low <= high) { - const mid = (low + high) >>> 1; - const c = cmp(key(buf[mid]), kx); - if (c < 0) { - low = mid + 1; - } else if (c > 0) { - high = mid - 1; - } else { - return mid; - } - } - return -low - 1; + const kx = key(x); + while (low <= high) { + const mid = (low + high) >>> 1; + const c = cmp(key(buf[mid]), kx); + if (c < 0) { + low = mid + 1; + } else if (c > 0) { + high = mid - 1; + } else { + return mid; + } + } + return -low - 1; }; /** @@ -67,29 +67,29 @@ export const binarySearch = ( * @param high - max index */ export const binarySearchNumeric = ( - buf: ArrayLike, - x: number, - cmp: Comparator = compareNumAsc, - low = 0, - high = buf.length - 1 + buf: ArrayLike, + x: number, + cmp: Comparator = compareNumAsc, + low = 0, + high = buf.length - 1 ) => { - while (low <= high) { - const mid = (low + high) >>> 1; - const c = cmp(buf[mid], x); - if (c < 0) { - low = mid + 1; - } else if (c > 0) { - high = mid - 1; - } else { - return mid; - } - } - return -low - 1; + while (low <= high) { + const mid = (low + high) >>> 1; + const c = cmp(buf[mid], x); + if (c < 0) { + low = mid + 1; + } else if (c > 0) { + high = mid - 1; + } else { + return mid; + } + } + return -low - 1; }; export const binarySearch2 = (buf: ArrayLike, x: number) => { - let idx = buf[1] <= x ? 1 : 0; - return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; + let idx = buf[1] <= x ? 1 : 0; + return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; }; /** @@ -100,9 +100,9 @@ export const binarySearch2 = (buf: ArrayLike, x: number) => { * @param x - */ export const binarySearch4 = (buf: ArrayLike, x: number) => { - let idx = buf[2] <= x ? 2 : 0; - idx |= buf[idx + 1] <= x ? 1 : 0; - return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; + let idx = buf[2] <= x ? 2 : 0; + idx |= buf[idx + 1] <= x ? 1 : 0; + return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; }; /** @@ -113,10 +113,10 @@ export const binarySearch4 = (buf: ArrayLike, x: number) => { * @param x - */ export const binarySearch8 = (buf: ArrayLike, x: number) => { - let idx = buf[4] <= x ? 4 : 0; - idx |= buf[idx + 2] <= x ? 2 : 0; - idx |= buf[idx + 1] <= x ? 1 : 0; - return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; + let idx = buf[4] <= x ? 4 : 0; + idx |= buf[idx + 2] <= x ? 2 : 0; + idx |= buf[idx + 1] <= x ? 1 : 0; + return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; }; /** @@ -127,11 +127,11 @@ export const binarySearch8 = (buf: ArrayLike, x: number) => { * @param x - */ export const binarySearch16 = (buf: ArrayLike, x: number) => { - let idx = buf[8] <= x ? 8 : 0; - idx |= buf[idx + 4] <= x ? 4 : 0; - idx |= buf[idx + 2] <= x ? 2 : 0; - idx |= buf[idx + 1] <= x ? 1 : 0; - return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; + let idx = buf[8] <= x ? 8 : 0; + idx |= buf[idx + 4] <= x ? 4 : 0; + idx |= buf[idx + 2] <= x ? 2 : 0; + idx |= buf[idx + 1] <= x ? 1 : 0; + return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; }; /** @@ -142,12 +142,12 @@ export const binarySearch16 = (buf: ArrayLike, x: number) => { * @param x - */ export const binarySearch32 = (buf: ArrayLike, x: number) => { - let idx = buf[16] <= x ? 16 : 0; - idx |= buf[idx + 4] <= x ? 8 : 0; - idx |= buf[idx + 4] <= x ? 4 : 0; - idx |= buf[idx + 2] <= x ? 2 : 0; - idx |= buf[idx + 1] <= x ? 1 : 0; - return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; + let idx = buf[16] <= x ? 16 : 0; + idx |= buf[idx + 4] <= x ? 8 : 0; + idx |= buf[idx + 4] <= x ? 4 : 0; + idx |= buf[idx + 2] <= x ? 2 : 0; + idx |= buf[idx + 1] <= x ? 1 : 0; + return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; }; /** @@ -192,7 +192,7 @@ export const bsLE: FnN = (i) => (i < 0 ? -i - 2 : i); * @param n - array length */ export const bsGT: FnN2 = (i, n) => ( - (i = i < 0 ? -i - 1 : i + 1), i < n ? i : -1 + (i = i < 0 ? -i - 1 : i + 1), i < n ? i : -1 ); /** diff --git a/packages/arrays/src/bisect.ts b/packages/arrays/src/bisect.ts index 2cfec96d01..a49f4e8884 100644 --- a/packages/arrays/src/bisect.ts +++ b/packages/arrays/src/bisect.ts @@ -7,8 +7,8 @@ import type { Predicate } from "@thi.ng/api"; * @param i - */ export const bisect = (src: T[], i = src.length >>> 1) => [ - src.slice(0, i), - src.slice(i), + src.slice(0, i), + src.slice(i), ]; /** @@ -21,6 +21,6 @@ export const bisect = (src: T[], i = src.length >>> 1) => [ * @param pred - */ export const bisectWith = (src: T[], pred: Predicate) => { - const i = src.findIndex(pred); - return i >= 0 ? bisect(src, i) : [src, []]; + const i = src.findIndex(pred); + return i >= 0 ? bisect(src, i) : [src, []]; }; diff --git a/packages/arrays/src/blit.ts b/packages/arrays/src/blit.ts index 22bebd77eb..b29e73bac9 100644 --- a/packages/arrays/src/blit.ts +++ b/packages/arrays/src/blit.ts @@ -30,25 +30,25 @@ import type { TypedArray } from "@thi.ng/api"; * @param mask */ export function blit1d( - dest: T, - dx: number, - src: ArrayLike, - mask: number + dest: T, + dx: number, + src: ArrayLike, + mask: number ): T; export function blit1d( - dest: T[], - dx: number, - src: ArrayLike, - mask: T + dest: T[], + dx: number, + src: ArrayLike, + mask: T ): T[]; export function blit1d(dest: any[], x: number, src: ArrayLike, mask: any) { - const [sx, sw, dx, dw] = __clip(0, src.length, x, dest.length); - if (sw < 1 || dx >= dw) return dest; - for (let i = 0; i < sw; i++) { - const val = src[sx + i]; - val !== mask && (dest[dx + i] = val); - } - return dest; + const [sx, sw, dx, dw] = __clip(0, src.length, x, dest.length); + if (sw < 1 || dx >= dw) return dest; + for (let i = 0; i < sw; i++) { + const val = src[sx + i]; + val !== mask && (dest[dx + i] = val); + } + return dest; } /** @@ -63,56 +63,56 @@ export function blit1d(dest: any[], x: number, src: ArrayLike, mask: any) { * @param mask */ export function blit2d( - dest: T, - dpos: ArrayLike, - dsize: ArrayLike, - src: ArrayLike, - ssize: ArrayLike, - mask: number + dest: T, + dpos: ArrayLike, + dsize: ArrayLike, + src: ArrayLike, + ssize: ArrayLike, + mask: number ): T; export function blit2d( - dest: T[], - dpos: ArrayLike, - dsize: ArrayLike, - src: ArrayLike, - ssize: ArrayLike, - mask: T + dest: T[], + dpos: ArrayLike, + dsize: ArrayLike, + src: ArrayLike, + ssize: ArrayLike, + mask: T ): T[]; export function blit2d( - dest: any[], - dpos: ArrayLike, - dsize: ArrayLike, - src: ArrayLike, - ssize: ArrayLike, - mask: any + dest: any[], + dpos: ArrayLike, + dsize: ArrayLike, + src: ArrayLike, + ssize: ArrayLike, + mask: any ) { - const [sx, sw, dx, dw] = __clip(0, ssize[0], dpos[0], dsize[0]); - const [sy, sh, dy, dh] = __clip(0, ssize[1], dpos[1], dsize[1]); - if (sw < 1 || sh < 1 || dx >= dw || dy >= dh) return dest; - const sstride = ssize[0]; - const dstride = dsize[0]; - for (let y = 0; y < sh; y++) { - for ( - let x = 0, - soff = (sy + y) * sstride + sx, - doff = (dy + y) * dstride + dx; - x < sw; - x++ - ) { - const val = src[soff + x]; - val !== mask && (dest[doff + x] = val); - } - } - return dest; + const [sx, sw, dx, dw] = __clip(0, ssize[0], dpos[0], dsize[0]); + const [sy, sh, dy, dh] = __clip(0, ssize[1], dpos[1], dsize[1]); + if (sw < 1 || sh < 1 || dx >= dw || dy >= dh) return dest; + const sstride = ssize[0]; + const dstride = dsize[0]; + for (let y = 0; y < sh; y++) { + for ( + let x = 0, + soff = (sy + y) * sstride + sx, + doff = (dy + y) * dstride + dx; + x < sw; + x++ + ) { + const val = src[soff + x]; + val !== mask && (dest[doff + x] = val); + } + } + return dest; } const __clip = (sx: number, sw: number, dx: number, dw: number) => { - if (dx < 0) { - sx -= dx; - sw += dx; - dx = 0; - } else if (dx + sw > dw) { - sw = dw - dx; - } - return [sx, sw, dx, dw]; + if (dx < 0) { + sx -= dx; + sw += dx; + dx = 0; + } else if (dx + sw > dw) { + sw = dw - dx; + } + return [sx, sw, dx, dw]; }; diff --git a/packages/arrays/src/ends-with.ts b/packages/arrays/src/ends-with.ts index a5ea0a5379..3f273ef12f 100644 --- a/packages/arrays/src/ends-with.ts +++ b/packages/arrays/src/ends-with.ts @@ -17,13 +17,13 @@ import { equiv as _eq } from "@thi.ng/equiv"; * @param equiv - equivalence predicate */ export const endsWith = ( - buf: ArrayLike, - needle: ArrayLike, - equiv = _eq + buf: ArrayLike, + needle: ArrayLike, + equiv = _eq ) => { - let i = buf.length; - let j = needle.length; - if (i < j) return false; - while ((--i, j-- > 0 && equiv(buf[i], needle[j]))) {} - return j < 0; + let i = buf.length; + let j = needle.length; + if (i < j) return false; + while ((--i, j-- > 0 && equiv(buf[i], needle[j]))) {} + return j < 0; }; diff --git a/packages/arrays/src/ensure-array.ts b/packages/arrays/src/ensure-array.ts index 58e5f5a5c5..44e71656ab 100644 --- a/packages/arrays/src/ensure-array.ts +++ b/packages/arrays/src/ensure-array.ts @@ -14,7 +14,7 @@ import { ensureIterable } from "./ensure-iterable.js"; * @param x - */ export const ensureArray = (x: any): any[] => - isArray(x) ? x : [...ensureIterable(x)]; + isArray(x) ? x : [...ensureIterable(x)]; /** * Similar to {@link ensureArray}, but for `ArrayLike` types. @@ -24,4 +24,4 @@ export const ensureArray = (x: any): any[] => * @param x - */ export const ensureArrayLike = (x: any): ArrayLike => - isArrayLike(x) ? x : [...ensureIterable(x)]; + isArrayLike(x) ? x : [...ensureIterable(x)]; diff --git a/packages/arrays/src/ensure-iterable.ts b/packages/arrays/src/ensure-iterable.ts index c85a440307..de74e90f23 100644 --- a/packages/arrays/src/ensure-iterable.ts +++ b/packages/arrays/src/ensure-iterable.ts @@ -7,7 +7,7 @@ import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; * @param x - */ export const ensureIterable = (x: any): Iterable => { - (x == null || !x[Symbol.iterator]) && - illegalArgs(`value is not iterable: ${x}`); - return x; + (x == null || !x[Symbol.iterator]) && + illegalArgs(`value is not iterable: ${x}`); + return x; }; diff --git a/packages/arrays/src/fill-range.ts b/packages/arrays/src/fill-range.ts index f20803e5f6..5a946bc78c 100644 --- a/packages/arrays/src/fill-range.ts +++ b/packages/arrays/src/fill-range.ts @@ -24,16 +24,16 @@ import type { TypedArray } from "@thi.ng/api"; * @param step - */ export const fillRange = ( - buf: T, - index = 0, - start = 0, - end = buf.length, - step = end > start ? 1 : -1 + buf: T, + index = 0, + start = 0, + end = buf.length, + step = end > start ? 1 : -1 ) => { - if (step > 0) { - for (; start < end; start += step) buf[index++] = start; - } else { - for (; start > end; start += step) buf[index++] = start; - } - return buf; + if (step > 0) { + for (; start < end; start += step) buf[index++] = start; + } else { + for (; start > end; start += step) buf[index++] = start; + } + return buf; }; diff --git a/packages/arrays/src/find.ts b/packages/arrays/src/find.ts index 1fd25d7472..2858fdb0db 100644 --- a/packages/arrays/src/find.ts +++ b/packages/arrays/src/find.ts @@ -10,12 +10,12 @@ import { equiv as _equiv } from "@thi.ng/equiv"; * @param equiv - equivalence predicate */ export const find = ( - buf: ArrayLike, - x: T, - equiv: Predicate2 = _equiv + buf: ArrayLike, + x: T, + equiv: Predicate2 = _equiv ) => { - const i = findIndex(buf, x, equiv); - return i !== -1 ? buf[i] : undefined; + const i = findIndex(buf, x, equiv); + return i !== -1 ? buf[i] : undefined; }; /** @@ -27,12 +27,12 @@ export const find = ( * @param equiv - equivalence predicate */ export const findIndex = ( - buf: ArrayLike, - x: T, - equiv: Predicate2 = _equiv + buf: ArrayLike, + x: T, + equiv: Predicate2 = _equiv ) => { - for (let i = buf.length; i-- > 0; ) { - if (equiv(x, buf[i])) return i; - } - return -1; + for (let i = buf.length; i-- > 0; ) { + if (equiv(x, buf[i])) return i; + } + return -1; }; diff --git a/packages/arrays/src/fuzzy-match.ts b/packages/arrays/src/fuzzy-match.ts index bdc99db5f1..a80b434242 100644 --- a/packages/arrays/src/fuzzy-match.ts +++ b/packages/arrays/src/fuzzy-match.ts @@ -19,26 +19,26 @@ import { equiv as _eq } from "@thi.ng/equiv"; * @param equiv - equivalence predicate */ export const fuzzyMatch = ( - domain: ArrayLike, - query: ArrayLike, - equiv: Predicate2 = _eq + domain: ArrayLike, + query: ArrayLike, + equiv: Predicate2 = _eq ) => { - const nd = domain.length; - const nq = query.length; - if (nq > nd) { - return false; - } - if (nq === nd) { - return equiv(query, domain); - } - next: for (let i = 0, j = 0; i < nq; i++) { - const q = query[i]; - while (j < nd) { - if (equiv(domain[j++], q)) { - continue next; - } - } - return false; - } - return true; + const nd = domain.length; + const nq = query.length; + if (nq > nd) { + return false; + } + if (nq === nd) { + return equiv(query, domain); + } + next: for (let i = 0, j = 0; i < nq; i++) { + const q = query[i]; + while (j < nd) { + if (equiv(domain[j++], q)) { + continue next; + } + } + return false; + } + return true; }; diff --git a/packages/arrays/src/insert.ts b/packages/arrays/src/insert.ts index 0dd2b05e67..a10daf29dd 100644 --- a/packages/arrays/src/insert.ts +++ b/packages/arrays/src/insert.ts @@ -18,7 +18,7 @@ * @param k - */ export const insert = (buf: T[], x: T, i: number, k = Infinity) => - i < 0 || i >= k || k < 1 ? buf : insertUnsafe(buf, x, i, k); + i < 0 || i >= k || k < 1 ? buf : insertUnsafe(buf, x, i, k); /** * Same as {@link insert} but without any bounds/index checks. @@ -29,8 +29,8 @@ export const insert = (buf: T[], x: T, i: number, k = Infinity) => * @param k - */ export const insertUnsafe = (buf: T[], x: T, i: number, k = Infinity) => { - let j = buf.length < k ? buf.length + 1 : k; - for (; --j > i; ) buf[j] = buf[j - 1]; - buf[i] = x; - return buf; + let j = buf.length < k ? buf.length + 1 : k; + for (; --j > i; ) buf[j] = buf[j - 1]; + buf[i] = x; + return buf; }; diff --git a/packages/arrays/src/into.ts b/packages/arrays/src/into.ts index 6fb052bb2b..bdccaf5e6b 100644 --- a/packages/arrays/src/into.ts +++ b/packages/arrays/src/into.ts @@ -7,9 +7,9 @@ * @param max - */ export const into = (dest: T[], src: Iterable, max = Infinity) => { - for (let x of src) { - if (--max < 0) break; - dest.push(x); - } - return dest; + for (let x of src) { + if (--max < 0) break; + dest.push(x); + } + return dest; }; diff --git a/packages/arrays/src/is-sorted.ts b/packages/arrays/src/is-sorted.ts index cd2e1411be..1071b516e9 100644 --- a/packages/arrays/src/is-sorted.ts +++ b/packages/arrays/src/is-sorted.ts @@ -26,16 +26,16 @@ import { compare } from "@thi.ng/compare/compare"; * @param end - end index */ export const isSorted = ( - arr: ArrayLike, - cmp: Comparator = compare, - start = 0, - end = arr.length + arr: ArrayLike, + cmp: Comparator = compare, + start = 0, + end = arr.length ) => { - let prev = arr[start]; - while (++start < end) { - const curr = arr[start]; - if (cmp(prev, curr) > 0) return false; - prev = curr; - } - return true; + let prev = arr[start]; + while (++start < end) { + const curr = arr[start]; + if (cmp(prev, curr) > 0) return false; + prev = curr; + } + return true; }; diff --git a/packages/arrays/src/iterator.ts b/packages/arrays/src/iterator.ts index bd5bbb2b0f..848fbaecf0 100644 --- a/packages/arrays/src/iterator.ts +++ b/packages/arrays/src/iterator.ts @@ -14,15 +14,15 @@ import type { Nullable } from "@thi.ng/api"; * @param end - end index (excluded) */ export function* arrayIterator( - buf: Nullable>, - start = 0, - end?: number + buf: Nullable>, + start = 0, + end?: number ) { - if (!buf) return; - start = start; - end === undefined && (end = buf.length); - const step = start <= end ? 1 : -1; - for (; start !== end; start += step) { - yield buf[start]; - } + if (!buf) return; + start = start; + end === undefined && (end = buf.length); + const step = start <= end ? 1 : -1; + for (; start !== end; start += step) { + yield buf[start]; + } } diff --git a/packages/arrays/src/levenshtein.ts b/packages/arrays/src/levenshtein.ts index ff6ece6efa..20a4bcdfe6 100644 --- a/packages/arrays/src/levenshtein.ts +++ b/packages/arrays/src/levenshtein.ts @@ -37,108 +37,108 @@ const eqStrict = (a: any, b: any) => a === b; * @param equiv - */ export const levenshtein = ( - a: ArrayLike, - b: ArrayLike, - maxDist = Infinity, - equiv: Predicate2 = eqStrict + a: ArrayLike, + b: ArrayLike, + maxDist = Infinity, + equiv: Predicate2 = eqStrict ): number => { - if (a === b) { - return 0; - } - if (a.length > b.length) { - const tmp = a; - a = b; - b = tmp; - } + if (a === b) { + return 0; + } + if (a.length > b.length) { + const tmp = a; + a = b; + b = tmp; + } - let la = a.length; - let lb = b.length; - while (la > 0 && equiv(a[~-la], b[~-lb])) { - la--; - lb--; - } + let la = a.length; + let lb = b.length; + while (la > 0 && equiv(a[~-la], b[~-lb])) { + la--; + lb--; + } - let offset = 0; - while (offset < la && equiv(a[offset], b[offset])) { - offset++; - } + let offset = 0; + while (offset < la && equiv(a[offset], b[offset])) { + offset++; + } - la -= offset; - lb -= offset; - if (la === 0 || lb < 3) { - return lb; - } + la -= offset; + lb -= offset; + if (la === 0 || lb < 3) { + return lb; + } - let x = 0; - let y: number; - let minDist: number; - let d0: number; - let d1: number; - let d2: number; - let d3: number; - let dd: number; - let dy: number; - let ay: T; - let bx0: T; - let bx1: T; - let bx2: T; - let bx3: T; + let x = 0; + let y: number; + let minDist: number; + let d0: number; + let d1: number; + let d2: number; + let d3: number; + let dd: number; + let dy: number; + let ay: T; + let bx0: T; + let bx1: T; + let bx2: T; + let bx3: T; - const _min = (d0: number, d1: number, d2: number, bx: T, ay: T) => { - return d0 < d1 || d2 < d1 - ? d0 > d2 - ? d2 + 1 - : d0 + 1 - : equiv(ay, bx) - ? d1 - : d1 + 1; - }; + const _min = (d0: number, d1: number, d2: number, bx: T, ay: T) => { + return d0 < d1 || d2 < d1 + ? d0 > d2 + ? d2 + 1 + : d0 + 1 + : equiv(ay, bx) + ? d1 + : d1 + 1; + }; - const vector: (T | number)[] = []; - for (y = 0; y < la; y++) { - vector.push(y + 1, a[offset + y]); - } + const vector: (T | number)[] = []; + for (y = 0; y < la; y++) { + vector.push(y + 1, a[offset + y]); + } - const len = vector.length - 1; - const lb3 = lb - 3; - for (; x < lb3; ) { - bx0 = b[offset + (d0 = x)]; - bx1 = b[offset + (d1 = x + 1)]; - bx2 = b[offset + (d2 = x + 2)]; - bx3 = b[offset + (d3 = x + 3)]; - dd = x += 4; - minDist = Infinity; - for (y = 0; y < len; y += 2) { - dy = vector[y]; - ay = vector[y + 1]; - d0 = _min(dy, d0, d1, bx0, ay); - d1 = _min(d0, d1, d2, bx1, ay); - d2 = _min(d1, d2, d3, bx2, ay); - dd = _min(d2, d3, dd, bx3, ay); - dd < minDist && (minDist = dd); - vector[y] = dd; - d3 = d2; - d2 = d1; - d1 = d0; - d0 = dy; - } - if (minDist > maxDist) return Infinity; - } + const len = vector.length - 1; + const lb3 = lb - 3; + for (; x < lb3; ) { + bx0 = b[offset + (d0 = x)]; + bx1 = b[offset + (d1 = x + 1)]; + bx2 = b[offset + (d2 = x + 2)]; + bx3 = b[offset + (d3 = x + 3)]; + dd = x += 4; + minDist = Infinity; + for (y = 0; y < len; y += 2) { + dy = vector[y]; + ay = vector[y + 1]; + d0 = _min(dy, d0, d1, bx0, ay); + d1 = _min(d0, d1, d2, bx1, ay); + d2 = _min(d1, d2, d3, bx2, ay); + dd = _min(d2, d3, dd, bx3, ay); + dd < minDist && (minDist = dd); + vector[y] = dd; + d3 = d2; + d2 = d1; + d1 = d0; + d0 = dy; + } + if (minDist > maxDist) return Infinity; + } - for (; x < lb; ) { - bx0 = b[offset + (d0 = x)]; - dd = ++x; - minDist = Infinity; - for (y = 0; y < len; y += 2) { - dy = vector[y]; - vector[y] = dd = _min(dy, d0, dd, bx0, vector[y + 1]); - dd < minDist && (minDist = dd); - d0 = dy; - } - if (minDist > maxDist) return Infinity; - } + for (; x < lb; ) { + bx0 = b[offset + (d0 = x)]; + dd = ++x; + minDist = Infinity; + for (y = 0; y < len; y += 2) { + dy = vector[y]; + vector[y] = dd = _min(dy, d0, dd, bx0, vector[y + 1]); + dd < minDist && (minDist = dd); + d0 = dy; + } + if (minDist > maxDist) return Infinity; + } - return dd!; + return dd!; }; /** @@ -152,11 +152,11 @@ export const levenshtein = ( * @param equiv - */ export const normalizedLevenshtein = ( - a: ArrayLike, - b: ArrayLike, - maxDist = Infinity, - equiv = eqStrict + a: ArrayLike, + b: ArrayLike, + maxDist = Infinity, + equiv = eqStrict ): number => { - const n = Math.max(a.length, b.length); - return n > 0 ? levenshtein(a, b, maxDist, equiv) / n : 0; + const n = Math.max(a.length, b.length); + return n > 0 ? levenshtein(a, b, maxDist, equiv) / n : 0; }; diff --git a/packages/arrays/src/quicksort.ts b/packages/arrays/src/quicksort.ts index 87616a7226..07f0459934 100644 --- a/packages/arrays/src/quicksort.ts +++ b/packages/arrays/src/quicksort.ts @@ -39,30 +39,45 @@ import { swap } from "./swap.js"; * @param start - start index * @param end - end index (inclusive) */ -// prettier-ignore -export function quickSort(arr: T[], _cmp?: Comparator, _swap?: Fn3, start?: number, end?: number): T[]; -// prettier-ignore -export function quickSort(arr: T, _cmp?: Comparator, _swap?: Fn3, start?: number, end?: number): T; -// prettier-ignore -export function quickSort(arr: any, _cmp: Comparator = compare, _swap: Fn3 = swap, start = 0, end = arr.length - 1): any { - if (start < end) { - const pivot = arr[start + ((end - start) >> 1)]; - let s = start - 1; - let e = end + 1; +export function quickSort( + arr: T[], + _cmp?: Comparator, + _swap?: Fn3, + start?: number, + end?: number +): T[]; +export function quickSort( + arr: T, + _cmp?: Comparator, + _swap?: Fn3, + start?: number, + end?: number +): T; +export function quickSort( + arr: any, + _cmp: Comparator = compare, + _swap: Fn3 = swap, + start = 0, + end = arr.length - 1 +): any { + if (start < end) { + const pivot = arr[start + ((end - start) >> 1)]; + let s = start - 1; + let e = end + 1; - while (true) { - do { - s++; - } while (_cmp(arr[s], pivot) < 0); - do { - e--; - } while (_cmp(arr[e], pivot) > 0); - if (s >= e) break; - _swap(arr, s, e); - } + while (true) { + do { + s++; + } while (_cmp(arr[s], pivot) < 0); + do { + e--; + } while (_cmp(arr[e], pivot) > 0); + if (s >= e) break; + _swap(arr, s, e); + } - quickSort(arr, _cmp, _swap, start, e); - quickSort(arr, _cmp, _swap, e + 1, end); - } - return arr; + quickSort(arr, _cmp, _swap, start, e); + quickSort(arr, _cmp, _swap, e + 1, end); + } + return arr; } diff --git a/packages/arrays/src/shuffle.ts b/packages/arrays/src/shuffle.ts index e180f7dded..ecc760bfc3 100644 --- a/packages/arrays/src/shuffle.ts +++ b/packages/arrays/src/shuffle.ts @@ -19,24 +19,24 @@ import type { AnyArray } from "./api.js"; * @param rnd - PRNG */ export const shuffleRange = ( - buf: T, - start = 0, - end = buf.length, - rnd: IRandom = SYSTEM + buf: T, + start = 0, + end = buf.length, + rnd: IRandom = SYSTEM ) => { - assert( - start >= 0 && end >= start && end <= buf.length, - `illegal range ${start}..${end}` - ); - if (end - start > 1) { - for (let i = end; i-- > start; ) { - const a = rnd.minmax(start, i + 1) | 0; - const t = buf[a]; - buf[a] = buf[i]; - buf[i] = t; - } - } - return buf; + assert( + start >= 0 && end >= start && end <= buf.length, + `illegal range ${start}..${end}` + ); + if (end - start > 1) { + for (let i = end; i-- > start; ) { + const a = rnd.minmax(start, i + 1) | 0; + const t = buf[a]; + buf[a] = buf[i]; + buf[i] = t; + } + } + return buf; }; /** @@ -50,7 +50,7 @@ export const shuffleRange = ( * @param rnd - PRNG */ export const shuffle = ( - buf: T, - n = buf.length, - rnd: IRandom = SYSTEM + buf: T, + n = buf.length, + rnd: IRandom = SYSTEM ) => shuffleRange(buf, 0, n, rnd); diff --git a/packages/arrays/src/sort-cached.ts b/packages/arrays/src/sort-cached.ts index aafedc17bc..5ab1f938dd 100644 --- a/packages/arrays/src/sort-cached.ts +++ b/packages/arrays/src/sort-cached.ts @@ -33,12 +33,12 @@ import { multiSwap } from "./swap.js"; * @param cmp - */ export const sortByCachedKey = ( - src: T[], - key: K[] | Fn, - cmp: Comparator = compare + src: T[], + key: K[] | Fn, + cmp: Comparator = compare ) => { - const keys = isFunction(key) ? src.map(key) : key; - assert(keys.length === src.length, `keys.length != src.length`); - quickSort(keys, cmp, multiSwap(src)); - return src; + const keys = isFunction(key) ? src.map(key) : key; + assert(keys.length === src.length, `keys.length != src.length`); + quickSort(keys, cmp, multiSwap(src)); + return src; }; diff --git a/packages/arrays/src/starts-with.ts b/packages/arrays/src/starts-with.ts index ac27ea142a..c580663dab 100644 --- a/packages/arrays/src/starts-with.ts +++ b/packages/arrays/src/starts-with.ts @@ -17,13 +17,13 @@ import { equiv as _eq } from "@thi.ng/equiv"; * @param equiv - equivalence predicate */ export const startsWith = ( - buf: ArrayLike, - needle: ArrayLike, - equiv = _eq + buf: ArrayLike, + needle: ArrayLike, + equiv = _eq ) => { - let i = buf.length; - let j = needle.length; - if (i < j) return false; - while (-j >= 0 && equiv(buf[j], needle[j])) {} - return j < 0; + let i = buf.length; + let j = needle.length; + if (i < j) return false; + while (-j >= 0 && equiv(buf[j], needle[j])) {} + return j < 0; }; diff --git a/packages/arrays/src/swap.ts b/packages/arrays/src/swap.ts index 20cbf997c3..6a355d8f85 100644 --- a/packages/arrays/src/swap.ts +++ b/packages/arrays/src/swap.ts @@ -8,9 +8,9 @@ import type { AnyArray, SwapFn } from "./api.js"; * @param y - other index */ export const swap = (arr: AnyArray, x: number, y: number) => { - const t = arr[x]; - arr[x] = arr[y]; - arr[y] = t; + const t = arr[x]; + arr[x] = arr[y]; + arr[y] = t; }; /** @@ -43,33 +43,33 @@ export const swap = (arr: AnyArray, x: number, y: number) => { * @param xs - arrays to swap in later */ export const multiSwap = (...xs: AnyArray[]): SwapFn => { - const [b, c, d] = xs; - const n = xs.length; - switch (n) { - case 0: - return swap; - case 1: - return (a, x, y) => { - swap(a, x, y); - swap(b, x, y); - }; - case 2: - return (a, x, y) => { - swap(a, x, y); - swap(b, x, y); - swap(c, x, y); - }; - case 3: - return (a, x, y) => { - swap(a, x, y); - swap(b, x, y); - swap(c, x, y); - swap(d, x, y); - }; - default: - return (a, x, y) => { - swap(a, x, y); - for (let i = n; i-- > 0; ) swap(xs[i], x, y); - }; - } + const [b, c, d] = xs; + const n = xs.length; + switch (n) { + case 0: + return swap; + case 1: + return (a, x, y) => { + swap(a, x, y); + swap(b, x, y); + }; + case 2: + return (a, x, y) => { + swap(a, x, y); + swap(b, x, y); + swap(c, x, y); + }; + case 3: + return (a, x, y) => { + swap(a, x, y); + swap(b, x, y); + swap(c, x, y); + swap(d, x, y); + }; + default: + return (a, x, y) => { + swap(a, x, y); + for (let i = n; i-- > 0; ) swap(xs[i], x, y); + }; + } }; diff --git a/packages/arrays/src/swizzle.ts b/packages/arrays/src/swizzle.ts index 4a06d2999b..2ccf07b83d 100644 --- a/packages/arrays/src/swizzle.ts +++ b/packages/arrays/src/swizzle.ts @@ -26,33 +26,33 @@ import type { Fn } from "@thi.ng/api"; * @param order - indices */ export const swizzle = (order: string | PropertyKey[]): Fn => { - const [a, b, c, d, e, f, g, h] = order; - switch (order.length) { - case 0: - return () => []; - case 1: - return (x: any) => [x[a]]; - case 2: - return (x: any) => [x[a], x[b]]; - case 3: - return (x: any) => [x[a], x[b], x[c]]; - case 4: - return (x: any) => [x[a], x[b], x[c], x[d]]; - case 5: - return (x: any) => [x[a], x[b], x[c], x[d], x[e]]; - case 6: - return (x: any) => [x[a], x[b], x[c], x[d], x[e], x[f]]; - case 7: - return (x: any) => [x[a], x[b], x[c], x[d], x[e], x[f], x[g]]; - case 8: - return (x: any) => [x[a], x[b], x[c], x[d], x[e], x[f], x[g], x[h]]; - default: - return (x: any) => { - const res = []; - for (let i = order.length; i-- > 0; ) { - res[i] = x[order[i]]; - } - return res; - }; - } + const [a, b, c, d, e, f, g, h] = order; + switch (order.length) { + case 0: + return () => []; + case 1: + return (x: any) => [x[a]]; + case 2: + return (x: any) => [x[a], x[b]]; + case 3: + return (x: any) => [x[a], x[b], x[c]]; + case 4: + return (x: any) => [x[a], x[b], x[c], x[d]]; + case 5: + return (x: any) => [x[a], x[b], x[c], x[d], x[e]]; + case 6: + return (x: any) => [x[a], x[b], x[c], x[d], x[e], x[f]]; + case 7: + return (x: any) => [x[a], x[b], x[c], x[d], x[e], x[f], x[g]]; + case 8: + return (x: any) => [x[a], x[b], x[c], x[d], x[e], x[f], x[g], x[h]]; + default: + return (x: any) => { + const res = []; + for (let i = order.length; i-- > 0; ) { + res[i] = x[order[i]]; + } + return res; + }; + } }; diff --git a/packages/arrays/test/binary-search.ts b/packages/arrays/test/binary-search.ts index d6c8b1c567..abff1b0180 100644 --- a/packages/arrays/test/binary-search.ts +++ b/packages/arrays/test/binary-search.ts @@ -1,34 +1,34 @@ import type { FnO } from "@thi.ng/api"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { binarySearch, bsEQ, bsGE, bsGT, bsLE, bsLT } from "../src/index.js" +import { binarySearch, bsEQ, bsGE, bsGT, bsLE, bsLT } from "../src/index.js"; const src = [10, 20, 30, 40]; const tests = [5, 10, 15, 20, 25, 45]; const checkPred = (pred: FnO, res: number[]) => { - for (let i = tests.length; --i >= 0; ) { - assert.strictEqual( - pred(binarySearch(src, tests[i]), src.length), - res[i] - ); - } + for (let i = tests.length; --i >= 0; ) { + assert.strictEqual( + pred(binarySearch(src, tests[i]), src.length), + res[i] + ); + } }; group("binarySearch", { - lt: () => { - checkPred(bsLT, [-1, -1, 0, 0, 1, 3]); - }, - le: () => { - checkPred(bsLE, [-1, 0, 0, 1, 1, 3]); - }, - gt: () => { - checkPred(bsGT, [0, 1, 1, 2, 2, -1]); - }, - ge: () => { - checkPred(bsGE, [0, 0, 1, 1, 2, -1]); - }, - eq: () => { - checkPred(bsEQ, [-1, 0, -1, 1, -1, -1]); - }, + lt: () => { + checkPred(bsLT, [-1, -1, 0, 0, 1, 3]); + }, + le: () => { + checkPred(bsLE, [-1, 0, 0, 1, 1, 3]); + }, + gt: () => { + checkPred(bsGT, [0, 1, 1, 2, 2, -1]); + }, + ge: () => { + checkPred(bsGE, [0, 0, 1, 1, 2, -1]); + }, + eq: () => { + checkPred(bsEQ, [-1, 0, -1, 1, -1, -1]); + }, }); diff --git a/packages/arrays/test/iterator.ts b/packages/arrays/test/iterator.ts index 1a2720a732..bc96051c07 100644 --- a/packages/arrays/test/iterator.ts +++ b/packages/arrays/test/iterator.ts @@ -1,17 +1,17 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { arrayIterator } from "../src/index.js" +import { arrayIterator } from "../src/index.js"; group("arrayIterator", { - basics: () => { - assert.deepStrictEqual([...arrayIterator(null)], []); - assert.deepStrictEqual([...arrayIterator([])], []); - assert.deepStrictEqual([...arrayIterator([1])], [1]); - assert.deepStrictEqual([...arrayIterator([1, 2, 3, 4], 2)], [3, 4]); - assert.deepStrictEqual([...arrayIterator([1, 2, 3, 4], 2, 3)], [3]); - assert.deepStrictEqual( - [...arrayIterator([1, 2, 3, 4], 3, -1)], - [4, 3, 2, 1] - ); - }, + basics: () => { + assert.deepStrictEqual([...arrayIterator(null)], []); + assert.deepStrictEqual([...arrayIterator([])], []); + assert.deepStrictEqual([...arrayIterator([1])], [1]); + assert.deepStrictEqual([...arrayIterator([1, 2, 3, 4], 2)], [3, 4]); + assert.deepStrictEqual([...arrayIterator([1, 2, 3, 4], 2, 3)], [3]); + assert.deepStrictEqual( + [...arrayIterator([1, 2, 3, 4], 3, -1)], + [4, 3, 2, 1] + ); + }, }); diff --git a/packages/arrays/test/shuffle.ts b/packages/arrays/test/shuffle.ts index 1e7fdcc465..b034df3da9 100644 --- a/packages/arrays/test/shuffle.ts +++ b/packages/arrays/test/shuffle.ts @@ -4,25 +4,25 @@ import * as assert from "assert"; import { shuffle, shuffleRange } from "../src/index.js"; group("arrays", { - shuffle: () => { - const src = "abcdefghijklmnopqrstuvwxyz"; - const buf = [...src]; - assert.strictEqual(shuffleRange(buf, 0, 0).join(""), src); - assert.strictEqual(shuffleRange(buf, 0, 1).join(""), src); - assert.strictEqual(shuffle(buf, 0).join(""), src); - assert.strictEqual(shuffle(buf, 1).join(""), src); - assert.throws(() => shuffleRange(buf, -1)); - assert.throws(() => shuffleRange(buf, 100)); - assert.throws(() => shuffleRange(buf, 1, 0)); - assert.throws(() => shuffleRange(buf, 0, 100)); - const rnd = new XsAdd(0xdeadbeef); - assert.strictEqual( - shuffleRange(buf, 10, 20, rnd).join(""), - "abcdefghijmqrotplksnuvwxyz" - ); - assert.strictEqual( - shuffle(buf, buf.length, rnd).join(""), - "osqkrelubvwfdmanixthjzgypc" - ); - }, + shuffle: () => { + const src = "abcdefghijklmnopqrstuvwxyz"; + const buf = [...src]; + assert.strictEqual(shuffleRange(buf, 0, 0).join(""), src); + assert.strictEqual(shuffleRange(buf, 0, 1).join(""), src); + assert.strictEqual(shuffle(buf, 0).join(""), src); + assert.strictEqual(shuffle(buf, 1).join(""), src); + assert.throws(() => shuffleRange(buf, -1)); + assert.throws(() => shuffleRange(buf, 100)); + assert.throws(() => shuffleRange(buf, 1, 0)); + assert.throws(() => shuffleRange(buf, 0, 100)); + const rnd = new XsAdd(0xdeadbeef); + assert.strictEqual( + shuffleRange(buf, 10, 20, rnd).join(""), + "abcdefghijmqrotplksnuvwxyz" + ); + assert.strictEqual( + shuffle(buf, buf.length, rnd).join(""), + "osqkrelubvwfdmanixthjzgypc" + ); + }, }); diff --git a/packages/arrays/test/sort-cached.ts b/packages/arrays/test/sort-cached.ts index dd31e21656..a515d70bd0 100644 --- a/packages/arrays/test/sort-cached.ts +++ b/packages/arrays/test/sort-cached.ts @@ -1,39 +1,39 @@ import { compare, reverse } from "@thi.ng/compare"; import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { sortByCachedKey } from "../src/index.js" +import { sortByCachedKey } from "../src/index.js"; group("sortCached", { - "key fn": () => { - assert.deepStrictEqual( - sortByCachedKey(["a", "bbbb", "ccc", "dd"], (x) => x), - ["a", "bbbb", "ccc", "dd"] - ); - assert.deepStrictEqual( - sortByCachedKey( - ["a", "bbbb", "ccc", "dd"], - (x) => x, - reverse(compare) - ), - ["dd", "ccc", "bbbb", "a"] - ); - assert.deepStrictEqual( - sortByCachedKey( - ["a", "bbbb", "ccc", "dd"], - (x) => x.length, - (a, b) => b - a - ), - ["bbbb", "ccc", "dd", "a"] - ); - }, + "key fn": () => { + assert.deepStrictEqual( + sortByCachedKey(["a", "bbbb", "ccc", "dd"], (x) => x), + ["a", "bbbb", "ccc", "dd"] + ); + assert.deepStrictEqual( + sortByCachedKey( + ["a", "bbbb", "ccc", "dd"], + (x) => x, + reverse(compare) + ), + ["dd", "ccc", "bbbb", "a"] + ); + assert.deepStrictEqual( + sortByCachedKey( + ["a", "bbbb", "ccc", "dd"], + (x) => x.length, + (a, b) => b - a + ), + ["bbbb", "ccc", "dd", "a"] + ); + }, - "key array": () => { - assert.deepStrictEqual( - sortByCachedKey(["a", "b", "c", "d"], [3, 2, 1, 0]), - ["d", "c", "b", "a"] - ); - }, + "key array": () => { + assert.deepStrictEqual( + sortByCachedKey(["a", "b", "c", "d"], [3, 2, 1, 0]), + ["d", "c", "b", "a"] + ); + }, - "wrong key length": () => - assert.throws(() => sortByCachedKey(["a", "b", "c", "d"], [])), + "wrong key length": () => + assert.throws(() => sortByCachedKey(["a", "b", "c", "d"], [])), }); diff --git a/packages/arrays/tsconfig.json b/packages/arrays/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/arrays/tsconfig.json +++ b/packages/arrays/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/associative/api-extractor.json b/packages/associative/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/associative/api-extractor.json +++ b/packages/associative/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/associative/package.json b/packages/associative/package.json index 7cdad36320..5106793b33 100644 --- a/packages/associative/package.json +++ b/packages/associative/package.json @@ -1,191 +1,191 @@ { - "name": "@thi.ng/associative", - "version": "6.2.0", - "description": "Alternative Map and Set implementations with customizable equality semantics & supporting operations", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/associative#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc internal", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/arrays": "^2.3.1", - "@thi.ng/binary": "^3.3.0", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/compare": "^2.1.8", - "@thi.ng/dcons": "^3.2.7", - "@thi.ng/equiv": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/transducers": "^8.3.7", - "tslib": "^2.4.0" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "cache", - "datastructure", - "equality", - "hash", - "intersection", - "join", - "map", - "set", - "skiplist", - "sort", - "sparse", - "trie", - "typescript", - "union", - "value-semantics" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "util": false - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "internal" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./array-set": { - "default": "./array-set.js" - }, - "./bidir-index": { - "default": "./bidir-index.js" - }, - "./checks": { - "default": "./checks.js" - }, - "./common-keys": { - "default": "./common-keys.js" - }, - "./copy": { - "default": "./copy.js" - }, - "./difference": { - "default": "./difference.js" - }, - "./dissoc": { - "default": "./dissoc.js" - }, - "./empty": { - "default": "./empty.js" - }, - "./equiv-map": { - "default": "./equiv-map.js" - }, - "./first": { - "default": "./first.js" - }, - "./hash-map": { - "default": "./hash-map.js" - }, - "./indexed": { - "default": "./indexed.js" - }, - "./intersection": { - "default": "./intersection.js" - }, - "./into": { - "default": "./into.js" - }, - "./invert": { - "default": "./invert.js" - }, - "./join": { - "default": "./join.js" - }, - "./ll-set": { - "default": "./ll-set.js" - }, - "./merge-apply": { - "default": "./merge-apply.js" - }, - "./merge-deep": { - "default": "./merge-deep.js" - }, - "./merge-with": { - "default": "./merge-with.js" - }, - "./merge": { - "default": "./merge.js" - }, - "./multi-trie": { - "default": "./multi-trie.js" - }, - "./rename-keys": { - "default": "./rename-keys.js" - }, - "./select-keys": { - "default": "./select-keys.js" - }, - "./sorted-map": { - "default": "./sorted-map.js" - }, - "./sorted-obj": { - "default": "./sorted-obj.js" - }, - "./sorted-set": { - "default": "./sorted-set.js" - }, - "./sparse-set": { - "default": "./sparse-set.js" - }, - "./trie-map": { - "default": "./trie-map.js" - }, - "./union": { - "default": "./union.js" - }, - "./without-keys": { - "default": "./without-keys.js" - } - }, - "thi.ng": { - "year": 2017 - } + "name": "@thi.ng/associative", + "version": "6.2.0", + "description": "Alternative Map and Set implementations with customizable equality semantics & supporting operations", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/associative#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc internal", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/arrays": "^2.3.1", + "@thi.ng/binary": "^3.3.0", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/compare": "^2.1.8", + "@thi.ng/dcons": "^3.2.7", + "@thi.ng/equiv": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/transducers": "^8.3.7", + "tslib": "^2.4.0" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "cache", + "datastructure", + "equality", + "hash", + "intersection", + "join", + "map", + "set", + "skiplist", + "sort", + "sparse", + "trie", + "typescript", + "union", + "value-semantics" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "util": false + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "internal" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./array-set": { + "default": "./array-set.js" + }, + "./bidir-index": { + "default": "./bidir-index.js" + }, + "./checks": { + "default": "./checks.js" + }, + "./common-keys": { + "default": "./common-keys.js" + }, + "./copy": { + "default": "./copy.js" + }, + "./difference": { + "default": "./difference.js" + }, + "./dissoc": { + "default": "./dissoc.js" + }, + "./empty": { + "default": "./empty.js" + }, + "./equiv-map": { + "default": "./equiv-map.js" + }, + "./first": { + "default": "./first.js" + }, + "./hash-map": { + "default": "./hash-map.js" + }, + "./indexed": { + "default": "./indexed.js" + }, + "./intersection": { + "default": "./intersection.js" + }, + "./into": { + "default": "./into.js" + }, + "./invert": { + "default": "./invert.js" + }, + "./join": { + "default": "./join.js" + }, + "./ll-set": { + "default": "./ll-set.js" + }, + "./merge-apply": { + "default": "./merge-apply.js" + }, + "./merge-deep": { + "default": "./merge-deep.js" + }, + "./merge-with": { + "default": "./merge-with.js" + }, + "./merge": { + "default": "./merge.js" + }, + "./multi-trie": { + "default": "./multi-trie.js" + }, + "./rename-keys": { + "default": "./rename-keys.js" + }, + "./select-keys": { + "default": "./select-keys.js" + }, + "./sorted-map": { + "default": "./sorted-map.js" + }, + "./sorted-obj": { + "default": "./sorted-obj.js" + }, + "./sorted-set": { + "default": "./sorted-set.js" + }, + "./sparse-set": { + "default": "./sparse-set.js" + }, + "./trie-map": { + "default": "./trie-map.js" + }, + "./union": { + "default": "./union.js" + }, + "./without-keys": { + "default": "./without-keys.js" + } + }, + "thi.ng": { + "year": 2017 + } } diff --git a/packages/associative/src/api.ts b/packages/associative/src/api.ts index da835b49e1..a082330721 100644 --- a/packages/associative/src/api.ts +++ b/packages/associative/src/api.ts @@ -1,51 +1,51 @@ import type { - Comparator, - Fn, - IClear, - ICopy, - IEmpty, - IEquiv, - IGet, - IInto, - Predicate2, + Comparator, + Fn, + IClear, + ICopy, + IEmpty, + IEquiv, + IGet, + IInto, + Predicate2, } from "@thi.ng/api"; export interface IEquivSet - extends Set, - IClear, - ICopy>, - IEmpty>, - IEquiv, - IGet, - IInto> { - disj(xs: Iterable): this; - first(): T | undefined; + extends Set, + IClear, + ICopy>, + IEmpty>, + IEquiv, + IGet, + IInto> { + disj(xs: Iterable): this; + first(): T | undefined; } export interface EquivSetConstructor { - new (): IEquivSet; - new (values?: Iterable | null, opts?: any): IEquivSet; - readonly prototype: IEquivSet; + new (): IEquivSet; + new (values?: Iterable | null, opts?: any): IEquivSet; + readonly prototype: IEquivSet; } export interface EquivSetOpts { - /** - * Key equivalence predicate. MUST return truthy result if given - * keys are considered equal. - * - * @defaultValue {@link @thi.ng/equiv#equiv} - */ - equiv: Predicate2; + /** + * Key equivalence predicate. MUST return truthy result if given + * keys are considered equal. + * + * @defaultValue {@link @thi.ng/equiv#equiv} + */ + equiv: Predicate2; } export interface EquivMapOpts extends EquivSetOpts { - /** - * Underlying {@link IEquivSet} implementation for storing the - * unique keys of the map. - * - * @defaultValue {@link ArraySet} - */ - keys: EquivSetConstructor; + /** + * Underlying {@link IEquivSet} implementation for storing the + * unique keys of the map. + * + * @defaultValue {@link ArraySet} + */ + keys: EquivSetConstructor; } /** @@ -57,65 +57,65 @@ export type HashFn = Fn; * Creation options for HashMap class. */ export interface HashMapOpts { - /** - * Function for computing key hash codes. MUST be supplied. Only - * numeric hashes are supported. - */ - hash: HashFn; - /** - * Optional key equality predicate. - * - * @defaultValue {@link @thi.ng/equiv#equiv} - */ - equiv?: Predicate2; - /** - * Normalized max load factor in the open (0..1) interval. The map - * will be resized (doubled in size) and all existing keys rehashed - * every time a new key is to be added and the current size exceeds - * this normalized load. - * - * @defaultValue 0.75 - */ - load?: number; - /** - * Initial capacity. Will be rounded up to next power of 2. - * - * @defaultValue 16 - */ - cap?: number; + /** + * Function for computing key hash codes. MUST be supplied. Only + * numeric hashes are supported. + */ + hash: HashFn; + /** + * Optional key equality predicate. + * + * @defaultValue {@link @thi.ng/equiv#equiv} + */ + equiv?: Predicate2; + /** + * Normalized max load factor in the open (0..1) interval. The map + * will be resized (doubled in size) and all existing keys rehashed + * every time a new key is to be added and the current size exceeds + * this normalized load. + * + * @defaultValue 0.75 + */ + load?: number; + /** + * Initial capacity. Will be rounded up to next power of 2. + * + * @defaultValue 16 + */ + cap?: number; } /** * SortedMapOpts implementation config settings. */ export interface SortedMapOpts { - /** - * Key comparison function. Must follow standard comparator contract - * and return: - * - negative if `a < b` - * - positive if `a > b` - * - `0` if `a == b` - * - * Note: The {@link SortedMap} implementation only uses `<` and `==` style - * comparisons. - * - * @defaultValue {@link @thi.ng/compare#compare} - */ - compare: Comparator; - /** - * Initial capacity before resizing (doubling) occurs. - * This value will be rounded up to next pow2. - * - * @defaultValue 8 - */ - capacity: number; - /** - * Probability for a value to exist in any express lane of the - * underlying Skip List implementation. - * - * @defaultValue `1 / Math.E` - */ - probability: number; + /** + * Key comparison function. Must follow standard comparator contract + * and return: + * - negative if `a < b` + * - positive if `a > b` + * - `0` if `a == b` + * + * Note: The {@link SortedMap} implementation only uses `<` and `==` style + * comparisons. + * + * @defaultValue {@link @thi.ng/compare#compare} + */ + compare: Comparator; + /** + * Initial capacity before resizing (doubling) occurs. + * This value will be rounded up to next pow2. + * + * @defaultValue 8 + */ + capacity: number; + /** + * Probability for a value to exist in any express lane of the + * underlying Skip List implementation. + * + * @defaultValue `1 / Math.E` + */ + probability: number; } export type SortedSetOpts = SortedMapOpts; diff --git a/packages/associative/src/array-set.ts b/packages/associative/src/array-set.ts index 7e94150bf0..a5e288eb93 100644 --- a/packages/associative/src/array-set.ts +++ b/packages/associative/src/array-set.ts @@ -9,8 +9,8 @@ import { __inspectable } from "./internal/inspect.js"; import { into } from "./into.js"; interface ArraySetProps { - vals: T[]; - equiv: Predicate2; + vals: T[]; + equiv: Predicate2; } const __private = new WeakMap, ArraySetProps>(); @@ -30,133 +30,133 @@ const __vals = (inst: ArraySet) => __private.get(inst)!.vals; */ @__inspectable export class ArraySet extends Set implements IEquivSet { - constructor( - vals?: Iterable | null, - opts: Partial> = {} - ) { - super(); - __private.set(this, { equiv: opts.equiv || equiv, vals: [] }); - vals && this.into(vals); - } - - *[Symbol.iterator](): IterableIterator { - yield* __vals(this); - } - - get [Symbol.species]() { - return ArraySet; - } - - get [Symbol.toStringTag]() { - return "ArraySet"; - } - - get size(): number { - return __vals(this).length; - } - - copy(): ArraySet { - const { equiv, vals } = __private.get(this)!; - const s = new ArraySet(null, { equiv }); - __private.get(s)!.vals = vals.slice(); - return s; - } - - empty() { - return new ArraySet(null, this.opts()); - } - - clear() { - __vals(this).length = 0; - } - - first(): T | undefined { - if (this.size) { - return __vals(this)[0]; - } - } - - add(key: T) { - !this.has(key) && __vals(this).push(key); - return this; - } - - into(keys: Iterable) { - return into(this, keys); - } - - has(key: T) { - return this.get(key, SEMAPHORE) !== SEMAPHORE; - } - - /** - * Returns the canonical value for `x`, if present. If the set - * contains no equivalent for `x`, returns `notFound`. - * - * @param key - search key - * @param notFound - default value - */ - get(key: T, notFound?: T): T | undefined { - const { equiv, vals } = __private.get(this)!; - const i = findIndex(vals, key, equiv); - return i >= 0 ? vals[i] : notFound; - } - - delete(key: T) { - const { equiv, vals } = __private.get(this)!; - for (let i = vals.length; i-- > 0; ) { - if (equiv(vals[i], key)) { - vals.splice(i, 1); - return true; - } - } - return false; - } - - disj(keys: Iterable) { - return dissoc(this, keys); - } - - equiv(o: any) { - return __equivSet(this, o); - } - - /** - * The value args given to the callback `fn` MUST be treated as - * readonly/immutable. This could be enforced via TS, but would - * break ES6 Set interface contract. - * - * @param fn - - * @param thisArg - - */ - forEach(fn: Fn3, void>, thisArg?: any) { - const vals = __vals(this); - for (let i = vals.length; i-- > 0; ) { - const v = vals[i]; - fn.call(thisArg, v, v, this); - } - } - - *entries(): IterableIterator> { - for (let v of __vals(this)) { - yield [v, v]; - } - } - - *keys(): IterableIterator { - yield* __vals(this); - } - - *values(): IterableIterator { - yield* __vals(this); - } - - opts(): EquivSetOpts { - return { equiv: __private.get(this)!.equiv }; - } + constructor( + vals?: Iterable | null, + opts: Partial> = {} + ) { + super(); + __private.set(this, { equiv: opts.equiv || equiv, vals: [] }); + vals && this.into(vals); + } + + *[Symbol.iterator](): IterableIterator { + yield* __vals(this); + } + + get [Symbol.species]() { + return ArraySet; + } + + get [Symbol.toStringTag]() { + return "ArraySet"; + } + + get size(): number { + return __vals(this).length; + } + + copy(): ArraySet { + const { equiv, vals } = __private.get(this)!; + const s = new ArraySet(null, { equiv }); + __private.get(s)!.vals = vals.slice(); + return s; + } + + empty() { + return new ArraySet(null, this.opts()); + } + + clear() { + __vals(this).length = 0; + } + + first(): T | undefined { + if (this.size) { + return __vals(this)[0]; + } + } + + add(key: T) { + !this.has(key) && __vals(this).push(key); + return this; + } + + into(keys: Iterable) { + return into(this, keys); + } + + has(key: T) { + return this.get(key, SEMAPHORE) !== SEMAPHORE; + } + + /** + * Returns the canonical value for `x`, if present. If the set + * contains no equivalent for `x`, returns `notFound`. + * + * @param key - search key + * @param notFound - default value + */ + get(key: T, notFound?: T): T | undefined { + const { equiv, vals } = __private.get(this)!; + const i = findIndex(vals, key, equiv); + return i >= 0 ? vals[i] : notFound; + } + + delete(key: T) { + const { equiv, vals } = __private.get(this)!; + for (let i = vals.length; i-- > 0; ) { + if (equiv(vals[i], key)) { + vals.splice(i, 1); + return true; + } + } + return false; + } + + disj(keys: Iterable) { + return dissoc(this, keys); + } + + equiv(o: any) { + return __equivSet(this, o); + } + + /** + * The value args given to the callback `fn` MUST be treated as + * readonly/immutable. This could be enforced via TS, but would + * break ES6 Set interface contract. + * + * @param fn - + * @param thisArg - + */ + forEach(fn: Fn3, void>, thisArg?: any) { + const vals = __vals(this); + for (let i = vals.length; i-- > 0; ) { + const v = vals[i]; + fn.call(thisArg, v, v, this); + } + } + + *entries(): IterableIterator> { + for (let v of __vals(this)) { + yield [v, v]; + } + } + + *keys(): IterableIterator { + yield* __vals(this); + } + + *values(): IterableIterator { + yield* __vals(this); + } + + opts(): EquivSetOpts { + return { equiv: __private.get(this)!.equiv }; + } } export const defArraySet = ( - vals?: Iterable | null, - opts?: Partial> + vals?: Iterable | null, + opts?: Partial> ) => new ArraySet(vals, opts); diff --git a/packages/associative/src/bidir-index.ts b/packages/associative/src/bidir-index.ts index 6f001de2a9..6a65cad08a 100644 --- a/packages/associative/src/bidir-index.ts +++ b/packages/associative/src/bidir-index.ts @@ -1,250 +1,250 @@ import { isString } from "@thi.ng/checks/is-string"; export interface SerializedBidirIndex { - pairs: [T, number][]; - nextID: number; + pairs: [T, number][]; + nextID: number; } export interface BidirIndexOpts { - /** - * Custom `key -> id` map implementation (e.g. {@link EquivMap} or - * {@link HashMap}). If omitted, a native JS `Map` will be used. - */ - map: Map; - /** - * Start ID for indexing new keys. - * - * @defaultValue 0 - */ - start: number; + /** + * Custom `key -> id` map implementation (e.g. {@link EquivMap} or + * {@link HashMap}). If omitted, a native JS `Map` will be used. + */ + map: Map; + /** + * Start ID for indexing new keys. + * + * @defaultValue 0 + */ + start: number; } /** * Bi-directional index to map arbitrary keys to numeric IDs and vice versa. */ export class BidirIndex { - fwd: Map; - rev: Map; - nextID: number; + fwd: Map; + rev: Map; + nextID: number; - constructor( - keys?: Iterable | null, - opts: Partial> = {} - ) { - this.nextID = opts.start || 0; - this.fwd = opts.map || new Map(); - this.rev = new Map(); - keys && this.addAll(keys); - } + constructor( + keys?: Iterable | null, + opts: Partial> = {} + ) { + this.nextID = opts.start || 0; + this.fwd = opts.map || new Map(); + this.rev = new Map(); + keys && this.addAll(keys); + } - get size() { - return this.fwd.size; - } + get size() { + return this.fwd.size; + } - /** - * Yields same result as {@link BidirIndex.entries}. - */ - [Symbol.iterator]() { - return this.entries(); - } + /** + * Yields same result as {@link BidirIndex.entries}. + */ + [Symbol.iterator]() { + return this.entries(); + } - /** - * Returns iterator of `[key,id]` pairs. - */ - entries() { - return this.fwd.entries(); - } + /** + * Returns iterator of `[key,id]` pairs. + */ + entries() { + return this.fwd.entries(); + } - /** - * Returns iterator of all indexed keys. - */ - keys() { - return this.fwd.keys(); - } + /** + * Returns iterator of all indexed keys. + */ + keys() { + return this.fwd.keys(); + } - /** - * Returns iterator of all indexed IDs. - */ - values() { - return this.fwd.values(); - } + /** + * Returns iterator of all indexed IDs. + */ + values() { + return this.fwd.values(); + } - /** - * Returns true if given `key` is known/indexed. - * - * @param key - */ - has(key: T) { - return this.fwd.has(key); - } + /** + * Returns true if given `key` is known/indexed. + * + * @param key + */ + has(key: T) { + return this.fwd.has(key); + } - /** - * Returns true if given `id` has a corresponding known/indexed key. - * - * @param id - */ - hasID(id: number) { - return this.rev.has(id); - } + /** + * Returns true if given `id` has a corresponding known/indexed key. + * + * @param id + */ + hasID(id: number) { + return this.rev.has(id); + } - /** - * Reverse lookup of {@link BidirIndex.getID}. Returns the matching ID for - * given `key` or undefined if the key is not known. - * - * @param key - */ - get(key: T) { - return this.fwd.get(key); - } + /** + * Reverse lookup of {@link BidirIndex.getID}. Returns the matching ID for + * given `key` or undefined if the key is not known. + * + * @param key + */ + get(key: T) { + return this.fwd.get(key); + } - /** - * Reverse lookup of {@link BidirIndex.get}. Returns the matching key for - * given `id` or undefined if the ID is not known. - * - * @param id - */ - getID(id: number) { - return this.rev.get(id); - } + /** + * Reverse lookup of {@link BidirIndex.get}. Returns the matching key for + * given `id` or undefined if the ID is not known. + * + * @param id + */ + getID(id: number) { + return this.rev.get(id); + } - /** - * Indexes given `key` and assigns & returns a new ID. If `key` is already - * known/indexed, returns its existing ID. - * - * @param key - */ - add(key: T) { - let id = this.fwd.get(key); - if (id === undefined) { - this.fwd.set(key, this.nextID); - this.rev.set(this.nextID, key); - id = this.nextID++; - } - return id; - } + /** + * Indexes given `key` and assigns & returns a new ID. If `key` is already + * known/indexed, returns its existing ID. + * + * @param key + */ + add(key: T) { + let id = this.fwd.get(key); + if (id === undefined) { + this.fwd.set(key, this.nextID); + this.rev.set(this.nextID, key); + id = this.nextID++; + } + return id; + } - /** - * Batch version of {@link BidirIndex.add}. Indexes all given keys and - * returns array of their corresponding IDs. - * - * @param keys - */ - addAll(keys: Iterable) { - const res: number[] = []; - for (let k of keys) { - res.push(this.add(k)); - } - return res; - } + /** + * Batch version of {@link BidirIndex.add}. Indexes all given keys and + * returns array of their corresponding IDs. + * + * @param keys + */ + addAll(keys: Iterable) { + const res: number[] = []; + for (let k of keys) { + res.push(this.add(k)); + } + return res; + } - /** - * Removes bi-directional mapping for given `key` from the index. Returns - * true if successful. - * - * @param key - */ - delete(key: T) { - const fwd = this.fwd; - const id = fwd.get(key); - if (id !== undefined) { - fwd.delete(key); - this.rev.delete(id); - return true; - } - return false; - } + /** + * Removes bi-directional mapping for given `key` from the index. Returns + * true if successful. + * + * @param key + */ + delete(key: T) { + const fwd = this.fwd; + const id = fwd.get(key); + if (id !== undefined) { + fwd.delete(key); + this.rev.delete(id); + return true; + } + return false; + } - /** - * Removes bi-directional mapping for given `id` from the index. Returns - * true if successful. - * - * @param id - */ - deleteID(id: number) { - const rev = this.rev; - const k = rev.get(id); - if (k !== undefined) { - rev.delete(id); - this.fwd.delete(k); - return true; - } - return false; - } + /** + * Removes bi-directional mapping for given `id` from the index. Returns + * true if successful. + * + * @param id + */ + deleteID(id: number) { + const rev = this.rev; + const k = rev.get(id); + if (k !== undefined) { + rev.delete(id); + this.fwd.delete(k); + return true; + } + return false; + } - /** - * Batch version of {@link BidirIndex.delete}. - * - * @param keys - */ - deleteAll(keys: Iterable) { - for (let k of keys) this.delete(k); - } + /** + * Batch version of {@link BidirIndex.delete}. + * + * @param keys + */ + deleteAll(keys: Iterable) { + for (let k of keys) this.delete(k); + } - /** - * Batch version of {@link BidirIndex.deleteID}. - * - * @param ids - */ - deleteAllIDs(ids: Iterable) { - for (let id of ids) this.deleteID(id); - } + /** + * Batch version of {@link BidirIndex.deleteID}. + * + * @param ids + */ + deleteAllIDs(ids: Iterable) { + for (let id of ids) this.deleteID(id); + } - /** - * Returns array of IDs for all given keys. If `fail` is true (default: - * false), throws error if any of the given keys is unknown/unindexed (use - * {@link BidirIndex.add} or {@link BidirIndex.addAll} first). - * - * @param keys - * @param fail - */ - getAll(keys: Iterable, fail = false) { - const index = this.fwd; - const res: number[] = []; - for (let k of keys) { - const id = index.get(k); - if (id === undefined) { - if (fail) throw new Error(`unknown key: ${k}`); - } else { - res.push(id); - } - } - return res; - } + /** + * Returns array of IDs for all given keys. If `fail` is true (default: + * false), throws error if any of the given keys is unknown/unindexed (use + * {@link BidirIndex.add} or {@link BidirIndex.addAll} first). + * + * @param keys + * @param fail + */ + getAll(keys: Iterable, fail = false) { + const index = this.fwd; + const res: number[] = []; + for (let k of keys) { + const id = index.get(k); + if (id === undefined) { + if (fail) throw new Error(`unknown key: ${k}`); + } else { + res.push(id); + } + } + return res; + } - /** - * Returns array of matching keys for all given IDs. If `fail` is true - * (default: false), throws error if any of the given IDs is - * unknown/unindexed (use {@link BidirIndex.add} or - * {@link BidirIndex.addAll} first). - * - * @param ids - * @param fail - */ - getAllIDs(ids: Iterable, fail = false) { - const index = this.rev; - const res: T[] = []; - for (let id of ids) { - const k = index.get(id); - if (k === undefined) { - if (fail) throw new Error(`unknwon ID: ${id}`); - } else { - res.push(k); - } - } - return res; - } + /** + * Returns array of matching keys for all given IDs. If `fail` is true + * (default: false), throws error if any of the given IDs is + * unknown/unindexed (use {@link BidirIndex.add} or + * {@link BidirIndex.addAll} first). + * + * @param ids + * @param fail + */ + getAllIDs(ids: Iterable, fail = false) { + const index = this.rev; + const res: T[] = []; + for (let id of ids) { + const k = index.get(id); + if (k === undefined) { + if (fail) throw new Error(`unknwon ID: ${id}`); + } else { + res.push(k); + } + } + return res; + } - /** - * Returns a compact JSON serializable version of the index. Use - * {@link bidirIndexFromJSON} to instantiate an index from such a JSON - * serialization. - */ - toJSON(): SerializedBidirIndex { - return { - pairs: [...this.entries()], - nextID: this.nextID, - }; - } + /** + * Returns a compact JSON serializable version of the index. Use + * {@link bidirIndexFromJSON} to instantiate an index from such a JSON + * serialization. + */ + toJSON(): SerializedBidirIndex { + return { + pairs: [...this.entries()], + nextID: this.nextID, + }; + } } /** @@ -254,8 +254,8 @@ export class BidirIndex { * @param opts */ export const defBidirIndex = ( - keys?: Iterable, - opts?: Partial> + keys?: Iterable, + opts?: Partial> ) => new BidirIndex(keys, opts); /** @@ -267,14 +267,14 @@ export const defBidirIndex = ( * @param map */ export const bidirIndexFromJSON = ( - src: string | SerializedBidirIndex, - map?: Map + src: string | SerializedBidirIndex, + map?: Map ) => { - const $src = isString(src) ? >JSON.parse(src) : src; - const res = new BidirIndex(null, { map, start: $src.nextID }); - $src.pairs.forEach(([k, id]) => { - res.fwd.set(k, id); - res.rev.set(id, k); - }); - return res; + const $src = isString(src) ? >JSON.parse(src) : src; + const res = new BidirIndex(null, { map, start: $src.nextID }); + $src.pairs.forEach(([k, id]) => { + res.fwd.set(k, id); + res.rev.set(id, k); + }); + return res; }; diff --git a/packages/associative/src/checks.ts b/packages/associative/src/checks.ts index 8bb98ee5d7..6dd4963d7e 100644 --- a/packages/associative/src/checks.ts +++ b/packages/associative/src/checks.ts @@ -3,7 +3,7 @@ import { isMap } from "@thi.ng/checks/is-map"; import { isSet } from "@thi.ng/checks/is-set"; export const ensureMap = (x: Iterable>) => - isMap(x) ? >x : new Map(x); + isMap(x) ? >x : new Map(x); export const ensureSet = (x: Iterable) => - isSet(x) ? >x : new Set(x); + isSet(x) ? >x : new Set(x); diff --git a/packages/associative/src/common-keys.ts b/packages/associative/src/common-keys.ts index 47b1dadc09..9ce8c8375e 100644 --- a/packages/associative/src/common-keys.ts +++ b/packages/associative/src/common-keys.ts @@ -6,14 +6,14 @@ * @param out - result array */ export const commonKeysMap = ( - a: Map, - b: Map, - out: K[] = [] + a: Map, + b: Map, + out: K[] = [] ) => { - for (let k of a.keys()) { - b.has(k) && out.push(k); - } - return out; + for (let k of a.keys()) { + b.has(k) && out.push(k); + } + return out; }; /** @@ -31,12 +31,12 @@ export const commonKeysMap = ( * @param out - result array */ export const commonKeysObj = ( - a: A, - b: B, - out: string[] = [] + a: A, + b: B, + out: string[] = [] ): (keyof A & keyof B)[] => { - for (let k in a) { - b.hasOwnProperty(k) && out.push(k); - } - return out; + for (let k in a) { + b.hasOwnProperty(k) && out.push(k); + } + return out; }; diff --git a/packages/associative/src/copy.ts b/packages/associative/src/copy.ts index a26b64bf38..90dd5ebe30 100644 --- a/packages/associative/src/copy.ts +++ b/packages/associative/src/copy.ts @@ -2,14 +2,14 @@ import { implementsFunction } from "@thi.ng/checks/implements-function"; import { isIllegalKey } from "@thi.ng/checks/is-proto-path"; export const copy = (x: any, ctor: Function) => - implementsFunction(x, "copy") - ? x.copy() - : new (x[Symbol.species] || ctor)(x); + implementsFunction(x, "copy") + ? x.copy() + : new (x[Symbol.species] || ctor)(x); export const copyObj = (x: any) => { - const res: any = {}; - for (let k in x) { - !isIllegalKey(k) && (res[k] = x[k]); - } - return res; + const res: any = {}; + for (let k in x) { + !isIllegalKey(k) && (res[k] = x[k]); + } + return res; }; diff --git a/packages/associative/src/difference.ts b/packages/associative/src/difference.ts index e4b1feb163..1da1a903f2 100644 --- a/packages/associative/src/difference.ts +++ b/packages/associative/src/difference.ts @@ -14,14 +14,14 @@ import { into } from "./into.js"; * @param out - optional result set */ export const difference = (a: Set, b: Set, out?: Set): Set => { - if (a === b) { - return out || empty(a, Set); - } - out = out ? into(out, a) : copy(a, Set); - for (let i of b) { - out!.delete(i); - } - return out!; + if (a === b) { + return out || empty(a, Set); + } + out = out ? into(out, a) : copy(a, Set); + for (let i of b) { + out!.delete(i); + } + return out!; }; /** @@ -35,5 +35,5 @@ export const difference = (a: Set, b: Set, out?: Set): Set => { export function differenceR(): Reducer, Iterable>; export function differenceR(src: Iterable>): Set; export function differenceR(src?: Iterable>) { - return __combineSet(differenceR, difference, src); + return __combineSet(differenceR, difference, src); } diff --git a/packages/associative/src/dissoc.ts b/packages/associative/src/dissoc.ts index 3e91a6c7de..988c30cd9b 100644 --- a/packages/associative/src/dissoc.ts +++ b/packages/associative/src/dissoc.ts @@ -3,18 +3,18 @@ import type { IObjectOf } from "@thi.ng/api"; export function dissoc(map: Map, keys: Iterable): Map; export function dissoc(set: Set, keys: Iterable): Set; export function dissoc(coll: Map | Set, keys: Iterable) { - for (let k of keys) { - coll.delete(k); - } - return coll; + for (let k of keys) { + coll.delete(k); + } + return coll; } export const dissocObj = ( - obj: IObjectOf, - keys: Iterable + obj: IObjectOf, + keys: Iterable ) => { - for (let k of keys) { - delete obj[k]; - } - return obj; + for (let k of keys) { + delete obj[k]; + } + return obj; }; diff --git a/packages/associative/src/empty.ts b/packages/associative/src/empty.ts index ccdfa6f1be..3c3a8a4f0c 100644 --- a/packages/associative/src/empty.ts +++ b/packages/associative/src/empty.ts @@ -1,6 +1,6 @@ import { implementsFunction } from "@thi.ng/checks/implements-function"; export const empty = (x: any, ctor: Function) => - implementsFunction(x, "empty") - ? x.empty() - : new (x[Symbol.species] || ctor)(); + implementsFunction(x, "empty") + ? x.empty() + : new (x[Symbol.species] || ctor)(); diff --git a/packages/associative/src/equiv-map.ts b/packages/associative/src/equiv-map.ts index f89ccff2d4..16dc4cef6c 100644 --- a/packages/associative/src/equiv-map.ts +++ b/packages/associative/src/equiv-map.ts @@ -11,9 +11,9 @@ import { __inspectable } from "./internal/inspect.js"; import { into } from "./into.js"; interface MapProps { - keys: IEquivSet; - map: Map; - opts: EquivMapOpts; + keys: IEquivSet; + map: Map; + opts: EquivMapOpts; } const __private = new WeakMap, MapProps>(); @@ -22,168 +22,168 @@ const __map = (map: EquivMap) => __private.get(map)!.map; @__inspectable export class EquivMap - extends Map - implements - Iterable>, - ICopy>, - IEmpty>, - IEquiv + extends Map + implements + Iterable>, + ICopy>, + IEmpty>, + IEquiv { - /** - * Creates a new instance with optional initial key-value pairs and - * provided options. If no `opts` are given, uses `ArraySet` for - * storing canonical keys and {@link @thi.ng/equiv#equiv} for - * checking key equivalence. - * - * @param pairs - key-value pairs - * @param opts - config options - */ - constructor( - pairs?: Iterable> | null, - opts?: Partial> - ) { - super(); - const _opts: EquivMapOpts = { equiv, keys: ArraySet, ...opts }; - __private.set(this, { - keys: new _opts.keys(null, { equiv: _opts.equiv }), - map: new Map(), - opts: _opts, - }); - if (pairs) { - this.into(pairs); - } - } - - [Symbol.iterator](): IterableIterator> { - return this.entries(); - } - - get [Symbol.species]() { - return EquivMap; - } - - get [Symbol.toStringTag]() { - return "EquivMap"; - } - - get size(): number { - return __private.get(this)!.keys.size; - } - - clear() { - const { keys, map } = __private.get(this)!; - keys.clear(); - map.clear(); - } - - empty(): EquivMap { - return new EquivMap(null, __private.get(this)!.opts); - } - - copy() { - const { keys, map, opts } = __private.get(this)!; - const m = new EquivMap(); - __private.set(m, { - keys: keys.copy(), - map: new Map(map), - opts, - }); - return m; - } - - equiv(o: any) { - return __equivMap(this, o); - } - - delete(key: K) { - const { keys, map } = __private.get(this)!; - key = keys.get(key, SEMAPHORE); - if (key !== SEMAPHORE) { - map.delete(key); - keys.delete(key); - return true; - } - return false; - } - - dissoc(keys: Iterable) { - return dissoc(this, keys); - } - - /** - * The key & value args given the callback `fn` MUST be treated as - * readonly/immutable. This could be enforced via TS, but would - * break ES6 Map interface contract. - * - * @param fn - - * @param thisArg - - */ - forEach(fn: Fn3, void>, thisArg?: any) { - for (let pair of __map(this)) { - fn.call(thisArg, pair[1], pair[0], this); - } - } - - get(key: K, notFound?: V): V | undefined { - const { keys, map } = __private.get(this)!; - key = keys.get(key, SEMAPHORE); - if (key !== SEMAPHORE) { - return map.get(key); - } - return notFound; - } - - has(key: K): boolean { - return __private.get(this)!.keys.has(key); - } - - set(key: K, value: V) { - const { keys, map } = __private.get(this)!; - const k = keys.get(key, SEMAPHORE); - if (k !== SEMAPHORE) { - map.set(k, value); - } else { - keys.add(key); - map.set(key, value); - } - return this; - } - - into(pairs: Iterable>) { - return into(this, pairs); - } - - entries(): IterableIterator> { - return __map(this).entries(); - } - - keys(): IterableIterator { - return __map(this).keys(); - } - - values(): IterableIterator { - return __map(this).values(); - } - - opts(): EquivMapOpts { - return __private.get(this)!.opts; - } + /** + * Creates a new instance with optional initial key-value pairs and + * provided options. If no `opts` are given, uses `ArraySet` for + * storing canonical keys and {@link @thi.ng/equiv#equiv} for + * checking key equivalence. + * + * @param pairs - key-value pairs + * @param opts - config options + */ + constructor( + pairs?: Iterable> | null, + opts?: Partial> + ) { + super(); + const _opts: EquivMapOpts = { equiv, keys: ArraySet, ...opts }; + __private.set(this, { + keys: new _opts.keys(null, { equiv: _opts.equiv }), + map: new Map(), + opts: _opts, + }); + if (pairs) { + this.into(pairs); + } + } + + [Symbol.iterator](): IterableIterator> { + return this.entries(); + } + + get [Symbol.species]() { + return EquivMap; + } + + get [Symbol.toStringTag]() { + return "EquivMap"; + } + + get size(): number { + return __private.get(this)!.keys.size; + } + + clear() { + const { keys, map } = __private.get(this)!; + keys.clear(); + map.clear(); + } + + empty(): EquivMap { + return new EquivMap(null, __private.get(this)!.opts); + } + + copy() { + const { keys, map, opts } = __private.get(this)!; + const m = new EquivMap(); + __private.set(m, { + keys: keys.copy(), + map: new Map(map), + opts, + }); + return m; + } + + equiv(o: any) { + return __equivMap(this, o); + } + + delete(key: K) { + const { keys, map } = __private.get(this)!; + key = keys.get(key, SEMAPHORE); + if (key !== SEMAPHORE) { + map.delete(key); + keys.delete(key); + return true; + } + return false; + } + + dissoc(keys: Iterable) { + return dissoc(this, keys); + } + + /** + * The key & value args given the callback `fn` MUST be treated as + * readonly/immutable. This could be enforced via TS, but would + * break ES6 Map interface contract. + * + * @param fn - + * @param thisArg - + */ + forEach(fn: Fn3, void>, thisArg?: any) { + for (let pair of __map(this)) { + fn.call(thisArg, pair[1], pair[0], this); + } + } + + get(key: K, notFound?: V): V | undefined { + const { keys, map } = __private.get(this)!; + key = keys.get(key, SEMAPHORE); + if (key !== SEMAPHORE) { + return map.get(key); + } + return notFound; + } + + has(key: K): boolean { + return __private.get(this)!.keys.has(key); + } + + set(key: K, value: V) { + const { keys, map } = __private.get(this)!; + const k = keys.get(key, SEMAPHORE); + if (k !== SEMAPHORE) { + map.set(k, value); + } else { + keys.add(key); + map.set(key, value); + } + return this; + } + + into(pairs: Iterable>) { + return into(this, pairs); + } + + entries(): IterableIterator> { + return __map(this).entries(); + } + + keys(): IterableIterator { + return __map(this).keys(); + } + + values(): IterableIterator { + return __map(this).values(); + } + + opts(): EquivMapOpts { + return __private.get(this)!.opts; + } } export function defEquivMap( - pairs?: Iterable> | null, - opts?: Partial> + pairs?: Iterable> | null, + opts?: Partial> ): EquivMap; export function defEquivMap( - obj: IObjectOf, - opts?: Partial> + obj: IObjectOf, + opts?: Partial> ): EquivMap; export function defEquivMap( - src: any, - opts?: Partial> + src: any, + opts?: Partial> ): EquivMap { - return new EquivMap( - isPlainObject(src) ? pairs(>src) : src, - opts - ); + return new EquivMap( + isPlainObject(src) ? pairs(>src) : src, + opts + ); } diff --git a/packages/associative/src/hash-map.ts b/packages/associative/src/hash-map.ts index cc52d2d9cc..25c58f3030 100644 --- a/packages/associative/src/hash-map.ts +++ b/packages/associative/src/hash-map.ts @@ -1,12 +1,12 @@ import type { - Fn, - Fn3, - ICopy, - IEmpty, - IEquiv, - IObjectOf, - Pair, - Predicate2, + Fn, + Fn3, + ICopy, + IEmpty, + IEquiv, + IObjectOf, + Pair, + Predicate2, } from "@thi.ng/api"; import { ceilPow2 } from "@thi.ng/binary/pow"; import { isPlainObject } from "@thi.ng/checks/is-plain-object"; @@ -19,22 +19,22 @@ import { __inspectable } from "./internal/inspect.js"; import { into } from "./into.js"; interface HashMapState { - hash: Fn; - equiv: Predicate2; - load: number; - bins: Pair[]; - mask: number; - size: number; + hash: Fn; + equiv: Predicate2; + load: number; + bins: Pair[]; + mask: number; + size: number; } const __private = new WeakMap, HashMapState>(); const __iterator = (map: HashMap, id: 0 | 1) => - function* () { - for (let p of __private.get(map)!.bins) { - if (p) yield p[id]; - } - }; + function* () { + for (let p of __private.get(map)!.bins) { + if (p) yield p[id]; + } + }; const DEFAULT_CAP = 16; const DEFAULT_LOAD = 0.75; @@ -61,214 +61,214 @@ const DEFAULT_LOAD = 0.75; */ @__inspectable export class HashMap - extends Map - implements - Iterable>, - ICopy>, - IEmpty>, - IEquiv + extends Map + implements + Iterable>, + ICopy>, + IEmpty>, + IEquiv { - constructor(pairs: Iterable> | null, opts: HashMapOpts) { - super(); - const m = ceilPow2(Math.min(opts.cap || DEFAULT_CAP, 4)) - 1; - __private.set(this, { - hash: opts.hash, - equiv: opts.equiv || equiv, - load: opts.load || DEFAULT_LOAD, - mask: m, - bins: new Array(m + 1), - size: 0, - }); - if (pairs) { - this.into(pairs); - } - } + constructor(pairs: Iterable> | null, opts: HashMapOpts) { + super(); + const m = ceilPow2(Math.min(opts.cap || DEFAULT_CAP, 4)) - 1; + __private.set(this, { + hash: opts.hash, + equiv: opts.equiv || equiv, + load: opts.load || DEFAULT_LOAD, + mask: m, + bins: new Array(m + 1), + size: 0, + }); + if (pairs) { + this.into(pairs); + } + } - get [Symbol.species]() { - return HashMap; - } + get [Symbol.species]() { + return HashMap; + } - get [Symbol.toStringTag]() { - return "HashMap"; - } + get [Symbol.toStringTag]() { + return "HashMap"; + } - get size(): number { - return __private.get(this)!.size; - } + get size(): number { + return __private.get(this)!.size; + } - [Symbol.iterator]() { - return this.entries(); - } + [Symbol.iterator]() { + return this.entries(); + } - *entries(): IterableIterator> { - for (let p of __private.get(this)!.bins) { - if (p) yield [p[0], p[1]]; - } - } + *entries(): IterableIterator> { + for (let p of __private.get(this)!.bins) { + if (p) yield [p[0], p[1]]; + } + } - keys(): IterableIterator { - return __iterator(this, 0)(); - } + keys(): IterableIterator { + return __iterator(this, 0)(); + } - values(): IterableIterator { - return __iterator(this, 1)(); - } + values(): IterableIterator { + return __iterator(this, 1)(); + } - /** - * The key & value args given the callback `fn` MUST be treated as - * readonly/immutable. This could be enforced via TS, but would - * break ES6 Map interface contract. - * - * @param fn - - * @param thisArg - - */ - forEach(fn: Fn3, void>, thisArg?: any) { - for (let pair of __private.get(this)!.bins) { - fn.call(thisArg, pair[1], pair[0], this); - } - } + /** + * The key & value args given the callback `fn` MUST be treated as + * readonly/immutable. This could be enforced via TS, but would + * break ES6 Map interface contract. + * + * @param fn - + * @param thisArg - + */ + forEach(fn: Fn3, void>, thisArg?: any) { + for (let pair of __private.get(this)!.bins) { + fn.call(thisArg, pair[1], pair[0], this); + } + } - clear() { - const $this = __private.get(this)!; - $this.bins = new Array(DEFAULT_CAP); - $this.mask = 15; - $this.size = 0; - } + clear() { + const $this = __private.get(this)!; + $this.bins = new Array(DEFAULT_CAP); + $this.mask = 15; + $this.size = 0; + } - empty() { - return new HashMap(null, this.opts({ cap: DEFAULT_CAP })); - } + empty() { + return new HashMap(null, this.opts({ cap: DEFAULT_CAP })); + } - copy() { - const $this = __private.get(this)!; - const m = new HashMap(null, this.opts({ cap: 4 })); - Object.assign(__private.get(m)!, { - bins: $this.bins.slice(), - mask: $this.mask, - size: $this.size, - }); - return m; - } + copy() { + const $this = __private.get(this)!; + const m = new HashMap(null, this.opts({ cap: 4 })); + Object.assign(__private.get(m)!, { + bins: $this.bins.slice(), + mask: $this.mask, + size: $this.size, + }); + return m; + } - equiv(o: any) { - return __equivMap(this, o); - } + equiv(o: any) { + return __equivMap(this, o); + } - has(key: K): boolean { - const $this = __private.get(this)!; - const i = this.find(key, $this); - return i >= 0 && $this.bins[i] != undefined; - } + has(key: K): boolean { + const $this = __private.get(this)!; + const i = this.find(key, $this); + return i >= 0 && $this.bins[i] != undefined; + } - get(key: K, notFound?: V): V | undefined { - const $this = __private.get(this)!; - const i = this.find(key, $this); - return i >= 0 && $this.bins[i] ? $this.bins[i][1] : notFound; - } + get(key: K, notFound?: V): V | undefined { + const $this = __private.get(this)!; + const i = this.find(key, $this); + return i >= 0 && $this.bins[i] ? $this.bins[i][1] : notFound; + } - set(key: K, val: V) { - const $this = __private.get(this)!; - let i = this.find(key, $this); - if (i >= 0 && $this.bins[i]) { - $this.bins[i][1] = val; - return this; - } - if ($this.size > $this.mask * $this.load) { - this.resize($this); - i = this.find(key, $this); - } - $this.bins[i] = [key, val]; - $this.size++; - return this; - } + set(key: K, val: V) { + const $this = __private.get(this)!; + let i = this.find(key, $this); + if (i >= 0 && $this.bins[i]) { + $this.bins[i][1] = val; + return this; + } + if ($this.size > $this.mask * $this.load) { + this.resize($this); + i = this.find(key, $this); + } + $this.bins[i] = [key, val]; + $this.size++; + return this; + } - delete(key: K) { - const $this = __private.get(this)!; - const { bins, mask } = $this; - let i = this.find(key, $this); - if (i >= 0 && !bins[i]) { - return false; - } - $this.size--; - let j = i; - let k: number; - while (true) { - delete bins[i]; - do { - j = (j + 1) & mask; - if (!bins[j]) return true; - k = $this.hash(bins[j][0]) & mask; - } while (i <= j ? i < k && k <= j : i < k || k <= j); - bins[i] = bins[j]; - i = j; - } - } + delete(key: K) { + const $this = __private.get(this)!; + const { bins, mask } = $this; + let i = this.find(key, $this); + if (i >= 0 && !bins[i]) { + return false; + } + $this.size--; + let j = i; + let k: number; + while (true) { + delete bins[i]; + do { + j = (j + 1) & mask; + if (!bins[j]) return true; + k = $this.hash(bins[j][0]) & mask; + } while (i <= j ? i < k && k <= j : i < k || k <= j); + bins[i] = bins[j]; + i = j; + } + } - into(pairs: Iterable>) { - return into(this, pairs); - } + into(pairs: Iterable>) { + return into(this, pairs); + } - dissoc(keys: Iterable) { - return dissoc(this, keys); - } + dissoc(keys: Iterable) { + return dissoc(this, keys); + } - opts(overrides?: Partial>): HashMapOpts { - const $this = __private.get(this)!; - return >{ - hash: $this.hash, - equiv: $this.equiv, - load: $this.load, - cap: $this.mask + 1, - ...overrides, - }; - } + opts(overrides?: Partial>): HashMapOpts { + const $this = __private.get(this)!; + return >{ + hash: $this.hash, + equiv: $this.equiv, + load: $this.load, + cap: $this.mask + 1, + ...overrides, + }; + } - protected find(key: K, $this: HashMapState) { - const { bins, equiv, mask } = $this; - let i = mask; - let h = $this.hash(key) & mask; - while (bins[h] && !equiv(bins[h][0], key)) { - i--; - if (i < 0) return -1; - h = (h + 1) & mask; - } - return h; - } + protected find(key: K, $this: HashMapState) { + const { bins, equiv, mask } = $this; + let i = mask; + let h = $this.hash(key) & mask; + while (bins[h] && !equiv(bins[h][0], key)) { + i--; + if (i < 0) return -1; + h = (h + 1) & mask; + } + return h; + } - protected resize($this: HashMapState) { - const src = $this.bins; - const cap = ($this.mask + 1) * 2; - $this.bins = new Array(cap); - $this.mask = cap - 1; - $this.size = 0; - for (let p of src) { - if (p) this.set(p[0], p[1]); - } - } + protected resize($this: HashMapState) { + const src = $this.bins; + const cap = ($this.mask + 1) * 2; + $this.bins = new Array(cap); + $this.mask = cap - 1; + $this.size = 0; + for (let p of src) { + if (p) this.set(p[0], p[1]); + } + } } export function defHashMap( - pairs: Iterable> | null, - opts: HashMapOpts + pairs: Iterable> | null, + opts: HashMapOpts ): HashMap; export function defHashMap( - obj: IObjectOf, - opts: HashMapOpts + obj: IObjectOf, + opts: HashMapOpts ): HashMap; export function defHashMap( - src: any, - opts: HashMapOpts + src: any, + opts: HashMapOpts ): HashMap { - if (isPlainObject(src)) { - const keys = Object.keys(src); - return new HashMap( - map((k) => >[k, (>src)[k]], keys), - { - cap: keys.length / (opts.load || DEFAULT_LOAD), - ...opts, - } - ); - } else { - return new HashMap(src, opts); - } + if (isPlainObject(src)) { + const keys = Object.keys(src); + return new HashMap( + map((k) => >[k, (>src)[k]], keys), + { + cap: keys.length / (opts.load || DEFAULT_LOAD), + ...opts, + } + ); + } else { + return new HashMap(src, opts); + } } diff --git a/packages/associative/src/indexed.ts b/packages/associative/src/indexed.ts index 67fb39912d..79805e214e 100644 --- a/packages/associative/src/indexed.ts +++ b/packages/associative/src/indexed.ts @@ -22,16 +22,16 @@ import { selectKeysObj } from "./select-keys.js"; * @param ks - keys used for indexing */ export const indexed = ( - records: Iterable, - ks: (keyof T)[] + records: Iterable, + ks: (keyof T)[] ) => { - const res = new EquivMap<{ [id in keyof T]?: T[id] }, Set>(); - let x, ik, rv; - for (x of records) { - ik = selectKeysObj(x, ks); - rv = res.get(ik); - !rv && res.set(ik, (rv = empty(records, Set))); - rv.add(x); - } - return res; + const res = new EquivMap<{ [id in keyof T]?: T[id] }, Set>(); + let x, ik, rv; + for (x of records) { + ik = selectKeysObj(x, ks); + rv = res.get(ik); + !rv && res.set(ik, (rv = empty(records, Set))); + rv.add(x); + } + return res; }; diff --git a/packages/associative/src/internal/combine.ts b/packages/associative/src/internal/combine.ts index f4c7a929ea..ff615b78d0 100644 --- a/packages/associative/src/internal/combine.ts +++ b/packages/associative/src/internal/combine.ts @@ -4,14 +4,14 @@ import { reduce } from "@thi.ng/transducers/reduce"; import { ensureSet } from "../checks.js"; export const __combineSet = ( - rfn: Fn0, Iterable>>, - op: (a: Set, b: Set, c?: Set) => Set, - src?: Iterable> + rfn: Fn0, Iterable>>, + op: (a: Set, b: Set, c?: Set) => Set, + src?: Iterable> ) => - src - ? reduce(rfn(), src) - : , Iterable>>[ - () => null, - (acc) => acc || new Set(), - (acc, x) => (!acc ? ensureSet(x) : op(acc, ensureSet(x))), - ]; + src + ? reduce(rfn(), src) + : , Iterable>>[ + () => null, + (acc) => acc || new Set(), + (acc, x) => (!acc ? ensureSet(x) : op(acc, ensureSet(x))), + ]; diff --git a/packages/associative/src/internal/equiv.ts b/packages/associative/src/internal/equiv.ts index a82e000f04..4b6fbe75fc 100644 --- a/packages/associative/src/internal/equiv.ts +++ b/packages/associative/src/internal/equiv.ts @@ -1,31 +1,31 @@ import { equiv } from "@thi.ng/equiv"; export const __equivMap = (a: Map, b: any) => { - if (a === b) { - return true; - } - if (!(b instanceof Map) || a.size !== b.size) { - return false; - } - for (let p of a.entries()) { - if (!equiv(b.get(p[0]), p[1])) { - return false; - } - } - return true; + if (a === b) { + return true; + } + if (!(b instanceof Map) || a.size !== b.size) { + return false; + } + for (let p of a.entries()) { + if (!equiv(b.get(p[0]), p[1])) { + return false; + } + } + return true; }; export const __equivSet = (a: Set, b: any) => { - if (a === b) { - return true; - } - if (!(b instanceof Set) || a.size !== b.size) { - return false; - } - for (let k of a.keys()) { - if (!b.has(k)) { - return false; - } - } - return true; + if (a === b) { + return true; + } + if (!(b instanceof Set) || a.size !== b.size) { + return false; + } + for (let k of a.keys()) { + if (!b.has(k)) { + return false; + } + } + return true; }; diff --git a/packages/associative/src/internal/inspect.ts b/packages/associative/src/internal/inspect.ts index eaa3db1cbb..b7800e8553 100644 --- a/packages/associative/src/internal/inspect.ts +++ b/packages/associative/src/internal/inspect.ts @@ -3,29 +3,29 @@ import { isNode } from "@thi.ng/checks/is-node"; import { map } from "@thi.ng/transducers/map"; let inspect: - | (( - object: any, - showHidden?: boolean, - depth?: number | null, - color?: boolean - ) => string) - | null = null; + | (( + object: any, + showHidden?: boolean, + depth?: number | null, + color?: boolean + ) => string) + | null = null; isNode() && - import("util").then((m) => { - inspect = m.inspect; - }); + import("util").then((m) => { + inspect = m.inspect; + }); const inspectSet = (coll: Set, opts: any) => - [...map((x) => inspect!(x, opts), coll)].join(", "); + [...map((x) => inspect!(x, opts), coll)].join(", "); const inspectMap = (coll: Map, opts: any) => - [ - ...map( - ([k, v]) => `${inspect!(k, opts)} => ${inspect!(v, opts)}`, - coll - ), - ].join(", "); + [ + ...map( + ([k, v]) => `${inspect!(k, opts)} => ${inspect!(v, opts)}`, + coll + ), + ].join(", "); /** * NodeJS inspection mixin @@ -37,24 +37,24 @@ const inspectMap = (coll: Map, opts: any) => * @internal */ export const __inspectable = mixin({ - [Symbol.for("nodejs.util.inspect.custom")](depth: number, opts: any) { - const name = this[Symbol.toStringTag]; - const childOpts = { - ...opts, - depth: opts.depth === null ? null : opts.depth - 1, - }; - return depth >= 0 - ? [ - `${name}(${this.size || 0}) {`, - inspect - ? this instanceof Set - ? inspectSet(this, childOpts) - : this instanceof Map - ? inspectMap(this, childOpts) - : "" - : "", - "}", - ].join(" ") - : opts.stylize(`[${name}]`, "special"); - }, + [Symbol.for("nodejs.util.inspect.custom")](depth: number, opts: any) { + const name = this[Symbol.toStringTag]; + const childOpts = { + ...opts, + depth: opts.depth === null ? null : opts.depth - 1, + }; + return depth >= 0 + ? [ + `${name}(${this.size || 0}) {`, + inspect + ? this instanceof Set + ? inspectSet(this, childOpts) + : this instanceof Map + ? inspectMap(this, childOpts) + : "" + : "", + "}", + ].join(" ") + : opts.stylize(`[${name}]`, "special"); + }, }); diff --git a/packages/associative/src/intersection.ts b/packages/associative/src/intersection.ts index 80254c2c4b..7e43b8f6b3 100644 --- a/packages/associative/src/intersection.ts +++ b/packages/associative/src/intersection.ts @@ -14,19 +14,19 @@ import { into } from "./into.js"; * @param out - result set */ export const intersection = (a: Set, b: Set, out?: Set): Set => { - out = out || empty(a, Set); - if (a === b) { - return into(out!, a); - } - if (b.size < a.size) { - return intersection(b, a, out); - } - for (let i of b) { - if (a.has(i)) { - out!.add(i); - } - } - return out!; + out = out || empty(a, Set); + if (a === b) { + return into(out!, a); + } + if (b.size < a.size) { + return intersection(b, a, out); + } + for (let i of b) { + if (a.has(i)) { + out!.add(i); + } + } + return out!; }; /** @@ -40,5 +40,5 @@ export const intersection = (a: Set, b: Set, out?: Set): Set => { export function intersectionR(): Reducer, Iterable>; export function intersectionR(src: Iterable>): Set; export function intersectionR(src?: Iterable>) { - return __combineSet(intersectionR, intersection, src); + return __combineSet(intersectionR, intersection, src); } diff --git a/packages/associative/src/into.ts b/packages/associative/src/into.ts index 72687fc4b4..eb98ffc437 100644 --- a/packages/associative/src/into.ts +++ b/packages/associative/src/into.ts @@ -7,18 +7,20 @@ import { isMap } from "@thi.ng/checks/is-map"; * @param dest - target collection * @param src - source collection */ -// prettier-ignore -export function into(dest: Map, src: Iterable>): Map; +export function into( + dest: Map, + src: Iterable> +): Map; export function into(dest: Set, src: Iterable): Set; export function into(dest: Map | Set, src: Iterable) { - if (isMap(dest)) { - for (let x of src) { - dest.set(x[0], x[1]); - } - } else { - for (let x of src) { - dest.add(x); - } - } - return dest; + if (isMap(dest)) { + for (let x of src) { + dest.set(x[0], x[1]); + } + } else { + for (let x of src) { + dest.add(x); + } + } + return dest; } diff --git a/packages/associative/src/invert.ts b/packages/associative/src/invert.ts index 594b282dc4..a968f49b13 100644 --- a/packages/associative/src/invert.ts +++ b/packages/associative/src/invert.ts @@ -18,11 +18,11 @@ import type { IObjectOf } from "@thi.ng/api"; * @param dest - result map */ export const invertMap = (src: Map, dest?: Map) => { - dest = dest || new Map(); - for (let p of src) { - dest.set(p[1], p[0]); - } - return dest; + dest = dest || new Map(); + for (let p of src) { + dest.set(p[1], p[0]); + } + return dest; }; /** @@ -40,11 +40,11 @@ export const invertMap = (src: Map, dest?: Map) => { * @param dest - result object */ export const invertObj = ( - src: IObjectOf, - dest: IObjectOf = {} + src: IObjectOf, + dest: IObjectOf = {} ) => { - for (let k in src) { - dest[src[k]] = k; - } - return dest; + for (let k in src) { + dest[src[k]] = k; + } + return dest; }; diff --git a/packages/associative/src/join.ts b/packages/associative/src/join.ts index 0efa8576ce..12e80952ed 100644 --- a/packages/associative/src/join.ts +++ b/packages/associative/src/join.ts @@ -35,32 +35,32 @@ import { selectKeysObj } from "./select-keys.js"; * @param b - other set */ export const join = ( - a: Set, - b: Set + a: Set, + b: Set ): Set & Pick> => { - if (a.size && b.size) { - const ks = commonKeysObj(first(a) || {}, first(b) || {}); - let aa: Set, bb: Set; - if (a.size <= b.size) { - aa = a; - bb = b; - } else { - aa = b; - bb = a; - } - const idx = indexed(aa, ks); - const res: Set = empty(a, Set); - for (let x of bb) { - const found = idx.get(selectKeysObj(x, ks)); - if (found) { - for (let f of found) { - res.add(mergeObj({ ...f }, x)); - } - } - } - return res; - } - return empty(a, Set); + if (a.size && b.size) { + const ks = commonKeysObj(first(a) || {}, first(b) || {}); + let aa: Set, bb: Set; + if (a.size <= b.size) { + aa = a; + bb = b; + } else { + aa = b; + bb = a; + } + const idx = indexed(aa, ks); + const res: Set = empty(a, Set); + for (let x of bb) { + const found = idx.get(selectKeysObj(x, ks)); + if (found) { + for (let f of found) { + res.add(mergeObj({ ...f }, x)); + } + } + } + return res; + } + return empty(a, Set); }; /** @@ -93,34 +93,34 @@ export const join = ( * @param kmap - keys to compute join for */ export const joinWith = ( - a: Set, - b: Set, - kmap: { [id in keyof A]?: keyof B } + a: Set, + b: Set, + kmap: { [id in keyof A]?: keyof B } ): Set => { - if (a.size && b.size) { - let aa: Set, bb: Set; - let k: { [id in keyof A]?: keyof B }; - if (a.size <= b.size) { - aa = a; - bb = b; - k = invertObj(kmap); - } else { - aa = b; - bb = a; - k = kmap; - } - const idx = indexed(aa, Object.values(k)); - const ks = Object.keys(k); - const res: Set = empty(a, Set); - for (let x of bb) { - const found = idx.get(renameKeysObj(selectKeysObj(x, ks), k)); - if (found) { - for (let f of found) { - res.add(mergeObj({ ...f }, x)); - } - } - } - return res; - } - return empty(a, Set); + if (a.size && b.size) { + let aa: Set, bb: Set; + let k: { [id in keyof A]?: keyof B }; + if (a.size <= b.size) { + aa = a; + bb = b; + k = invertObj(kmap); + } else { + aa = b; + bb = a; + k = kmap; + } + const idx = indexed(aa, Object.values(k)); + const ks = Object.keys(k); + const res: Set = empty(a, Set); + for (let x of bb) { + const found = idx.get(renameKeysObj(selectKeysObj(x, ks), k)); + if (found) { + for (let f of found) { + res.add(mergeObj({ ...f }, x)); + } + } + } + return res; + } + return empty(a, Set); }; diff --git a/packages/associative/src/ll-set.ts b/packages/associative/src/ll-set.ts index 52c225ca7f..6b121351e0 100644 --- a/packages/associative/src/ll-set.ts +++ b/packages/associative/src/ll-set.ts @@ -9,8 +9,8 @@ import { __inspectable } from "./internal/inspect.js"; import { into } from "./into.js"; interface SetProps { - vals: DCons; - equiv: Predicate2; + vals: DCons; + equiv: Predicate2; } const __private = new WeakMap, SetProps>(); @@ -30,143 +30,143 @@ const __vals = (inst: LLSet) => __private.get(inst)!.vals; */ @__inspectable export class LLSet extends Set implements IEquivSet { - constructor( - vals?: Iterable | null, - opts: Partial> = {} - ) { - super(); - __private.set(this, { - equiv: opts.equiv || equiv, - vals: new DCons(), - }); - vals && this.into(vals); - } - - *[Symbol.iterator](): IterableIterator { - yield* __vals(this); - } - - get [Symbol.species]() { - return LLSet; - } - - get [Symbol.toStringTag]() { - return "LLSet"; - } - - get size(): number { - return __vals(this).length; - } - - copy() { - const s = new LLSet(null, this.opts()); - __private.get(s)!.vals = __vals(this).copy(); - return s; - } - - empty() { - return new LLSet(null, this.opts()); - } - - clear() { - __vals(this).clear(); - } - - first(): T | undefined { - if (this.size) { - return __vals(this).head!.value; - } - } - - add(key: T) { - !this.has(key) && __vals(this).push(key); - return this; - } - - into(keys: Iterable) { - return into(this, keys); - } - - has(key: T) { - return this.get(key, SEMAPHORE) !== SEMAPHORE; - } - - /** - * Returns the canonical (stored) value for `key`, if present. If - * the set contains no equivalent for `key`, returns `notFound`. - * - * @param key - search key - * @param notFound - default value - */ - get(key: T, notFound?: T): T | undefined { - const { equiv, vals } = __private.get(this)!; - let i = vals.head; - while (i) { - if (equiv(i.value, key)) { - return i.value; - } - i = i.next; - } - return notFound; - } - - delete(key: T) { - const { equiv, vals } = __private.get(this)!; - let i = vals.head; - while (i) { - if (equiv(i.value, key)) { - vals.splice(i, 1); - return true; - } - i = i.next; - } - return false; - } - - disj(keys: Iterable) { - return dissoc(this, keys); - } - - equiv(o: any) { - return __equivSet(this, o); - } - - /** - * The value args given to the callback `fn` MUST be treated as - * readonly/immutable. This could be enforced via TS, but would - * break ES6 Set interface contract. - * - * @param fn - - * @param thisArg - - */ - forEach(fn: Fn3, void>, thisArg?: any) { - let i = __vals(this).head; - while (i) { - fn.call(thisArg, i.value, i.value, this); - i = i.next; - } - } - - *entries(): IterableIterator> { - for (let v of __vals(this)) { - yield [v, v]; - } - } - - *keys(): IterableIterator { - yield* __vals(this); - } - - *values(): IterableIterator { - yield* __vals(this); - } - - opts(): EquivSetOpts { - return { equiv: __private.get(this)!.equiv }; - } + constructor( + vals?: Iterable | null, + opts: Partial> = {} + ) { + super(); + __private.set(this, { + equiv: opts.equiv || equiv, + vals: new DCons(), + }); + vals && this.into(vals); + } + + *[Symbol.iterator](): IterableIterator { + yield* __vals(this); + } + + get [Symbol.species]() { + return LLSet; + } + + get [Symbol.toStringTag]() { + return "LLSet"; + } + + get size(): number { + return __vals(this).length; + } + + copy() { + const s = new LLSet(null, this.opts()); + __private.get(s)!.vals = __vals(this).copy(); + return s; + } + + empty() { + return new LLSet(null, this.opts()); + } + + clear() { + __vals(this).clear(); + } + + first(): T | undefined { + if (this.size) { + return __vals(this).head!.value; + } + } + + add(key: T) { + !this.has(key) && __vals(this).push(key); + return this; + } + + into(keys: Iterable) { + return into(this, keys); + } + + has(key: T) { + return this.get(key, SEMAPHORE) !== SEMAPHORE; + } + + /** + * Returns the canonical (stored) value for `key`, if present. If + * the set contains no equivalent for `key`, returns `notFound`. + * + * @param key - search key + * @param notFound - default value + */ + get(key: T, notFound?: T): T | undefined { + const { equiv, vals } = __private.get(this)!; + let i = vals.head; + while (i) { + if (equiv(i.value, key)) { + return i.value; + } + i = i.next; + } + return notFound; + } + + delete(key: T) { + const { equiv, vals } = __private.get(this)!; + let i = vals.head; + while (i) { + if (equiv(i.value, key)) { + vals.splice(i, 1); + return true; + } + i = i.next; + } + return false; + } + + disj(keys: Iterable) { + return dissoc(this, keys); + } + + equiv(o: any) { + return __equivSet(this, o); + } + + /** + * The value args given to the callback `fn` MUST be treated as + * readonly/immutable. This could be enforced via TS, but would + * break ES6 Set interface contract. + * + * @param fn - + * @param thisArg - + */ + forEach(fn: Fn3, void>, thisArg?: any) { + let i = __vals(this).head; + while (i) { + fn.call(thisArg, i.value, i.value, this); + i = i.next; + } + } + + *entries(): IterableIterator> { + for (let v of __vals(this)) { + yield [v, v]; + } + } + + *keys(): IterableIterator { + yield* __vals(this); + } + + *values(): IterableIterator { + yield* __vals(this); + } + + opts(): EquivSetOpts { + return { equiv: __private.get(this)!.equiv }; + } } export const defLLSet = ( - vals?: Iterable | null, - opts?: Partial> + vals?: Iterable | null, + opts?: Partial> ) => new LLSet(vals, opts); diff --git a/packages/associative/src/merge-apply.ts b/packages/associative/src/merge-apply.ts index 4be8cf3d63..29f502db7b 100644 --- a/packages/associative/src/merge-apply.ts +++ b/packages/associative/src/merge-apply.ts @@ -10,14 +10,14 @@ import { copy, copyObj } from "./copy.js"; * @param xs - map w/ transformation functions */ export const mergeApplyMap = ( - src: Map, - xs: Map> + src: Map, + xs: Map> ): Map => { - const res: Map = copy(src, Map); - for (let [k, v] of xs) { - res.set(k, isFunction(v) ? v(res.get(k)) : v); - } - return res; + const res: Map = copy(src, Map); + for (let [k, v] of xs) { + res.set(k, isFunction(v) ? v(res.get(k)) : v); + } + return res; }; /** @@ -49,8 +49,8 @@ export const mergeApplyMap = ( * @param xs - object w/ transformation functions */ export const mergeApplyObj = ( - src: IObjectOf, - xs: IObjectOf> + src: IObjectOf, + xs: IObjectOf> ) => meldApplyObj(copyObj(src), xs); /** @@ -65,13 +65,13 @@ export const mergeApplyObj = ( * @param xs - */ export const meldApplyObj = ( - src: IObjectOf, - xs: IObjectOf> + src: IObjectOf, + xs: IObjectOf> ) => { - for (let k in xs) { - if (isIllegalKey(k)) continue; - const v = xs[k]; - src[k] = isFunction(v) ? v(src[k]) : v; - } - return src; + for (let k in xs) { + if (isIllegalKey(k)) continue; + const v = xs[k]; + src[k] = isFunction(v) ? v(src[k]) : v; + } + return src; }; diff --git a/packages/associative/src/merge-deep.ts b/packages/associative/src/merge-deep.ts index f7c6cf8c51..e39bd22664 100644 --- a/packages/associative/src/merge-deep.ts +++ b/packages/associative/src/merge-deep.ts @@ -3,23 +3,23 @@ import { isPlainObject } from "@thi.ng/checks/is-plain-object"; import { meldObjWith, mergeObjWith } from "./merge-with.js"; export const mergeDeepObj = ( - dest: IObjectOf, - ...xs: Nullable>[] + dest: IObjectOf, + ...xs: Nullable>[] ): any => - mergeObjWith( - (a, b) => - isPlainObject(a) && isPlainObject(b) ? mergeDeepObj(a, b) : b, - dest, - ...xs - ); + mergeObjWith( + (a, b) => + isPlainObject(a) && isPlainObject(b) ? mergeDeepObj(a, b) : b, + dest, + ...xs + ); export const meldDeepObj = ( - dest: IObjectOf, - ...xs: Nullable>[] + dest: IObjectOf, + ...xs: Nullable>[] ): any => - meldObjWith( - (a, b) => - isPlainObject(a) && isPlainObject(b) ? meldDeepObj(a, b) : b, - dest, - ...xs - ); + meldObjWith( + (a, b) => + isPlainObject(a) && isPlainObject(b) ? meldDeepObj(a, b) : b, + dest, + ...xs + ); diff --git a/packages/associative/src/merge-with.ts b/packages/associative/src/merge-with.ts index 6a257b956e..a07b06982e 100644 --- a/packages/associative/src/merge-with.ts +++ b/packages/associative/src/merge-with.ts @@ -3,19 +3,19 @@ import { isIllegalKey } from "@thi.ng/checks/is-proto-path"; import { copy, copyObj } from "./copy.js"; export const mergeMapWith = ( - f: Fn2, - dest: Map, - ...xs: Nullable>[] + f: Fn2, + dest: Map, + ...xs: Nullable>[] ) => { - const res: Map = copy(dest, Map); - for (let x of xs) { - if (x != null) { - for (let [k, v] of x) { - res.set(k, res.has(k) ? f(res.get(k)!, v) : v); - } - } - } - return res; + const res: Map = copy(dest, Map); + for (let x of xs) { + if (x != null) { + for (let [k, v] of x) { + res.set(k, res.has(k) ? f(res.get(k)!, v) : v); + } + } + } + return res; }; /** @@ -27,14 +27,14 @@ export const mergeMapWith = ( * Since v4.4.0, the `__proto__` property will be ignored to avoid * prototype pollution. * - * @param f - - * @param dest - - * @param xs - + * @param f - + * @param dest - + * @param xs - */ export const mergeObjWith = ( - f: Fn2, - dest: IObjectOf, - ...xs: Nullable>[] + f: Fn2, + dest: IObjectOf, + ...xs: Nullable>[] ) => meldObjWith(f, copyObj(dest), ...xs); /** @@ -50,18 +50,18 @@ export const mergeObjWith = ( * @param xs - */ export const meldObjWith = ( - f: Fn2, - dest: IObjectOf, - ...xs: Nullable>[] + f: Fn2, + dest: IObjectOf, + ...xs: Nullable>[] ) => { - for (let x of xs) { - if (x != null) { - for (let k in x) { - if (isIllegalKey(k)) continue; - const v = x[k]; - dest[k] = dest.hasOwnProperty(k) ? f(dest[k], v) : v; - } - } - } - return dest; + for (let x of xs) { + if (x != null) { + for (let k in x) { + if (isIllegalKey(k)) continue; + const v = x[k]; + dest[k] = dest.hasOwnProperty(k) ? f(dest[k], v) : v; + } + } + } + return dest; }; diff --git a/packages/associative/src/merge.ts b/packages/associative/src/merge.ts index 92061950da..0a41aae8c3 100644 --- a/packages/associative/src/merge.ts +++ b/packages/associative/src/merge.ts @@ -8,17 +8,17 @@ import type { IObjectOf, Nullable } from "@thi.ng/api"; * @param xs - input maps */ export const mergeMap = ( - dest: Map, - ...xs: Nullable>[] + dest: Map, + ...xs: Nullable>[] ) => { - for (let x of xs) { - if (x != null) { - for (let pair of x) { - dest.set(pair[0], pair[1]); - } - } - } - return dest; + for (let x of xs) { + if (x != null) { + for (let pair of x) { + dest.set(pair[0], pair[1]); + } + } + } + return dest; }; /** @@ -29,6 +29,6 @@ export const mergeMap = ( * @param xs - input objects */ export const mergeObj = ( - dest: IObjectOf, - ...xs: Nullable>[] + dest: IObjectOf, + ...xs: Nullable>[] ): IObjectOf => Object.assign(dest, ...xs); diff --git a/packages/associative/src/multi-trie.ts b/packages/associative/src/multi-trie.ts index 319cee6a93..7a28df0640 100644 --- a/packages/associative/src/multi-trie.ts +++ b/packages/associative/src/multi-trie.ts @@ -4,196 +4,196 @@ import { map } from "@thi.ng/transducers/map"; import { vals } from "@thi.ng/transducers/vals"; export interface MultiTrieOpts { - /** - * Custom value set factory (e.g. for using `Set` implementations from this - * package). Uses native ES6 Set by default. - */ - vals: Fn0>; + /** + * Custom value set factory (e.g. for using `Set` implementations from this + * package). Uses native ES6 Set by default. + */ + vals: Fn0>; } export class MultiTrie, V> { - protected next: IObjectOf> = {}; - protected vals?: Set; - protected n = 0; - - constructor( - pairs?: Nullable>>, - protected opts?: Partial> - ) { - pairs && this.into(pairs); - } - - *[Symbol.iterator]() { - const queue: [string, MultiTrie][] = [["", this]]; - while (queue.length) { - const [prefix, node] = queue.pop()!; - if (node.vals) { - yield* map((v) => [prefix, v], node.vals); - } else { - node.queueChildren(queue, prefix); - } - } - } - - *keys(sep = "", prefix = "") { - const queue: [string, MultiTrie][] = [[prefix, this]]; - while (queue.length) { - const [key, node] = queue.pop()!; - if (node.vals) { - yield key; - } else { - node.queueChildren(queue, key, sep); - } - } - } - - *values() { - const queue: MultiTrie[] = [this]; - while (queue.length) { - const node = queue.pop()!; - if (node.vals) { - yield* node.vals; - } else { - queue.push(...vals(node.next)); - } - } - } - - *suffixes(prefix: K, withPrefix = false, sep = "") { - const node = this.find(prefix); - if (node) { - yield* node.keys( - sep, - withPrefix - ? isArray(prefix) - ? prefix.join(sep) - : prefix.toString() - : "" - ); - } - } - - clear() { - this.next = {}; - this.n = 0; - this.vals = undefined; - } - - has(key: K) { - return !!this.get(key); - } - - hasPrefix(prefix: K) { - return !!this.find(prefix); - } - - get(key: K): Set | undefined { - const node = this.find(key); - return node ? node.vals : undefined; - } - - find(key: K) { - let node: MultiTrie | undefined = this; - for (let i = 0, n = key.length; i < n; i++) { - node = node!.next[key[i].toString()]; - if (!node) return; - } - return node; - } - - /** - * Returns longest known prefix for `key` as array. If array is - * empty, the given key has no partial matches. - * - * @param key - - */ - knownPrefix(key: K) { - let node: MultiTrie | undefined = this; - const prefix: K[] = []; - for (let i = 0, n = key.length; i < n; i++) { - const k = key[i].toString(); - const next: MultiTrie | undefined = node!.next[k]; - if (!next) break; - prefix.push(k); - node = next; - } - return prefix; - } - - hasKnownPrefix(key: K) { - return this.knownPrefix(key).length > 0; - } - - add(key: K, val: V) { - let node: MultiTrie = this; - for (let i = 0, n = key.length; i < n; i++) { - const k = key[i].toString(); - const next = node.next[k]; - node = !next - ? (node.n++, (node.next[k] = new MultiTrie(null, this.opts))) - : next; - } - if (!node.vals) { - const ctor = this.opts?.vals; - node.vals = ctor ? ctor() : new Set(); - } - node.vals.add(val); - } - - into(xs: Iterable<[K, V]>) { - for (let [k, v] of xs) { - this.add(k, v); - } - } - - delete(prefix: K, val?: V) { - const n = prefix.length; - if (n < 1) return false; - const path: MultiTrie[] = []; - const key: string[] = []; - let i = 0; - let node: MultiTrie | undefined = this; - for (; i < n; i++) { - const k = prefix[i].toString(); - key.push(k); - path.push(node); - node = node.next[k]; - if (!node) return false; - } - // if val is given, remove from set - // and only collapse path if no other vals for key - if (val !== undefined) { - const vals = node.vals; - if (vals && vals.has(val)) { - vals.delete(val); - if (vals.size > 0) return true; - } else { - return false; - } - } - // collapse path - while ((node = path[--i])) { - delete node.next[key[i]]; - if (--node.n) break; - } - return true; - } - - protected queueChildren( - queue: [string, MultiTrie][], - prefix: string, - sep = "" - ) { - prefix = prefix.length ? prefix + sep : prefix; - queue.push( - ...Object.keys(this.next).map( - (k) => <[string, MultiTrie]>[prefix + k, this.next[k]] - ) - ); - } + protected next: IObjectOf> = {}; + protected vals?: Set; + protected n = 0; + + constructor( + pairs?: Nullable>>, + protected opts?: Partial> + ) { + pairs && this.into(pairs); + } + + *[Symbol.iterator]() { + const queue: [string, MultiTrie][] = [["", this]]; + while (queue.length) { + const [prefix, node] = queue.pop()!; + if (node.vals) { + yield* map((v) => [prefix, v], node.vals); + } else { + node.queueChildren(queue, prefix); + } + } + } + + *keys(sep = "", prefix = "") { + const queue: [string, MultiTrie][] = [[prefix, this]]; + while (queue.length) { + const [key, node] = queue.pop()!; + if (node.vals) { + yield key; + } else { + node.queueChildren(queue, key, sep); + } + } + } + + *values() { + const queue: MultiTrie[] = [this]; + while (queue.length) { + const node = queue.pop()!; + if (node.vals) { + yield* node.vals; + } else { + queue.push(...vals(node.next)); + } + } + } + + *suffixes(prefix: K, withPrefix = false, sep = "") { + const node = this.find(prefix); + if (node) { + yield* node.keys( + sep, + withPrefix + ? isArray(prefix) + ? prefix.join(sep) + : prefix.toString() + : "" + ); + } + } + + clear() { + this.next = {}; + this.n = 0; + this.vals = undefined; + } + + has(key: K) { + return !!this.get(key); + } + + hasPrefix(prefix: K) { + return !!this.find(prefix); + } + + get(key: K): Set | undefined { + const node = this.find(key); + return node ? node.vals : undefined; + } + + find(key: K) { + let node: MultiTrie | undefined = this; + for (let i = 0, n = key.length; i < n; i++) { + node = node!.next[key[i].toString()]; + if (!node) return; + } + return node; + } + + /** + * Returns longest known prefix for `key` as array. If array is + * empty, the given key has no partial matches. + * + * @param key - + */ + knownPrefix(key: K) { + let node: MultiTrie | undefined = this; + const prefix: K[] = []; + for (let i = 0, n = key.length; i < n; i++) { + const k = key[i].toString(); + const next: MultiTrie | undefined = node!.next[k]; + if (!next) break; + prefix.push(k); + node = next; + } + return prefix; + } + + hasKnownPrefix(key: K) { + return this.knownPrefix(key).length > 0; + } + + add(key: K, val: V) { + let node: MultiTrie = this; + for (let i = 0, n = key.length; i < n; i++) { + const k = key[i].toString(); + const next = node.next[k]; + node = !next + ? (node.n++, (node.next[k] = new MultiTrie(null, this.opts))) + : next; + } + if (!node.vals) { + const ctor = this.opts?.vals; + node.vals = ctor ? ctor() : new Set(); + } + node.vals.add(val); + } + + into(xs: Iterable<[K, V]>) { + for (let [k, v] of xs) { + this.add(k, v); + } + } + + delete(prefix: K, val?: V) { + const n = prefix.length; + if (n < 1) return false; + const path: MultiTrie[] = []; + const key: string[] = []; + let i = 0; + let node: MultiTrie | undefined = this; + for (; i < n; i++) { + const k = prefix[i].toString(); + key.push(k); + path.push(node); + node = node.next[k]; + if (!node) return false; + } + // if val is given, remove from set + // and only collapse path if no other vals for key + if (val !== undefined) { + const vals = node.vals; + if (vals && vals.has(val)) { + vals.delete(val); + if (vals.size > 0) return true; + } else { + return false; + } + } + // collapse path + while ((node = path[--i])) { + delete node.next[key[i]]; + if (--node.n) break; + } + return true; + } + + protected queueChildren( + queue: [string, MultiTrie][], + prefix: string, + sep = "" + ) { + prefix = prefix.length ? prefix + sep : prefix; + queue.push( + ...Object.keys(this.next).map( + (k) => <[string, MultiTrie]>[prefix + k, this.next[k]] + ) + ); + } } export const defMultiTrie = , V>( - pairs?: Iterable>, - opts?: Partial> + pairs?: Iterable>, + opts?: Partial> ) => new MultiTrie(pairs, opts); diff --git a/packages/associative/src/rename-keys.ts b/packages/associative/src/rename-keys.ts index 006b8db22e..3f35f81bd1 100644 --- a/packages/associative/src/rename-keys.ts +++ b/packages/associative/src/rename-keys.ts @@ -11,15 +11,15 @@ import { empty } from "./empty.js"; * @param out - result map */ export const renameKeysMap = ( - src: Map, - km: Map, - out?: Map + src: Map, + km: Map, + out?: Map ) => { - out = out || empty(src, Map); - for (let [k, v] of src) { - out!.set(km.has(k) ? km.get(k)! : k, v); - } - return out; + out = out || empty(src, Map); + for (let [k, v] of src) { + out!.set(km.has(k) ? km.get(k)! : k, v); + } + return out; }; /** @@ -37,14 +37,14 @@ export const renameKeysMap = ( * @param out - result object */ export const renameKeysObj = ( - src: T, - km: { [id in keyof T]?: PropertyKey }, - out: any = {} + src: T, + km: { [id in keyof T]?: PropertyKey }, + out: any = {} ) => { - for (let k in src) { - out[km.hasOwnProperty(k) ? km[k] : k] = src[k]; - } - return out; + for (let k in src) { + out[km.hasOwnProperty(k) ? km[k] : k] = src[k]; + } + return out; }; /** @@ -80,20 +80,20 @@ export const renameKeysObj = ( * // { aa: 1, bb: 21 } * ``` * - * @param src - - * @param keys - + * @param src - + * @param keys - */ export const renameTransformedKeys = ( - src: Nullable, - keys: Record]> + src: Nullable, + keys: Record]> ) => { - if (!src) return {}; - const res: any = {}; - for (let $k in keys) { - const spec = keys[$k]; - const [k, fn] = isArray(spec) ? spec : [spec]; - const val = src[$k]; - if (val != null) res[k] = fn ? fn(val, src) : val; - } - return res; + if (!src) return {}; + const res: any = {}; + for (let $k in keys) { + const spec = keys[$k]; + const [k, fn] = isArray(spec) ? spec : [spec]; + const val = src[$k]; + if (val != null) res[k] = fn ? fn(val, src) : val; + } + return res; }; diff --git a/packages/associative/src/select-keys.ts b/packages/associative/src/select-keys.ts index 2594aa420c..77b10d8d5c 100644 --- a/packages/associative/src/select-keys.ts +++ b/packages/associative/src/select-keys.ts @@ -8,33 +8,33 @@ import { empty } from "./empty.js"; * @param ks - selected keys */ export const selectKeysMap = ( - src: Map, - ks: Iterable + src: Map, + ks: Iterable ): Map => { - const dest = empty(src, Map); - for (let k of ks) { - src.has(k) && dest.set(k, src.get(k)); - } - return dest; + const dest = empty(src, Map); + for (let k of ks) { + src.has(k) && dest.set(k, src.get(k)); + } + return dest; }; /** * Similar to {@link selectKeysMap}, but only selects keys if their value is * defined (i.e. non-nullish). * - * @param src - - * @param ks - + * @param src - + * @param ks - */ export const selectDefinedKeysMap = ( - src: Map, - ks: Iterable + src: Map, + ks: Iterable ): Map => { - const dest = empty(src, Map); - for (let k of ks) { - const val = src.get(k); - if (val != null) dest.set(k, val); - } - return dest; + const dest = empty(src, Map); + for (let k of ks) { + const val = src.get(k); + if (val != null) dest.set(k, val); + } + return dest; }; /** @@ -45,31 +45,31 @@ export const selectDefinedKeysMap = ( * @param ks - selected keys */ export const selectKeysObj = ( - src: T, - ks: Iterable + src: T, + ks: Iterable ): Partial => { - const dest: any = {}; - for (let k of ks) { - src.hasOwnProperty(k) && (dest[k] = (src)[k]); - } - return dest; + const dest: any = {}; + for (let k of ks) { + src.hasOwnProperty(k) && (dest[k] = (src)[k]); + } + return dest; }; /** * Similar to {@link selectKeysObj}, but only selects keys if their value is * defined (i.e. non-nullish). * - * @param src - - * @param ks - + * @param src - + * @param ks - */ export const selectDefinedKeysObj = ( - src: T, - ks: Iterable + src: T, + ks: Iterable ) => { - const res: Partial = {}; - for (let k of ks) { - const val = src[k]; - if (val != null) res[k] = val; - } - return res; + const res: Partial = {}; + for (let k of ks) { + const val = src[k]; + if (val != null) res[k] = val; + } + return res; }; diff --git a/packages/associative/src/sorted-map.ts b/packages/associative/src/sorted-map.ts index c6862633e4..0e3985e5f8 100644 --- a/packages/associative/src/sorted-map.ts +++ b/packages/associative/src/sorted-map.ts @@ -12,25 +12,25 @@ import { __inspectable } from "./internal/inspect.js"; import { into } from "./into.js"; interface SortedMapState { - head: Node; - cmp: Comparator; - maxh: number; - h: number; - length: number; - cap: number; - p: number; + head: Node; + cmp: Comparator; + maxh: number; + h: number; + length: number; + cap: number; + p: number; } class Node { - k: K | null; - v: V | null; - next: Node[]; + k: K | null; + v: V | null; + next: Node[]; - constructor(k: K | null, v: V | null, h: number) { - this.k = k; - this.v = v; - this.next = new Array(h + 1); - } + constructor(k: K | null, v: V | null, h: number) { + this.k = k; + this.v = v; + this.next = new Array(h + 1); + } } // stores private properties for all instances @@ -39,285 +39,285 @@ const __private = new WeakMap, SortedMapState>(); @__inspectable export class SortedMap extends Map { - static DEFAULT_CAP = 8; - static DEFAULT_P = 1 / Math.E; + static DEFAULT_CAP = 8; + static DEFAULT_P = 1 / Math.E; - /** - * Creates new {@link SortedMap} instance with optionally given pairs - * and/or options. - * - * @param pairs - key-value pairs - * @param opts - config options - */ - constructor( - pairs?: Iterable> | null, - opts: Partial> = {} - ) { - super(); - const cap = opts.capacity || SortedMap.DEFAULT_CAP; - const maxh = Math.ceil(Math.log2(cap)); - __private.set(this, { - head: new Node(null, null, 0), - cap: Math.pow(2, maxh), - cmp: opts.compare || compare, - p: opts.probability || SortedMap.DEFAULT_P, - maxh, - length: 0, - h: 0, - }); - if (pairs) { - this.into(pairs); - } - } + /** + * Creates new {@link SortedMap} instance with optionally given pairs + * and/or options. + * + * @param pairs - key-value pairs + * @param opts - config options + */ + constructor( + pairs?: Iterable> | null, + opts: Partial> = {} + ) { + super(); + const cap = opts.capacity || SortedMap.DEFAULT_CAP; + const maxh = Math.ceil(Math.log2(cap)); + __private.set(this, { + head: new Node(null, null, 0), + cap: Math.pow(2, maxh), + cmp: opts.compare || compare, + p: opts.probability || SortedMap.DEFAULT_P, + maxh, + length: 0, + h: 0, + }); + if (pairs) { + this.into(pairs); + } + } - get [Symbol.species]() { - return SortedMap; - } + get [Symbol.species]() { + return SortedMap; + } - *[Symbol.iterator](): IterableIterator> { - let node = __private.get(this)!.head; - while ((node = node.next[0])) { - yield [node.k, node.v]; - } - } + *[Symbol.iterator](): IterableIterator> { + let node = __private.get(this)!.head; + while ((node = node.next[0])) { + yield [node.k, node.v]; + } + } - *entries(key?: K, max = false): IterableIterator> { - let { head: node, cmp } = __private.get(this)!; - let code: number | undefined; - if (max) { - while ((node = node.next[0])) { - if (key === undefined || (code = cmp(node.k, key)) <= 0) { - yield [node.k, node.v]; - if (code === 0) return; - } - } - } else { - while ((node = node.next[0])) { - if (key === undefined || (code = cmp(node.k, key)) >= 0) { - yield [node.k, node.v]; - } - } - } - } + *entries(key?: K, max = false): IterableIterator> { + let { head: node, cmp } = __private.get(this)!; + let code: number | undefined; + if (max) { + while ((node = node.next[0])) { + if (key === undefined || (code = cmp(node.k, key)) <= 0) { + yield [node.k, node.v]; + if (code === 0) return; + } + } + } else { + while ((node = node.next[0])) { + if (key === undefined || (code = cmp(node.k, key)) >= 0) { + yield [node.k, node.v]; + } + } + } + } - keys(key?: K, max = false): IterableIterator { - return map((p) => p[0], this.entries(key, max)); - } + keys(key?: K, max = false): IterableIterator { + return map((p) => p[0], this.entries(key, max)); + } - values(key?: K, max = false): IterableIterator { - return map((p) => p[1], this.entries(key, max)); - } + values(key?: K, max = false): IterableIterator { + return map((p) => p[1], this.entries(key, max)); + } - get size(): number { - return __private.get(this)!.length; - } + get size(): number { + return __private.get(this)!.length; + } - clear() { - const $this = __private.get(this)!; - $this.head = new Node(null, null, 0); - $this.length = 0; - $this.h = 0; - } + clear() { + const $this = __private.get(this)!; + $this.head = new Node(null, null, 0); + $this.length = 0; + $this.h = 0; + } - empty(): SortedMap { - return new SortedMap(null, { - ...this.opts(), - capacity: SortedMap.DEFAULT_CAP, - }); - } + empty(): SortedMap { + return new SortedMap(null, { + ...this.opts(), + capacity: SortedMap.DEFAULT_CAP, + }); + } - copy(): SortedMap { - return new SortedMap(this, this.opts()); - } + copy(): SortedMap { + return new SortedMap(this, this.opts()); + } - compare(o: Map) { - const n = this.size; - const m = o.size; - if (n < m) return -1; - if (n > m) return 1; - const i = this.entries(); - const j = o.entries(); - let x: IteratorResult>, y: IteratorResult>; - let c: number; - while (((x = i.next()), (y = j.next()), !x.done && !y.done)) { - if ( - (c = compare(x.value[0], y.value[0])) !== 0 || - (c = compare(x.value[1], y.value[1])) !== 0 - ) { - return c; - } - } - return 0; - } + compare(o: Map) { + const n = this.size; + const m = o.size; + if (n < m) return -1; + if (n > m) return 1; + const i = this.entries(); + const j = o.entries(); + let x: IteratorResult>, y: IteratorResult>; + let c: number; + while (((x = i.next()), (y = j.next()), !x.done && !y.done)) { + if ( + (c = compare(x.value[0], y.value[0])) !== 0 || + (c = compare(x.value[1], y.value[1])) !== 0 + ) { + return c; + } + } + return 0; + } - equiv(o: any) { - return __equivMap(this, o); - } + equiv(o: any) { + return __equivMap(this, o); + } - first(): Pair | undefined { - const node = __private.get(this)!.head.next[0]; - return node ? [node.k, node.v] : undefined; - } + first(): Pair | undefined { + const node = __private.get(this)!.head.next[0]; + return node ? [node.k, node.v] : undefined; + } - get(k: K, notFound?: V): V | undefined { - const node = this.findPredNode(k).next[0]; - return node && __private.get(this)!.cmp(node.k, k) === 0 - ? node.v - : notFound; - } + get(k: K, notFound?: V): V | undefined { + const node = this.findPredNode(k).next[0]; + return node && __private.get(this)!.cmp(node.k, k) === 0 + ? node.v + : notFound; + } - has(key: K) { - return this.get(key, SEMAPHORE) !== SEMAPHORE; - } + has(key: K) { + return this.get(key, SEMAPHORE) !== SEMAPHORE; + } - set(k: K, v: V) { - const $this = __private.get(this)!; - let node = $this.head; - let level = $this.h; - let stack = new Array(level); - const cmp = $this.cmp; - let code: number | undefined; - while (level >= 0) { - while ( - node.next[level] && - (code = cmp(node.next[level].k, k)) < 0 - ) { - node = node.next[level]; - } - if (node.next[level] && code === 0) { - do { - node.next[level].v = v; - } while (level-- > 0); - return this; - } - stack[level--] = node; - } - const h = this.pickHeight($this.maxh, $this.h, $this.p); - node = new Node(k, v, h); - while ($this.h < h) { - stack[++$this.h] = $this.head; - } - for (let i = 0; i <= h; i++) { - node.next[i] = stack[i].next[i]; - stack[i].next[i] = node; - } - $this.length++; - if ($this.length >= $this.cap) { - $this.cap *= 2; - $this.maxh++; - } - return this; - } + set(k: K, v: V) { + const $this = __private.get(this)!; + let node = $this.head; + let level = $this.h; + let stack = new Array(level); + const cmp = $this.cmp; + let code: number | undefined; + while (level >= 0) { + while ( + node.next[level] && + (code = cmp(node.next[level].k, k)) < 0 + ) { + node = node.next[level]; + } + if (node.next[level] && code === 0) { + do { + node.next[level].v = v; + } while (level-- > 0); + return this; + } + stack[level--] = node; + } + const h = this.pickHeight($this.maxh, $this.h, $this.p); + node = new Node(k, v, h); + while ($this.h < h) { + stack[++$this.h] = $this.head; + } + for (let i = 0; i <= h; i++) { + node.next[i] = stack[i].next[i]; + stack[i].next[i] = node; + } + $this.length++; + if ($this.length >= $this.cap) { + $this.cap *= 2; + $this.maxh++; + } + return this; + } - delete(k: K) { - const $this = __private.get(this)!; - let node: Node = $this.head; - let level = $this.h; - let removed = false; - const cmp = $this.cmp; - let code: number | undefined; - while (level >= 0) { - while ( - node.next[level] && - (code = cmp(node.next[level].k, k)) < 0 - ) { - node = node.next[level]; - } - if (node.next[level] && code === 0) { - removed = true; - node.next[level] = node.next[level].next[level]; - if (node == $this.head && !node.next[level]) { - $this.h = Math.max(0, $this.h - 1); - } - } - level--; - } - if (removed) $this.length--; - return removed; - } + delete(k: K) { + const $this = __private.get(this)!; + let node: Node = $this.head; + let level = $this.h; + let removed = false; + const cmp = $this.cmp; + let code: number | undefined; + while (level >= 0) { + while ( + node.next[level] && + (code = cmp(node.next[level].k, k)) < 0 + ) { + node = node.next[level]; + } + if (node.next[level] && code === 0) { + removed = true; + node.next[level] = node.next[level].next[level]; + if (node == $this.head && !node.next[level]) { + $this.h = Math.max(0, $this.h - 1); + } + } + level--; + } + if (removed) $this.length--; + return removed; + } - into(pairs: Iterable>) { - return into(this, pairs); - } + into(pairs: Iterable>) { + return into(this, pairs); + } - dissoc(keys: Iterable) { - return dissoc(this, keys); - } + dissoc(keys: Iterable) { + return dissoc(this, keys); + } - /** - * The key & value args given the callback `fn` MUST be treated as - * readonly/immutable. This could be enforced via TS, but would - * break ES6 Map interface contract. - * - * @param fn - - * @param thisArg - - */ - forEach(fn: Fn3, void>, thisArg?: any) { - for (let p of this) { - fn.call(thisArg, p[1], p[0], this); - } - } + /** + * The key & value args given the callback `fn` MUST be treated as + * readonly/immutable. This could be enforced via TS, but would + * break ES6 Map interface contract. + * + * @param fn - + * @param thisArg - + */ + forEach(fn: Fn3, void>, thisArg?: any) { + for (let p of this) { + fn.call(thisArg, p[1], p[0], this); + } + } - $reduce(rfn: ReductionFn>, acc: any) { - let node = __private.get(this)!.head; - while ((node = node.next[0]) && !isReduced(acc)) { - acc = rfn(acc, [node.k, node.v]); - } - return acc; - } + $reduce(rfn: ReductionFn>, acc: any) { + let node = __private.get(this)!.head; + while ((node = node.next[0]) && !isReduced(acc)) { + acc = rfn(acc, [node.k, node.v]); + } + return acc; + } - opts(): SortedMapOpts { - const $this = __private.get(this)!; - return { - capacity: $this.cap, - compare: $this.cmp, - probability: $this.p, - }; - } + opts(): SortedMapOpts { + const $this = __private.get(this)!; + return { + capacity: $this.cap, + compare: $this.cmp, + probability: $this.p, + }; + } - protected findPredNode(k: K) { - let { cmp, head: node, h: level } = __private.get(this)!; - while (level >= 0) { - while (node.next[level] && cmp(node.next[level].k, k) < 0) { - node = node.next[level]; - } - level--; - } - return node; - } + protected findPredNode(k: K) { + let { cmp, head: node, h: level } = __private.get(this)!; + while (level >= 0) { + while (node.next[level] && cmp(node.next[level].k, k) < 0) { + node = node.next[level]; + } + level--; + } + return node; + } - protected pickHeight(maxh: number, h: number, p: number) { - const max = Math.min(maxh, h + 1); - let level = 0; - while (Math.random() < p && level < max) { - level++; - } - return level; - } + protected pickHeight(maxh: number, h: number, p: number) { + const max = Math.min(maxh, h + 1); + let level = 0; + while (Math.random() < p && level < max) { + level++; + } + return level; + } } export function defSortedMap( - pairs?: Iterable> | null, - opts?: Partial> + pairs?: Iterable> | null, + opts?: Partial> ): SortedMap; export function defSortedMap( - obj: IObjectOf, - opts?: Partial> + obj: IObjectOf, + opts?: Partial> ): SortedMap; export function defSortedMap( - src: any, - opts?: Partial> + src: any, + opts?: Partial> ): SortedMap { - if (isPlainObject(src)) { - const keys = Object.keys(src); - return new SortedMap( - map((k) => >[k, (>src)[k]], keys), - { - capacity: keys.length, - ...opts, - } - ); - } else { - return new SortedMap(src, opts); - } + if (isPlainObject(src)) { + const keys = Object.keys(src); + return new SortedMap( + map((k) => >[k, (>src)[k]], keys), + { + capacity: keys.length, + ...opts, + } + ); + } else { + return new SortedMap(src, opts); + } } diff --git a/packages/associative/src/sorted-obj.ts b/packages/associative/src/sorted-obj.ts index 9af5d7c963..d6b70a867c 100644 --- a/packages/associative/src/sorted-obj.ts +++ b/packages/associative/src/sorted-obj.ts @@ -10,7 +10,7 @@ import { assocObj } from "@thi.ng/transducers/assoc-obj"; * Note: Object keys are not guaranteed to keep their order and behavior will * depend on JS runtime and object size (number of keys). * - * @param obj - + * @param obj - */ export const sortedObject = (obj: IObjectOf) => - assocObj(Object.entries(obj).sort(compareByKey(0))); + assocObj(Object.entries(obj).sort(compareByKey(0))); diff --git a/packages/associative/src/sorted-set.ts b/packages/associative/src/sorted-set.ts index 94f863a6e2..1a48d1728e 100644 --- a/packages/associative/src/sorted-set.ts +++ b/packages/associative/src/sorted-set.ts @@ -31,141 +31,141 @@ const __private = new WeakMap, SortedMap>(); */ @__inspectable export class SortedSet - extends Set - implements IEquivSet, ICompare>, IReducible + extends Set + implements IEquivSet, ICompare>, IReducible { - /** - * Creates new instance with optional given values and/or - * implementation options. The options are the same as used by - * {@link SortedMap}. - * - * @param values - input values - * @param opts - config options - */ - constructor(values?: Iterable | null, opts?: Partial>) { - super(); - __private.set( - this, - new SortedMap( - values ? map((x) => >[x, x], values) : null, - opts - ) - ); - } - - [Symbol.iterator](): IterableIterator { - return this.keys(); - } - - get [Symbol.species]() { - return SortedSet; - } - - get [Symbol.toStringTag]() { - return "SortedSet"; - } - - get size(): number { - return __private.get(this)!.size; - } - - copy(): SortedSet { - return new SortedSet(this.keys(), this.opts()); - } - - empty() { - return new SortedSet(null, { - ...this.opts(), - capacity: SortedMap.DEFAULT_CAP, - }); - } - - compare(o: Set) { - const n = this.size; - const m = o.size; - if (n < m) return -1; - if (n > m) return 1; - const i = this.entries(); - const j = o.entries(); - let x: IteratorResult>, y: IteratorResult>; - let c: number; - while (((x = i.next()), (y = j.next()), !x.done && !y.done)) { - if ((c = compare(x.value[0], y.value[0])) !== 0) { - return c; - } - } - return 0; - } - - equiv(o: any) { - return __equivSet(this, o); - } - - $reduce(rfn: ReductionFn, acc: any): any { - return __private.get(this)!.$reduce((_acc, x) => rfn(_acc, x[0]), acc); - } - - entries(key?: T, max = false): IterableIterator> { - return __private.get(this)!.entries(key, max); - } - - keys(key?: T, max = false): IterableIterator { - return __private.get(this)!.keys(key, max); - } - - values(key?: T, max = false): IterableIterator { - return __private.get(this)!.values(key, max); - } - - add(key: T) { - __private.get(this)!.set(key, key); - return this; - } - - into(keys: Iterable) { - return into(this, keys); - } - - clear(): void { - __private.get(this)!.clear(); - } - - first(): T { - const first = __private.get(this)!.first(); - return first ? first[0] : undefined; - } - - delete(key: T): boolean { - return __private.get(this)!.delete(key); - } - - disj(keys: Iterable) { - return dissoc(this, keys); - } - - forEach( - fn: Fn3, Readonly, Set, void>, - thisArg?: any - ): void { - for (let p of this) { - fn.call(thisArg, p, p, this); - } - } - - has(key: T): boolean { - return __private.get(this)!.has(key); - } - - get(key: T, notFound?: T): T | undefined { - return __private.get(this)!.get(key, notFound); - } - - opts(): SortedSetOpts { - return __private.get(this)!.opts(); - } + /** + * Creates new instance with optional given values and/or + * implementation options. The options are the same as used by + * {@link SortedMap}. + * + * @param values - input values + * @param opts - config options + */ + constructor(values?: Iterable | null, opts?: Partial>) { + super(); + __private.set( + this, + new SortedMap( + values ? map((x) => >[x, x], values) : null, + opts + ) + ); + } + + [Symbol.iterator](): IterableIterator { + return this.keys(); + } + + get [Symbol.species]() { + return SortedSet; + } + + get [Symbol.toStringTag]() { + return "SortedSet"; + } + + get size(): number { + return __private.get(this)!.size; + } + + copy(): SortedSet { + return new SortedSet(this.keys(), this.opts()); + } + + empty() { + return new SortedSet(null, { + ...this.opts(), + capacity: SortedMap.DEFAULT_CAP, + }); + } + + compare(o: Set) { + const n = this.size; + const m = o.size; + if (n < m) return -1; + if (n > m) return 1; + const i = this.entries(); + const j = o.entries(); + let x: IteratorResult>, y: IteratorResult>; + let c: number; + while (((x = i.next()), (y = j.next()), !x.done && !y.done)) { + if ((c = compare(x.value[0], y.value[0])) !== 0) { + return c; + } + } + return 0; + } + + equiv(o: any) { + return __equivSet(this, o); + } + + $reduce(rfn: ReductionFn, acc: any): any { + return __private.get(this)!.$reduce((_acc, x) => rfn(_acc, x[0]), acc); + } + + entries(key?: T, max = false): IterableIterator> { + return __private.get(this)!.entries(key, max); + } + + keys(key?: T, max = false): IterableIterator { + return __private.get(this)!.keys(key, max); + } + + values(key?: T, max = false): IterableIterator { + return __private.get(this)!.values(key, max); + } + + add(key: T) { + __private.get(this)!.set(key, key); + return this; + } + + into(keys: Iterable) { + return into(this, keys); + } + + clear(): void { + __private.get(this)!.clear(); + } + + first(): T { + const first = __private.get(this)!.first(); + return first ? first[0] : undefined; + } + + delete(key: T): boolean { + return __private.get(this)!.delete(key); + } + + disj(keys: Iterable) { + return dissoc(this, keys); + } + + forEach( + fn: Fn3, Readonly, Set, void>, + thisArg?: any + ): void { + for (let p of this) { + fn.call(thisArg, p, p, this); + } + } + + has(key: T): boolean { + return __private.get(this)!.has(key); + } + + get(key: T, notFound?: T): T | undefined { + return __private.get(this)!.get(key, notFound); + } + + opts(): SortedSetOpts { + return __private.get(this)!.opts(); + } } export const defSortedSet = ( - vals?: Iterable | null, - opts?: Partial> + vals?: Iterable | null, + opts?: Partial> ) => new SortedSet(vals, opts); diff --git a/packages/associative/src/sparse-set.ts b/packages/associative/src/sparse-set.ts index bb3ff47499..9bfc9d29e8 100644 --- a/packages/associative/src/sparse-set.ts +++ b/packages/associative/src/sparse-set.ts @@ -7,9 +7,9 @@ import { __inspectable } from "./internal/inspect.js"; import { into } from "./into.js"; interface SparseSetProps { - dense: UIntArray; - sparse: UIntArray; - n: number; + dense: UIntArray; + sparse: UIntArray; + n: number; } const __private = new WeakMap, SparseSetProps>(); @@ -26,225 +26,225 @@ const fail = () => illegalArgs(`dense & sparse arrays must be of same size`); */ @__inspectable export abstract class ASparseSet - extends Set - implements IEquiv + extends Set + implements IEquiv { - protected constructor(dense: T, sparse: T) { - super(); - __private.set(this, { dense, sparse, n: 0 }); - } - - [Symbol.iterator]() { - return this.keys(); - } - - get size(): number { - return __private.get(this)!.n; - } - - get capacity(): number { - return __private.get(this)!.dense.length; - } - - clear() { - __private.get(this)!.n = 0; - } - - equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Set) || this.size !== o.size) { - return false; - } - const $this = __private.get(this)!; - const d = $this.dense; - for (let i = $this.n; i-- > 0; ) { - if (!o.has(d[i])) { - return false; - } - } - return true; - } - - add(key: number) { - const $this = __private.get(this)!; - const { dense, sparse, n } = $this; - const max = dense.length; - const i = sparse[key]; - if (key < max && n < max && !(i < n && dense[i] === key)) { - dense[n] = key; - sparse[key] = n; - $this.n++; - } - return this; - } - - delete(key: number) { - const $this = __private.get(this)!; - const { dense, sparse } = $this; - const i = sparse[key]; - if (i < $this.n && dense[i] === key) { - const j = dense[--$this.n]; - dense[i] = j; - sparse[j] = i; - return true; - } - return false; - } - - has(key: number): boolean { - const $this = __private.get(this)!; - const i = $this.sparse[key]; - return i < $this.n && $this.dense[i] === key; - } - - get(key: number, notFound = -1) { - return this.has(key) ? key : notFound; - } - - first() { - const $this = __private.get(this)!; - return $this.n ? $this.dense[0] : undefined; - } - - into(keys: Iterable) { - return into(this, keys); - } - - disj(keys: Iterable) { - return dissoc(this, keys); - } - - forEach(fn: Fn3, void>, thisArg?: any) { - const $this = __private.get(this)!; - const d = $this.dense; - const n = $this.n; - for (let i = 0; i < n; i++) { - const v = d[i]; - fn.call(thisArg, v, v, this); - } - } - - *entries(): IterableIterator> { - const { dense, n } = __private.get(this)!; - for (let i = 0; i < n; i++) { - yield [dense[i], dense[i]]; - } - } - - *keys(): IterableIterator { - const { dense, n } = __private.get(this)!; - for (let i = 0; i < n; i++) { - yield dense[i]; - } - } - - values() { - return this.keys(); - } - - protected __copyTo>(dest: S) { - const $this = __private.get(this)!; - const $c = __private.get(dest)!; - $c.dense = $this.dense.slice(); - $c.sparse = $this.sparse.slice(); - $c.n = $this.n; - return dest; - } + protected constructor(dense: T, sparse: T) { + super(); + __private.set(this, { dense, sparse, n: 0 }); + } + + [Symbol.iterator]() { + return this.keys(); + } + + get size(): number { + return __private.get(this)!.n; + } + + get capacity(): number { + return __private.get(this)!.dense.length; + } + + clear() { + __private.get(this)!.n = 0; + } + + equiv(o: any) { + if (this === o) { + return true; + } + if (!(o instanceof Set) || this.size !== o.size) { + return false; + } + const $this = __private.get(this)!; + const d = $this.dense; + for (let i = $this.n; i-- > 0; ) { + if (!o.has(d[i])) { + return false; + } + } + return true; + } + + add(key: number) { + const $this = __private.get(this)!; + const { dense, sparse, n } = $this; + const max = dense.length; + const i = sparse[key]; + if (key < max && n < max && !(i < n && dense[i] === key)) { + dense[n] = key; + sparse[key] = n; + $this.n++; + } + return this; + } + + delete(key: number) { + const $this = __private.get(this)!; + const { dense, sparse } = $this; + const i = sparse[key]; + if (i < $this.n && dense[i] === key) { + const j = dense[--$this.n]; + dense[i] = j; + sparse[j] = i; + return true; + } + return false; + } + + has(key: number): boolean { + const $this = __private.get(this)!; + const i = $this.sparse[key]; + return i < $this.n && $this.dense[i] === key; + } + + get(key: number, notFound = -1) { + return this.has(key) ? key : notFound; + } + + first() { + const $this = __private.get(this)!; + return $this.n ? $this.dense[0] : undefined; + } + + into(keys: Iterable) { + return into(this, keys); + } + + disj(keys: Iterable) { + return dissoc(this, keys); + } + + forEach(fn: Fn3, void>, thisArg?: any) { + const $this = __private.get(this)!; + const d = $this.dense; + const n = $this.n; + for (let i = 0; i < n; i++) { + const v = d[i]; + fn.call(thisArg, v, v, this); + } + } + + *entries(): IterableIterator> { + const { dense, n } = __private.get(this)!; + for (let i = 0; i < n; i++) { + yield [dense[i], dense[i]]; + } + } + + *keys(): IterableIterator { + const { dense, n } = __private.get(this)!; + for (let i = 0; i < n; i++) { + yield dense[i]; + } + } + + values() { + return this.keys(); + } + + protected __copyTo>(dest: S) { + const $this = __private.get(this)!; + const $c = __private.get(dest)!; + $c.dense = $this.dense.slice(); + $c.sparse = $this.sparse.slice(); + $c.n = $this.n; + return dest; + } } export class SparseSet8 - extends ASparseSet - implements IEquivSet + extends ASparseSet + implements IEquivSet { - constructor(dense: Uint8Array, sparse: Uint8Array); - constructor(n: number); - constructor(n: number | Uint8Array, sparse?: Uint8Array) { - isNumber(n) - ? super(new Uint8Array(n), new Uint8Array(n)) - : n.length === sparse!.length - ? super(n, sparse!) - : fail(); - } - - get [Symbol.species]() { - return SparseSet8; - } - - get [Symbol.toStringTag]() { - return "SparseSet8"; - } - - copy() { - return this.__copyTo(new SparseSet8(0)); - } - - empty() { - return new SparseSet8(this.capacity); - } + constructor(dense: Uint8Array, sparse: Uint8Array); + constructor(n: number); + constructor(n: number | Uint8Array, sparse?: Uint8Array) { + isNumber(n) + ? super(new Uint8Array(n), new Uint8Array(n)) + : n.length === sparse!.length + ? super(n, sparse!) + : fail(); + } + + get [Symbol.species]() { + return SparseSet8; + } + + get [Symbol.toStringTag]() { + return "SparseSet8"; + } + + copy() { + return this.__copyTo(new SparseSet8(0)); + } + + empty() { + return new SparseSet8(this.capacity); + } } export class SparseSet16 - extends ASparseSet - implements IEquivSet + extends ASparseSet + implements IEquivSet { - constructor(dense: Uint16Array, sparse: Uint16Array); - constructor(n: number); - constructor(n: number | Uint16Array, sparse?: Uint16Array) { - isNumber(n) - ? super(new Uint16Array(n), new Uint16Array(n)) - : n.length === sparse!.length - ? super(n, sparse!) - : fail(); - } - - get [Symbol.species]() { - return SparseSet16; - } - - get [Symbol.toStringTag]() { - return "SparseSet16"; - } - - copy() { - return this.__copyTo(new SparseSet16(0)); - } - - empty() { - return new SparseSet16(this.capacity); - } + constructor(dense: Uint16Array, sparse: Uint16Array); + constructor(n: number); + constructor(n: number | Uint16Array, sparse?: Uint16Array) { + isNumber(n) + ? super(new Uint16Array(n), new Uint16Array(n)) + : n.length === sparse!.length + ? super(n, sparse!) + : fail(); + } + + get [Symbol.species]() { + return SparseSet16; + } + + get [Symbol.toStringTag]() { + return "SparseSet16"; + } + + copy() { + return this.__copyTo(new SparseSet16(0)); + } + + empty() { + return new SparseSet16(this.capacity); + } } export class SparseSet32 - extends ASparseSet - implements IEquivSet + extends ASparseSet + implements IEquivSet { - constructor(dense: Uint32Array, sparse: Uint32Array); - constructor(n: number); - constructor(n: number | Uint32Array, sparse?: Uint32Array) { - isNumber(n) - ? super(new Uint32Array(n), new Uint32Array(n)) - : n.length === sparse!.length - ? super(n, sparse!) - : fail(); - } - - get [Symbol.species]() { - return SparseSet32; - } - - get [Symbol.toStringTag]() { - return "SparseSet32"; - } - - copy() { - return this.__copyTo(new SparseSet32(0)); - } - - empty() { - return new SparseSet32(this.capacity); - } + constructor(dense: Uint32Array, sparse: Uint32Array); + constructor(n: number); + constructor(n: number | Uint32Array, sparse?: Uint32Array) { + isNumber(n) + ? super(new Uint32Array(n), new Uint32Array(n)) + : n.length === sparse!.length + ? super(n, sparse!) + : fail(); + } + + get [Symbol.species]() { + return SparseSet32; + } + + get [Symbol.toStringTag]() { + return "SparseSet32"; + } + + copy() { + return this.__copyTo(new SparseSet32(0)); + } + + empty() { + return new SparseSet32(this.capacity); + } } /** @@ -255,8 +255,8 @@ export class SparseSet32 * @param n - max capacity, ID range: [0...n) */ export const defSparseSet = (n: number) => - n <= 0x100 - ? new SparseSet8(n) - : n <= 0x10000 - ? new SparseSet16(n) - : new SparseSet32(n); + n <= 0x100 + ? new SparseSet8(n) + : n <= 0x10000 + ? new SparseSet16(n) + : new SparseSet32(n); diff --git a/packages/associative/src/trie-map.ts b/packages/associative/src/trie-map.ts index de1ab8e4ed..468293f7b1 100644 --- a/packages/associative/src/trie-map.ts +++ b/packages/associative/src/trie-map.ts @@ -2,153 +2,153 @@ import type { IObjectOf, Pair } from "@thi.ng/api"; import { vals } from "@thi.ng/transducers/vals"; export class TrieMap { - protected next: IObjectOf> = {}; - protected val?: T; - protected n = 0; - - constructor(pairs?: Iterable>) { - pairs && this.into(pairs); - } - - *[Symbol.iterator]() { - const queue: Pair>[] = [["", this]]; - while (queue.length) { - const [prefix, node] = queue.pop()!; - if (node.val !== undefined) { - yield [prefix, node.val]; - } else { - node.queueChildren(queue, prefix); - } - } - } - - *keys(prefix = "") { - const queue: Pair>[] = [[prefix, this]]; - while (queue.length) { - const [key, node] = queue.pop()!; - if (node.val !== undefined) { - yield key; - } else { - node.queueChildren(queue, key); - } - } - } - - *values() { - const queue: TrieMap[] = [this]; - while (queue.length) { - const node = queue.pop()!; - if (node.val !== undefined) { - yield node.val; - } else { - queue.push(...vals(node.next)); - } - } - } - - *suffixes(prefix: string, withPrefix = false) { - const node = this.find(prefix); - if (node) { - yield* node.keys(withPrefix ? prefix : ""); - } - } - - clear() { - this.next = {}; - this.n = 0; - this.val = undefined; - } - - has(key: string) { - return this.get(key) !== undefined; - } - - hasPrefix(prefix: string) { - return !!this.find(prefix); - } - - get(key: string, notFound?: T): T | undefined { - const node = this.find(key); - return node ? node.val : notFound; - } - - find(key: string) { - let node: TrieMap | undefined = this; - for (let i = 0, n = key.length; i < n; i++) { - node = node!.next[key[i]]; - if (!node) return; - } - return node; - } - - /** - * Returns longest known prefix for `key`. Returns undefined if given key - * has no partial matches. - * - * @param key - - */ - knownPrefix(key: string) { - let node: TrieMap | undefined = this; - let prefix: string = ""; - for (let i = 0, n = key.length; i < n; i++) { - const k = key[i]; - const next: TrieMap | undefined = node!.next[k]; - if (!next) break; - prefix += k; - node = next; - } - return prefix || undefined; - } - - hasKnownPrefix(key: string) { - return !!this.knownPrefix(key); - } - - set(key: string, val: T) { - let node: TrieMap = this; - for (let i = 0, n = key.length; i < n; i++) { - const k = key[i]; - const next = node.next[k]; - node = !next ? (node.n++, (node.next[k] = new TrieMap())) : next; - } - node.val = val; - } - - into(pairs: Iterable>) { - for (let [k, v] of pairs) { - this.set(k, v); - } - } - - delete(prefix: string) { - const n = prefix.length; - if (n < 1) return false; - const path: TrieMap[] = []; - const key: string[] = []; - let i = 0; - let node: TrieMap | undefined = this; - for (; i < n; i++) { - const k = prefix[i]; - key.push(k); - path.push(node); - node = node.next[k]; - if (!node) return false; - } - while ((node = path[--i])) { - delete node.next[key[i]]; - if (--node.n) break; - } - return true; - } - - protected queueChildren(queue: [string, TrieMap][], prefix: string) { - queue.push( - ...Object.keys(this.next).map( - (k) => >>[prefix + k, this.next[k]] - ) - ); - } + protected next: IObjectOf> = {}; + protected val?: T; + protected n = 0; + + constructor(pairs?: Iterable>) { + pairs && this.into(pairs); + } + + *[Symbol.iterator]() { + const queue: Pair>[] = [["", this]]; + while (queue.length) { + const [prefix, node] = queue.pop()!; + if (node.val !== undefined) { + yield [prefix, node.val]; + } else { + node.queueChildren(queue, prefix); + } + } + } + + *keys(prefix = "") { + const queue: Pair>[] = [[prefix, this]]; + while (queue.length) { + const [key, node] = queue.pop()!; + if (node.val !== undefined) { + yield key; + } else { + node.queueChildren(queue, key); + } + } + } + + *values() { + const queue: TrieMap[] = [this]; + while (queue.length) { + const node = queue.pop()!; + if (node.val !== undefined) { + yield node.val; + } else { + queue.push(...vals(node.next)); + } + } + } + + *suffixes(prefix: string, withPrefix = false) { + const node = this.find(prefix); + if (node) { + yield* node.keys(withPrefix ? prefix : ""); + } + } + + clear() { + this.next = {}; + this.n = 0; + this.val = undefined; + } + + has(key: string) { + return this.get(key) !== undefined; + } + + hasPrefix(prefix: string) { + return !!this.find(prefix); + } + + get(key: string, notFound?: T): T | undefined { + const node = this.find(key); + return node ? node.val : notFound; + } + + find(key: string) { + let node: TrieMap | undefined = this; + for (let i = 0, n = key.length; i < n; i++) { + node = node!.next[key[i]]; + if (!node) return; + } + return node; + } + + /** + * Returns longest known prefix for `key`. Returns undefined if given key + * has no partial matches. + * + * @param key - + */ + knownPrefix(key: string) { + let node: TrieMap | undefined = this; + let prefix: string = ""; + for (let i = 0, n = key.length; i < n; i++) { + const k = key[i]; + const next: TrieMap | undefined = node!.next[k]; + if (!next) break; + prefix += k; + node = next; + } + return prefix || undefined; + } + + hasKnownPrefix(key: string) { + return !!this.knownPrefix(key); + } + + set(key: string, val: T) { + let node: TrieMap = this; + for (let i = 0, n = key.length; i < n; i++) { + const k = key[i]; + const next = node.next[k]; + node = !next ? (node.n++, (node.next[k] = new TrieMap())) : next; + } + node.val = val; + } + + into(pairs: Iterable>) { + for (let [k, v] of pairs) { + this.set(k, v); + } + } + + delete(prefix: string) { + const n = prefix.length; + if (n < 1) return false; + const path: TrieMap[] = []; + const key: string[] = []; + let i = 0; + let node: TrieMap | undefined = this; + for (; i < n; i++) { + const k = prefix[i]; + key.push(k); + path.push(node); + node = node.next[k]; + if (!node) return false; + } + while ((node = path[--i])) { + delete node.next[key[i]]; + if (--node.n) break; + } + return true; + } + + protected queueChildren(queue: [string, TrieMap][], prefix: string) { + queue.push( + ...Object.keys(this.next).map( + (k) => >>[prefix + k, this.next[k]] + ) + ); + } } export const defTrieMap = (pairs?: Iterable>) => - new TrieMap(pairs); + new TrieMap(pairs); diff --git a/packages/associative/src/union.ts b/packages/associative/src/union.ts index 4c1799fb98..005d44d1a8 100644 --- a/packages/associative/src/union.ts +++ b/packages/associative/src/union.ts @@ -12,13 +12,13 @@ import { into } from "./into.js"; * @param out - result set */ export const union = (a: Set, b: Set, out?: Set): Set => { - if (a.size < b.size) { - const t = a; - a = b; - b = t; - } - out = out ? into(out, a) : copy(a, Set); - return a === b ? out! : into(out!, b); + if (a.size < b.size) { + const t = a; + a = b; + b = t; + } + out = out ? into(out, a) : copy(a, Set); + return a === b ? out! : into(out!, b); }; /** @@ -32,5 +32,5 @@ export const union = (a: Set, b: Set, out?: Set): Set => { export function unionR(): Reducer, Iterable>; export function unionR(src: Iterable>): Set; export function unionR(src?: Iterable>) { - return __combineSet(unionR, union, src); + return __combineSet(unionR, union, src); } diff --git a/packages/associative/src/without-keys.ts b/packages/associative/src/without-keys.ts index dbca0c01ea..d0722ea96b 100644 --- a/packages/associative/src/without-keys.ts +++ b/packages/associative/src/without-keys.ts @@ -3,23 +3,23 @@ import { ensureSet } from "./checks.js"; import { empty } from "./empty.js"; export const withoutKeysMap = (src: Map, keys: Iterable) => { - const ks = ensureSet(keys); - const dest = empty(src, Map); - for (let p of src.entries()) { - const k = p[0]; - !ks.has(k) && dest.set(k, p[1]); - } - return dest; + const ks = ensureSet(keys); + const dest = empty(src, Map); + for (let p of src.entries()) { + const k = p[0]; + !ks.has(k) && dest.set(k, p[1]); + } + return dest; }; export const withoutKeysObj = ( - src: IObjectOf, - keys: Iterable + src: IObjectOf, + keys: Iterable ) => { - const ks = ensureSet(keys); - const dest: IObjectOf = {}; - for (let k in src) { - src.hasOwnProperty(k) && !ks.has(k) && (dest[k] = src[k]); - } - return dest; + const ks = ensureSet(keys); + const dest: IObjectOf = {}; + for (let k in src) { + src.hasOwnProperty(k) && !ks.has(k) && (dest[k] = src[k]); + } + return dest; }; diff --git a/packages/associative/test/bidir.ts b/packages/associative/test/bidir.ts index 3ddd4c449a..5e4e5273ac 100644 --- a/packages/associative/test/bidir.ts +++ b/packages/associative/test/bidir.ts @@ -3,46 +3,46 @@ import * as assert from "assert"; import { defBidirIndex } from "../src/index.js"; group("BidirIndex", { - addAll: () => { - const idx = defBidirIndex("abc", { start: 100 }); - assert.deepStrictEqual( - idx.fwd, - new Map([ - ["a", 100], - ["b", 101], - ["c", 102], - ]) - ); - assert.deepStrictEqual( - idx.rev, - new Map([ - [100, "a"], - [101, "b"], - [102, "c"], - ]) - ); - }, - getAll: () => { - const idx = defBidirIndex("abc"); - assert.deepStrictEqual(idx.getAll("cba"), [2, 1, 0]); - assert.deepStrictEqual(idx.getAllIDs([2, 1, 0]), ["c", "b", "a"]); - }, - deleteAll: () => { - const idx = defBidirIndex("abcd", { start: 100 }); - idx.deleteAll("bd"); - assert.deepStrictEqual( - idx.fwd, - new Map([ - ["a", 100], - ["c", 102], - ]) - ); - assert.deepStrictEqual( - idx.rev, - new Map([ - [100, "a"], - [102, "c"], - ]) - ); - }, + addAll: () => { + const idx = defBidirIndex("abc", { start: 100 }); + assert.deepStrictEqual( + idx.fwd, + new Map([ + ["a", 100], + ["b", 101], + ["c", 102], + ]) + ); + assert.deepStrictEqual( + idx.rev, + new Map([ + [100, "a"], + [101, "b"], + [102, "c"], + ]) + ); + }, + getAll: () => { + const idx = defBidirIndex("abc"); + assert.deepStrictEqual(idx.getAll("cba"), [2, 1, 0]); + assert.deepStrictEqual(idx.getAllIDs([2, 1, 0]), ["c", "b", "a"]); + }, + deleteAll: () => { + const idx = defBidirIndex("abcd", { start: 100 }); + idx.deleteAll("bd"); + assert.deepStrictEqual( + idx.fwd, + new Map([ + ["a", 100], + ["c", 102], + ]) + ); + assert.deepStrictEqual( + idx.rev, + new Map([ + [100, "a"], + [102, "c"], + ]) + ); + }, }); diff --git a/packages/associative/test/difference.ts b/packages/associative/test/difference.ts index 12c54a3634..3f12037258 100644 --- a/packages/associative/test/difference.ts +++ b/packages/associative/test/difference.ts @@ -1,50 +1,50 @@ import { equiv } from "@thi.ng/equiv"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { ArraySet, difference } from "../src/index.js" +import { ArraySet, difference } from "../src/index.js"; group("difference", { - "native (numbers)": () => { - const a = new Set([1, 2, 3, 4]); - const b = new Set([3, 4, 5]); - assert.deepStrictEqual(difference(a, b), new Set([1, 2])); - }, + "native (numbers)": () => { + const a = new Set([1, 2, 3, 4]); + const b = new Set([3, 4, 5]); + assert.deepStrictEqual(difference(a, b), new Set([1, 2])); + }, - "array (numbers)": () => { - const a = new ArraySet([1, 2, 3, 4]); - const b = new ArraySet([3, 4, 5]); - assert.deepStrictEqual(difference(a, b), new ArraySet([1, 2])); - }, + "array (numbers)": () => { + const a = new ArraySet([1, 2, 3, 4]); + const b = new ArraySet([3, 4, 5]); + assert.deepStrictEqual(difference(a, b), new ArraySet([1, 2])); + }, - "native (obj)": () => { - const a = new Set([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]); - const b = new Set([{ a: 3 }, { a: 4 }, { a: 5 }]); - const d = difference(a, b); - assert.strictEqual(d.size, 4); // verifies that it doesn't work w/ native sets! - assert.deepStrictEqual(d, a); - assert.notStrictEqual(d, a); - assert.notStrictEqual(d, b); - }, + "native (obj)": () => { + const a = new Set([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]); + const b = new Set([{ a: 3 }, { a: 4 }, { a: 5 }]); + const d = difference(a, b); + assert.strictEqual(d.size, 4); // verifies that it doesn't work w/ native sets! + assert.deepStrictEqual(d, a); + assert.notStrictEqual(d, a); + assert.notStrictEqual(d, b); + }, - "array (obj)": () => { - const a = new ArraySet([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]); - const b = new ArraySet([{ a: 3 }, { a: 4 }, { a: 5 }]); - const d = difference(a, b); - assert.strictEqual(d.size, 2); - assert.ok(equiv(d, new ArraySet([{ a: 1 }, { a: 2 }]))); - assert.notStrictEqual(d, a); - assert.notStrictEqual(d, b); - }, + "array (obj)": () => { + const a = new ArraySet([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]); + const b = new ArraySet([{ a: 3 }, { a: 4 }, { a: 5 }]); + const d = difference(a, b); + assert.strictEqual(d.size, 2); + assert.ok(equiv(d, new ArraySet([{ a: 1 }, { a: 2 }]))); + assert.notStrictEqual(d, a); + assert.notStrictEqual(d, b); + }, - "w/ out": () => { - assert.deepStrictEqual( - difference(new Set([1, 2, 3]), new Set([2, 4]), new Set([5])), - new Set([1, 3, 5]) - ); - }, + "w/ out": () => { + assert.deepStrictEqual( + difference(new Set([1, 2, 3]), new Set([2, 4]), new Set([5])), + new Set([1, 3, 5]) + ); + }, - same: () => { - const a = new Set([1]); - assert.deepStrictEqual(difference(a, a), new Set()); - }, + same: () => { + const a = new Set([1]); + assert.deepStrictEqual(difference(a, a), new Set()); + }, }); diff --git a/packages/associative/test/intersection.ts b/packages/associative/test/intersection.ts index a341db2665..1f13e1c858 100644 --- a/packages/associative/test/intersection.ts +++ b/packages/associative/test/intersection.ts @@ -1,40 +1,40 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { ArraySet, intersection } from "../src/index.js" +import { ArraySet, intersection } from "../src/index.js"; group("intersection", { - "native (numbers)": () => { - const a = new Set([1, 2, 3, 4]); - const b = new Set([3, 4, 5, 6]); - assert.deepStrictEqual(intersection(a, b), new Set([3, 4])); - }, + "native (numbers)": () => { + const a = new Set([1, 2, 3, 4]); + const b = new Set([3, 4, 5, 6]); + assert.deepStrictEqual(intersection(a, b), new Set([3, 4])); + }, - "array (numbers)": () => { - const a = new ArraySet([1, 2, 3, 4]); - const b = new ArraySet([3, 4, 5, 6]); - assert.deepStrictEqual(intersection(a, b), new ArraySet([3, 4])); - }, + "array (numbers)": () => { + const a = new ArraySet([1, 2, 3, 4]); + const b = new ArraySet([3, 4, 5, 6]); + assert.deepStrictEqual(intersection(a, b), new ArraySet([3, 4])); + }, - "native (obj)": () => { - const a = new Set([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]); - const b = new Set([{ a: 3 }, { a: 4 }, { a: 5 }]); - const i = intersection(a, b); - assert.deepStrictEqual(i, new Set()); // verifies that it doesn't work w/ native sets! - assert.notStrictEqual(i, a); - assert.notStrictEqual(i, b); - }, + "native (obj)": () => { + const a = new Set([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]); + const b = new Set([{ a: 3 }, { a: 4 }, { a: 5 }]); + const i = intersection(a, b); + assert.deepStrictEqual(i, new Set()); // verifies that it doesn't work w/ native sets! + assert.notStrictEqual(i, a); + assert.notStrictEqual(i, b); + }, - "array (obj)": () => { - const a = new ArraySet([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]); - const b = new ArraySet([{ a: 3 }, { a: 4 }, { a: 5 }]); - const i = intersection(a, b); - assert.deepStrictEqual(i, new ArraySet([{ a: 3 }, { a: 4 }])); - }, + "array (obj)": () => { + const a = new ArraySet([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]); + const b = new ArraySet([{ a: 3 }, { a: 4 }, { a: 5 }]); + const i = intersection(a, b); + assert.deepStrictEqual(i, new ArraySet([{ a: 3 }, { a: 4 }])); + }, - "w/ out": () => { - assert.deepStrictEqual( - intersection(new Set([1, 2, 3]), new Set([2, 4]), new Set([5])), - new Set([2, 5]) - ); - }, + "w/ out": () => { + assert.deepStrictEqual( + intersection(new Set([1, 2, 3]), new Set([2, 4]), new Set([5])), + new Set([2, 5]) + ); + }, }); diff --git a/packages/associative/test/join.ts b/packages/associative/test/join.ts index f7efadadf3..c86c648064 100644 --- a/packages/associative/test/join.ts +++ b/packages/associative/test/join.ts @@ -1,61 +1,61 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { ArraySet, join, joinWith } from "../src/index.js" +import { ArraySet, join, joinWith } from "../src/index.js"; group("join", { - simple: () => { - const a = new ArraySet([{ a: 1 }, { a: 2 }]); - const b = new ArraySet([{ b: 1 }, { b: 2 }]); - assert.deepStrictEqual( - join(a, b), - new ArraySet([ - { a: 1, b: 1 }, - { a: 2, b: 1 }, - { a: 1, b: 2 }, - { a: 2, b: 2 }, - ]) - ); - }, + simple: () => { + const a = new ArraySet([{ a: 1 }, { a: 2 }]); + const b = new ArraySet([{ b: 1 }, { b: 2 }]); + assert.deepStrictEqual( + join(a, b), + new ArraySet([ + { a: 1, b: 1 }, + { a: 2, b: 1 }, + { a: 1, b: 2 }, + { a: 2, b: 2 }, + ]) + ); + }, - "simple isec": () => { - const a = new ArraySet([ - { id: "a", type: 1 }, - { id: "b", type: 1 }, - { id: "c", type: 2 }, - ]); - const b = new ArraySet([ - { type: 1, label: "foo" }, - { type: 2, label: "bar" }, - { type: 3, label: "baz" }, - ]); - assert.deepStrictEqual( - join(a, b), - new ArraySet([ - { id: "a", type: 1, label: "foo" }, - { id: "b", type: 1, label: "foo" }, - { id: "c", type: 2, label: "bar" }, - ]) - ); - }, + "simple isec": () => { + const a = new ArraySet([ + { id: "a", type: 1 }, + { id: "b", type: 1 }, + { id: "c", type: 2 }, + ]); + const b = new ArraySet([ + { type: 1, label: "foo" }, + { type: 2, label: "bar" }, + { type: 3, label: "baz" }, + ]); + assert.deepStrictEqual( + join(a, b), + new ArraySet([ + { id: "a", type: 1, label: "foo" }, + { id: "b", type: 1, label: "foo" }, + { id: "c", type: 2, label: "bar" }, + ]) + ); + }, - joinWith: () => { - const a = new ArraySet([ - { id: "a", type: 1 }, - { id: "b", type: 1 }, - { id: "c", type: 2 }, - ]); - const b = new ArraySet([ - { xyz: 1, label: "foo" }, - { xyz: 2, label: "bar" }, - { xyz: 3, label: "baz" }, - ]); - assert.deepStrictEqual( - joinWith(a, b, { type: "xyz" }), - new ArraySet([ - { id: "a", type: 1, xyz: 1, label: "foo" }, - { id: "b", type: 1, xyz: 1, label: "foo" }, - { id: "c", type: 2, xyz: 2, label: "bar" }, - ]) - ); - }, + joinWith: () => { + const a = new ArraySet([ + { id: "a", type: 1 }, + { id: "b", type: 1 }, + { id: "c", type: 2 }, + ]); + const b = new ArraySet([ + { xyz: 1, label: "foo" }, + { xyz: 2, label: "bar" }, + { xyz: 3, label: "baz" }, + ]); + assert.deepStrictEqual( + joinWith(a, b, { type: "xyz" }), + new ArraySet([ + { id: "a", type: 1, xyz: 1, label: "foo" }, + { id: "b", type: 1, xyz: 1, label: "foo" }, + { id: "c", type: 2, xyz: 2, label: "bar" }, + ]) + ); + }, }); diff --git a/packages/associative/test/merge.ts b/packages/associative/test/merge.ts index 8a868884c5..5544661a41 100644 --- a/packages/associative/test/merge.ts +++ b/packages/associative/test/merge.ts @@ -2,106 +2,106 @@ import type { Fn, FnN } from "@thi.ng/api"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { - meldApplyObj, - meldDeepObj, - mergeApplyMap, - mergeApplyObj, - mergeDeepObj, -} from "../src/index.js" + meldApplyObj, + meldDeepObj, + mergeApplyMap, + mergeApplyObj, + mergeDeepObj, +} from "../src/index.js"; group("mergeApply", { - map: () => { - assert.deepStrictEqual( - mergeApplyMap( - new Map([ - ["a", 1], - ["b", 2], - ["c", 3], - ]), - new Map>([ - ["a", (x) => x + 10], - ["b", 20], - ["d", 40], - ]) - ), - new Map([ - ["a", 11], - ["b", 20], - ["c", 3], - ["d", 40], - ]) - ); - }, + map: () => { + assert.deepStrictEqual( + mergeApplyMap( + new Map([ + ["a", 1], + ["b", 2], + ["c", 3], + ]), + new Map>([ + ["a", (x) => x + 10], + ["b", 20], + ["d", 40], + ]) + ), + new Map([ + ["a", 11], + ["b", 20], + ["c", 3], + ["d", 40], + ]) + ); + }, - object: () => { - const orig = { a: 1, b: 2, c: 3 }; - const src = { ...orig }; - assert.deepStrictEqual( - mergeApplyObj(src, { a: (x) => x + 10, b: 20, d: 40 }), - { a: 11, b: 20, c: 3, d: 40 } - ); - assert.deepStrictEqual(src, orig); - }, + object: () => { + const orig = { a: 1, b: 2, c: 3 }; + const src = { ...orig }; + assert.deepStrictEqual( + mergeApplyObj(src, { a: (x) => x + 10, b: 20, d: 40 }), + { a: 11, b: 20, c: 3, d: 40 } + ); + assert.deepStrictEqual(src, orig); + }, - pollute: () => { - const inc: FnN = (x) => x + 1; - assert.deepStrictEqual( - mergeApplyObj( - { a: 1, ["__proto__"]: 1 }, - { a: inc, ["__proto__"]: inc } - ), - { a: 2 } - ); - assert.deepStrictEqual( - meldApplyObj( - { a: 1, ["__proto__"]: 1 }, - { a: inc, ["__proto__"]: inc } - ), - { - a: 2, - ["__proto__"]: 1, - } - ); - }, + pollute: () => { + const inc: FnN = (x) => x + 1; + assert.deepStrictEqual( + mergeApplyObj( + { a: 1, ["__proto__"]: 1 }, + { a: inc, ["__proto__"]: inc } + ), + { a: 2 } + ); + assert.deepStrictEqual( + meldApplyObj( + { a: 1, ["__proto__"]: 1 }, + { a: inc, ["__proto__"]: inc } + ), + { + a: 2, + ["__proto__"]: 1, + } + ); + }, }); group("mergeDeepObj", { - basic: () => { - const orig = { a: { b: { c: 1 } } }; - const src = { ...orig }; - assert.deepStrictEqual( - mergeDeepObj(src, { a: { b: { d: 2 }, e: { f: 3 } }, g: 4 }), - { a: { b: { c: 1, d: 2 }, e: { f: 3 } }, g: 4 } - ); - assert.deepStrictEqual(src, orig); - }, + basic: () => { + const orig = { a: { b: { c: 1 } } }; + const src = { ...orig }; + assert.deepStrictEqual( + mergeDeepObj(src, { a: { b: { d: 2 }, e: { f: 3 } }, g: 4 }), + { a: { b: { c: 1, d: 2 }, e: { f: 3 } }, g: 4 } + ); + assert.deepStrictEqual(src, orig); + }, }); group("meldDeepObj", { - basic: () => { - const orig = { a: { b: { c: 1 } } }; - const src = { ...orig }; - const dest = meldDeepObj(src, { - a: { b: { d: 2 }, e: { f: 3 } }, - g: 4, - }); - assert.deepStrictEqual(dest, { - a: { b: { c: 1, d: 2 }, e: { f: 3 } }, - g: 4, - }); - assert.strictEqual(src, dest); - assert.notDeepEqual(src, orig); - }, + basic: () => { + const orig = { a: { b: { c: 1 } } }; + const src = { ...orig }; + const dest = meldDeepObj(src, { + a: { b: { d: 2 }, e: { f: 3 } }, + g: 4, + }); + assert.deepStrictEqual(dest, { + a: { b: { c: 1, d: 2 }, e: { f: 3 } }, + g: 4, + }); + assert.strictEqual(src, dest); + assert.notDeepEqual(src, orig); + }, - pollute: () => { - const p1 = JSON.parse(`{ "a": 1, "__proto__": { "eek": 2 } }`); - const p2 = JSON.parse(`{ "a": 1, "b": { "__proto__": { "eek": 2 } } }`); - assert.deepStrictEqual(meldDeepObj({}, p1), { a: 1 }, "p1"); - assert.deepStrictEqual(meldDeepObj({}, p2), p2, "p2"); - assert.deepStrictEqual( - meldDeepObj({ b: { c: 1 } }, p2), - { a: 1, b: { c: 1 } }, - "p3" - ); - }, + pollute: () => { + const p1 = JSON.parse(`{ "a": 1, "__proto__": { "eek": 2 } }`); + const p2 = JSON.parse(`{ "a": 1, "b": { "__proto__": { "eek": 2 } } }`); + assert.deepStrictEqual(meldDeepObj({}, p1), { a: 1 }, "p1"); + assert.deepStrictEqual(meldDeepObj({}, p2), p2, "p2"); + assert.deepStrictEqual( + meldDeepObj({ b: { c: 1 } }, p2), + { a: 1, b: { c: 1 } }, + "p3" + ); + }, }); diff --git a/packages/associative/test/multi-trie.ts b/packages/associative/test/multi-trie.ts index d63b6b37d3..0f79e52654 100644 --- a/packages/associative/test/multi-trie.ts +++ b/packages/associative/test/multi-trie.ts @@ -1,87 +1,87 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { MultiTrie } from "../src/index.js" +import { MultiTrie } from "../src/index.js"; let root: MultiTrie; group( - "MultiTrie", - { - keys: () => { - assert.deepStrictEqual( - new Set(root.keys()), - new Set([ - "hey", - "hello", - "hallo", - "hallo", - "hola", - "hold", - "hej", - ]) - ); - assert.deepStrictEqual( - new Set(root.find("he")!.keys()), - new Set(["y", "llo", "j"]) - ); - assert.deepStrictEqual( - new Set(root.find("ho")!.keys("", "ho")), - new Set(["hola", "hold"]) - ); - assert.deepStrictEqual( - new Set(root.find("he")!.keys("-")), - new Set(["l-l-o", "y", "j"]) - ); - }, + "MultiTrie", + { + keys: () => { + assert.deepStrictEqual( + new Set(root.keys()), + new Set([ + "hey", + "hello", + "hallo", + "hallo", + "hola", + "hold", + "hej", + ]) + ); + assert.deepStrictEqual( + new Set(root.find("he")!.keys()), + new Set(["y", "llo", "j"]) + ); + assert.deepStrictEqual( + new Set(root.find("ho")!.keys("", "ho")), + new Set(["hola", "hold"]) + ); + assert.deepStrictEqual( + new Set(root.find("he")!.keys("-")), + new Set(["l-l-o", "y", "j"]) + ); + }, - "keys (words)": () => { - const t = new MultiTrie(); - t.add("foo bar baz".split(" "), "a"); - t.add("foo boo zoo".split(" "), "b"); - assert.deepStrictEqual( - new Set(t.keys("/")), - new Set(["foo/bar/baz", "foo/boo/zoo"]) - ); - }, + "keys (words)": () => { + const t = new MultiTrie(); + t.add("foo bar baz".split(" "), "a"); + t.add("foo boo zoo".split(" "), "b"); + assert.deepStrictEqual( + new Set(t.keys("/")), + new Set(["foo/bar/baz", "foo/boo/zoo"]) + ); + }, - values: () => { - assert.deepStrictEqual( - new Set(root.values()), - new Set(["en", "es", "de", "de-at", "se"]) - ); - assert.deepStrictEqual( - new Set(root.find("he")!.values()), - new Set(["en", "se"]) - ); - }, + values: () => { + assert.deepStrictEqual( + new Set(root.values()), + new Set(["en", "es", "de", "de-at", "se"]) + ); + assert.deepStrictEqual( + new Set(root.find("he")!.values()), + new Set(["en", "se"]) + ); + }, - delete: () => { - assert.ok(root.delete("he")); - assert.deepStrictEqual( - new Set(root.keys()), - new Set(["hola", "hold", "hallo"]) - ); - assert.ok(root.delete("hallo", "de")); - assert.deepStrictEqual(root.get("hallo"), new Set(["de-at"])); - assert.ok(root.delete("h")); - assert.deepStrictEqual([...root], []); - }, + delete: () => { + assert.ok(root.delete("he")); + assert.deepStrictEqual( + new Set(root.keys()), + new Set(["hola", "hold", "hallo"]) + ); + assert.ok(root.delete("hallo", "de")); + assert.deepStrictEqual(root.get("hallo"), new Set(["de-at"])); + assert.ok(root.delete("h")); + assert.deepStrictEqual([...root], []); + }, - "known prefix": () => { - assert.deepStrictEqual(root.knownPrefix("hole"), ["h", "o", "l"]); - assert.deepStrictEqual(root.knownPrefix("whole"), []); - }, - }, - { - beforeEach: () => { - root = new MultiTrie(); - root.add("hey", "en"); - root.add("hello", "en"); - root.add("hallo", "de"); - root.add("hallo", "de-at"); - root.add("hola", "es"); - root.add("hold", "en"); - root.add("hej", "se"); - }, - } + "known prefix": () => { + assert.deepStrictEqual(root.knownPrefix("hole"), ["h", "o", "l"]); + assert.deepStrictEqual(root.knownPrefix("whole"), []); + }, + }, + { + beforeEach: () => { + root = new MultiTrie(); + root.add("hey", "en"); + root.add("hello", "en"); + root.add("hallo", "de"); + root.add("hallo", "de-at"); + root.add("hola", "es"); + root.add("hold", "en"); + root.add("hej", "se"); + }, + } ); diff --git a/packages/associative/test/object.ts b/packages/associative/test/object.ts index 4fdc419047..995e1fc10a 100644 --- a/packages/associative/test/object.ts +++ b/packages/associative/test/object.ts @@ -1,20 +1,20 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { renameTransformedKeys } from "../src/index.js" +import { renameTransformedKeys } from "../src/index.js"; group("object ops", { - renameTransformedKeys: () => { - assert.deepStrictEqual( - renameTransformedKeys( - { a: 1, b: 2, c: null }, - { - a: "aa", - b: ["bb", (x, src) => x * 10 + src.a], - c: "cc", - } - ), - { aa: 1, bb: 21 } - ); - assert.deepStrictEqual(renameTransformedKeys(null, { a: "aa" }), {}); - }, + renameTransformedKeys: () => { + assert.deepStrictEqual( + renameTransformedKeys( + { a: 1, b: 2, c: null }, + { + a: "aa", + b: ["bb", (x, src) => x * 10 + src.a], + c: "cc", + } + ), + { aa: 1, bb: 21 } + ); + assert.deepStrictEqual(renameTransformedKeys(null, { a: "aa" }), {}); + }, }); diff --git a/packages/associative/test/sorted-map.ts b/packages/associative/test/sorted-map.ts index d388e3311d..5d6c552042 100644 --- a/packages/associative/test/sorted-map.ts +++ b/packages/associative/test/sorted-map.ts @@ -3,197 +3,197 @@ import { equiv } from "@thi.ng/equiv"; import { range, repeat, zip } from "@thi.ng/transducers"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { defSortedMap, SortedMap } from "../src/index.js" +import { defSortedMap, SortedMap } from "../src/index.js"; let m: SortedMap; group( - "SortedMap", - { - size: () => { - assert.strictEqual(m.size, 3); - m.set("a", 10); - assert.strictEqual(m.size, 3); - m.set("d", 10); - assert.strictEqual(m.size, 4); - m.delete("d"); - assert.strictEqual(m.size, 3); - m.delete("d"); - assert.strictEqual(m.size, 3); - }, - - clear: () => { - m.clear(); - assert.strictEqual(m.size, 0); - assert.deepStrictEqual([...m.entries()], []); - }, - - empty: () => { - const m2 = m.empty(); - assert.strictEqual(m.size, 3); - assert.strictEqual(m2.size, 0); - assert.deepStrictEqual([...m2.entries()], []); - }, - - copy: () => { - assert.deepStrictEqual(m.copy(), m); - }, - - equiv: () => { - assert.ok(equiv(m.copy(), m)); - assert.ok(!equiv(m, new SortedMap())); - }, - - has: () => { - assert.ok(m.has("a")); - assert.ok(m.has("b")); - assert.ok(m.has("c")); - assert.ok(!m.has("aa")); - assert.ok(!m.has("d")); - assert.ok(!m.has("@")); - }, - - first: () => { - assert.deepStrictEqual(["a", 1], m.first()); - m.set("A", 10); - assert.deepStrictEqual(["A", 10], m.first()); - }, - - get: () => { - assert.strictEqual(m.get("a"), 1); - assert.strictEqual(m.get("b"), 2); - assert.strictEqual(m.get("c"), 3); - assert.strictEqual(m.get("aa"), undefined); - assert.strictEqual(m.get("d"), undefined); - assert.strictEqual(m.get("@", -1), -1); - }, - - entries: () => { - assert.deepStrictEqual( - [...m], - [ - ["a", 1], - ["b", 2], - ["c", 3], - ] - ); - }, - - // "entries rev": () => { - // assert. deepStrictEqual([...m.entries(undefined, true)], [["c", 3], ["b", 2], ["a", 1]]); - // }, - - "entries a": () => { - assert.deepStrictEqual( - [...m.entries("a")], - [ - ["a", 1], - ["b", 2], - ["c", 3], - ] - ); - }, - - // "entries a rev": () => { - // assert. deepStrictEqual([...m.entries("a", true)], [["a", 1]]); - // }, - - "entries aa": () => { - assert.deepStrictEqual( - [...m.entries("aa")], - [ - ["b", 2], - ["c", 3], - ] - ); - }, - - // "entries aa rev": () => { - // assert. deepStrictEqual([...m.entries("aa", true)], [["a", 1]]); - // }, - - "entries bb": () => { - assert.deepStrictEqual([...m.entries("bb")], [["c", 3]]); - }, - - // "entries bb rev": () => { - // assert. deepStrictEqual([...m.entries("bb", true)], [["b", 2], ["a", 1]]); - // }, - - "entries c": () => { - assert.deepStrictEqual([...m.entries("c")], [["c", 3]]); - }, - - // "entries c rev": () => { - // assert. deepStrictEqual([...m.entries("c", true)], [["c", 3], ["b", 2], ["a", 1]]); - // }, - - "entries 0": () => { - assert.deepStrictEqual( - [...m.entries("0")], - [ - ["a", 1], - ["b", 2], - ["c", 3], - ] - ); - }, - - // "entries 0 rev": () => { - // assert. deepStrictEqual([...m.entries("0", true)], []); - // }); - - "entries d": () => { - assert.deepStrictEqual([...m.entries("d")], []); - }, - - // "entries d rev": () => { - // assert. deepStrictEqual([...m.entries("d", true)], [["c", 3], ["b", 2], ["a", 1]]); - // }, - - keys: () => { - assert.deepStrictEqual([...m.keys()], ["a", "b", "c"]); - m.set("aa", 0); - m.set("d", 0); - assert.deepStrictEqual([...m.keys()], ["a", "aa", "b", "c", "d"]); - }, - - values: () => { - assert.deepStrictEqual([...m.values()], [1, 2, 3]); - m.set("aa", 0); - m.set("d", 0); - assert.deepStrictEqual([...m.values()], [1, 0, 2, 3, 0]); - }, - - comparator: () => { - m = defSortedMap( - { a: 1, b: 2, c: 3 }, - { - compare: (a: string, b: string) => - a === b ? 0 : a < b ? 1 : -1, - } - ); - assert.deepStrictEqual( - [ - ["c", 3], - ["b", 2], - ["a", 1], - ], - [...m.entries()] - ); - }, - - fuzz: () => { - const keys = [...range(32)]; - for (let i = 0; i < 1000; i++) { - m = new SortedMap(zip(shuffle(keys.slice()), repeat(1))); - assert.deepStrictEqual([...m.keys()], keys); - } - }, - }, - { - beforeEach: () => { - m = defSortedMap({ a: 1, b: 2, c: 3 }); - }, - } + "SortedMap", + { + size: () => { + assert.strictEqual(m.size, 3); + m.set("a", 10); + assert.strictEqual(m.size, 3); + m.set("d", 10); + assert.strictEqual(m.size, 4); + m.delete("d"); + assert.strictEqual(m.size, 3); + m.delete("d"); + assert.strictEqual(m.size, 3); + }, + + clear: () => { + m.clear(); + assert.strictEqual(m.size, 0); + assert.deepStrictEqual([...m.entries()], []); + }, + + empty: () => { + const m2 = m.empty(); + assert.strictEqual(m.size, 3); + assert.strictEqual(m2.size, 0); + assert.deepStrictEqual([...m2.entries()], []); + }, + + copy: () => { + assert.deepStrictEqual(m.copy(), m); + }, + + equiv: () => { + assert.ok(equiv(m.copy(), m)); + assert.ok(!equiv(m, new SortedMap())); + }, + + has: () => { + assert.ok(m.has("a")); + assert.ok(m.has("b")); + assert.ok(m.has("c")); + assert.ok(!m.has("aa")); + assert.ok(!m.has("d")); + assert.ok(!m.has("@")); + }, + + first: () => { + assert.deepStrictEqual(["a", 1], m.first()); + m.set("A", 10); + assert.deepStrictEqual(["A", 10], m.first()); + }, + + get: () => { + assert.strictEqual(m.get("a"), 1); + assert.strictEqual(m.get("b"), 2); + assert.strictEqual(m.get("c"), 3); + assert.strictEqual(m.get("aa"), undefined); + assert.strictEqual(m.get("d"), undefined); + assert.strictEqual(m.get("@", -1), -1); + }, + + entries: () => { + assert.deepStrictEqual( + [...m], + [ + ["a", 1], + ["b", 2], + ["c", 3], + ] + ); + }, + + // "entries rev": () => { + // assert. deepStrictEqual([...m.entries(undefined, true)], [["c", 3], ["b", 2], ["a", 1]]); + // }, + + "entries a": () => { + assert.deepStrictEqual( + [...m.entries("a")], + [ + ["a", 1], + ["b", 2], + ["c", 3], + ] + ); + }, + + // "entries a rev": () => { + // assert. deepStrictEqual([...m.entries("a", true)], [["a", 1]]); + // }, + + "entries aa": () => { + assert.deepStrictEqual( + [...m.entries("aa")], + [ + ["b", 2], + ["c", 3], + ] + ); + }, + + // "entries aa rev": () => { + // assert. deepStrictEqual([...m.entries("aa", true)], [["a", 1]]); + // }, + + "entries bb": () => { + assert.deepStrictEqual([...m.entries("bb")], [["c", 3]]); + }, + + // "entries bb rev": () => { + // assert. deepStrictEqual([...m.entries("bb", true)], [["b", 2], ["a", 1]]); + // }, + + "entries c": () => { + assert.deepStrictEqual([...m.entries("c")], [["c", 3]]); + }, + + // "entries c rev": () => { + // assert. deepStrictEqual([...m.entries("c", true)], [["c", 3], ["b", 2], ["a", 1]]); + // }, + + "entries 0": () => { + assert.deepStrictEqual( + [...m.entries("0")], + [ + ["a", 1], + ["b", 2], + ["c", 3], + ] + ); + }, + + // "entries 0 rev": () => { + // assert. deepStrictEqual([...m.entries("0", true)], []); + // }); + + "entries d": () => { + assert.deepStrictEqual([...m.entries("d")], []); + }, + + // "entries d rev": () => { + // assert. deepStrictEqual([...m.entries("d", true)], [["c", 3], ["b", 2], ["a", 1]]); + // }, + + keys: () => { + assert.deepStrictEqual([...m.keys()], ["a", "b", "c"]); + m.set("aa", 0); + m.set("d", 0); + assert.deepStrictEqual([...m.keys()], ["a", "aa", "b", "c", "d"]); + }, + + values: () => { + assert.deepStrictEqual([...m.values()], [1, 2, 3]); + m.set("aa", 0); + m.set("d", 0); + assert.deepStrictEqual([...m.values()], [1, 0, 2, 3, 0]); + }, + + comparator: () => { + m = defSortedMap( + { a: 1, b: 2, c: 3 }, + { + compare: (a: string, b: string) => + a === b ? 0 : a < b ? 1 : -1, + } + ); + assert.deepStrictEqual( + [ + ["c", 3], + ["b", 2], + ["a", 1], + ], + [...m.entries()] + ); + }, + + fuzz: () => { + const keys = [...range(32)]; + for (let i = 0; i < 1000; i++) { + m = new SortedMap(zip(shuffle(keys.slice()), repeat(1))); + assert.deepStrictEqual([...m.keys()], keys); + } + }, + }, + { + beforeEach: () => { + m = defSortedMap({ a: 1, b: 2, c: 3 }); + }, + } ); diff --git a/packages/associative/test/sparse-set.ts b/packages/associative/test/sparse-set.ts index bcce51b1e9..74463b5959 100644 --- a/packages/associative/test/sparse-set.ts +++ b/packages/associative/test/sparse-set.ts @@ -2,81 +2,86 @@ import { isSet } from "@thi.ng/checks"; import { equiv } from "@thi.ng/equiv"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { defSparseSet, SparseSet16, SparseSet32, SparseSet8 } from "../src/index.js" +import { + defSparseSet, + SparseSet16, + SparseSet32, + SparseSet8, +} from "../src/index.js"; let set: SparseSet8; group( - "SparseSet", - { - "factory / max value": () => { - let a = defSparseSet(0x100); - a.into([0xff, 0x100]); - assert.ok(a instanceof SparseSet8, "u8"); - assert.deepStrictEqual([...a], [0xff]); + "SparseSet", + { + "factory / max value": () => { + let a = defSparseSet(0x100); + a.into([0xff, 0x100]); + assert.ok(a instanceof SparseSet8, "u8"); + assert.deepStrictEqual([...a], [0xff]); - a = defSparseSet(0x10000); - a.into([0xffff, 0x10000]); - assert.ok(a instanceof SparseSet16, "u16"); - assert.deepStrictEqual([...a], [0xffff]); + a = defSparseSet(0x10000); + a.into([0xffff, 0x10000]); + assert.ok(a instanceof SparseSet16, "u16"); + assert.deepStrictEqual([...a], [0xffff]); - a = defSparseSet(0x10001); - a.into([0x10000, 0x10001]); - assert.ok(a instanceof SparseSet32, "u32"); - assert.deepStrictEqual([...a], [0x10000]); - }, + a = defSparseSet(0x10001); + a.into([0x10000, 0x10001]); + assert.ok(a instanceof SparseSet32, "u32"); + assert.deepStrictEqual([...a], [0x10000]); + }, - "ctor(n)": () => { - assert.ok(isSet(set)); - assert.strictEqual(set.size, 0); - assert.strictEqual(set.capacity, 8); - }, + "ctor(n)": () => { + assert.ok(isSet(set)); + assert.strictEqual(set.size, 0); + assert.strictEqual(set.capacity, 8); + }, - "ctor(arrays)": () => { - const d = new Uint8Array(8); - const s = new Uint8Array(8); - set = new SparseSet8(d, s); - assert.strictEqual(set.size, 0); - assert.strictEqual(set.capacity, 8); - assert.throws(() => new SparseSet8(new Uint8Array(4), s)); - }, + "ctor(arrays)": () => { + const d = new Uint8Array(8); + const s = new Uint8Array(8); + set = new SparseSet8(d, s); + assert.strictEqual(set.size, 0); + assert.strictEqual(set.capacity, 8); + assert.throws(() => new SparseSet8(new Uint8Array(4), s)); + }, - add: () => { - assert.ok( - equiv( - set.into([1, 4, 3, 7, 9, 2, 0, 1, 2]), - new Set([0, 1, 2, 3, 4, 7]) - ) - ); - }, + add: () => { + assert.ok( + equiv( + set.into([1, 4, 3, 7, 9, 2, 0, 1, 2]), + new Set([0, 1, 2, 3, 4, 7]) + ) + ); + }, - delete: () => { - set.into([1, 4, 3, 7, 9, 2, 0, 1, 2]); - assert.ok(set.delete(4)); - assert.ok(equiv(set, new Set([0, 1, 2, 3, 7]))); - assert.ok(set.delete(0)); - assert.ok(equiv(set, new Set([1, 2, 3, 7]))); - assert.ok(set.delete(7)); - assert.ok(equiv(set, new Set([1, 2, 3]))); - assert.ok(!set.delete(7)); - assert.ok(!set.delete(4)); - set.add(4); - assert.ok(equiv(set, new Set([1, 2, 3, 4]))); - }, + delete: () => { + set.into([1, 4, 3, 7, 9, 2, 0, 1, 2]); + assert.ok(set.delete(4)); + assert.ok(equiv(set, new Set([0, 1, 2, 3, 7]))); + assert.ok(set.delete(0)); + assert.ok(equiv(set, new Set([1, 2, 3, 7]))); + assert.ok(set.delete(7)); + assert.ok(equiv(set, new Set([1, 2, 3]))); + assert.ok(!set.delete(7)); + assert.ok(!set.delete(4)); + set.add(4); + assert.ok(equiv(set, new Set([1, 2, 3, 4]))); + }, - has: () => { - assert.ok(!set.has(0)); - set.add(0); - set.add(0); - assert.ok(set.has(0)); - set.delete(0); - assert.ok(!set.has(0)); - set.into([3, 1, 2]); - }, - }, - { - beforeEach: () => { - set = new SparseSet8(8); - }, - } + has: () => { + assert.ok(!set.has(0)); + set.add(0); + set.add(0); + assert.ok(set.has(0)); + set.delete(0); + assert.ok(!set.has(0)); + set.into([3, 1, 2]); + }, + }, + { + beforeEach: () => { + set = new SparseSet8(8); + }, + } ); diff --git a/packages/associative/test/trie-map.ts b/packages/associative/test/trie-map.ts index d2e67546b9..69be55a1a8 100644 --- a/packages/associative/test/trie-map.ts +++ b/packages/associative/test/trie-map.ts @@ -1,78 +1,78 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { TrieMap } from "../src/index.js" +import { TrieMap } from "../src/index.js"; let root: TrieMap; group( - "TrieMap", - { - keys: () => { - assert.deepStrictEqual( - new Set(root.keys()), - new Set([ - "hey", - "hello", - "hallo", - "hallo", - "hola", - "hold", - "hej", - ]) - ); - assert.deepStrictEqual( - new Set(root.find("he")!.keys()), - new Set(["y", "llo", "j"]) - ); - }, + "TrieMap", + { + keys: () => { + assert.deepStrictEqual( + new Set(root.keys()), + new Set([ + "hey", + "hello", + "hallo", + "hallo", + "hola", + "hold", + "hej", + ]) + ); + assert.deepStrictEqual( + new Set(root.find("he")!.keys()), + new Set(["y", "llo", "j"]) + ); + }, - values: () => { - assert.deepStrictEqual( - new Set(root.values()), - new Set(["en", "es", "de-at", "se"]) - ); - assert.deepStrictEqual( - new Set(root.find("he")!.values()), - new Set(["en", "se"]) - ); - }, + values: () => { + assert.deepStrictEqual( + new Set(root.values()), + new Set(["en", "es", "de-at", "se"]) + ); + assert.deepStrictEqual( + new Set(root.find("he")!.values()), + new Set(["en", "se"]) + ); + }, - delete: () => { - assert.ok(root.delete("he")); - assert.deepStrictEqual( - new Set(root.keys()), - new Set(["hola", "hold", "hallo"]) - ); - assert.ok(root.delete("hallo")); - assert.strictEqual(root.get("hallo"), undefined); - assert.ok(root.delete("h")); - assert.deepStrictEqual([...root], []); - }, + delete: () => { + assert.ok(root.delete("he")); + assert.deepStrictEqual( + new Set(root.keys()), + new Set(["hola", "hold", "hallo"]) + ); + assert.ok(root.delete("hallo")); + assert.strictEqual(root.get("hallo"), undefined); + assert.ok(root.delete("h")); + assert.deepStrictEqual([...root], []); + }, - "known prefix": () => { - assert.deepStrictEqual(root.knownPrefix("hole"), "hol"); - assert.deepStrictEqual(root.knownPrefix("whole"), undefined); - }, + "known prefix": () => { + assert.deepStrictEqual(root.knownPrefix("hole"), "hol"); + assert.deepStrictEqual(root.knownPrefix("whole"), undefined); + }, - suffixes: () => { - assert.deepStrictEqual([...root.suffixes("he")], ["j", "llo", "y"]); - assert.deepStrictEqual( - [...root.suffixes("he", true)], - ["hej", "hello", "hey"] - ); - }, - }, - { - beforeEach: () => { - root = new TrieMap([ - ["hey", "en"], - ["hello", "en"], - ["hallo", "de"], - ["hallo", "de-at"], - ["hola", "es"], - ["hold", "en"], - ["hej", "se"], - ]); - }, - } + suffixes: () => { + assert.deepStrictEqual([...root.suffixes("he")], ["j", "llo", "y"]); + assert.deepStrictEqual( + [...root.suffixes("he", true)], + ["hej", "hello", "hey"] + ); + }, + }, + { + beforeEach: () => { + root = new TrieMap([ + ["hey", "en"], + ["hello", "en"], + ["hallo", "de"], + ["hallo", "de-at"], + ["hola", "es"], + ["hold", "en"], + ["hej", "se"], + ]); + }, + } ); diff --git a/packages/associative/test/union.ts b/packages/associative/test/union.ts index d6983262d2..a148151efc 100644 --- a/packages/associative/test/union.ts +++ b/packages/associative/test/union.ts @@ -1,47 +1,47 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { ArraySet, union } from "../src/index.js" +import { ArraySet, union } from "../src/index.js"; group("union", { - "native (numbers)": () => { - const a = new Set([1, 2, 3, 4]); - const b = new Set([3, 4, 5, 6]); - assert.deepStrictEqual(union(a, b), new Set([1, 2, 3, 4, 5, 6])); - }, + "native (numbers)": () => { + const a = new Set([1, 2, 3, 4]); + const b = new Set([3, 4, 5, 6]); + assert.deepStrictEqual(union(a, b), new Set([1, 2, 3, 4, 5, 6])); + }, - "equiv (numbers)": () => { - const a = new ArraySet([1, 2, 3, 4]); - const b = new ArraySet([3, 4, 5, 6]); - assert.deepStrictEqual(union(a, b), new ArraySet([1, 2, 3, 4, 5, 6])); - }, + "equiv (numbers)": () => { + const a = new ArraySet([1, 2, 3, 4]); + const b = new ArraySet([3, 4, 5, 6]); + assert.deepStrictEqual(union(a, b), new ArraySet([1, 2, 3, 4, 5, 6])); + }, - "native (obj)": () => { - const a = new Set([{ a: 1 }, { a: 2 }]); - const b = new Set([{ a: 2 }, { a: 3 }]); - const u = union(a, b); - assert.strictEqual(u.size, 4); - assert.deepStrictEqual( - u, - new Set([{ a: 1 }, { a: 2 }, { a: 2 }, { a: 3 }]) - ); - assert.notStrictEqual(u, a); - assert.notStrictEqual(u, b); - }, + "native (obj)": () => { + const a = new Set([{ a: 1 }, { a: 2 }]); + const b = new Set([{ a: 2 }, { a: 3 }]); + const u = union(a, b); + assert.strictEqual(u.size, 4); + assert.deepStrictEqual( + u, + new Set([{ a: 1 }, { a: 2 }, { a: 2 }, { a: 3 }]) + ); + assert.notStrictEqual(u, a); + assert.notStrictEqual(u, b); + }, - "equiv (obj)": () => { - const a = new ArraySet([{ a: 1 }, { a: 2 }]); - const b = new ArraySet([{ a: 2 }, { a: 3 }]); - const u = union(a, b); - assert.strictEqual(u.size, 3); - assert.deepStrictEqual(u, new ArraySet([{ a: 1 }, { a: 2 }, { a: 3 }])); - assert.notStrictEqual(u, a); - assert.notStrictEqual(u, b); - }, + "equiv (obj)": () => { + const a = new ArraySet([{ a: 1 }, { a: 2 }]); + const b = new ArraySet([{ a: 2 }, { a: 3 }]); + const u = union(a, b); + assert.strictEqual(u.size, 3); + assert.deepStrictEqual(u, new ArraySet([{ a: 1 }, { a: 2 }, { a: 3 }])); + assert.notStrictEqual(u, a); + assert.notStrictEqual(u, b); + }, - "w/ out": () => { - assert.deepStrictEqual( - union(new Set([1, 2, 3]), new Set([2, 4]), new Set([5])), - new Set([1, 2, 3, 4, 5]) - ); - }, + "w/ out": () => { + assert.deepStrictEqual( + union(new Set([1, 2, 3]), new Set([2, 4]), new Set([5])), + new Set([1, 2, 3, 4, 5]) + ); + }, }); diff --git a/packages/associative/tsconfig.json b/packages/associative/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/associative/tsconfig.json +++ b/packages/associative/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/atom/api-extractor.json b/packages/atom/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/atom/api-extractor.json +++ b/packages/atom/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/atom/package.json b/packages/atom/package.json index 77b6698e14..e251db7e70 100644 --- a/packages/atom/package.json +++ b/packages/atom/package.json @@ -1,110 +1,110 @@ { - "name": "@thi.ng/atom", - "version": "5.1.9", - "description": "Mutable wrappers for nested immutable values with optional undo/redo history and transaction support", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/atom#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/equiv": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/paths": "^5.1.9", - "tslib": "^2.4.0" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "clojure", - "cursor", - "datastructure", - "history", - "immutable", - "redo", - "state", - "subscription", - "transaction", - "typescript", - "undo", - "wrapper" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./atom": { - "default": "./atom.js" - }, - "./cursor": { - "default": "./cursor.js" - }, - "./history": { - "default": "./history.js" - }, - "./transacted": { - "default": "./transacted.js" - }, - "./view": { - "default": "./view.js" - } - }, - "thi.ng": { - "related": [ - "interceptors", - "paths", - "rstream" - ], - "year": 2017 - } + "name": "@thi.ng/atom", + "version": "5.1.9", + "description": "Mutable wrappers for nested immutable values with optional undo/redo history and transaction support", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/atom#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/equiv": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/paths": "^5.1.9", + "tslib": "^2.4.0" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "clojure", + "cursor", + "datastructure", + "history", + "immutable", + "redo", + "state", + "subscription", + "transaction", + "typescript", + "undo", + "wrapper" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./atom": { + "default": "./atom.js" + }, + "./cursor": { + "default": "./cursor.js" + }, + "./history": { + "default": "./history.js" + }, + "./transacted": { + "default": "./transacted.js" + }, + "./view": { + "default": "./view.js" + } + }, + "thi.ng": { + "related": [ + "interceptors", + "paths", + "rstream" + ], + "year": 2017 + } } diff --git a/packages/atom/src/api.ts b/packages/atom/src/api.ts index 8171abd4d9..5a9e672898 100644 --- a/packages/atom/src/api.ts +++ b/packages/atom/src/api.ts @@ -1,22 +1,22 @@ import type { - FnO, - IClear, - IDeref, - IID, - INotify, - IRelease, - IWatch, - OptPathVal, - Path, - Path0, - Path1, - Path2, - Path3, - Path4, - Path5, - Path6, - PathVal, - Predicate, + FnO, + IClear, + IDeref, + IID, + INotify, + IRelease, + IWatch, + OptPathVal, + Path, + Path0, + Path1, + Path2, + Path3, + Path4, + Path5, + Path6, + PathVal, + Predicate, } from "@thi.ng/api"; export type SwapFn = FnO; @@ -26,121 +26,121 @@ export interface ReadonlyAtom extends IDeref, IRelease, IWatch {} export interface IAtom extends ReadonlyAtom, IReset, ISwap {} export interface IReset { - reset(val: T): T; + reset(val: T): T; - resetIn(path: Path0, val: T): T; - resetIn(path: Path1, val: PathVal): T; - resetIn(path: Path2, val: PathVal): T; - resetIn(path: Path3, val: PathVal): T; - resetIn( - path: Path4, - val: PathVal - ): T; - resetIn( - path: Path5, - val: PathVal - ): T; - resetIn( - path: Path6, - val: PathVal - ): T; - // resetIn( - // path: Path7, - // val: PathVal - // ): T; - // resetIn( - // path: Path8, - // val: PathVal - // ): T; - // resetIn( - // path: DeepPath, - // val: any - // ): T; + resetIn(path: Path0, val: T): T; + resetIn(path: Path1, val: PathVal): T; + resetIn(path: Path2, val: PathVal): T; + resetIn(path: Path3, val: PathVal): T; + resetIn( + path: Path4, + val: PathVal + ): T; + resetIn( + path: Path5, + val: PathVal + ): T; + resetIn( + path: Path6, + val: PathVal + ): T; + // resetIn( + // path: Path7, + // val: PathVal + // ): T; + // resetIn( + // path: Path8, + // val: PathVal + // ): T; + // resetIn( + // path: DeepPath, + // val: any + // ): T; - resetInUnsafe(path: Path, val: any): T; + resetInUnsafe(path: Path, val: any): T; } export interface ISwap { - swap(fn: SwapFn, ...args: any[]): T; + swap(fn: SwapFn, ...args: any[]): T; - swapIn(path: Path0, fn: SwapFn, ...args: any[]): T; - swapIn( - path: Path1, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path2, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path3, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path4, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path5, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path6, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - // swapIn( - // path: Path7, - // fn: SwapFn< - // OptPathVal, - // PathVal - // >, - // ...args: any[] - // ): T; - // swapIn( - // path: Path8, - // fn: SwapFn< - // OptPathVal, - // PathVal - // >, - // ...args: any[] - // ): T; - // swapIn( - // path: DeepPath, - // fn: SwapFn, - // ...args: any[] - // ): T; + swapIn(path: Path0, fn: SwapFn, ...args: any[]): T; + swapIn( + path: Path1, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path2, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path3, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path4, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path5, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path6, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + // swapIn( + // path: Path7, + // fn: SwapFn< + // OptPathVal, + // PathVal + // >, + // ...args: any[] + // ): T; + // swapIn( + // path: Path8, + // fn: SwapFn< + // OptPathVal, + // PathVal + // >, + // ...args: any[] + // ): T; + // swapIn( + // path: DeepPath, + // fn: SwapFn, + // ...args: any[] + // ): T; - swapInUnsafe(path: Path, fn: SwapFn, ...args: any[]): T; + swapInUnsafe(path: Path, fn: SwapFn, ...args: any[]): T; } export interface IView extends IDeref, IID, IRelease { - readonly path: Path; - readonly value: T | undefined; + readonly path: Path; + readonly value: T | undefined; - view(): T | undefined; - changed(): boolean; + view(): T | undefined; + changed(): boolean; } export interface CursorOpts { - validate: Predicate; - id: string; + validate: Predicate; + id: string; } export interface IHistory extends IAtom, IClear, INotify { - canUndo(): boolean; - canRedo(): boolean; + canUndo(): boolean; + canRedo(): boolean; - undo(): T | undefined; - redo(): T | undefined; + undo(): T | undefined; + redo(): T | undefined; - record(): void; + record(): void; } diff --git a/packages/atom/src/atom.ts b/packages/atom/src/atom.ts index 4d22e66cf5..388224b07d 100644 --- a/packages/atom/src/atom.ts +++ b/packages/atom/src/atom.ts @@ -1,20 +1,20 @@ import type { - DeepPath, - IEquiv, - OptPathVal, - Path, - Path0, - Path1, - Path2, - Path3, - Path4, - Path5, - Path6, - Path7, - Path8, - PathVal, - Predicate, - Watch, + DeepPath, + IEquiv, + OptPathVal, + Path, + Path0, + Path1, + Path2, + Path3, + Path4, + Path5, + Path6, + Path7, + Path8, + PathVal, + Predicate, + Watch, } from "@thi.ng/api"; import { IWatchMixin } from "@thi.ng/api/mixins/iwatch"; import { illegalState } from "@thi.ng/errors/illegal-state"; @@ -23,7 +23,7 @@ import { updateInUnsafe } from "@thi.ng/paths/update-in"; import type { IAtom, SwapFn } from "./api.js"; export const defAtom = (value: T, valid?: Predicate) => - new Atom(value, valid); + new Atom(value, valid); /** * Mutable wrapper for an (usually) immutable value. Support for @@ -31,159 +31,159 @@ export const defAtom = (value: T, valid?: Predicate) => */ @IWatchMixin export class Atom implements IAtom, IEquiv { - protected _value: T; - protected valid: Predicate | undefined; - protected _watches: any; - - constructor(val: T, valid?: Predicate) { - if (valid && !valid(val)) { - illegalState("initial state value did not validate"); - } - this._value = val; - this.valid = valid; - } - - get value() { - return this._value; - } - - set value(val: T) { - this.reset(val); - } - - deref() { - return this._value; - } - - equiv(o: any) { - return this === o; - } - - reset(val: T) { - const old = this._value; - if (this.valid && !this.valid(val)) { - return old; - } - this._value = val; - this.notifyWatches(old, val); - return val; - } - - resetIn(path: Path0, val: T): T; - resetIn(path: Path1, val: PathVal): T; - resetIn(path: Path2, val: PathVal): T; - resetIn(path: Path3, val: PathVal): T; - resetIn( - path: Path4, - val: PathVal - ): T; - resetIn( - path: Path5, - val: PathVal - ): T; - resetIn( - path: Path6, - val: PathVal - ): T; - resetIn( - path: Path7, - val: PathVal - ): T; - resetIn( - path: Path8, - val: PathVal - ): T; - resetIn( - path: DeepPath, - val: any - ): T; - resetIn(path: Path, val: any) { - return this.reset(setInUnsafe(this._value, path, val)); - } - - resetInUnsafe(path: Path, val: any): T { - return this.reset(setInUnsafe(this._value, path, val)); - } - - swap(fn: SwapFn, ...args: any[]) { - return this.reset(fn.apply(null, [this._value, ...args])); - } - - swapIn(path: Path0, fn: SwapFn, ...args: any[]): T; - swapIn( - path: Path1, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path2, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path3, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path4, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path5, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path6, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: Path7, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: Path8, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: DeepPath, - fn: SwapFn, - ...args: any[] - ): T; - swapIn(path: Path, fn: SwapFn, ...args: any[]) { - return this.reset(updateInUnsafe(this._value, path, fn, ...args)); - } - - swapInUnsafe(path: Path, fn: SwapFn, ...args: any[]) { - return this.reset(updateInUnsafe(this._value, path, fn, ...args)); - } - - // @ts-ignore: mixin - addWatch(id: string, fn: Watch): boolean {} - - // @ts-ignore: mixin - removeWatch(id: string): boolean {} - - // @ts-ignore: mixin - notifyWatches(old: T, prev: T) {} - - release() { - delete this._watches; - delete (this)._value; - return true; - } + protected _value: T; + protected valid: Predicate | undefined; + protected _watches: any; + + constructor(val: T, valid?: Predicate) { + if (valid && !valid(val)) { + illegalState("initial state value did not validate"); + } + this._value = val; + this.valid = valid; + } + + get value() { + return this._value; + } + + set value(val: T) { + this.reset(val); + } + + deref() { + return this._value; + } + + equiv(o: any) { + return this === o; + } + + reset(val: T) { + const old = this._value; + if (this.valid && !this.valid(val)) { + return old; + } + this._value = val; + this.notifyWatches(old, val); + return val; + } + + resetIn(path: Path0, val: T): T; + resetIn(path: Path1, val: PathVal): T; + resetIn(path: Path2, val: PathVal): T; + resetIn(path: Path3, val: PathVal): T; + resetIn( + path: Path4, + val: PathVal + ): T; + resetIn( + path: Path5, + val: PathVal + ): T; + resetIn( + path: Path6, + val: PathVal + ): T; + resetIn( + path: Path7, + val: PathVal + ): T; + resetIn( + path: Path8, + val: PathVal + ): T; + resetIn( + path: DeepPath, + val: any + ): T; + resetIn(path: Path, val: any) { + return this.reset(setInUnsafe(this._value, path, val)); + } + + resetInUnsafe(path: Path, val: any): T { + return this.reset(setInUnsafe(this._value, path, val)); + } + + swap(fn: SwapFn, ...args: any[]) { + return this.reset(fn.apply(null, [this._value, ...args])); + } + + swapIn(path: Path0, fn: SwapFn, ...args: any[]): T; + swapIn( + path: Path1, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path2, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path3, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path4, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path5, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path6, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: Path7, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: Path8, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: DeepPath, + fn: SwapFn, + ...args: any[] + ): T; + swapIn(path: Path, fn: SwapFn, ...args: any[]) { + return this.reset(updateInUnsafe(this._value, path, fn, ...args)); + } + + swapInUnsafe(path: Path, fn: SwapFn, ...args: any[]) { + return this.reset(updateInUnsafe(this._value, path, fn, ...args)); + } + + // @ts-ignore: mixin + addWatch(id: string, fn: Watch): boolean {} + + // @ts-ignore: mixin + removeWatch(id: string): boolean {} + + // @ts-ignore: mixin + notifyWatches(old: T, prev: T) {} + + release() { + delete this._watches; + delete (this)._value; + return true; + } } diff --git a/packages/atom/src/cursor.ts b/packages/atom/src/cursor.ts index 14e001af99..14da1ddd7b 100644 --- a/packages/atom/src/cursor.ts +++ b/packages/atom/src/cursor.ts @@ -1,20 +1,20 @@ import type { - DeepPath, - IID, - IRelease, - OptPathVal, - Path, - Path0, - Path1, - Path2, - Path3, - Path4, - Path5, - Path6, - Path7, - Path8, - PathVal, - Watch, + DeepPath, + IID, + IRelease, + OptPathVal, + Path, + Path0, + Path1, + Path2, + Path3, + Path4, + Path5, + Path6, + Path7, + Path8, + PathVal, + Watch, } from "@thi.ng/api"; import { defGetterUnsafe } from "@thi.ng/paths/getter"; import { defSetterUnsafe } from "@thi.ng/paths/setter"; @@ -23,64 +23,64 @@ import { Atom } from "./atom.js"; import { nextID } from "./idgen.js"; export function defCursor( - parent: IAtom, - path: Path1, - opts?: Partial>> + parent: IAtom, + path: Path1, + opts?: Partial>> ): Cursor>; export function defCursor( - parent: IAtom, - path: Path2, - opts?: Partial>> + parent: IAtom, + path: Path2, + opts?: Partial>> ): Cursor>; export function defCursor( - parent: IAtom, - path: Path3, - opts?: Partial>> + parent: IAtom, + path: Path3, + opts?: Partial>> ): Cursor>; export function defCursor( - parent: IAtom, - path: Path4, - opts?: Partial>> + parent: IAtom, + path: Path4, + opts?: Partial>> ): Cursor>; export function defCursor( - parent: IAtom, - path: Path5, - opts?: Partial>> + parent: IAtom, + path: Path5, + opts?: Partial>> ): Cursor>; export function defCursor( - parent: IAtom, - path: Path6, - opts?: Partial>> + parent: IAtom, + path: Path6, + opts?: Partial>> ): Cursor>; export function defCursor( - parent: IAtom, - path: Path7, - opts?: Partial>> + parent: IAtom, + path: Path7, + opts?: Partial>> ): Cursor>; export function defCursor( - parent: IAtom, - path: Path8, - opts?: Partial>> + parent: IAtom, + path: Path8, + opts?: Partial>> ): Cursor>; export function defCursor( - parent: IAtom, - path: DeepPath, - opts?: Partial> + parent: IAtom, + path: DeepPath, + opts?: Partial> ): Cursor; export function defCursor( - parent: IAtom, - path: Path, - opts?: Partial> + parent: IAtom, + path: Path, + opts?: Partial> ): Cursor { - return new Cursor(parent, path, opts); + return new Cursor(parent, path, opts); } export function defCursorUnsafe( - parent: IAtom, - path: Path, - opts?: Partial> + parent: IAtom, + path: Path, + opts?: Partial> ) { - return new Cursor(parent, path, opts); + return new Cursor(parent, path, opts); } /** @@ -113,177 +113,177 @@ export function defCursorUnsafe( * the hierarchy. */ export class Cursor implements IAtom, IID, IRelease { - readonly id: string; - parent: IAtom; + readonly id: string; + parent: IAtom; - protected local: Atom; - protected selfUpdate: boolean; + protected local: Atom; + protected selfUpdate: boolean; - constructor( - parent: IAtom, - path: Path, - opts: Partial> = {} - ) { - const validate = opts.validate; - const lookup = defGetterUnsafe(path); - const update = defSetterUnsafe(path); - this.parent = parent; - this.id = opts.id || `cursor-${nextID()}`; - this.selfUpdate = false; - this.local = new Atom(lookup(parent.deref()), validate); - this.local.addWatch(this.id, (_, prev, curr) => { - if (prev !== curr) { - this.selfUpdate = true; - parent.swap((state) => update(state, curr)); - this.selfUpdate = false; - } - }); - parent.addWatch(this.id, (_, prev, curr) => { - if (!this.selfUpdate) { - const cval = lookup!(curr); - if (cval !== lookup!(prev)) { - this.local.reset(cval); - } - } - }); - } + constructor( + parent: IAtom, + path: Path, + opts: Partial> = {} + ) { + const validate = opts.validate; + const lookup = defGetterUnsafe(path); + const update = defSetterUnsafe(path); + this.parent = parent; + this.id = opts.id || `cursor-${nextID()}`; + this.selfUpdate = false; + this.local = new Atom(lookup(parent.deref()), validate); + this.local.addWatch(this.id, (_, prev, curr) => { + if (prev !== curr) { + this.selfUpdate = true; + parent.swap((state) => update(state, curr)); + this.selfUpdate = false; + } + }); + parent.addWatch(this.id, (_, prev, curr) => { + if (!this.selfUpdate) { + const cval = lookup!(curr); + if (cval !== lookup!(prev)) { + this.local.reset(cval); + } + } + }); + } - get value() { - return this.deref(); - } + get value() { + return this.deref(); + } - set value(val: T) { - this.reset(val); - } + set value(val: T) { + this.reset(val); + } - deref() { - return this.local.deref(); - } + deref() { + return this.local.deref(); + } - release() { - this.local.release(); - this.parent.removeWatch(this.id); - delete (this).local; - delete (this).parent; - return true; - } + release() { + this.local.release(); + this.parent.removeWatch(this.id); + delete (this).local; + delete (this).parent; + return true; + } - reset(val: T) { - return this.local.reset(val); - } + reset(val: T) { + return this.local.reset(val); + } - resetIn(path: Path0, val: T): T; - resetIn(path: Path1, val: PathVal): T; - resetIn(path: Path2, val: PathVal): T; - resetIn(path: Path3, val: PathVal): T; - resetIn( - path: Path4, - val: PathVal - ): T; - resetIn( - path: Path5, - val: PathVal - ): T; - resetIn( - path: Path6, - val: PathVal - ): T; - resetIn( - path: Path7, - val: PathVal - ): T; - resetIn( - path: Path8, - val: PathVal - ): T; - resetIn( - path: DeepPath, - val: any - ): T; - resetIn(path: Path, val: any) { - return this.local.resetInUnsafe(path, val); - } + resetIn(path: Path0, val: T): T; + resetIn(path: Path1, val: PathVal): T; + resetIn(path: Path2, val: PathVal): T; + resetIn(path: Path3, val: PathVal): T; + resetIn( + path: Path4, + val: PathVal + ): T; + resetIn( + path: Path5, + val: PathVal + ): T; + resetIn( + path: Path6, + val: PathVal + ): T; + resetIn( + path: Path7, + val: PathVal + ): T; + resetIn( + path: Path8, + val: PathVal + ): T; + resetIn( + path: DeepPath, + val: any + ): T; + resetIn(path: Path, val: any) { + return this.local.resetInUnsafe(path, val); + } - resetInUnsafe(path: Path, val: any) { - return this.local.resetInUnsafe(path, val); - } + resetInUnsafe(path: Path, val: any) { + return this.local.resetInUnsafe(path, val); + } - swap(fn: SwapFn, ...args: any[]): T { - return this.local.swap(fn, ...args); - } + swap(fn: SwapFn, ...args: any[]): T { + return this.local.swap(fn, ...args); + } - swapIn(path: Path0, fn: SwapFn, ...args: any[]): T; - swapIn( - path: Path1, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path2, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path3, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path4, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path5, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path6, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: Path7, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: Path8, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: DeepPath, - fn: SwapFn, - ...args: any[] - ): T; - swapIn(path: Path, fn: SwapFn, ...args: any[]) { - return this.local.swapInUnsafe(path, fn, ...args); - } + swapIn(path: Path0, fn: SwapFn, ...args: any[]): T; + swapIn( + path: Path1, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path2, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path3, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path4, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path5, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path6, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: Path7, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: Path8, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: DeepPath, + fn: SwapFn, + ...args: any[] + ): T; + swapIn(path: Path, fn: SwapFn, ...args: any[]) { + return this.local.swapInUnsafe(path, fn, ...args); + } - swapInUnsafe(path: Path, fn: SwapFn, ...args: any[]) { - return this.local.swapInUnsafe(path, fn, ...args); - } + swapInUnsafe(path: Path, fn: SwapFn, ...args: any[]) { + return this.local.swapInUnsafe(path, fn, ...args); + } - addWatch(id: string, fn: Watch) { - return this.local.addWatch(id, fn); - } + addWatch(id: string, fn: Watch) { + return this.local.addWatch(id, fn); + } - removeWatch(id: string): boolean { - return this.local.removeWatch(id); - } + removeWatch(id: string): boolean { + return this.local.removeWatch(id); + } - notifyWatches(oldState: T, newState: T) { - return this.local.notifyWatches(oldState, newState); - } + notifyWatches(oldState: T, newState: T) { + return this.local.notifyWatches(oldState, newState); + } } diff --git a/packages/atom/src/history.ts b/packages/atom/src/history.ts index ed14bd84c8..01e3872aaf 100644 --- a/packages/atom/src/history.ts +++ b/packages/atom/src/history.ts @@ -1,21 +1,21 @@ import type { - DeepPath, - Event, - Listener, - OptPathVal, - Path, - Path0, - Path1, - Path2, - Path3, - Path4, - Path5, - Path6, - Path7, - Path8, - PathVal, - Predicate2, - Watch, + DeepPath, + Event, + Listener, + OptPathVal, + Path, + Path0, + Path1, + Path2, + Path3, + Path4, + Path5, + Path6, + Path7, + Path8, + PathVal, + Predicate2, + Watch, } from "@thi.ng/api"; import { INotifyMixin } from "@thi.ng/api/mixins/inotify"; import { equiv } from "@thi.ng/equiv"; @@ -25,9 +25,9 @@ import { updateInUnsafe } from "@thi.ng/paths/update-in"; import type { IAtom, IHistory, SwapFn } from "./api.js"; export const defHistory = ( - state: IAtom, - maxLen?: number, - changed?: Predicate2 + state: IAtom, + maxLen?: number, + changed?: Predicate2 ) => new History(state, maxLen, changed); /** @@ -44,337 +44,337 @@ export const defHistory = ( */ @INotifyMixin export class History implements IHistory { - static readonly EVENT_UNDO = "undo"; - static readonly EVENT_REDO = "redo"; - static readonly EVENT_RECORD = "record"; + static readonly EVENT_UNDO = "undo"; + static readonly EVENT_REDO = "redo"; + static readonly EVENT_RECORD = "record"; - state: IAtom; - maxLen: number; - changed: Predicate2; + state: IAtom; + maxLen: number; + changed: Predicate2; - history!: T[]; - future!: T[]; + history!: T[]; + future!: T[]; - /** - * @param state - parent state - * @param maxLen - max size of undo stack - * @param changed - predicate to determine changed values (default `!equiv(a,b)`) - */ - constructor(state: IAtom, maxLen = 100, changed?: Predicate2) { - this.state = state; - this.maxLen = maxLen; - this.changed = changed || ((a: T, b: T) => !equiv(a, b)); - this.clear(); - } + /** + * @param state - parent state + * @param maxLen - max size of undo stack + * @param changed - predicate to determine changed values (default `!equiv(a,b)`) + */ + constructor(state: IAtom, maxLen = 100, changed?: Predicate2) { + this.state = state; + this.maxLen = maxLen; + this.changed = changed || ((a: T, b: T) => !equiv(a, b)); + this.clear(); + } - get value() { - return this.deref(); - } + get value() { + return this.deref(); + } - set value(val: T) { - this.reset(val); - } + set value(val: T) { + this.reset(val); + } - canUndo() { - return this.history.length > 0; - } + canUndo() { + return this.history.length > 0; + } - canRedo() { - return this.future.length > 0; - } + canRedo() { + return this.future.length > 0; + } - /** - * Clears history & future stacks - */ - clear() { - this.history = []; - this.future = []; - } + /** + * Clears history & future stacks + */ + clear() { + this.history = []; + this.future = []; + } - /** - * Attempts to re-apply most recent historical value to atom and - * returns it if successful (i.e. there's a history). - * - * @remarks - * Before the switch, first records the atom's current value into - * the future stack (to enable {@link History.redo} feature). - * Returns `undefined` if there's no history. - * - * If undo was possible, the `History.EVENT_UNDO` event is emitted - * after the restoration with both the `prev` and `curr` (restored) - * states provided as event value (and object with these two keys). - * This allows for additional state handling to be executed, e.g. - * application of the "Command pattern". See - * {@link History.addListener} for registering event listeners. - */ - undo() { - if (this.history.length) { - const prev = this.state.deref(); - this.future.push(prev); - const curr = this.state.reset(this.history.pop()!); - this.notify({ id: History.EVENT_UNDO, value: { prev, curr } }); - return curr; - } - } + /** + * Attempts to re-apply most recent historical value to atom and + * returns it if successful (i.e. there's a history). + * + * @remarks + * Before the switch, first records the atom's current value into + * the future stack (to enable {@link History.redo} feature). + * Returns `undefined` if there's no history. + * + * If undo was possible, the `History.EVENT_UNDO` event is emitted + * after the restoration with both the `prev` and `curr` (restored) + * states provided as event value (and object with these two keys). + * This allows for additional state handling to be executed, e.g. + * application of the "Command pattern". See + * {@link History.addListener} for registering event listeners. + */ + undo() { + if (this.history.length) { + const prev = this.state.deref(); + this.future.push(prev); + const curr = this.state.reset(this.history.pop()!); + this.notify({ id: History.EVENT_UNDO, value: { prev, curr } }); + return curr; + } + } - /** - * Attempts to re-apply most recent value from future stack to atom - * and returns it if successful (i.e. there's a future). - * - * @remarks - * Before the switch, first records the atom's current value into - * the history stack (to enable {@link History.undo} feature). - * Returns `undefined` if there's no future (so sad!). - * - * If redo was possible, the `History.EVENT_REDO` event is emitted - * after the restoration with both the `prev` and `curr` (restored) - * states provided as event value (and object with these two keys). - * This allows for additional state handling to be executed, e.g. - * application of the "Command pattern". See - * {@link History.addListener} for registering event listeners. - */ - redo() { - if (this.future.length) { - const prev = this.state.deref(); - this.history.push(prev); - const curr = this.state.reset(this.future.pop()!); - this.notify({ id: History.EVENT_REDO, value: { prev, curr } }); - return curr; - } - } + /** + * Attempts to re-apply most recent value from future stack to atom + * and returns it if successful (i.e. there's a future). + * + * @remarks + * Before the switch, first records the atom's current value into + * the history stack (to enable {@link History.undo} feature). + * Returns `undefined` if there's no future (so sad!). + * + * If redo was possible, the `History.EVENT_REDO` event is emitted + * after the restoration with both the `prev` and `curr` (restored) + * states provided as event value (and object with these two keys). + * This allows for additional state handling to be executed, e.g. + * application of the "Command pattern". See + * {@link History.addListener} for registering event listeners. + */ + redo() { + if (this.future.length) { + const prev = this.state.deref(); + this.history.push(prev); + const curr = this.state.reset(this.future.pop()!); + this.notify({ id: History.EVENT_REDO, value: { prev, curr } }); + return curr; + } + } - /** - * `IReset.reset()` implementation. Delegates to wrapped - * atom/cursor, but too applies `changed` predicate to determine if - * there was a change and if the previous value should be recorded. - * - * @param val - replacement value - */ - reset(val: T) { - const prev = this.state.deref(); - this.state.reset(val); - const changed = this.changed(prev, this.state.deref()); - if (changed) { - this.record(prev); - } - return val; - } + /** + * `IReset.reset()` implementation. Delegates to wrapped + * atom/cursor, but too applies `changed` predicate to determine if + * there was a change and if the previous value should be recorded. + * + * @param val - replacement value + */ + reset(val: T) { + const prev = this.state.deref(); + this.state.reset(val); + const changed = this.changed(prev, this.state.deref()); + if (changed) { + this.record(prev); + } + return val; + } - resetIn(path: Path0, val: T): T; - resetIn(path: Path1, val: PathVal): T; - resetIn(path: Path2, val: PathVal): T; - resetIn(path: Path3, val: PathVal): T; - resetIn( - path: Path4, - val: PathVal - ): T; - resetIn( - path: Path5, - val: PathVal - ): T; - resetIn( - path: Path6, - val: PathVal - ): T; - resetIn( - path: Path7, - val: PathVal - ): T; - resetIn( - path: Path8, - val: PathVal - ): T; - resetIn( - path: DeepPath, - val: any - ): T; - resetIn(path: Path, val: any) { - const prev = this.state.deref(); - const get = defGetterUnsafe(path); - const prevV = get(prev); - const curr = setInUnsafe(prev, path, val); - this.state.reset(curr); - this.changed(prevV, get(curr)) && this.record(prev); - return curr; - } + resetIn(path: Path0, val: T): T; + resetIn(path: Path1, val: PathVal): T; + resetIn(path: Path2, val: PathVal): T; + resetIn(path: Path3, val: PathVal): T; + resetIn( + path: Path4, + val: PathVal + ): T; + resetIn( + path: Path5, + val: PathVal + ): T; + resetIn( + path: Path6, + val: PathVal + ): T; + resetIn( + path: Path7, + val: PathVal + ): T; + resetIn( + path: Path8, + val: PathVal + ): T; + resetIn( + path: DeepPath, + val: any + ): T; + resetIn(path: Path, val: any) { + const prev = this.state.deref(); + const get = defGetterUnsafe(path); + const prevV = get(prev); + const curr = setInUnsafe(prev, path, val); + this.state.reset(curr); + this.changed(prevV, get(curr)) && this.record(prev); + return curr; + } - resetInUnsafe(path: Path, val: any) { - return this.resetIn(path, val); - } + resetInUnsafe(path: Path, val: any) { + return this.resetIn(path, val); + } - /** - * `ISwap.swap()` implementation. Delegates to wrapped atom/cursor, - * but too applies `changed` predicate to determine if there was a - * change and if the previous value should be recorded. - * - * @param fn - update function - * @param args - additional args passed to `fn` - */ - swap(fn: SwapFn, ...args: any[]): T { - return this.reset(fn(this.state.deref(), ...args)); - } + /** + * `ISwap.swap()` implementation. Delegates to wrapped atom/cursor, + * but too applies `changed` predicate to determine if there was a + * change and if the previous value should be recorded. + * + * @param fn - update function + * @param args - additional args passed to `fn` + */ + swap(fn: SwapFn, ...args: any[]): T { + return this.reset(fn(this.state.deref(), ...args)); + } - swapIn(path: Path0, fn: SwapFn, ...args: any[]): T; - swapIn( - path: Path1, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path2, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path3, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path4, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path5, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path6, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: Path7, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: Path8, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: DeepPath, - fn: SwapFn, - ...args: any[] - ): T; - swapIn(path: Path, fn: SwapFn, ...args: any[]) { - const prev = this.state.deref(); - const get = defGetterUnsafe(path); - const prevV = get(prev); - const curr = updateInUnsafe(this.state.deref(), path, fn, ...args); - this.state.reset(curr); - this.changed(prevV, get(curr)) && this.record(prev); - return curr; - } + swapIn(path: Path0, fn: SwapFn, ...args: any[]): T; + swapIn( + path: Path1, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path2, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path3, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path4, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path5, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path6, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: Path7, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: Path8, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: DeepPath, + fn: SwapFn, + ...args: any[] + ): T; + swapIn(path: Path, fn: SwapFn, ...args: any[]) { + const prev = this.state.deref(); + const get = defGetterUnsafe(path); + const prevV = get(prev); + const curr = updateInUnsafe(this.state.deref(), path, fn, ...args); + this.state.reset(curr); + this.changed(prevV, get(curr)) && this.record(prev); + return curr; + } - swapInUnsafe(path: Path, fn: SwapFn, ...args: any[]) { - return this.swapIn(path, fn, ...args); - } + swapInUnsafe(path: Path, fn: SwapFn, ...args: any[]) { + return this.swapIn(path, fn, ...args); + } - /** - * Records given state in history. This method is only needed when - * manually managing snapshots, i.e. when applying multiple swaps on - * the wrapped atom directly, but not wanting to create an history - * entry for each change. - * - * @remarks - * **DO NOT call this explicitly if using {@link History.reset} / - * {@link History.swap} etc.** - * - * If no `state` is given, uses the wrapped atom's current state - * value (user code SHOULD always call without arg). - * - * If recording succeeded, the `History.EVENT_RECORD` event is - * emitted with the recorded state provided as event value. - * - * @param state - state to record - */ - record(state?: T) { - const history = this.history; - const n = history.length; - let ok = true; - // check for arg given and not if `state == null` we want to - // allow null/undefined as possible values - if (!arguments.length) { - state = this.state.deref(); - ok = !n || this.changed(history[n - 1], state); - } - if (ok) { - if (n >= this.maxLen) { - history.shift(); - } - history.push(state!); - this.notify({ id: History.EVENT_RECORD, value: state }); - this.future.length = 0; - } - } + /** + * Records given state in history. This method is only needed when + * manually managing snapshots, i.e. when applying multiple swaps on + * the wrapped atom directly, but not wanting to create an history + * entry for each change. + * + * @remarks + * **DO NOT call this explicitly if using {@link History.reset} / + * {@link History.swap} etc.** + * + * If no `state` is given, uses the wrapped atom's current state + * value (user code SHOULD always call without arg). + * + * If recording succeeded, the `History.EVENT_RECORD` event is + * emitted with the recorded state provided as event value. + * + * @param state - state to record + */ + record(state?: T) { + const history = this.history; + const n = history.length; + let ok = true; + // check for arg given and not if `state == null` we want to + // allow null/undefined as possible values + if (!arguments.length) { + state = this.state.deref(); + ok = !n || this.changed(history[n - 1], state); + } + if (ok) { + if (n >= this.maxLen) { + history.shift(); + } + history.push(state!); + this.notify({ id: History.EVENT_RECORD, value: state }); + this.future.length = 0; + } + } - /** - * Returns wrapped atom's **current** value. - */ - deref(): T { - return this.state.deref(); - } + /** + * Returns wrapped atom's **current** value. + */ + deref(): T { + return this.state.deref(); + } - /** - * `IWatch.addWatch()` implementation. Delegates to wrapped - * atom/cursor. - * - * @param id - watch ID - * @param fn - watch function - */ - addWatch(id: string, fn: Watch) { - return this.state.addWatch(id, fn); - } + /** + * `IWatch.addWatch()` implementation. Delegates to wrapped + * atom/cursor. + * + * @param id - watch ID + * @param fn - watch function + */ + addWatch(id: string, fn: Watch) { + return this.state.addWatch(id, fn); + } - /** - * `IWatch.removeWatch()` implementation. Delegates to wrapped - * atom/cursor. - * - * @param id - watch iD - */ - removeWatch(id: string) { - return this.state.removeWatch(id); - } + /** + * `IWatch.removeWatch()` implementation. Delegates to wrapped + * atom/cursor. + * + * @param id - watch iD + */ + removeWatch(id: string) { + return this.state.removeWatch(id); + } - /** - * `IWatch.notifyWatches()` implementation. Delegates to wrapped - * atom/cursor. - * - * @param oldState - - * @param newState - - */ - notifyWatches(oldState: T, newState: T) { - return this.state.notifyWatches(oldState, newState); - } + /** + * `IWatch.notifyWatches()` implementation. Delegates to wrapped + * atom/cursor. + * + * @param oldState - + * @param newState - + */ + notifyWatches(oldState: T, newState: T) { + return this.state.notifyWatches(oldState, newState); + } - release() { - this.state.release(); - delete (this).state; - return true; - } + release() { + this.state.release(); + delete (this).state; + return true; + } - /** {@inheritDoc @thi.ng/api#INotify.addListener} */ - // @ts-ignore: mixin - addListener(id: string, fn: Listener, scope?: any): boolean {} + /** {@inheritDoc @thi.ng/api#INotify.addListener} */ + // @ts-ignore: mixin + addListener(id: string, fn: Listener, scope?: any): boolean {} - /** {@inheritDoc @thi.ng/api#INotify.removeListener} */ - // @ts-ignore: mixin - removeListener(id: string, fn: Listener, scope?: any): boolean {} + /** {@inheritDoc @thi.ng/api#INotify.removeListener} */ + // @ts-ignore: mixin + removeListener(id: string, fn: Listener, scope?: any): boolean {} - /** {@inheritDoc @thi.ng/api#INotify.notify} */ - // @ts-ignore: mixin - notify(e: Event): void {} + /** {@inheritDoc @thi.ng/api#INotify.notify} */ + // @ts-ignore: mixin + notify(e: Event): void {} } diff --git a/packages/atom/src/transacted.ts b/packages/atom/src/transacted.ts index 3c7590c4fd..564b43353e 100644 --- a/packages/atom/src/transacted.ts +++ b/packages/atom/src/transacted.ts @@ -1,18 +1,18 @@ import type { - DeepPath, - OptPathVal, - Path, - Path0, - Path1, - Path2, - Path3, - Path4, - Path5, - Path6, - Path7, - Path8, - PathVal, - Watch, + DeepPath, + OptPathVal, + Path, + Path0, + Path1, + Path2, + Path3, + Path4, + Path5, + Path6, + Path7, + Path8, + PathVal, + Watch, } from "@thi.ng/api"; import { assert } from "@thi.ng/errors/assert"; import { illegalState } from "@thi.ng/errors/illegal-state"; @@ -24,7 +24,7 @@ import { nextID } from "./idgen.js"; /** * Return a new {@link Transacted} state wrapper. * - * @param parent - + * @param parent - */ export const defTransacted = (parent: IAtom) => new Transacted(parent); @@ -32,205 +32,205 @@ export const defTransacted = (parent: IAtom) => new Transacted(parent); * Like {@link defTransacted}, but immediately starts new transaction as * well, i.e. same as `defTransacted(state).begin()`. * - * @param parent - + * @param parent - */ export const beginTransaction = (parent: IAtom) => - new Transacted(parent).begin(); + new Transacted(parent).begin(); export class Transacted implements IAtom { - parent: IAtom; - current: T | undefined; - protected id: string; - protected isActive: boolean; - protected _watches: any; - - constructor(parent: IAtom) { - this.parent = parent; - this.current = undefined; - this.isActive = false; - this.id = `tx-${nextID()}`; - } - - get value() { - return this.deref(); - } - - set value(val: T) { - this.reset(val); - } - - get isTransaction() { - return this.isActive; - } - - deref() { - return this.isActive ? this.current! : this.parent.deref(); - } - - equiv(o: any) { - return this === o; - } - - reset(val: T) { - this.ensureTx(); - this.current = val; - return val; - } - - resetIn(path: Path0, val: T): T; - resetIn(path: Path1, val: PathVal): T; - resetIn(path: Path2, val: PathVal): T; - resetIn(path: Path3, val: PathVal): T; - resetIn( - path: Path4, - val: PathVal - ): T; - resetIn( - path: Path5, - val: PathVal - ): T; - resetIn( - path: Path6, - val: PathVal - ): T; - resetIn( - path: Path7, - val: PathVal - ): T; - resetIn( - path: Path8, - val: PathVal - ): T; - resetIn( - path: DeepPath, - val: any - ): T; - resetIn(path: Path, val: any) { - this.ensureTx(); - return (this.current = setInUnsafe(this.current, path, val)); - } - - resetInUnsafe(path: Path, val: any) { - return this.resetIn(path, val); - } - - swap(fn: SwapFn, ...args: any[]) { - this.ensureTx(); - return (this.current = fn.apply(null, [this.current!, ...args])); - } - - swapIn(path: Path0, fn: SwapFn, ...args: any[]): T; - swapIn( - path: Path1, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path2, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path3, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path4, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path5, - fn: SwapFn, PathVal>, - ...args: any[] - ): T; - swapIn( - path: Path6, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: Path7, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: Path8, - fn: SwapFn< - OptPathVal, - PathVal - >, - ...args: any[] - ): T; - swapIn( - path: DeepPath, - fn: SwapFn, - ...args: any[] - ): T; - swapIn(path: Path, fn: SwapFn, ...args: any[]) { - this.ensureTx(); - return (this.current = updateInUnsafe(this.current, path, fn, ...args)); - } - - swapInUnsafe(path: Path, fn: SwapFn, ...args: any[]) { - return this.swapIn(path, fn, ...args); - } - - begin() { - assert(!this.isActive, "transaction already started"); - this.current = this.parent.deref(); - this.isActive = true; - this.parent.addWatch(this.id + "--guard--", () => - illegalState( - `${this.id} parent state changed during active transaction` - ) - ); - return this; - } - - commit() { - const val = this.current!; - this.cancel(); - return this.parent.reset(val); - } - - cancel() { - this.ensureTx(); - this.parent.removeWatch(this.id + "--guard--"); - this.current = undefined; - this.isActive = false; - } - - addWatch(id: string, watch: Watch) { - return this.parent.addWatch(this.id + id, (_, prev, curr) => - watch(id, prev, curr) - ); - } - - removeWatch(id: string) { - return this.parent.removeWatch(this.id + id); - } - - notifyWatches(old: T, curr: T) { - this.parent.notifyWatches(old, curr); - } - - release() { - delete (this).parent; - delete (this).current; - delete (this).isActive; - delete (this)._watches; - return true; - } - - protected ensureTx() { - assert(this.isActive, "no active transaction"); - } + parent: IAtom; + current: T | undefined; + protected id: string; + protected isActive: boolean; + protected _watches: any; + + constructor(parent: IAtom) { + this.parent = parent; + this.current = undefined; + this.isActive = false; + this.id = `tx-${nextID()}`; + } + + get value() { + return this.deref(); + } + + set value(val: T) { + this.reset(val); + } + + get isTransaction() { + return this.isActive; + } + + deref() { + return this.isActive ? this.current! : this.parent.deref(); + } + + equiv(o: any) { + return this === o; + } + + reset(val: T) { + this.ensureTx(); + this.current = val; + return val; + } + + resetIn(path: Path0, val: T): T; + resetIn(path: Path1, val: PathVal): T; + resetIn(path: Path2, val: PathVal): T; + resetIn(path: Path3, val: PathVal): T; + resetIn( + path: Path4, + val: PathVal + ): T; + resetIn( + path: Path5, + val: PathVal + ): T; + resetIn( + path: Path6, + val: PathVal + ): T; + resetIn( + path: Path7, + val: PathVal + ): T; + resetIn( + path: Path8, + val: PathVal + ): T; + resetIn( + path: DeepPath, + val: any + ): T; + resetIn(path: Path, val: any) { + this.ensureTx(); + return (this.current = setInUnsafe(this.current, path, val)); + } + + resetInUnsafe(path: Path, val: any) { + return this.resetIn(path, val); + } + + swap(fn: SwapFn, ...args: any[]) { + this.ensureTx(); + return (this.current = fn.apply(null, [this.current!, ...args])); + } + + swapIn(path: Path0, fn: SwapFn, ...args: any[]): T; + swapIn( + path: Path1, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path2, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path3, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path4, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path5, + fn: SwapFn, PathVal>, + ...args: any[] + ): T; + swapIn( + path: Path6, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: Path7, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: Path8, + fn: SwapFn< + OptPathVal, + PathVal + >, + ...args: any[] + ): T; + swapIn( + path: DeepPath, + fn: SwapFn, + ...args: any[] + ): T; + swapIn(path: Path, fn: SwapFn, ...args: any[]) { + this.ensureTx(); + return (this.current = updateInUnsafe(this.current, path, fn, ...args)); + } + + swapInUnsafe(path: Path, fn: SwapFn, ...args: any[]) { + return this.swapIn(path, fn, ...args); + } + + begin() { + assert(!this.isActive, "transaction already started"); + this.current = this.parent.deref(); + this.isActive = true; + this.parent.addWatch(this.id + "--guard--", () => + illegalState( + `${this.id} parent state changed during active transaction` + ) + ); + return this; + } + + commit() { + const val = this.current!; + this.cancel(); + return this.parent.reset(val); + } + + cancel() { + this.ensureTx(); + this.parent.removeWatch(this.id + "--guard--"); + this.current = undefined; + this.isActive = false; + } + + addWatch(id: string, watch: Watch) { + return this.parent.addWatch(this.id + id, (_, prev, curr) => + watch(id, prev, curr) + ); + } + + removeWatch(id: string) { + return this.parent.removeWatch(this.id + id); + } + + notifyWatches(old: T, curr: T) { + this.parent.notifyWatches(old, curr); + } + + release() { + delete (this).parent; + delete (this).current; + delete (this).isActive; + delete (this)._watches; + return true; + } + + protected ensureTx() { + assert(this.isActive, "no active transaction"); + } } diff --git a/packages/atom/src/view.ts b/packages/atom/src/view.ts index f4b22a08a1..c8cbc2e755 100644 --- a/packages/atom/src/view.ts +++ b/packages/atom/src/view.ts @@ -1,18 +1,18 @@ import type { - DeepPath, - Fn, - OptPathVal, - Path, - Path0, - Path1, - Path2, - Path3, - Path4, - Path5, - Path6, - Path7, - Path8, - Predicate2, + DeepPath, + Fn, + OptPathVal, + Path, + Path0, + Path1, + Path2, + Path3, + Path4, + Path5, + Path6, + Path7, + Path8, + Predicate2, } from "@thi.ng/api"; import { equiv as _equiv } from "@thi.ng/equiv"; import { defGetterUnsafe } from "@thi.ng/paths/getter"; @@ -21,93 +21,93 @@ import type { IView, ReadonlyAtom } from "./api.js"; import { nextID } from "./idgen.js"; export function defView( - parent: ReadonlyAtom, - path: Path0, - tx?: Fn, - lazy?: boolean, - equiv?: Predicate2 + parent: ReadonlyAtom, + path: Path0, + tx?: Fn, + lazy?: boolean, + equiv?: Predicate2 ): View; export function defView( - parent: ReadonlyAtom, - path: Path1, - tx?: Fn, R>, - lazy?: boolean, - equiv?: Predicate2> + parent: ReadonlyAtom, + path: Path1, + tx?: Fn, R>, + lazy?: boolean, + equiv?: Predicate2> ): View : R>; export function defView( - parent: ReadonlyAtom, - path: Path2, - tx?: Fn, R>, - lazy?: boolean, - equiv?: Predicate2> + parent: ReadonlyAtom, + path: Path2, + tx?: Fn, R>, + lazy?: boolean, + equiv?: Predicate2> ): View : R>; export function defView( - parent: ReadonlyAtom, - path: Path3, - tx?: Fn, R>, - lazy?: boolean, - equiv?: Predicate2> + parent: ReadonlyAtom, + path: Path3, + tx?: Fn, R>, + lazy?: boolean, + equiv?: Predicate2> ): View : R>; export function defView( - parent: ReadonlyAtom, - path: Path4, - tx?: Fn, R>, - lazy?: boolean, - equiv?: Predicate2> + parent: ReadonlyAtom, + path: Path4, + tx?: Fn, R>, + lazy?: boolean, + equiv?: Predicate2> ): View : R>; export function defView( - parent: ReadonlyAtom, - path: Path5, - tx?: Fn, R>, - lazy?: boolean, - equiv?: Predicate2> + parent: ReadonlyAtom, + path: Path5, + tx?: Fn, R>, + lazy?: boolean, + equiv?: Predicate2> ): View : R>; export function defView( - parent: ReadonlyAtom, - path: Path6, - tx?: Fn, R>, - lazy?: boolean, - equiv?: Predicate2> + parent: ReadonlyAtom, + path: Path6, + tx?: Fn, R>, + lazy?: boolean, + equiv?: Predicate2> ): View : R>; export function defView( - parent: ReadonlyAtom, - path: Path7, - tx?: Fn, R>, - lazy?: boolean, - equiv?: Predicate2> + parent: ReadonlyAtom, + path: Path7, + tx?: Fn, R>, + lazy?: boolean, + equiv?: Predicate2> ): View : R>; export function defView( - parent: ReadonlyAtom, - path: Path8, - tx?: Fn, R>, - lazy?: boolean, - equiv?: Predicate2> + parent: ReadonlyAtom, + path: Path8, + tx?: Fn, R>, + lazy?: boolean, + equiv?: Predicate2> ): View : R>; export function defView( - parent: ReadonlyAtom, - path: DeepPath, - tx?: Fn, - lazy?: boolean, - equiv?: Predicate2 + parent: ReadonlyAtom, + path: DeepPath, + tx?: Fn, + lazy?: boolean, + equiv?: Predicate2 ): View; export function defView( - parent: ReadonlyAtom, - path: Path, - tx?: Fn, - lazy?: boolean, - equiv?: Predicate2 + parent: ReadonlyAtom, + path: Path, + tx?: Fn, + lazy?: boolean, + equiv?: Predicate2 ) { - return new View(parent, path, tx, lazy, equiv); + return new View(parent, path, tx, lazy, equiv); } export function defViewUnsafe( - parent: ReadonlyAtom, - path: Path, - tx?: Fn, - lazy?: boolean, - equiv?: Predicate2 + parent: ReadonlyAtom, + path: Path, + tx?: Fn, + lazy?: boolean, + equiv?: Predicate2 ): View { - return new View(parent, path, tx, lazy, equiv); + return new View(parent, path, tx, lazy, equiv); } /** @@ -156,111 +156,111 @@ export function defViewUnsafe( * ``` */ export class View implements IView { - readonly id: string; + readonly id: string; - readonly parent: ReadonlyAtom; - readonly path: Path; + readonly parent: ReadonlyAtom; + readonly path: Path; - protected state: T | undefined; - protected tx: Fn; - protected unprocessed: any; - protected isDirty: boolean; - protected isLazy: boolean; + protected state: T | undefined; + protected tx: Fn; + protected unprocessed: any; + protected isDirty: boolean; + protected isLazy: boolean; - constructor( - parent: ReadonlyAtom, - path: Path, - tx?: Fn, - lazy = true, - equiv = _equiv - ) { - this.parent = parent; - this.id = `view-${nextID()}`; - this.tx = tx || ((x: any) => x); - this.path = toPath(path); - this.isDirty = true; - this.isLazy = lazy; - const lookup = defGetterUnsafe(this.path); - const state = this.parent.deref(); - this.unprocessed = state ? lookup(state) : undefined; - if (!lazy) { - this.state = this.tx(this.unprocessed); - this.unprocessed = undefined; - } - parent.addWatch(this.id, (_, prev, curr) => { - const pval: T = prev ? lookup(prev) : prev; - const val: T = curr ? lookup(curr) : curr; - if (!equiv(val, pval)) { - if (lazy) { - this.unprocessed = val; - } else { - this.state = this.tx(val); - } - this.isDirty = true; - } - }); - } + constructor( + parent: ReadonlyAtom, + path: Path, + tx?: Fn, + lazy = true, + equiv = _equiv + ) { + this.parent = parent; + this.id = `view-${nextID()}`; + this.tx = tx || ((x: any) => x); + this.path = toPath(path); + this.isDirty = true; + this.isLazy = lazy; + const lookup = defGetterUnsafe(this.path); + const state = this.parent.deref(); + this.unprocessed = state ? lookup(state) : undefined; + if (!lazy) { + this.state = this.tx(this.unprocessed); + this.unprocessed = undefined; + } + parent.addWatch(this.id, (_, prev, curr) => { + const pval: T = prev ? lookup(prev) : prev; + const val: T = curr ? lookup(curr) : curr; + if (!equiv(val, pval)) { + if (lazy) { + this.unprocessed = val; + } else { + this.state = this.tx(val); + } + this.isDirty = true; + } + }); + } - get value() { - return this.deref(); - } + get value() { + return this.deref(); + } - /** - * Returns view's value. If the view has a transformer, the - * transformed value is returned. The transformer is only run once - * per value change. - * - * @remarks - * See class comments about difference between lazy/eager behaviors. - */ - deref() { - if (this.isDirty) { - if (this.isLazy) { - this.state = this.tx(this.unprocessed); - this.unprocessed = undefined; - } - this.isDirty = false; - } - return this.state; - } + /** + * Returns view's value. If the view has a transformer, the + * transformed value is returned. The transformer is only run once + * per value change. + * + * @remarks + * See class comments about difference between lazy/eager behaviors. + */ + deref() { + if (this.isDirty) { + if (this.isLazy) { + this.state = this.tx(this.unprocessed); + this.unprocessed = undefined; + } + this.isDirty = false; + } + return this.state; + } - /** - * Returns true, if the view's value has changed since last - * {@link @thi.ng/api#IDeref.deref}. - */ - changed() { - return this.isDirty; - } + /** + * Returns true, if the view's value has changed since last + * {@link @thi.ng/api#IDeref.deref}. + */ + changed() { + return this.isDirty; + } - /** - * Like {@link @thi.ng/api#IDeref.deref}, but doesn't update view's - * cached state and dirty flag if value has changed. - * - * @remarks - * If there's an unprocessed value change, returns result of this - * sub's transformer or else the cached value. - * - * **Important:** Use this function only if the view has none or or - * a stateless transformer. Else might cause undefined/inconsistent - * behavior when calling `view` or {@link @thi.ng/api#IDeref.deref} - * subsequently. - */ - view() { - return this.isDirty && this.isLazy - ? this.tx(this.unprocessed) - : this.state; - } + /** + * Like {@link @thi.ng/api#IDeref.deref}, but doesn't update view's + * cached state and dirty flag if value has changed. + * + * @remarks + * If there's an unprocessed value change, returns result of this + * sub's transformer or else the cached value. + * + * **Important:** Use this function only if the view has none or or + * a stateless transformer. Else might cause undefined/inconsistent + * behavior when calling `view` or {@link @thi.ng/api#IDeref.deref} + * subsequently. + */ + view() { + return this.isDirty && this.isLazy + ? this.tx(this.unprocessed) + : this.state; + } - /** - * Disconnects this view from parent state, marks itself - * dirty/changed and sets its unprocessed raw value to `undefined`. - */ - release() { - this.unprocessed = undefined; - if (!this.isLazy) { - this.state = this.tx(undefined); - } - this.isDirty = true; - return this.parent.removeWatch(this.id); - } + /** + * Disconnects this view from parent state, marks itself + * dirty/changed and sets its unprocessed raw value to `undefined`. + */ + release() { + this.unprocessed = undefined; + if (!this.isLazy) { + this.state = this.tx(undefined); + } + this.isDirty = true; + return this.parent.removeWatch(this.id); + } } diff --git a/packages/atom/test/atom.ts b/packages/atom/test/atom.ts index 9b094f733c..c5dc103d8c 100644 --- a/packages/atom/test/atom.ts +++ b/packages/atom/test/atom.ts @@ -1,79 +1,79 @@ import { isNumber } from "@thi.ng/checks"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { Atom } from "../src/index.js" +import { Atom } from "../src/index.js"; let a: Atom; group( - "atom", - { - "can be deref'd": () => { - assert.strictEqual(a.deref(), 23); - }, + "atom", + { + "can be deref'd": () => { + assert.strictEqual(a.deref(), 23); + }, - "can be equiv'd": () => { - assert.ok(a.equiv(a)); - assert.ok(!a.equiv(new Atom(23))); - }, + "can be equiv'd": () => { + assert.ok(a.equiv(a)); + assert.ok(!a.equiv(new Atom(23))); + }, - "can be reset": () => { - assert.strictEqual(a.reset(24), 24); - assert.strictEqual(a.deref(), 24); - }, + "can be reset": () => { + assert.strictEqual(a.reset(24), 24); + assert.strictEqual(a.deref(), 24); + }, - "can be swapped": () => { - assert.strictEqual( - a.swap((x) => x + 1), - 24 - ); - assert.strictEqual(a.deref(), 24); - }, + "can be swapped": () => { + assert.strictEqual( + a.swap((x) => x + 1), + 24 + ); + assert.strictEqual(a.deref(), 24); + }, - "can add & remove watch": () => { - assert.ok( - a.addWatch("foo", () => {}), - "can't add watch" - ); - assert.ok( - (a)._watches && (a)._watches.foo, - "watch missing" - ); - assert.ok(a.removeWatch("foo"), "can't remove watch"); - assert.ok( - !a.removeWatch("foo"), - "should fail to remove invalid watch id" - ); - }, + "can add & remove watch": () => { + assert.ok( + a.addWatch("foo", () => {}), + "can't add watch" + ); + assert.ok( + (a)._watches && (a)._watches.foo, + "watch missing" + ); + assert.ok(a.removeWatch("foo"), "can't remove watch"); + assert.ok( + !a.removeWatch("foo"), + "should fail to remove invalid watch id" + ); + }, - "can be watched": () => { - a.addWatch("foo", (id, prev, curr) => { - assert.strictEqual(id, "foo", "wrong id"); - assert.strictEqual(prev, 23, "wrong prev"); - assert.strictEqual(curr, 24, "wrong curr"); - }); - a.swap((x) => x + 1); - }, + "can be watched": () => { + a.addWatch("foo", (id, prev, curr) => { + assert.strictEqual(id, "foo", "wrong id"); + assert.strictEqual(prev, 23, "wrong prev"); + assert.strictEqual(curr, 24, "wrong curr"); + }); + a.swap((x) => x + 1); + }, - "can be validated": () => { - assert.throws(() => new Atom("", isNumber)); - a = new Atom(1, isNumber); - assert.strictEqual(a.reset(2), 2); - assert.strictEqual(a.reset("3"), 2); - assert.strictEqual(a.reset(null), 2); - assert.strictEqual( - a.swap(() => "3"), - 2 - ); - assert.strictEqual( - a.swap(() => null), - 2 - ); - }, - }, - { - beforeEach: () => { - a = new Atom(23); - }, - } + "can be validated": () => { + assert.throws(() => new Atom("", isNumber)); + a = new Atom(1, isNumber); + assert.strictEqual(a.reset(2), 2); + assert.strictEqual(a.reset("3"), 2); + assert.strictEqual(a.reset(null), 2); + assert.strictEqual( + a.swap(() => "3"), + 2 + ); + assert.strictEqual( + a.swap(() => null), + 2 + ); + }, + }, + { + beforeEach: () => { + a = new Atom(23); + }, + } ); diff --git a/packages/atom/test/cursor.ts b/packages/atom/test/cursor.ts index 6c7590ea07..5bd8ad458f 100644 --- a/packages/atom/test/cursor.ts +++ b/packages/atom/test/cursor.ts @@ -1,21 +1,21 @@ import { isNumber } from "@thi.ng/checks"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { Atom, Cursor, defAtom, defCursor } from "../src/index.js" +import { Atom, Cursor, defAtom, defCursor } from "../src/index.js"; interface State { - a: { - b: { - c: number; - g: { - h: number; - }; - }; - d: { - e: number; - }; - }; - f: number; + a: { + b: { + c: number; + g: { + h: number; + }; + }; + d: { + e: number; + }; + }; + f: number; } let a: Atom; @@ -23,134 +23,134 @@ let c: Cursor; let src: any; group( - "cursor", - { - "can be deref'd (a)": () => { - c = defCursor(a, ["a"]); - assert.strictEqual(c.parent, a); - assert.deepStrictEqual(c.deref(), src.a); - }, + "cursor", + { + "can be deref'd (a)": () => { + c = defCursor(a, ["a"]); + assert.strictEqual(c.parent, a); + assert.deepStrictEqual(c.deref(), src.a); + }, - "can be deref'd (a.b)": () => { - c = defCursor(a, ["a", "b"]); - assert.deepStrictEqual(c.deref(), src.a.b); - }, + "can be deref'd (a.b)": () => { + c = defCursor(a, ["a", "b"]); + assert.deepStrictEqual(c.deref(), src.a.b); + }, - "can be deref'd (a.b.c)": () => { - c = defCursor(a, ["a", "b", "c"]); - assert.strictEqual(c.deref(), src.a.b.c); - }, + "can be deref'd (a.b.c)": () => { + c = defCursor(a, ["a", "b", "c"]); + assert.strictEqual(c.deref(), src.a.b.c); + }, - "can be deref'd (path array)": () => { - c = defCursor(a, ["a", "b", "g", "h"]); - assert.strictEqual(c.deref(), src.a.b.g.h); - }, + "can be deref'd (path array)": () => { + c = defCursor(a, ["a", "b", "g", "h"]); + assert.strictEqual(c.deref(), src.a.b.g.h); + }, - "doesn't fail w/ invalid path": () => { - c = defCursor(>a, ["a", "b", "x", "y", "z"]); - assert.strictEqual(c.deref(), undefined); - c = defCursor(new Atom(null), ["a"]); - assert.strictEqual(c.deref(), undefined); - c = defCursor(new Atom(null), [0]); - assert.strictEqual(c.deref(), undefined); - }, + "doesn't fail w/ invalid path": () => { + c = defCursor(>a, ["a", "b", "x", "y", "z"]); + assert.strictEqual(c.deref(), undefined); + c = defCursor(new Atom(null), ["a"]); + assert.strictEqual(c.deref(), undefined); + c = defCursor(new Atom(null), [0]); + assert.strictEqual(c.deref(), undefined); + }, - "can be validated": () => { - c = defCursor(a, ["a", "b", "c"], { validate: isNumber }); - assert.strictEqual(c.reset(42), 42); - assert.strictEqual(c.reset("a"), 42); - assert.strictEqual(c.reset(null), 42); - assert.throws(() => - defCursor(>a, ["x"], { validate: isNumber }) - ); - }, + "can be validated": () => { + c = defCursor(a, ["a", "b", "c"], { validate: isNumber }); + assert.strictEqual(c.reset(42), 42); + assert.strictEqual(c.reset("a"), 42); + assert.strictEqual(c.reset(null), 42); + assert.throws(() => + defCursor(>a, ["x"], { validate: isNumber }) + ); + }, - "can be swapped'd (a.b.c)": () => { - c = defCursor(a, ["a", "b", "c"]); - assert.strictEqual( - c.swap((x) => x + 1), - src.a.b.c + 1 - ); - assert.strictEqual(c.deref(), src.a.b.c + 1); - assert.strictEqual(a.deref().a.b.c, src.a.b.c + 1); - assert.strictEqual(a.deref().a.d, src.a.d); - assert.strictEqual(a.deref().f, src.f); - let v = c.deref(); - assert.strictEqual(c.reset(v), v); - a.reset(a.deref()); - assert.strictEqual(c.deref(), v); - }, + "can be swapped'd (a.b.c)": () => { + c = defCursor(a, ["a", "b", "c"]); + assert.strictEqual( + c.swap((x) => x + 1), + src.a.b.c + 1 + ); + assert.strictEqual(c.deref(), src.a.b.c + 1); + assert.strictEqual(a.deref().a.b.c, src.a.b.c + 1); + assert.strictEqual(a.deref().a.d, src.a.d); + assert.strictEqual(a.deref().f, src.f); + let v = c.deref(); + assert.strictEqual(c.reset(v), v); + a.reset(a.deref()); + assert.strictEqual(c.deref(), v); + }, - "can be reset (a.b.c)": () => { - c = defCursor(a, ["a", "b", "c"]); - assert.strictEqual(c.reset(100), 100); - assert.strictEqual(c.deref(), 100); - assert.strictEqual(a.deref().a.b.c, 100); - assert.strictEqual(a.deref().a.d, src.a.d); - assert.strictEqual(a.deref().f, src.f); - }, + "can be reset (a.b.c)": () => { + c = defCursor(a, ["a", "b", "c"]); + assert.strictEqual(c.reset(100), 100); + assert.strictEqual(c.deref(), 100); + assert.strictEqual(a.deref().a.b.c, 100); + assert.strictEqual(a.deref().a.d, src.a.d); + assert.strictEqual(a.deref().f, src.f); + }, - "can update invalid path (x.y.z)": () => { - c = defCursor(>a, ["x", "y", "z"]); - let add = (x: any) => (x != null ? x + 1 : 0); - assert.strictEqual(c.swap(add), 0); - assert.strictEqual(c.deref(), 0); - assert.strictEqual(c.swap(add), 1); - assert.strictEqual(c.deref(), 1); - assert.strictEqual(c.reset(100), 100); - assert.strictEqual(c.deref(), 100); - assert.strictEqual((>a).deref().x.y.z, 100); - assert.strictEqual(src.x, undefined); - }, + "can update invalid path (x.y.z)": () => { + c = defCursor(>a, ["x", "y", "z"]); + let add = (x: any) => (x != null ? x + 1 : 0); + assert.strictEqual(c.swap(add), 0); + assert.strictEqual(c.deref(), 0); + assert.strictEqual(c.swap(add), 1); + assert.strictEqual(c.deref(), 1); + assert.strictEqual(c.reset(100), 100); + assert.strictEqual(c.deref(), 100); + assert.strictEqual((>a).deref().x.y.z, 100); + assert.strictEqual(src.x, undefined); + }, - "reflects parent update": () => { - c = defCursor(a, ["a", "d"]); - assert.deepStrictEqual(c.deref(), src.a.d); - let src2 = { a: { b: { c: 23 }, d: { e: 42 } }, f: 66 }; - (>a).reset(src2); - assert.deepStrictEqual(c.deref(), src2.a.d); - }, + "reflects parent update": () => { + c = defCursor(a, ["a", "d"]); + assert.deepStrictEqual(c.deref(), src.a.d); + let src2 = { a: { b: { c: 23 }, d: { e: 42 } }, f: 66 }; + (>a).reset(src2); + assert.deepStrictEqual(c.deref(), src2.a.d); + }, - "can be released": () => { - c = defCursor(a, ["a"]); - let id = c.id; - assert.notEqual((a)._watches[id], null); - assert.ok(c.release()); - assert.strictEqual(c.parent, undefined); - assert.strictEqual((a)._watches[id], undefined); - }, + "can be released": () => { + c = defCursor(a, ["a"]); + let id = c.id; + assert.notEqual((a)._watches[id], null); + assert.ok(c.release()); + assert.strictEqual(c.parent, undefined); + assert.strictEqual((a)._watches[id], undefined); + }, - "can add & remove watch": () => { - c = defCursor(a, ["a", "b", "c"]); - assert.ok( - c.addWatch("foo", () => {}), - "can't add watch" - ); - assert.ok( - (c).local._watches && (c).local._watches.foo, - "watch missing" - ); - assert.ok(c.removeWatch("foo"), "can't remove watch"); - assert.ok( - !c.removeWatch("foo"), - "should fail to remove invalid watch id" - ); - }, + "can add & remove watch": () => { + c = defCursor(a, ["a", "b", "c"]); + assert.ok( + c.addWatch("foo", () => {}), + "can't add watch" + ); + assert.ok( + (c).local._watches && (c).local._watches.foo, + "watch missing" + ); + assert.ok(c.removeWatch("foo"), "can't remove watch"); + assert.ok( + !c.removeWatch("foo"), + "should fail to remove invalid watch id" + ); + }, - "can be watched": () => { - c = defCursor(a, ["a", "b", "c"]); - c.addWatch("foo", (id, prev, curr) => { - assert.strictEqual(id, "foo", "wrong id"); - assert.strictEqual(prev, 23, "wrong prev"); - assert.strictEqual(curr, 24, "wrong curr"); - }); - c.swap((x) => x + 1); - }, - }, - { - beforeEach: () => { - src = { a: { b: { c: 23, g: { h: 88 } }, d: { e: 42 } }, f: 66 }; - a = defAtom(src); - }, - } + "can be watched": () => { + c = defCursor(a, ["a", "b", "c"]); + c.addWatch("foo", (id, prev, curr) => { + assert.strictEqual(id, "foo", "wrong id"); + assert.strictEqual(prev, 23, "wrong prev"); + assert.strictEqual(curr, 24, "wrong curr"); + }); + c.swap((x) => x + 1); + }, + }, + { + beforeEach: () => { + src = { a: { b: { c: 23, g: { h: 88 } }, d: { e: 42 } }, f: 66 }; + a = defAtom(src); + }, + } ); diff --git a/packages/atom/test/history.ts b/packages/atom/test/history.ts index 7cb74b862e..1083b604fa 100644 --- a/packages/atom/test/history.ts +++ b/packages/atom/test/history.ts @@ -1,178 +1,178 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { Atom, defCursor, defHistory } from "../src/index.js" +import { Atom, defCursor, defHistory } from "../src/index.js"; let a: Atom; let add = (x: number) => x + 1; group( - "history", - { - "has initial state": () => { - let c = defCursor(a, ["b", "c"]); - let h = defHistory(c, 3); - assert.strictEqual(h.history.length, 0); - assert.strictEqual(h.future.length, 0); - assert.strictEqual(h.deref(), c.deref()); - }, - - "does record & shift (simple)": () => { - let c = defCursor(a, ["b", "c"]); - let h = defHistory(c, 3); - h.swap(add); - assert.strictEqual(h.history.length, 1); - assert.deepStrictEqual(h.history, [20]); - - h.swap(add); - assert.strictEqual(h.history.length, 2); - assert.deepStrictEqual(h.history, [20, 21]); - - h.swap(add); - assert.strictEqual(h.history.length, 3); - assert.deepStrictEqual(h.history, [20, 21, 22]); - - h.swap(add); - assert.strictEqual(h.history.length, 3); - assert.deepStrictEqual(h.history, [21, 22, 23]); - }, - - "does record & shift (nested)": () => { - let c = defCursor(a, ["b"]); - let h = defHistory(c, 3); - h.swap((s) => ({ ...s, c: 21 })); - assert.strictEqual(h.history.length, 1); - assert.deepStrictEqual(h.history, [{ c: 20, d: 30 }]); - - h.swap((s) => ({ ...s, d: 31 })); - assert.strictEqual(h.history.length, 2); - assert.deepStrictEqual(h.history, [ - { c: 20, d: 30 }, - { c: 21, d: 30 }, - ]); - - h.swap((s) => ({ ...s, x: 100 })); - assert.strictEqual(h.history.length, 3); - assert.deepStrictEqual(h.history, [ - { c: 20, d: 30 }, - { c: 21, d: 30 }, - { c: 21, d: 31 }, - ]); - - h.reset(null); - assert.strictEqual(h.history.length, 3); - assert.deepStrictEqual(h.history, [ - { c: 21, d: 30 }, - { c: 21, d: 31 }, - { c: 21, d: 31, x: 100 }, - ]); - - h.clear(); - assert.strictEqual(h.history.length, 0); - assert.strictEqual(h.future.length, 0); - }, - - "doesn't record if same val": () => { - let h = defHistory(a, 3); - h.reset(a.deref()); - assert.strictEqual(h.history.length, 0); - h.swap((s) => s); - assert.strictEqual(h.history.length, 0); - }, - - "does undo / redo": () => { - let c = defCursor(a, ["b", "c"]); - let h = defHistory(c, 3); - h.swap(add); // 21 - h.swap(add); // 22 - h.swap(add); // 23 - assert.strictEqual(c.deref(), 23); - assert.deepStrictEqual(a.deref(), { - a: 10, - b: { c: 23, d: 30 }, - e: 40, - }); - assert.deepStrictEqual(h.history, [20, 21, 22]); - - assert.strictEqual(h.undo(), 22); - assert.strictEqual(c.deref(), 22); - assert.deepStrictEqual(a.deref(), { - a: 10, - b: { c: 22, d: 30 }, - e: 40, - }); - assert.deepStrictEqual(h.history, [20, 21]); - assert.deepStrictEqual(h.future, [23]); - - assert.strictEqual(h.undo(), 21); - assert.strictEqual(c.deref(), 21); - assert.deepStrictEqual(a.deref(), { - a: 10, - b: { c: 21, d: 30 }, - e: 40, - }); - assert.deepStrictEqual(h.history, [20]); - assert.deepStrictEqual(h.future, [23, 22]); - - assert.strictEqual(h.undo(), 20); - assert.strictEqual(c.deref(), 20); - assert.deepStrictEqual(a.deref(), { - a: 10, - b: { c: 20, d: 30 }, - e: 40, - }); - assert.deepStrictEqual(h.history, []); - assert.deepStrictEqual(h.future, [23, 22, 21]); - - assert.strictEqual(h.undo(), undefined); - - assert.strictEqual(h.redo(), 21); - assert.strictEqual(c.deref(), 21); - assert.deepStrictEqual(a.deref(), { - a: 10, - b: { c: 21, d: 30 }, - e: 40, - }); - assert.deepStrictEqual(h.history, [20]); - assert.deepStrictEqual(h.future, [23, 22]); - - assert.strictEqual(h.redo(), 22); - assert.strictEqual(c.deref(), 22); - assert.deepStrictEqual(a.deref(), { - a: 10, - b: { c: 22, d: 30 }, - e: 40, - }); - assert.deepStrictEqual(h.history, [20, 21]); - assert.deepStrictEqual(h.future, [23]); - - assert.strictEqual(h.redo(), 23); - assert.strictEqual(c.deref(), 23); - assert.deepStrictEqual(a.deref(), { - a: 10, - b: { c: 23, d: 30 }, - e: 40, - }); - assert.deepStrictEqual(h.history, [20, 21, 22]); - assert.deepStrictEqual(h.future, []); - - assert.strictEqual(h.redo(), undefined); - - h.swap(add); // 24 - assert.strictEqual(c.deref(), 24); - assert.deepStrictEqual(a.deref(), { - a: 10, - b: { c: 24, d: 30 }, - e: 40, - }); - assert.deepStrictEqual(h.history, [21, 22, 23]); - - h.reset(c.deref()); - assert.strictEqual(c.deref(), 24); - }, - }, - { - beforeEach: () => { - a = new Atom({ a: 10, b: { c: 20, d: 30 }, e: 40 }); - }, - } + "history", + { + "has initial state": () => { + let c = defCursor(a, ["b", "c"]); + let h = defHistory(c, 3); + assert.strictEqual(h.history.length, 0); + assert.strictEqual(h.future.length, 0); + assert.strictEqual(h.deref(), c.deref()); + }, + + "does record & shift (simple)": () => { + let c = defCursor(a, ["b", "c"]); + let h = defHistory(c, 3); + h.swap(add); + assert.strictEqual(h.history.length, 1); + assert.deepStrictEqual(h.history, [20]); + + h.swap(add); + assert.strictEqual(h.history.length, 2); + assert.deepStrictEqual(h.history, [20, 21]); + + h.swap(add); + assert.strictEqual(h.history.length, 3); + assert.deepStrictEqual(h.history, [20, 21, 22]); + + h.swap(add); + assert.strictEqual(h.history.length, 3); + assert.deepStrictEqual(h.history, [21, 22, 23]); + }, + + "does record & shift (nested)": () => { + let c = defCursor(a, ["b"]); + let h = defHistory(c, 3); + h.swap((s) => ({ ...s, c: 21 })); + assert.strictEqual(h.history.length, 1); + assert.deepStrictEqual(h.history, [{ c: 20, d: 30 }]); + + h.swap((s) => ({ ...s, d: 31 })); + assert.strictEqual(h.history.length, 2); + assert.deepStrictEqual(h.history, [ + { c: 20, d: 30 }, + { c: 21, d: 30 }, + ]); + + h.swap((s) => ({ ...s, x: 100 })); + assert.strictEqual(h.history.length, 3); + assert.deepStrictEqual(h.history, [ + { c: 20, d: 30 }, + { c: 21, d: 30 }, + { c: 21, d: 31 }, + ]); + + h.reset(null); + assert.strictEqual(h.history.length, 3); + assert.deepStrictEqual(h.history, [ + { c: 21, d: 30 }, + { c: 21, d: 31 }, + { c: 21, d: 31, x: 100 }, + ]); + + h.clear(); + assert.strictEqual(h.history.length, 0); + assert.strictEqual(h.future.length, 0); + }, + + "doesn't record if same val": () => { + let h = defHistory(a, 3); + h.reset(a.deref()); + assert.strictEqual(h.history.length, 0); + h.swap((s) => s); + assert.strictEqual(h.history.length, 0); + }, + + "does undo / redo": () => { + let c = defCursor(a, ["b", "c"]); + let h = defHistory(c, 3); + h.swap(add); // 21 + h.swap(add); // 22 + h.swap(add); // 23 + assert.strictEqual(c.deref(), 23); + assert.deepStrictEqual(a.deref(), { + a: 10, + b: { c: 23, d: 30 }, + e: 40, + }); + assert.deepStrictEqual(h.history, [20, 21, 22]); + + assert.strictEqual(h.undo(), 22); + assert.strictEqual(c.deref(), 22); + assert.deepStrictEqual(a.deref(), { + a: 10, + b: { c: 22, d: 30 }, + e: 40, + }); + assert.deepStrictEqual(h.history, [20, 21]); + assert.deepStrictEqual(h.future, [23]); + + assert.strictEqual(h.undo(), 21); + assert.strictEqual(c.deref(), 21); + assert.deepStrictEqual(a.deref(), { + a: 10, + b: { c: 21, d: 30 }, + e: 40, + }); + assert.deepStrictEqual(h.history, [20]); + assert.deepStrictEqual(h.future, [23, 22]); + + assert.strictEqual(h.undo(), 20); + assert.strictEqual(c.deref(), 20); + assert.deepStrictEqual(a.deref(), { + a: 10, + b: { c: 20, d: 30 }, + e: 40, + }); + assert.deepStrictEqual(h.history, []); + assert.deepStrictEqual(h.future, [23, 22, 21]); + + assert.strictEqual(h.undo(), undefined); + + assert.strictEqual(h.redo(), 21); + assert.strictEqual(c.deref(), 21); + assert.deepStrictEqual(a.deref(), { + a: 10, + b: { c: 21, d: 30 }, + e: 40, + }); + assert.deepStrictEqual(h.history, [20]); + assert.deepStrictEqual(h.future, [23, 22]); + + assert.strictEqual(h.redo(), 22); + assert.strictEqual(c.deref(), 22); + assert.deepStrictEqual(a.deref(), { + a: 10, + b: { c: 22, d: 30 }, + e: 40, + }); + assert.deepStrictEqual(h.history, [20, 21]); + assert.deepStrictEqual(h.future, [23]); + + assert.strictEqual(h.redo(), 23); + assert.strictEqual(c.deref(), 23); + assert.deepStrictEqual(a.deref(), { + a: 10, + b: { c: 23, d: 30 }, + e: 40, + }); + assert.deepStrictEqual(h.history, [20, 21, 22]); + assert.deepStrictEqual(h.future, []); + + assert.strictEqual(h.redo(), undefined); + + h.swap(add); // 24 + assert.strictEqual(c.deref(), 24); + assert.deepStrictEqual(a.deref(), { + a: 10, + b: { c: 24, d: 30 }, + e: 40, + }); + assert.deepStrictEqual(h.history, [21, 22, 23]); + + h.reset(c.deref()); + assert.strictEqual(c.deref(), 24); + }, + }, + { + beforeEach: () => { + a = new Atom({ a: 10, b: { c: 20, d: 30 }, e: 40 }); + }, + } ); diff --git a/packages/atom/test/transacted.ts b/packages/atom/test/transacted.ts index 7c1200a10d..b981702425 100644 --- a/packages/atom/test/transacted.ts +++ b/packages/atom/test/transacted.ts @@ -1,156 +1,156 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; import { - Atom, - beginTransaction, - defAtom, - defTransacted, - defView, - Transacted, -} from "../src/index.js" + Atom, + beginTransaction, + defAtom, + defTransacted, + defView, + Transacted, +} from "../src/index.js"; interface State { - a: number; - b: number; + a: number; + b: number; } let db: Atom; let tx: Transacted; group( - "transacted", - { - initial: () => { - assert.deepStrictEqual(db.deref(), { a: 1, b: 2 }); - assert.deepStrictEqual(tx.deref(), db.deref()); - }, + "transacted", + { + initial: () => { + assert.deepStrictEqual(db.deref(), { a: 1, b: 2 }); + assert.deepStrictEqual(tx.deref(), db.deref()); + }, - transaction: () => { - tx.begin(); - assert.deepStrictEqual(tx.deref(), { a: 1, b: 2 }); - assert.throws(() => tx.begin(), "no nested tx"); - tx.swapIn(["a"], (x) => x + 10); - tx.swapIn(["b"], (x) => x + 20); - assert.deepStrictEqual(tx.deref(), { a: 11, b: 22 }); - assert.deepStrictEqual(db.deref(), { a: 1, b: 2 }); - assert.deepStrictEqual(tx.commit(), { a: 11, b: 22 }); - assert.deepStrictEqual(tx.deref(), { a: 11, b: 22 }); - assert.deepStrictEqual(tx.deref(), db.deref()); - assert.throws(() => tx.commit(), "no double commit"); - }, + transaction: () => { + tx.begin(); + assert.deepStrictEqual(tx.deref(), { a: 1, b: 2 }); + assert.throws(() => tx.begin(), "no nested tx"); + tx.swapIn(["a"], (x) => x + 10); + tx.swapIn(["b"], (x) => x + 20); + assert.deepStrictEqual(tx.deref(), { a: 11, b: 22 }); + assert.deepStrictEqual(db.deref(), { a: 1, b: 2 }); + assert.deepStrictEqual(tx.commit(), { a: 11, b: 22 }); + assert.deepStrictEqual(tx.deref(), { a: 11, b: 22 }); + assert.deepStrictEqual(tx.deref(), db.deref()); + assert.throws(() => tx.commit(), "no double commit"); + }, - cancel: () => { - tx.begin(); - tx.swapIn(["a"], (x) => x + 10); - assert.deepStrictEqual(tx.deref(), { a: 11, b: 2 }); - tx.cancel(); - assert.deepStrictEqual(tx.deref(), { a: 1, b: 2 }); - assert.deepStrictEqual(tx.deref(), db.deref()); - assert.throws(() => tx.cancel(), "no double cancel"); - }, + cancel: () => { + tx.begin(); + tx.swapIn(["a"], (x) => x + 10); + assert.deepStrictEqual(tx.deref(), { a: 11, b: 2 }); + tx.cancel(); + assert.deepStrictEqual(tx.deref(), { a: 1, b: 2 }); + assert.deepStrictEqual(tx.deref(), db.deref()); + assert.throws(() => tx.cancel(), "no double cancel"); + }, - "no edits outside tx": () => { - const _tx = >tx; - assert.throws(() => _tx.reset({}), "no reset"); - assert.throws(() => _tx.swap(() => ({})), "no swap"); - assert.throws(() => _tx.resetIn(["a"], {}), "no resetIn"); - assert.throws(() => _tx.swapIn(["a"], () => ({})), "no swapIn"); - assert.throws(() => (_tx.value = {}), "no .value"); - }, + "no edits outside tx": () => { + const _tx = >tx; + assert.throws(() => _tx.reset({}), "no reset"); + assert.throws(() => _tx.swap(() => ({})), "no swap"); + assert.throws(() => _tx.resetIn(["a"], {}), "no resetIn"); + assert.throws(() => _tx.swapIn(["a"], () => ({})), "no swapIn"); + assert.throws(() => (_tx.value = {}), "no .value"); + }, - "no ext edits inside tx": () => { - tx.begin(); - tx.resetIn(["a"], 10); - assert.throws(() => db.resetIn(["a"], 2)); - tx.commit(); - assert.deepStrictEqual(db.deref(), { a: 10, b: 2 }); - assert.deepStrictEqual(tx.deref(), { a: 10, b: 2 }); + "no ext edits inside tx": () => { + tx.begin(); + tx.resetIn(["a"], 10); + assert.throws(() => db.resetIn(["a"], 2)); + tx.commit(); + assert.deepStrictEqual(db.deref(), { a: 10, b: 2 }); + assert.deepStrictEqual(tx.deref(), { a: 10, b: 2 }); - tx.begin(); - tx.resetIn(["b"], 20); - assert.throws(() => db.resetIn(["b"], 3)); - tx.cancel(); - // `b=3` because we caught the guard error - assert.deepStrictEqual(db.deref(), { a: 10, b: 3 }); - assert.deepStrictEqual(tx.deref(), { a: 10, b: 3 }); - }, + tx.begin(); + tx.resetIn(["b"], 20); + assert.throws(() => db.resetIn(["b"], 3)); + tx.cancel(); + // `b=3` because we caught the guard error + assert.deepStrictEqual(db.deref(), { a: 10, b: 3 }); + assert.deepStrictEqual(tx.deref(), { a: 10, b: 3 }); + }, - beginTransaction: () => { - tx = beginTransaction(db); - assert.ok(tx instanceof Transacted); - tx.resetIn(["a"], 10); - tx.commit(); - assert.deepStrictEqual(db.deref(), { a: 10, b: 2 }); - }, + beginTransaction: () => { + tx = beginTransaction(db); + assert.ok(tx instanceof Transacted); + tx.resetIn(["a"], 10); + tx.commit(); + assert.deepStrictEqual(db.deref(), { a: 10, b: 2 }); + }, - "race (2x transactions)": () => { - let tx1 = beginTransaction(db); - let tx2 = beginTransaction(db); - tx1.resetIn(["a"], 10); - tx2.resetIn(["b"], 20); - assert.throws(() => tx1.commit()); - tx2.commit(); - // tx2 succeeds only because we caught tx1.commit() error - assert.deepStrictEqual(db.deref(), { a: 1, b: 20 }); - }, + "race (2x transactions)": () => { + let tx1 = beginTransaction(db); + let tx2 = beginTransaction(db); + tx1.resetIn(["a"], 10); + tx2.resetIn(["b"], 20); + assert.throws(() => tx1.commit()); + tx2.commit(); + // tx2 succeeds only because we caught tx1.commit() error + assert.deepStrictEqual(db.deref(), { a: 1, b: 20 }); + }, - "nested transactions": () => { - let tx1 = beginTransaction(db); - tx1.resetIn(["a"], 10); - let tx2 = beginTransaction(tx1); - tx2.resetIn(["b"], 20); - tx2.commit(); - assert.deepStrictEqual(tx1.deref(), { a: 10, b: 20 }); - tx1.commit(); - assert.deepStrictEqual(db.deref(), { a: 10, b: 20 }); - assert.deepStrictEqual(tx1.deref(), { a: 10, b: 20 }); - }, + "nested transactions": () => { + let tx1 = beginTransaction(db); + tx1.resetIn(["a"], 10); + let tx2 = beginTransaction(tx1); + tx2.resetIn(["b"], 20); + tx2.commit(); + assert.deepStrictEqual(tx1.deref(), { a: 10, b: 20 }); + tx1.commit(); + assert.deepStrictEqual(db.deref(), { a: 10, b: 20 }); + assert.deepStrictEqual(tx1.deref(), { a: 10, b: 20 }); + }, - watches: () => { - let count = 0; - const _tx = >tx; - _tx.addWatch("foo", (id, old, curr) => { - count++; - assert.strictEqual(id, "foo"); - assert.deepStrictEqual(old, { a: 1, b: 2 }); - assert.deepStrictEqual(curr, { a: 22 }); - }); - _tx.begin(); - _tx.reset({ a: 11 }); - _tx.reset({ a: 22 }); - _tx.commit(); - assert.strictEqual(count, 1); - }, + watches: () => { + let count = 0; + const _tx = >tx; + _tx.addWatch("foo", (id, old, curr) => { + count++; + assert.strictEqual(id, "foo"); + assert.deepStrictEqual(old, { a: 1, b: 2 }); + assert.deepStrictEqual(curr, { a: 22 }); + }); + _tx.begin(); + _tx.reset({ a: 11 }); + _tx.reset({ a: 22 }); + _tx.commit(); + assert.strictEqual(count, 1); + }, - "view (lazy)": () => { - const acc: any[] = []; - const _tx = >tx; - const view = defView(_tx, ["a"], (x) => (acc.push(x), x), true); - assert.strictEqual(view.deref(), 1); - _tx.begin(); - _tx.reset({ a: 11 }); - _tx.reset({ a: 22 }); - _tx.commit(); - assert.strictEqual(view.deref(), 22); - assert.deepStrictEqual(acc, [1, 22]); - }, + "view (lazy)": () => { + const acc: any[] = []; + const _tx = >tx; + const view = defView(_tx, ["a"], (x) => (acc.push(x), x), true); + assert.strictEqual(view.deref(), 1); + _tx.begin(); + _tx.reset({ a: 11 }); + _tx.reset({ a: 22 }); + _tx.commit(); + assert.strictEqual(view.deref(), 22); + assert.deepStrictEqual(acc, [1, 22]); + }, - "view (eager)": () => { - const acc: any[] = []; - const _tx = >tx; - const view = defView(_tx, ["a"], (x) => (acc.push(x), x), false); - _tx.begin(); - _tx.reset({ a: 11 }); - _tx.reset({ a: 22 }); - _tx.commit(); - assert.deepStrictEqual(acc, [1, 22]); - assert.strictEqual(view.deref(), 22); - }, - }, - { - beforeEach: () => { - db = defAtom({ a: 1, b: 2 }); - tx = defTransacted(db); - }, - } + "view (eager)": () => { + const acc: any[] = []; + const _tx = >tx; + const view = defView(_tx, ["a"], (x) => (acc.push(x), x), false); + _tx.begin(); + _tx.reset({ a: 11 }); + _tx.reset({ a: 22 }); + _tx.commit(); + assert.deepStrictEqual(acc, [1, 22]); + assert.strictEqual(view.deref(), 22); + }, + }, + { + beforeEach: () => { + db = defAtom({ a: 1, b: 2 }); + tx = defTransacted(db); + }, + } ); diff --git a/packages/atom/test/view.ts b/packages/atom/test/view.ts index 429eeb23e0..57ddeae68f 100644 --- a/packages/atom/test/view.ts +++ b/packages/atom/test/view.ts @@ -1,113 +1,113 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { Atom, defCursor, defView, IView, View } from "../src/index.js" +import { Atom, defCursor, defView, IView, View } from "../src/index.js"; interface State { - a: number; - b: { c: number; d: number }; - e: number; + a: number; + b: { c: number; d: number }; + e: number; } let a: Atom; let v: IView; group( - "view", - { - "can be created from atom": () => { - v = defView(a, ["e"]); - assert.ok(v instanceof View); - assert.strictEqual(v.deref(), 4); - v = defView(a, ["e"], (x) => x * 10); - assert.ok(v instanceof View); - assert.strictEqual(v.deref(), 40); - }, + "view", + { + "can be created from atom": () => { + v = defView(a, ["e"]); + assert.ok(v instanceof View); + assert.strictEqual(v.deref(), 4); + v = defView(a, ["e"], (x) => x * 10); + assert.ok(v instanceof View); + assert.strictEqual(v.deref(), 40); + }, - "can be created from cursor": () => { - let c = defCursor(a, ["b"]); - v = defView(c, ["d"]); - assert.ok(v instanceof View); - assert.strictEqual(v.deref(), 3); - v = defView(c, ["c"], (x: number) => x * 10); - assert.ok(v instanceof View); - assert.strictEqual(v.deref(), 20); - }, + "can be created from cursor": () => { + let c = defCursor(a, ["b"]); + v = defView(c, ["d"]); + assert.ok(v instanceof View); + assert.strictEqual(v.deref(), 3); + v = defView(c, ["c"], (x: number) => x * 10); + assert.ok(v instanceof View); + assert.strictEqual(v.deref(), 20); + }, - "can be deref'd": () => { - assert.strictEqual(defView(a, ["b", "c"]).deref(), 2); - assert.strictEqual(defView(defCursor(a, ["b"]), ["d"]).deref(), 3); - }, + "can be deref'd": () => { + assert.strictEqual(defView(a, ["b", "c"]).deref(), 2); + assert.strictEqual(defView(defCursor(a, ["b"]), ["d"]).deref(), 3); + }, - "can be deref'd w/ transformer": () => { - v = defView(a, ["b", "c"], (x) => x * 10); - assert.strictEqual(v.deref(), 20); - assert.strictEqual(v.deref(), 20); - }, + "can be deref'd w/ transformer": () => { + v = defView(a, ["b", "c"], (x) => x * 10); + assert.strictEqual(v.deref(), 20); + assert.strictEqual(v.deref(), 20); + }, - "can read .value": () => { - assert.strictEqual(defView(a, ["b", "c"]).value, 2); - assert.strictEqual(defView(defCursor(a, ["b"]), ["d"]).value, 3); - // assert.strictEqual(new View(new Cursor(a, "b"), "d").value, 3); - // assert.strictEqual(new Cursor(a, "b").addView("d").value, 3); - }, + "can read .value": () => { + assert.strictEqual(defView(a, ["b", "c"]).value, 2); + assert.strictEqual(defView(defCursor(a, ["b"]), ["d"]).value, 3); + // assert.strictEqual(new View(new Cursor(a, "b"), "d").value, 3); + // assert.strictEqual(new Cursor(a, "b").addView("d").value, 3); + }, - "reflects updates": () => { - v = defView(a, ["b", "c"], (x) => x * 10); - assert.ok(v.changed(), "not dirty"); - assert.strictEqual(v.deref(), 20); - assert.ok(!v.changed(), "changed"); - a.swapIn(["b", "c"], (x) => x + 1); - assert.ok(v.changed(), "not dirty #2"); - assert.strictEqual(v.deref(), 30); - assert.ok(!v.changed(), "changed #2"); - }, + "reflects updates": () => { + v = defView(a, ["b", "c"], (x) => x * 10); + assert.ok(v.changed(), "not dirty"); + assert.strictEqual(v.deref(), 20); + assert.ok(!v.changed(), "changed"); + a.swapIn(["b", "c"], (x) => x + 1); + assert.ok(v.changed(), "not dirty #2"); + assert.strictEqual(v.deref(), 30); + assert.ok(!v.changed(), "changed #2"); + }, - "reflects updates (initially undefined)": () => { - const _a = >a; - const v = defView(_a, ["f"]); - assert.ok(v.changed(), "not dirty"); - assert.strictEqual(v.deref(), undefined); - assert.ok(!v.changed(), "changed"); - _a.resetIn(["f"], 100); - assert.ok(v.changed(), "not dirty #2"); - assert.strictEqual(v.deref(), 100); - }, + "reflects updates (initially undefined)": () => { + const _a = >a; + const v = defView(_a, ["f"]); + assert.ok(v.changed(), "not dirty"); + assert.strictEqual(v.deref(), undefined); + assert.ok(!v.changed(), "changed"); + _a.resetIn(["f"], 100); + assert.ok(v.changed(), "not dirty #2"); + assert.strictEqual(v.deref(), 100); + }, - "can be released": () => { - v = defView(a, ["b", "c"]); - assert.strictEqual(v.deref(), 2); - assert.ok(!v.changed(), "changed"); - assert.ok(v.release()); - assert.ok(v.changed(), "not dirty"); - assert.strictEqual(v.deref(), undefined); - assert.ok(!v.changed(), "changed #2"); - assert.strictEqual(v.deref(), undefined); - }, + "can be released": () => { + v = defView(a, ["b", "c"]); + assert.strictEqual(v.deref(), 2); + assert.ok(!v.changed(), "changed"); + assert.ok(v.release()); + assert.ok(v.changed(), "not dirty"); + assert.strictEqual(v.deref(), undefined); + assert.ok(!v.changed(), "changed #2"); + assert.strictEqual(v.deref(), undefined); + }, - "is lazy by default": () => { - let x; - v = defView(a, ["b", "c"], (y) => ((x = y), y * 10)); - assert.strictEqual(x, undefined); - assert.strictEqual(v.deref(), 20); - assert.strictEqual(x, 2); - x = undefined; - assert.strictEqual(v.deref(), 20); - assert.strictEqual(x, undefined); - }, + "is lazy by default": () => { + let x; + v = defView(a, ["b", "c"], (y) => ((x = y), y * 10)); + assert.strictEqual(x, undefined); + assert.strictEqual(v.deref(), 20); + assert.strictEqual(x, 2); + x = undefined; + assert.strictEqual(v.deref(), 20); + assert.strictEqual(x, undefined); + }, - "can be eager": () => { - let x; - v = defView(a, ["b", "c"], (y) => ((x = y), y * 10), false); - assert.strictEqual(x, 2); - assert.strictEqual(v.deref(), 20); - x = undefined; - assert.strictEqual(v.deref(), 20); - assert.strictEqual(x, undefined); - }, - }, - { - beforeEach: () => { - a = new Atom({ a: 1, b: { c: 2, d: 3 }, e: 4 }); - }, - } + "can be eager": () => { + let x; + v = defView(a, ["b", "c"], (y) => ((x = y), y * 10), false); + assert.strictEqual(x, 2); + assert.strictEqual(v.deref(), 20); + x = undefined; + assert.strictEqual(v.deref(), 20); + assert.strictEqual(x, undefined); + }, + }, + { + beforeEach: () => { + a = new Atom({ a: 1, b: { c: 2, d: 3 }, e: 4 }); + }, + } ); diff --git a/packages/atom/tsconfig.json b/packages/atom/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/atom/tsconfig.json +++ b/packages/atom/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/base-n/api-extractor.json b/packages/base-n/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/base-n/api-extractor.json +++ b/packages/base-n/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/base-n/package.json b/packages/base-n/package.json index 4696ed6f9e..f8dc118cd4 100644 --- a/packages/base-n/package.json +++ b/packages/base-n/package.json @@ -1,112 +1,112 @@ { - "name": "@thi.ng/base-n", - "version": "2.3.3", - "description": "Arbitrary base-n conversions w/ presets for base16/32/36/58/62/64/85, support for arrays & bigints", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/base-n#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/hex": "^2.1.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "array", - "base16", - "base32", - "base36", - "base58", - "base62", - "base64", - "base85", - "bigint", - "binary", - "conversion", - "encode", - "decode", - "string", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./16": { - "default": "./16.js" - }, - "./32": { - "default": "./32.js" - }, - "./36": { - "default": "./36.js" - }, - "./58": { - "default": "./58.js" - }, - "./62": { - "default": "./62.js" - }, - "./64": { - "default": "./64.js" - }, - "./8": { - "default": "./8.js" - }, - "./85": { - "default": "./85.js" - }, - "./api": { - "default": "./api.js" - }, - "./base": { - "default": "./base.js" - } - }, - "thi.ng": { - "year": 2017 - } + "name": "@thi.ng/base-n", + "version": "2.3.3", + "description": "Arbitrary base-n conversions w/ presets for base16/32/36/58/62/64/85, support for arrays & bigints", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/base-n#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/hex": "^2.1.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "array", + "base16", + "base32", + "base36", + "base58", + "base62", + "base64", + "base85", + "bigint", + "binary", + "conversion", + "encode", + "decode", + "string", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./16": { + "default": "./16.js" + }, + "./32": { + "default": "./32.js" + }, + "./36": { + "default": "./36.js" + }, + "./58": { + "default": "./58.js" + }, + "./62": { + "default": "./62.js" + }, + "./64": { + "default": "./64.js" + }, + "./8": { + "default": "./8.js" + }, + "./85": { + "default": "./85.js" + }, + "./api": { + "default": "./api.js" + }, + "./base": { + "default": "./base.js" + } + }, + "thi.ng": { + "year": 2017 + } } diff --git a/packages/base-n/src/api.ts b/packages/base-n/src/api.ts index 230c4f67f7..466d68b701 100644 --- a/packages/base-n/src/api.ts +++ b/packages/base-n/src/api.ts @@ -1,48 +1,48 @@ export interface IBase { - readonly N: number; - readonly base: string; + readonly N: number; + readonly base: string; - /** - * Encodes `x` into a baseN encoded string. `x` MUST be < 2^53. Use - * `encodeBigInt()` for arbitrary values. - * - * @param x - - */ - encode(x: number): string; - /** - * Encodes bigint `x` into a baseN encoded string. - * - * @param x - - */ - encodeBigInt(x: bigint): string; + /** + * Encodes `x` into a baseN encoded string. `x` MUST be < 2^53. Use + * `encodeBigInt()` for arbitrary values. + * + * @param x - + */ + encode(x: number): string; + /** + * Encodes bigint `x` into a baseN encoded string. + * + * @param x - + */ + encodeBigInt(x: bigint): string; - /** - * Encodes given byte array into a bigint and then baseN encodes it. - * - * @param buf - - */ - encodeBytes(buf: Uint8Array): string; + /** + * Encodes given byte array into a bigint and then baseN encodes it. + * + * @param buf - + */ + encodeBytes(buf: Uint8Array): string; - /** - * Decodes baseN encoded string `x` into a numeric value. Assumes the - * resulting `x` will be < 2^53. Use `decodeBigInt()` for arbitrary values. - * - * @param x - - */ - decode(x: string): number; - /** - * Decodes baseN encoded string `x` into a bigint value. - * - * @param x - - */ - decodeBigInt(x: string): bigint; - /** - * Decodes given string in a byte array. The byte values will be big endian - * order, with the LSB aligned to end of the given array. If `buf` is - * shorter than the space required by the encoded source string, the most - * significant bytes will be ignored. - * - * @param buf - - */ - decodeBytes(x: string, buf: Uint8Array): Uint8Array; + /** + * Decodes baseN encoded string `x` into a numeric value. Assumes the + * resulting `x` will be < 2^53. Use `decodeBigInt()` for arbitrary values. + * + * @param x - + */ + decode(x: string): number; + /** + * Decodes baseN encoded string `x` into a bigint value. + * + * @param x - + */ + decodeBigInt(x: string): bigint; + /** + * Decodes given string in a byte array. The byte values will be big endian + * order, with the LSB aligned to end of the given array. If `buf` is + * shorter than the space required by the encoded source string, the most + * significant bytes will be ignored. + * + * @param buf - + */ + decodeBytes(x: string, buf: Uint8Array): Uint8Array; } diff --git a/packages/base-n/src/base.ts b/packages/base-n/src/base.ts index 75bbdc27e2..07fee40ea4 100644 --- a/packages/base-n/src/base.ts +++ b/packages/base-n/src/base.ts @@ -4,82 +4,82 @@ import type { IBase } from "./api.js"; export const defBase = (chars: string) => new BaseN(chars); export class BaseN implements IBase { - readonly N: number; - readonly index: Record; + readonly N: number; + readonly index: Record; - constructor(public readonly base: string) { - this.N = base.length; - this.index = [...base].reduce( - (acc, x, i) => ((acc[x] = i), acc), - >{} - ); - } + constructor(public readonly base: string) { + this.N = base.length; + this.index = [...base].reduce( + (acc, x, i) => ((acc[x] = i), acc), + >{} + ); + } - encode(x: number) { - const { base, N } = this; - if (x === 0) return base[0]; - let res = ""; - while (x > 0) { - res = base[x % N] + res; - x = Math.floor(x / N); - } - return res; - } + encode(x: number) { + const { base, N } = this; + if (x === 0) return base[0]; + let res = ""; + while (x > 0) { + res = base[x % N] + res; + x = Math.floor(x / N); + } + return res; + } - encodeBigInt(x: bigint) { - if (x < BigInt(2 ** 53)) return this.encode(Number(x)); - const { base, N } = this; - if (x === BigInt(0)) return base[0]; - const NN = BigInt(N); - let res = ""; - while (x > 0) { - res = base[Number(x % NN)] + res; - x /= NN; - } - return res; - } + encodeBigInt(x: bigint) { + if (x < BigInt(2 ** 53)) return this.encode(Number(x)); + const { base, N } = this; + if (x === BigInt(0)) return base[0]; + const NN = BigInt(N); + let res = ""; + while (x > 0) { + res = base[Number(x % NN)] + res; + x /= NN; + } + return res; + } - encodeBytes(buf: Uint8Array) { - let hex = ""; - for (let i = 0, n = buf.length; i < n; i++) hex += U8(buf[i]); - return this.encodeBigInt(BigInt(`0x${hex}`)); - } + encodeBytes(buf: Uint8Array) { + let hex = ""; + for (let i = 0, n = buf.length; i < n; i++) hex += U8(buf[i]); + return this.encodeBigInt(BigInt(`0x${hex}`)); + } - decode(x: string) { - const { index, N } = this; - let res = 0; - for (let n = x.length - 1, i = 0; i <= n; i++) { - res += index[x[i]] * N ** (n - i); - } - return res; - } + decode(x: string) { + const { index, N } = this; + let res = 0; + for (let n = x.length - 1, i = 0; i <= n; i++) { + res += index[x[i]] * N ** (n - i); + } + return res; + } - decodeBigInt(x: string): bigint { - const { index, N } = this; - const NN = BigInt(N); - let res = BigInt(0); - for (let n = x.length - 1, i = 0; i <= n; i++) { - res += BigInt(index[x[i]]) * NN ** BigInt(n - i); - } - return res; - } + decodeBigInt(x: string): bigint { + const { index, N } = this; + const NN = BigInt(N); + let res = BigInt(0); + for (let n = x.length - 1, i = 0; i <= n; i++) { + res += BigInt(index[x[i]]) * NN ** BigInt(n - i); + } + return res; + } - decodeBytes(x: string, buf: Uint8Array): Uint8Array { - let y = this.decodeBigInt(x); - const M = BigInt(255); - const SHIFT = BigInt(8); - for (let i = buf.length; i-- > 0; ) { - buf[i] = Number(y & M); - y >>= SHIFT; - } - return buf; - } + decodeBytes(x: string, buf: Uint8Array): Uint8Array { + let y = this.decodeBigInt(x); + const M = BigInt(255); + const SHIFT = BigInt(8); + for (let i = buf.length; i-- > 0; ) { + buf[i] = Number(y & M); + y >>= SHIFT; + } + return buf; + } - validate(x: string) { - return new RegExp(`^[${this.base}]+$`).test(x); - } + validate(x: string) { + return new RegExp(`^[${this.base}]+$`).test(x); + } - size(x: number) { - return Math.ceil(Math.log(x) / Math.log(this.N)); - } + size(x: number) { + return Math.ceil(Math.log(x) / Math.log(this.N)); + } } diff --git a/packages/base-n/test/index.ts b/packages/base-n/test/index.ts index ce36f4c121..9e9dd3a60e 100644 --- a/packages/base-n/test/index.ts +++ b/packages/base-n/test/index.ts @@ -1,35 +1,35 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { - BASE32_HEX, - BASE32_RFC4648, - BASE36, - BASE58, - BASE62, - BASE64, - BASE85, - IBase, + BASE32_HEX, + BASE32_RFC4648, + BASE36, + BASE58, + BASE62, + BASE64, + BASE85, + IBase, } from "../src/index.js"; group("base-n", { - roundtrip: () => { - const X = BigInt(2) ** BigInt(128) - BigInt(1); + roundtrip: () => { + const X = BigInt(2) ** BigInt(128) - BigInt(1); - const check = ( - base: IBase, - expected: string, - id: string | number = base.N - ) => { - assert.strictEqual(base.encodeBigInt(X), expected, `encode: ${id}`); - assert.strictEqual(base.decodeBigInt(expected), X, `decode: ${id}`); - }; + const check = ( + base: IBase, + expected: string, + id: string | number = base.N + ) => { + assert.strictEqual(base.encodeBigInt(X), expected, `encode: ${id}`); + assert.strictEqual(base.decodeBigInt(expected), X, `decode: ${id}`); + }; - check(BASE32_RFC4648, "H7777777777777777777777777", "32rfc"); - check(BASE32_HEX, "7VVVVVVVVVVVVVVVVVVVVVVVVV", "32hex"); - check(BASE36, "F5LXX1ZZ5PNORYNQGLHZMSP33"); - check(BASE58, "YcVfxkQb6JRzqk5kF2tNLv"); - check(BASE62, "7n42DGM5Tflk9n8mt7Fhc7"); - check(BASE64, "3/////////////////////"); - check(BASE85, "=r54lj&NUUO~Hi%c2ym0"); - }, + check(BASE32_RFC4648, "H7777777777777777777777777", "32rfc"); + check(BASE32_HEX, "7VVVVVVVVVVVVVVVVVVVVVVVVV", "32hex"); + check(BASE36, "F5LXX1ZZ5PNORYNQGLHZMSP33"); + check(BASE58, "YcVfxkQb6JRzqk5kF2tNLv"); + check(BASE62, "7n42DGM5Tflk9n8mt7Fhc7"); + check(BASE64, "3/////////////////////"); + check(BASE85, "=r54lj&NUUO~Hi%c2ym0"); + }, }); diff --git a/packages/base-n/tsconfig.json b/packages/base-n/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/base-n/tsconfig.json +++ b/packages/base-n/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/bench/api-extractor.json b/packages/bench/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/bench/api-extractor.json +++ b/packages/bench/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/bench/package.json b/packages/bench/package.json index f8ee7f212a..b8f5a26023 100644 --- a/packages/bench/package.json +++ b/packages/bench/package.json @@ -1,117 +1,117 @@ { - "name": "@thi.ng/bench", - "version": "3.1.8", - "description": "Benchmarking utilities w/ various statistics & formatters (CSV, Markdown etc.)", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/bench#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "@types/node": "^17.0.41", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "benchmark", - "bigint", - "csv", - "execution", - "format", - "functional", - "hrtime", - "markdown", - "measure", - "statistics", - "table", - "time", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "format" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./bench": { - "default": "./bench.js" - }, - "./benchmark": { - "default": "./benchmark.js" - }, - "./format/csv": { - "default": "./format/csv.js" - }, - "./format/default": { - "default": "./format/default.js" - }, - "./format/markdown": { - "default": "./format/markdown.js" - }, - "./now": { - "default": "./now.js" - }, - "./suite": { - "default": "./suite.js" - }, - "./timed": { - "default": "./timed.js" - } - }, - "thi.ng": { - "related": [ - "csv", - "hiccup-markdown" - ], - "year": 2018 - } + "name": "@thi.ng/bench", + "version": "3.1.8", + "description": "Benchmarking utilities w/ various statistics & formatters (CSV, Markdown etc.)", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/bench#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "@types/node": "^17.0.41", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "benchmark", + "bigint", + "csv", + "execution", + "format", + "functional", + "hrtime", + "markdown", + "measure", + "statistics", + "table", + "time", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "format" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./bench": { + "default": "./bench.js" + }, + "./benchmark": { + "default": "./benchmark.js" + }, + "./format/csv": { + "default": "./format/csv.js" + }, + "./format/default": { + "default": "./format/default.js" + }, + "./format/markdown": { + "default": "./format/markdown.js" + }, + "./now": { + "default": "./now.js" + }, + "./suite": { + "default": "./suite.js" + }, + "./timed": { + "default": "./timed.js" + } + }, + "thi.ng": { + "related": [ + "csv", + "hiccup-markdown" + ], + "year": 2018 + } } diff --git a/packages/bench/src/api.ts b/packages/bench/src/api.ts index 3a038805fd..eae018baa6 100644 --- a/packages/bench/src/api.ts +++ b/packages/bench/src/api.ts @@ -5,41 +5,41 @@ export type Timestamp = number | bigint; export type TimingResult = [T, number]; export interface BenchmarkOpts { - /** - * Benchmark title (only used if `print` enabled) - */ - title: string; - /** - * Number of iterations - * - * @defaultValue 1000 - */ - iter: number; - /** - * Number of calls per iteration, i.e. total number of iterations will be - * `iter * size`. - * - * @defaultValue 1 - */ - size: number; - /** - * Number of warmup iterations (not included in results). - * - * @defaultValue 10 - */ - warmup: number; - /** - * Result formatter - * - * @defaultValue FORMAT_DEFAULT - */ - format: BenchmarkFormatter; - /** - * If false, all output will be supressed. - * - * @defaultValue true - */ - output: boolean; + /** + * Benchmark title (only used if `print` enabled) + */ + title: string; + /** + * Number of iterations + * + * @defaultValue 1000 + */ + iter: number; + /** + * Number of calls per iteration, i.e. total number of iterations will be + * `iter * size`. + * + * @defaultValue 1 + */ + size: number; + /** + * Number of warmup iterations (not included in results). + * + * @defaultValue 10 + */ + warmup: number; + /** + * Result formatter + * + * @defaultValue FORMAT_DEFAULT + */ + format: BenchmarkFormatter; + /** + * If false, all output will be supressed. + * + * @defaultValue true + */ + output: boolean; } export type OptsWithoutTitle = Omit; @@ -47,96 +47,96 @@ export type OptsWithoutTitle = Omit; export interface BenchmarkSuiteOpts extends OptsWithoutTitle {} export interface BenchmarkResult { - title: string; - /** - * Number of iterations - */ - iter: number; - /** - * Number of calls per iteration - */ - size: number; - /** - * Total execution time for all runs (in ms) - */ - total: number; - /** - * Mean execution time (in ms) - */ - mean: number; - /** - * Median execution time (in ms) - */ - median: number; - /** - * Min execution time (in ms) - */ - min: number; - /** - * Max execution time (in ms) - */ - max: number; - /** - * First quartile execution time (in ms). I.e. 25% of all runs were - * faster/equal to this measurement. - */ - q1: number; - /** - * Third quartile execution time (in ms). I.e. 25% of all runs were - * equal/slower than this measurement. - */ - q3: number; - /** - * Standard deviation (in percent) - */ - sd: number; + title: string; + /** + * Number of iterations + */ + iter: number; + /** + * Number of calls per iteration + */ + size: number; + /** + * Total execution time for all runs (in ms) + */ + total: number; + /** + * Mean execution time (in ms) + */ + mean: number; + /** + * Median execution time (in ms) + */ + median: number; + /** + * Min execution time (in ms) + */ + min: number; + /** + * Max execution time (in ms) + */ + max: number; + /** + * First quartile execution time (in ms). I.e. 25% of all runs were + * faster/equal to this measurement. + */ + q1: number; + /** + * Third quartile execution time (in ms). I.e. 25% of all runs were + * equal/slower than this measurement. + */ + q3: number; + /** + * Standard deviation (in percent) + */ + sd: number; } export interface BenchmarkFormatter { - /** - * Called once before the benchmark suite runs any benchmarks. - */ - prefix: Fn0; - /** - * Called once for each given benchmark in the suite. Receives benchmark - * options. - */ - start: Fn; - /** - * Called once per benchmark, just after warmup. Receives warmup time taken - * (in milliseconds) and benchmark opts. - */ - warmup: Fn2; - /** - * Called once per benchmark with collected result. - */ - result: Fn; - /** - * Called once after all benchmarks have run. Receives array of all results. - */ - total: Fn; - /** - * Called at the very end of the benchmark suite. Useful if a format - * requires any form of final suffix. - */ - suffix: Fn0; + /** + * Called once before the benchmark suite runs any benchmarks. + */ + prefix: Fn0; + /** + * Called once for each given benchmark in the suite. Receives benchmark + * options. + */ + start: Fn; + /** + * Called once per benchmark, just after warmup. Receives warmup time taken + * (in milliseconds) and benchmark opts. + */ + warmup: Fn2; + /** + * Called once per benchmark with collected result. + */ + result: Fn; + /** + * Called once after all benchmarks have run. Receives array of all results. + */ + total: Fn; + /** + * Called at the very end of the benchmark suite. Useful if a format + * requires any form of final suffix. + */ + suffix: Fn0; } export interface Benchmark { - /** - * Benchmark title - */ - title: string; - /** - * Benchmark function. Will be called `size` times per `iter`ation (see - * {@link BenchmarkOpts}). - */ - fn: Fn0; - /** - * Optional & partial benchmark specific option overrides (merged with opts - * given to suite) - */ - opts?: Partial; + /** + * Benchmark title + */ + title: string; + /** + * Benchmark function. Will be called `size` times per `iter`ation (see + * {@link BenchmarkOpts}). + */ + fn: Fn0; + /** + * Optional & partial benchmark specific option overrides (merged with opts + * given to suite) + */ + opts?: Partial; } export const FLOAT = (x: number) => x.toFixed(2); diff --git a/packages/bench/src/bench.ts b/packages/bench/src/bench.ts index e8b460bec6..461141d340 100644 --- a/packages/bench/src/bench.ts +++ b/packages/bench/src/bench.ts @@ -10,13 +10,13 @@ import { timed, timedResult } from "./timed.js"; * @param n - number of iterations */ export const bench = (fn: () => T, n = 1e6, prefix = "") => { - let res: T; - return timed(() => { - while (n-- > 0) { - res = fn(); - } - return res; - }, prefix); + let res: T; + return timed(() => { + while (n-- > 0) { + res = fn(); + } + return res; + }, prefix); }; /** @@ -27,11 +27,11 @@ export const bench = (fn: () => T, n = 1e6, prefix = "") => { * @param n - number of iterations */ export const benchResult = (fn: () => T, n = 1e6): TimingResult => { - let res: T; - return timedResult(() => { - while (n-- > 0) { - res = fn(); - } - return res; - }); + let res: T; + return timedResult(() => { + while (n-- > 0) { + res = fn(); + } + return res; + }); }; diff --git a/packages/bench/src/benchmark.ts b/packages/bench/src/benchmark.ts index 62eb67145b..3498d0f29f 100644 --- a/packages/bench/src/benchmark.ts +++ b/packages/bench/src/benchmark.ts @@ -3,56 +3,56 @@ import { benchResult } from "./bench.js"; import { FORMAT_DEFAULT } from "./format/default.js"; export const DEFAULT_OPTS: BenchmarkOpts = { - title: "benchmark", - iter: 1e3, - size: 1, - warmup: 10, - output: true, - format: FORMAT_DEFAULT, + title: "benchmark", + iter: 1e3, + size: 1, + warmup: 10, + output: true, + format: FORMAT_DEFAULT, }; export const benchmark = ( - fn: () => void, - opts?: Partial + fn: () => void, + opts?: Partial ): BenchmarkResult => { - const _opts = { ...DEFAULT_OPTS, ...opts }; - const { iter, size, warmup, output, format } = _opts; - output && outputString(format!.start(_opts)); - const t = benchResult(fn, warmup * size)[1]; - output && outputString(format!.warmup(t, _opts)); - const samples: number[] = []; - for (let i = iter!; i-- > 0; ) { - samples.push(benchResult(fn, size)[1]); - } - samples.sort((a, b) => a - b); - const total = samples.reduce((acc, x) => acc + x, 0); - const mean = total / iter!; - const median = samples[iter! >> 1]; - const min = samples[0]; - const max = samples[iter! - 1]; - const q1 = samples[Math.ceil(iter! * 0.25)]; - const q3 = samples[Math.ceil(iter! * 0.75)]; - const sd = - (Math.sqrt( - samples.reduce((acc, x) => acc + (mean - x) ** 2, 0) / iter! - ) / - mean) * - 100; - const res: BenchmarkResult = { - title: _opts.title, - iter, - size, - total, - mean, - median, - min, - max, - q1, - q3, - sd, - }; - output && outputString(format!.result(res)); - return res; + const _opts = { ...DEFAULT_OPTS, ...opts }; + const { iter, size, warmup, output, format } = _opts; + output && outputString(format!.start(_opts)); + const t = benchResult(fn, warmup * size)[1]; + output && outputString(format!.warmup(t, _opts)); + const samples: number[] = []; + for (let i = iter!; i-- > 0; ) { + samples.push(benchResult(fn, size)[1]); + } + samples.sort((a, b) => a - b); + const total = samples.reduce((acc, x) => acc + x, 0); + const mean = total / iter!; + const median = samples[iter! >> 1]; + const min = samples[0]; + const max = samples[iter! - 1]; + const q1 = samples[Math.ceil(iter! * 0.25)]; + const q3 = samples[Math.ceil(iter! * 0.75)]; + const sd = + (Math.sqrt( + samples.reduce((acc, x) => acc + (mean - x) ** 2, 0) / iter! + ) / + mean) * + 100; + const res: BenchmarkResult = { + title: _opts.title, + iter, + size, + total, + mean, + median, + min, + max, + q1, + q3, + sd, + }; + output && outputString(format!.result(res)); + return res; }; /** diff --git a/packages/bench/src/format/csv.ts b/packages/bench/src/format/csv.ts index 51f86d3198..d2bd64a162 100644 --- a/packages/bench/src/format/csv.ts +++ b/packages/bench/src/format/csv.ts @@ -1,22 +1,22 @@ import { BenchmarkFormatter, EMPTY, FLOAT } from "../api.js"; export const FORMAT_CSV: BenchmarkFormatter = { - prefix: () => `Title,Iterations,Size,Total,Mean,Median,Min,Max,Q1,Q3,SD%`, - start: EMPTY, - warmup: EMPTY, - result: (res) => - `"${res.title}",${res.iter},${res.size},${[ - res.total, - res.mean, - res.median, - res.min, - res.max, - res.q1, - res.q3, - res.sd, - ] - .map(FLOAT) - .join(",")}`, - total: EMPTY, - suffix: EMPTY, + prefix: () => `Title,Iterations,Size,Total,Mean,Median,Min,Max,Q1,Q3,SD%`, + start: EMPTY, + warmup: EMPTY, + result: (res) => + `"${res.title}",${res.iter},${res.size},${[ + res.total, + res.mean, + res.median, + res.min, + res.max, + res.q1, + res.q3, + res.sd, + ] + .map(FLOAT) + .join(",")}`, + total: EMPTY, + suffix: EMPTY, }; diff --git a/packages/bench/src/format/default.ts b/packages/bench/src/format/default.ts index 80f342cdea..ea456a9ce2 100644 --- a/packages/bench/src/format/default.ts +++ b/packages/bench/src/format/default.ts @@ -1,18 +1,18 @@ import { BenchmarkFormatter, EMPTY, FLOAT } from "../api.js"; export const FORMAT_DEFAULT: BenchmarkFormatter = { - prefix: EMPTY, - start: ({ title }) => `benchmarking: ${title}`, - warmup: (t, { warmup }) => `\twarmup... ${FLOAT(t)}ms (${warmup} runs)`, - result: ({ iter, size, total, mean, median, min, max, q1, q3, sd }) => - // prettier-ignore - `\ttotal: ${FLOAT(total)}ms, runs: ${iter} (@ ${size} calls/iter) + prefix: EMPTY, + start: ({ title }) => `benchmarking: ${title}`, + warmup: (t, { warmup }) => `\twarmup... ${FLOAT(t)}ms (${warmup} runs)`, + result: ({ iter, size, total, mean, median, min, max, q1, q3, sd }) => + // prettier-ignore + `\ttotal: ${FLOAT(total)}ms, runs: ${iter} (@ ${size} calls/iter) \tmean: ${FLOAT(mean)}ms, median: ${FLOAT(median)}ms, range: [${FLOAT(min)}..${FLOAT(max)}] \tq1: ${FLOAT(q1)}ms, q3: ${FLOAT(q3)}ms \tsd: ${FLOAT(sd)}%`, - total: (res) => { - const fastest = res.slice().sort((a, b) => a.mean - b.mean)[0]; - return `Fastest: "${fastest.title}"`; - }, - suffix: () => `---`, + total: (res) => { + const fastest = res.slice().sort((a, b) => a.mean - b.mean)[0]; + return `Fastest: "${fastest.title}"`; + }, + suffix: () => `---`, }; diff --git a/packages/bench/src/format/markdown.ts b/packages/bench/src/format/markdown.ts index 520119c2ab..e676794026 100644 --- a/packages/bench/src/format/markdown.ts +++ b/packages/bench/src/format/markdown.ts @@ -4,13 +4,13 @@ import { BenchmarkFormatter, EMPTY, FLOAT } from "../api.js"; const $n = (n: number, char = "-") => new Array(n).fill(char).join(""); const pad = (w: number) => { - const column = $n(w, " "); - return (x: NumOrString) => { - const s = typeof x === "number" ? FLOAT(x) : x; - return s.length < w - ? column.substr(0, w - s.length) + s - : s.substr(0, w); - }; + const column = $n(w, " "); + return (x: NumOrString) => { + const s = typeof x === "number" ? FLOAT(x) : x; + return s.length < w + ? column.substr(0, w - s.length) + s + : s.substr(0, w); + }; }; const c24 = pad(24); @@ -25,39 +25,39 @@ const COLUMNS = [c24, c8, c8, c12, c8, c8, c8, c8, c8, c8, c8]; const DASHES = [d24, d8, d8, d12, d8, d8, d8, d8, d8, d8, d8]; const row = (cols: NumOrString[]) => - `|${cols.map((x, i) => COLUMNS[i](x)).join("|")}|`; + `|${cols.map((x, i) => COLUMNS[i](x)).join("|")}|`; export const FORMAT_MD: BenchmarkFormatter = { - prefix: () => - row([ - "Title", - "Iter", - "Size", - "Total", - "Mean", - "Median", - "Min", - "Max", - "Q1", - "Q3", - "SD%", - ]) + `\n|${DASHES.join("|")}|`, - start: EMPTY, - warmup: EMPTY, - result: (res) => - row([ - res.title, - "" + res.iter, - "" + res.size, - res.total, - res.mean, - res.median, - res.min, - res.max, - res.q1, - res.q3, - res.sd, - ]), - total: EMPTY, - suffix: EMPTY, + prefix: () => + row([ + "Title", + "Iter", + "Size", + "Total", + "Mean", + "Median", + "Min", + "Max", + "Q1", + "Q3", + "SD%", + ]) + `\n|${DASHES.join("|")}|`, + start: EMPTY, + warmup: EMPTY, + result: (res) => + row([ + res.title, + "" + res.iter, + "" + res.size, + res.total, + res.mean, + res.median, + res.min, + res.max, + res.q1, + res.q3, + res.sd, + ]), + total: EMPTY, + suffix: EMPTY, }; diff --git a/packages/bench/src/now.ts b/packages/bench/src/now.ts index f851cbd815..17be3d5123 100644 --- a/packages/bench/src/now.ts +++ b/packages/bench/src/now.ts @@ -7,13 +7,13 @@ import type { Timestamp } from "./api.js"; * timestamp, either as `bigint` or `number`. */ export const now: Fn0 = - typeof BigInt !== "undefined" - ? typeof process !== "undefined" && - typeof process.hrtime !== "undefined" && - typeof process.hrtime.bigint === "function" - ? () => process.hrtime.bigint() - : () => BigInt(Date.now() * 1e6) - : () => Date.now() * 1e6; + typeof BigInt !== "undefined" + ? typeof process !== "undefined" && + typeof process.hrtime !== "undefined" && + typeof process.hrtime.bigint === "function" + ? () => process.hrtime.bigint() + : () => BigInt(Date.now() * 1e6) + : () => Date.now() * 1e6; /** * Returns the difference in milliseconds between 2 given @@ -23,6 +23,6 @@ export const now: Fn0 = * @param b */ export const timeDiff: FnU2 = (a, b) => - (typeof BigInt !== "undefined" - ? Number(b - a) - : b - a) * 1e-6; + (typeof BigInt !== "undefined" + ? Number(b - a) + : b - a) * 1e-6; diff --git a/packages/bench/src/suite.ts b/packages/bench/src/suite.ts index a4a5010d38..6772c7bf3a 100644 --- a/packages/bench/src/suite.ts +++ b/packages/bench/src/suite.ts @@ -2,19 +2,19 @@ import type { Benchmark, BenchmarkResult, BenchmarkSuiteOpts } from "./api.js"; import { benchmark, DEFAULT_OPTS, outputString } from "./benchmark.js"; export const suite = ( - cases: Benchmark[], - opts?: Partial + cases: Benchmark[], + opts?: Partial ) => { - const _opts = { - ...DEFAULT_OPTS, - ...opts, - }; - _opts.output && outputString(_opts.format.prefix()); - const results: BenchmarkResult[] = []; - for (let c of cases) { - results.push(benchmark(c.fn, { ..._opts, ...c.opts, title: c.title })); - } - _opts.output && outputString(_opts.format.total(results)); - _opts.output && outputString(_opts.format.suffix()); - return results; + const _opts = { + ...DEFAULT_OPTS, + ...opts, + }; + _opts.output && outputString(_opts.format.prefix()); + const results: BenchmarkResult[] = []; + for (let c of cases) { + results.push(benchmark(c.fn, { ..._opts, ...c.opts, title: c.title })); + } + _opts.output && outputString(_opts.format.total(results)); + _opts.output && outputString(_opts.format.suffix()); + return results; }; diff --git a/packages/bench/src/timed.ts b/packages/bench/src/timed.ts index bcda8c2018..84479f906a 100644 --- a/packages/bench/src/timed.ts +++ b/packages/bench/src/timed.ts @@ -10,9 +10,9 @@ import { now, timeDiff } from "./now.js"; * @param prefix - log prefix */ export const timed = (fn: () => T, prefix = "") => { - const [res, t] = timedResult(fn); - console.log(`${prefix} ${t.toFixed(2)}ms`); - return res; + const [res, t] = timedResult(fn); + console.log(`${prefix} ${t.toFixed(2)}ms`); + return res; }; /** @@ -22,8 +22,8 @@ export const timed = (fn: () => T, prefix = "") => { * @param fn - function to time */ export const timedResult = (fn: () => T): TimingResult => { - const t0 = now(); - const res = fn(); - const t1 = now(); - return [res, timeDiff(t0, t1)]; + const t0 = now(); + const res = fn(); + const t1 = now(); + return [res, timeDiff(t0, t1)]; }; diff --git a/packages/bench/tsconfig.json b/packages/bench/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/bench/tsconfig.json +++ b/packages/bench/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/bencode/api-extractor.json b/packages/bencode/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/bencode/api-extractor.json +++ b/packages/bencode/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/bencode/package.json b/packages/bencode/package.json index a17c78b203..7cb01d3714 100644 --- a/packages/bencode/package.json +++ b/packages/bencode/package.json @@ -1,82 +1,82 @@ { - "name": "@thi.ng/bencode", - "version": "2.1.12", - "description": "Bencode binary encoder / decoder with optional UTF8 encoding & floating point support", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/bencode#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/arrays": "^2.3.1", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/defmulti": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/transducers-binary": "^2.1.12" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "array", - "bencode", - "binary", - "encode", - "fileformat", - "transducer", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./decode": { - "default": "./decode.js" - }, - "./encode": { - "default": "./encode.js" - } - } + "name": "@thi.ng/bencode", + "version": "2.1.12", + "description": "Bencode binary encoder / decoder with optional UTF8 encoding & floating point support", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/bencode#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/arrays": "^2.3.1", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/defmulti": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/transducers-binary": "^2.1.12" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "array", + "bencode", + "binary", + "encode", + "fileformat", + "transducer", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./decode": { + "default": "./decode.js" + }, + "./encode": { + "default": "./encode.js" + } + } } diff --git a/packages/bencode/src/decode.ts b/packages/bencode/src/decode.ts index 68948439a3..fbf4f86f9b 100644 --- a/packages/bencode/src/decode.ts +++ b/packages/bencode/src/decode.ts @@ -5,162 +5,162 @@ import { illegalState } from "@thi.ng/errors/illegal-state"; import { utf8Decode } from "@thi.ng/transducers-binary/utf8"; const enum Type { - INT, - FLOAT, - STR, - BINARY, - DICT, - LIST, + INT, + FLOAT, + STR, + BINARY, + DICT, + LIST, } const enum Lit { - MINUS = 0x2d, - DOT = 0x2e, - ZERO = 0x30, - NINE = 0x39, - COLON = 0x3a, - DICT = 0x64, - END = 0x65, - FLOAT = 0x66, - INT = 0x69, - LIST = 0x6c, + MINUS = 0x2d, + DOT = 0x2e, + ZERO = 0x30, + NINE = 0x39, + COLON = 0x3a, + DICT = 0x64, + END = 0x65, + FLOAT = 0x66, + INT = 0x69, + LIST = 0x6c, } export const decode = (buf: Iterable, utf8 = true) => { - const iter = buf[Symbol.iterator](); - const stack = []; - let i: IteratorResult; - let x: any; - while (!(i = iter.next()).done) { - x = i.value; - switch (x) { - case Lit.DICT: - ensureNotKey(stack, "dict"); - stack.push({ type: Type.DICT, val: {} }); - break; - case Lit.LIST: - ensureNotKey(stack, "list"); - stack.push({ type: Type.LIST, val: [] }); - break; - case Lit.INT: - x = collect(stack, readInt(iter, 0)); - if (x !== undefined) { - return x; - } - break; - case Lit.FLOAT: - x = collect(stack, readFloat(iter)); - if (x !== undefined) { - return x; - } - break; - case Lit.END: - x = stack.pop(); - if (x) { - const parent = peek(stack); - if (parent) { - if (parent.type === Type.LIST) { - (parent.val).push(x.val); - } else if (parent.type === Type.DICT) { - (parent.val)[(parent).key] = x.val; - (parent).key = null; - } - } else { - return x.val; - } - } else { - illegalState("unmatched end literal"); - } - break; - default: - if (x >= Lit.ZERO && x <= Lit.NINE) { - x = readBytes( - iter, - readInt(iter, x - Lit.ZERO, Lit.COLON)! - ); - x = collect(stack, x, utf8); - if (x !== undefined) { - return x; - } - } else { - illegalState( - `unexpected value type: 0x${i.value.toString(16)}` - ); - } - } - } - return peek(stack).val; + const iter = buf[Symbol.iterator](); + const stack = []; + let i: IteratorResult; + let x: any; + while (!(i = iter.next()).done) { + x = i.value; + switch (x) { + case Lit.DICT: + ensureNotKey(stack, "dict"); + stack.push({ type: Type.DICT, val: {} }); + break; + case Lit.LIST: + ensureNotKey(stack, "list"); + stack.push({ type: Type.LIST, val: [] }); + break; + case Lit.INT: + x = collect(stack, readInt(iter, 0)); + if (x !== undefined) { + return x; + } + break; + case Lit.FLOAT: + x = collect(stack, readFloat(iter)); + if (x !== undefined) { + return x; + } + break; + case Lit.END: + x = stack.pop(); + if (x) { + const parent = peek(stack); + if (parent) { + if (parent.type === Type.LIST) { + (parent.val).push(x.val); + } else if (parent.type === Type.DICT) { + (parent.val)[(parent).key] = x.val; + (parent).key = null; + } + } else { + return x.val; + } + } else { + illegalState("unmatched end literal"); + } + break; + default: + if (x >= Lit.ZERO && x <= Lit.NINE) { + x = readBytes( + iter, + readInt(iter, x - Lit.ZERO, Lit.COLON)! + ); + x = collect(stack, x, utf8); + if (x !== undefined) { + return x; + } + } else { + illegalState( + `unexpected value type: 0x${i.value.toString(16)}` + ); + } + } + } + return peek(stack).val; }; const ensureNotKey = (stack: any[], type: string) => { - const x = peek(stack); - assert( - !x || x.type !== Type.DICT || x.key, - type + " not supported as dict key" - ); + const x = peek(stack); + assert( + !x || x.type !== Type.DICT || x.key, + type + " not supported as dict key" + ); }; const collect = (stack: any[], x: any, utf8 = false) => { - const parent = peek(stack); - if (!parent) return x; - if (parent.type === Type.LIST) { - parent.val.push(utf8 && isArray(x) ? utf8Decode(x) : x); - } else { - if (!parent.key) { - parent.key = isArray(x) ? utf8Decode(x) : x; - } else { - parent.val[parent.key] = utf8 ? utf8Decode(x) : x; - parent.key = null; - } - } + const parent = peek(stack); + if (!parent) return x; + if (parent.type === Type.LIST) { + parent.val.push(utf8 && isArray(x) ? utf8Decode(x) : x); + } else { + if (!parent.key) { + parent.key = isArray(x) ? utf8Decode(x) : x; + } else { + parent.val[parent.key] = utf8 ? utf8Decode(x) : x; + parent.key = null; + } + } }; const readInt = (iter: Iterator, acc: number, end = Lit.END) => { - let i: IteratorResult; - let x: number; - let isSigned = false; - while (!(i = iter.next()).done) { - x = i.value; - if (x >= Lit.ZERO && x <= Lit.NINE) { - acc = acc * 10 + x - Lit.ZERO; - } else if (x === Lit.MINUS) { - assert(!isSigned, `invalid int literal`); - isSigned = true; - } else if (x === end) { - return isSigned ? -acc : acc; - } else { - illegalState(`expected digit, got 0x${x.toString(16)}`); - } - } - illegalState(`incomplete int`); + let i: IteratorResult; + let x: number; + let isSigned = false; + while (!(i = iter.next()).done) { + x = i.value; + if (x >= Lit.ZERO && x <= Lit.NINE) { + acc = acc * 10 + x - Lit.ZERO; + } else if (x === Lit.MINUS) { + assert(!isSigned, `invalid int literal`); + isSigned = true; + } else if (x === end) { + return isSigned ? -acc : acc; + } else { + illegalState(`expected digit, got 0x${x.toString(16)}`); + } + } + illegalState(`incomplete int`); }; const readFloat = (iter: Iterator) => { - let i: IteratorResult; - let x: number; - let acc = ""; - while (!(i = iter.next()).done) { - x = i.value; - if ( - (x >= Lit.ZERO && x <= Lit.NINE) || - x === Lit.DOT || - x === Lit.MINUS - ) { - acc += String.fromCharCode(x); - } else if (x === Lit.END) { - return parseFloat(acc); - } else { - illegalState(`expected digit or dot, got 0x${x.toString(16)}`); - } - } - illegalState(`incomplete float`); + let i: IteratorResult; + let x: number; + let acc = ""; + while (!(i = iter.next()).done) { + x = i.value; + if ( + (x >= Lit.ZERO && x <= Lit.NINE) || + x === Lit.DOT || + x === Lit.MINUS + ) { + acc += String.fromCharCode(x); + } else if (x === Lit.END) { + return parseFloat(acc); + } else { + illegalState(`expected digit or dot, got 0x${x.toString(16)}`); + } + } + illegalState(`incomplete float`); }; const readBytes = (iter: Iterator, len: number) => { - let i: IteratorResult; - let buf: number[] = []; - while (len-- > 0 && !(i = iter.next()).done) { - buf.push(i.value); - } - return len < 0 ? buf : illegalState(`expected string, reached EOF`); + let i: IteratorResult; + let buf: number[] = []; + while (len-- > 0 && !(i = iter.next()).done) { + buf.push(i.value); + } + return len < 0 ? buf : illegalState(`expected string, reached EOF`); }; diff --git a/packages/bencode/src/encode.ts b/packages/bencode/src/encode.ts index 951ef4989e..60503d77df 100644 --- a/packages/bencode/src/encode.ts +++ b/packages/bencode/src/encode.ts @@ -13,18 +13,18 @@ import { utf8Length } from "@thi.ng/transducers-binary/utf8"; import { mapcat } from "@thi.ng/transducers/mapcat"; const enum Type { - INT, - FLOAT, - STR, - BINARY, - DICT, - LIST, + INT, + FLOAT, + STR, + BINARY, + DICT, + LIST, } const enum Lit { - DICT = 0x64, - END = 0x65, - LIST = 0x6c, + DICT = 0x64, + END = 0x65, + LIST = 0x6c, } const FLOAT_RE = /^[0-9.-]+$/; @@ -32,67 +32,67 @@ const FLOAT_RE = /^[0-9.-]+$/; export const encode = (x: any, cap = 1024) => bytes(cap, encodeBin(x)); const encodeBin: MultiFn1 = defmulti< - any, - BinStructItem[] + any, + BinStructItem[] >( - (x: any): Type => - isNumber(x) - ? Math.floor(x) !== x - ? Type.FLOAT - : Type.INT - : isBoolean(x) - ? Type.INT - : isString(x) - ? Type.STR - : x instanceof Uint8Array - ? Type.BINARY - : isArrayLike(x) - ? Type.LIST - : isPlainObject(x) - ? Type.DICT - : unsupported(`unsupported data type: ${x}`), - {}, - { - [Type.INT]: (x: number) => { - __ensureValidNumber(x); - return [str(`i${Math.floor(x)}e`)]; - }, + (x: any): Type => + isNumber(x) + ? Math.floor(x) !== x + ? Type.FLOAT + : Type.INT + : isBoolean(x) + ? Type.INT + : isString(x) + ? Type.STR + : x instanceof Uint8Array + ? Type.BINARY + : isArrayLike(x) + ? Type.LIST + : isPlainObject(x) + ? Type.DICT + : unsupported(`unsupported data type: ${x}`), + {}, + { + [Type.INT]: (x: number) => { + __ensureValidNumber(x); + return [str(`i${Math.floor(x)}e`)]; + }, - [Type.FLOAT]: (x: number) => { - __ensureValidNumber(x); - assert( - FLOAT_RE.test(x.toString()), - `values requiring exponential notation not allowed (${x})` - ); - return [str(`f${x}e`)]; - }, + [Type.FLOAT]: (x: number) => { + __ensureValidNumber(x); + assert( + FLOAT_RE.test(x.toString()), + `values requiring exponential notation not allowed (${x})` + ); + return [str(`f${x}e`)]; + }, - [Type.BINARY]: (buf: Uint8Array) => [ - str(buf.length + ":"), - u8array(buf), - ], + [Type.BINARY]: (buf: Uint8Array) => [ + str(buf.length + ":"), + u8array(buf), + ], - [Type.STR]: (x: string) => [str(utf8Length(x) + ":" + x)], + [Type.STR]: (x: string) => [str(utf8Length(x) + ":" + x)], - [Type.LIST]: (x: Iterable) => [ - u8(Lit.LIST), - ...mapcat(encodeBin, x), - u8(Lit.END), - ], + [Type.LIST]: (x: Iterable) => [ + u8(Lit.LIST), + ...mapcat(encodeBin, x), + u8(Lit.END), + ], - [Type.DICT]: (x: any) => [ - u8(Lit.DICT), - ...mapcat( - (k: string) => encodeBin(k).concat(encodeBin(x[k])), - Object.keys(x).sort() - ), - u8(Lit.END), - ], - } + [Type.DICT]: (x: any) => [ + u8(Lit.DICT), + ...mapcat( + (k: string) => encodeBin(k).concat(encodeBin(x[k])), + Object.keys(x).sort() + ), + u8(Lit.END), + ], + } ); /** @internal */ const __ensureValidNumber = (x: number) => { - assert(isFinite(x), `can't encode infinite value`); - assert(!isNaN(x), `can't encode NaN`); + assert(isFinite(x), `can't encode infinite value`); + assert(!isNaN(x), `can't encode NaN`); }; diff --git a/packages/bencode/tsconfig.json b/packages/bencode/tsconfig.json index adce2e5052..3e3eb91698 100644 --- a/packages/bencode/tsconfig.json +++ b/packages/bencode/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": ".", - "preserveConstEnums": false, - "isolatedModules": false - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "preserveConstEnums": false, + "isolatedModules": false + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/binary/api-extractor.json b/packages/binary/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/binary/api-extractor.json +++ b/packages/binary/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/binary/package.json b/packages/binary/package.json index 3e5b1c3091..3657ebade9 100644 --- a/packages/binary/package.json +++ b/packages/binary/package.json @@ -1,132 +1,132 @@ { - "name": "@thi.ng/binary", - "version": "3.3.0", - "description": "100+ assorted binary / bitwise operations, conversions, utilities, lookup tables", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/binary#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "align", - "binary", - "bitwise", - "conversion", - "count", - "float", - "graycode", - "int", - "logic", - "lut", - "mask", - "math", - "shift", - "splat", - "swap", - "swizzle", - "typedarray", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./align": { - "default": "./align.js" - }, - "./api": { - "default": "./api.js" - }, - "./bytes": { - "default": "./bytes.js" - }, - "./constants": { - "default": "./constants.js" - }, - "./count": { - "default": "./count.js" - }, - "./edit": { - "default": "./edit.js" - }, - "./float": { - "default": "./float.js" - }, - "./gray": { - "default": "./gray.js" - }, - "./logic": { - "default": "./logic.js" - }, - "./mask": { - "default": "./mask.js" - }, - "./one-hot": { - "default": "./one-hot.js" - }, - "./pow": { - "default": "./pow.js" - }, - "./rotate": { - "default": "./rotate.js" - }, - "./splat": { - "default": "./splat.js" - }, - "./swizzle": { - "default": "./swizzle.js" - } - }, - "thi.ng": { - "related": [ - "transducers-binary" - ] - } + "name": "@thi.ng/binary", + "version": "3.3.0", + "description": "100+ assorted binary / bitwise operations, conversions, utilities, lookup tables", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/binary#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "align", + "binary", + "bitwise", + "conversion", + "count", + "float", + "graycode", + "int", + "logic", + "lut", + "mask", + "math", + "shift", + "splat", + "swap", + "swizzle", + "typedarray", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./align": { + "default": "./align.js" + }, + "./api": { + "default": "./api.js" + }, + "./bytes": { + "default": "./bytes.js" + }, + "./constants": { + "default": "./constants.js" + }, + "./count": { + "default": "./count.js" + }, + "./edit": { + "default": "./edit.js" + }, + "./float": { + "default": "./float.js" + }, + "./gray": { + "default": "./gray.js" + }, + "./logic": { + "default": "./logic.js" + }, + "./mask": { + "default": "./mask.js" + }, + "./one-hot": { + "default": "./one-hot.js" + }, + "./pow": { + "default": "./pow.js" + }, + "./rotate": { + "default": "./rotate.js" + }, + "./splat": { + "default": "./splat.js" + }, + "./swizzle": { + "default": "./swizzle.js" + } + }, + "thi.ng": { + "related": [ + "transducers-binary" + ] + } } diff --git a/packages/binary/src/align.ts b/packages/binary/src/align.ts index 32d4ca2604..1acf02a39c 100644 --- a/packages/binary/src/align.ts +++ b/packages/binary/src/align.ts @@ -8,7 +8,7 @@ import type { Pow2 } from "./api.js"; * @param size - alignment value */ export const align = (addr: number, size: Pow2) => ( - size--, (addr + size) & ~size + size--, (addr + size) & ~size ); /** diff --git a/packages/binary/src/api.ts b/packages/binary/src/api.ts index 042a6d63ac..09b8c9ef81 100644 --- a/packages/binary/src/api.ts +++ b/packages/binary/src/api.ts @@ -5,87 +5,87 @@ export type Lane8 = 0 | 1 | 2 | 3; export type Lane4 = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; export type Lane2 = - | 0 - | 1 - | 2 - | 3 - | 4 - | 5 - | 6 - | 7 - | 8 - | 9 - | 10 - | 11 - | 12 - | 13 - | 14 - | 15; + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15; export type Bit = - | 0 - | 1 - | 2 - | 3 - | 4 - | 5 - | 6 - | 7 - | 8 - | 9 - | 10 - | 11 - | 12 - | 13 - | 14 - | 15 - | 16 - | 17 - | 18 - | 19 - | 20 - | 21 - | 22 - | 23 - | 24 - | 25 - | 26 - | 27 - | 28 - | 29 - | 30 - | 31; + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31; export type Pow2 = - | 0x1 - | 0x2 - | 0x4 - | 0x8 - | 0x10 - | 0x20 - | 0x40 - | 0x80 - | 0x100 - | 0x200 - | 0x400 - | 0x800 - | 0x1000 - | 0x2000 - | 0x4000 - | 0x8000 - | 0x10000 - | 0x20000 - | 0x40000 - | 0x80000 - | 0x100000 - | 0x200000 - | 0x400000 - | 0x800000 - | 0x1000000 - | 0x2000000 - | 0x4000000 - | 0x8000000 - | 0x10000000 - | 0x20000000 - | 0x40000000 - | 0x80000000; + | 0x1 + | 0x2 + | 0x4 + | 0x8 + | 0x10 + | 0x20 + | 0x40 + | 0x80 + | 0x100 + | 0x200 + | 0x400 + | 0x800 + | 0x1000 + | 0x2000 + | 0x4000 + | 0x8000 + | 0x10000 + | 0x20000 + | 0x40000 + | 0x80000 + | 0x100000 + | 0x200000 + | 0x400000 + | 0x800000 + | 0x1000000 + | 0x2000000 + | 0x4000000 + | 0x8000000 + | 0x10000000 + | 0x20000000 + | 0x40000000 + | 0x80000000; diff --git a/packages/binary/src/bytes.ts b/packages/binary/src/bytes.ts index 0802aa7a16..f0fecdb0a4 100644 --- a/packages/binary/src/bytes.ts +++ b/packages/binary/src/bytes.ts @@ -1,35 +1,35 @@ import { floatToUintBits, floatToUintBits64 } from "./float.js"; export const bytes16 = (x: number, le = false) => { - const b0 = x & 0xff; - const b1 = (x >> 8) & 0xff; - return le ? [b0, b1] : [b1, b0]; + const b0 = x & 0xff; + const b1 = (x >> 8) & 0xff; + return le ? [b0, b1] : [b1, b0]; }; export const bytes24 = (x: number, le = false) => { - const b0 = x & 0xff; - const b1 = (x >> 8) & 0xff; - const b2 = (x >> 16) & 0xff; - return le ? [b0, b1, b2] : [b2, b1, b0]; + const b0 = x & 0xff; + const b1 = (x >> 8) & 0xff; + const b2 = (x >> 16) & 0xff; + return le ? [b0, b1, b2] : [b2, b1, b0]; }; export const bytes32 = (x: number, le = false) => { - const b0 = x & 0xff; - const b1 = (x >> 8) & 0xff; - const b2 = (x >> 16) & 0xff; - const b3 = (x >> 24) & 0xff; - return le ? [b0, b1, b2, b3] : [b3, b2, b1, b0]; + const b0 = x & 0xff; + const b1 = (x >> 8) & 0xff; + const b2 = (x >> 16) & 0xff; + const b3 = (x >> 24) & 0xff; + return le ? [b0, b1, b2, b3] : [b3, b2, b1, b0]; }; export const bytes64 = (hi: number, lo: number, le = false) => { - return le - ? bytes32(lo, le).concat(bytes32(hi, le)) - : bytes32(hi, le).concat(bytes32(lo, le)); + return le + ? bytes32(lo, le).concat(bytes32(hi, le)) + : bytes32(hi, le).concat(bytes32(lo, le)); }; export const bytesF32 = (x: number, le = false) => - bytes32(floatToUintBits(x), le); + bytes32(floatToUintBits(x), le); export const bytesF64 = (x: number, le = false) => - //@ts-ignore - bytes64(...floatToUintBits64(x), le); + //@ts-ignore + bytes64(...floatToUintBits64(x), le); diff --git a/packages/binary/src/constants.ts b/packages/binary/src/constants.ts index c23db8c61f..55acdb30fc 100644 --- a/packages/binary/src/constants.ts +++ b/packages/binary/src/constants.ts @@ -1,5 +1,5 @@ const defBits = (n: number) => - new Array(n).fill(0).map((_, i) => 1 << (n - 1 - i)); + new Array(n).fill(0).map((_, i) => 1 << (n - 1 - i)); /** * 8bit values in MSB order (i.e. MSB_BITS[0] = 0x80) */ diff --git a/packages/binary/src/count.ts b/packages/binary/src/count.ts index f511462da5..3491a61f8e 100644 --- a/packages/binary/src/count.ts +++ b/packages/binary/src/count.ts @@ -6,9 +6,9 @@ import type { FnN, FnN2 } from "@thi.ng/api"; * @param x - */ export const popCount: FnN = (x) => ( - (x = x - ((x >>> 1) & 0x55555555)), - (x = (x & 0x33333333) + ((x >>> 2) & 0x33333333)), - (((x + (x >>> 4)) & 0xf0f0f0f) * 0x1010101) >>> 24 + (x = x - ((x >>> 1) & 0x55555555)), + (x = (x & 0x33333333) + ((x >>> 2) & 0x33333333)), + (((x + (x >>> 4)) & 0xf0f0f0f) * 0x1010101) >>> 24 ); /** @@ -19,16 +19,16 @@ export const popCount: FnN = (x) => ( * @param n - */ export const popCountArray = ( - data: Uint32Array, - start = 0, - n = data.length + data: Uint32Array, + start = 0, + n = data.length ) => { - let num = 0; - for (let end = start + n; start < end; start++) { - const x = data[start]; - x > 0 && (num += popCount(x)); - } - return num; + let num = 0; + for (let end = start + n; start < end; start++) { + const x = data[start]; + x > 0 && (num += popCount(x)); + } + return num; }; /** @@ -49,18 +49,18 @@ export const hammingDist: FnN2 = (x, y) => popCount(x ^ y); * @param x - */ export const clz32: FnN = (x) => - x !== 0 ? 31 - ((Math.log(x >>> 0) / Math.LN2) | 0) : 32; + x !== 0 ? 31 - ((Math.log(x >>> 0) / Math.LN2) | 0) : 32; export const ctz32: FnN = (x) => { - let c = 32; - x &= -x; - x && c--; - x & 0x0000ffff && (c -= 16); - x & 0x00ff00ff && (c -= 8); - x & 0x0f0f0f0f && (c -= 4); - x & 0x33333333 && (c -= 2); - x & 0x55555555 && (c -= 1); - return c; + let c = 32; + x &= -x; + x && c--; + x & 0x0000ffff && (c -= 16); + x & 0x00ff00ff && (c -= 8); + x & 0x0f0f0f0f && (c -= 4); + x & 0x33333333 && (c -= 2); + x & 0x55555555 && (c -= 1); + return c; }; /** diff --git a/packages/binary/src/edit.ts b/packages/binary/src/edit.ts index d3a6b54e3e..5d61858918 100644 --- a/packages/binary/src/edit.ts +++ b/packages/binary/src/edit.ts @@ -27,14 +27,14 @@ export const bitFlip = (x: number, bit: Bit) => (x ^ (1 << bit)) >>> 0; export const bitSet = (x: number, bit: Bit) => (x | (1 << bit)) >>> 0; export const bitSetWindow = ( - x: number, - y: number, - from: number, - to: number + x: number, + y: number, + from: number, + to: number ) => { - const m = defMask(from, to); - return (x & ~m) | ((y << (1 << from)) & m); + const m = defMask(from, to); + return (x & ~m) | ((y << (1 << from)) & m); }; export const bitClearWindow: FnU3 = (x, from, to) => - x & ~defMask(from, to); + x & ~defMask(from, to); diff --git a/packages/binary/src/float.ts b/packages/binary/src/float.ts index 45aa6591a8..511f6919ff 100644 --- a/packages/binary/src/float.ts +++ b/packages/binary/src/float.ts @@ -22,42 +22,42 @@ export const uintBitsToFloat: FnN = (x) => ((U32[0] = x), F32[0]); * Returns i32 representation of f64 as [hi, lo] tuple (takes * environment's Little Endianess into account). * - * @param x - + * @param x - */ export const floatToIntBits64 = (x: number): [number, number] => ( - (F64[0] = x), IS_LE ? [I32[1], I32[0]] : [I32[0], I32[1]] + (F64[0] = x), IS_LE ? [I32[1], I32[0]] : [I32[0], I32[1]] ); /** * Returns u32 representation of f64 as [hi, lo] tuple (takes * environment's Little Endianess into account). * - * @param x - + * @param x - */ export const floatToUintBits64 = (x: number): [number, number] => ( - (F64[0] = x), IS_LE ? [U32[1], U32[0]] : [U32[0], U32[1]] + (F64[0] = x), IS_LE ? [U32[1], U32[0]] : [U32[0], U32[1]] ); /** * Reverse op of {@link floatToIntBits64}. * - * @param hi - - * @param lo - + * @param hi - + * @param lo - */ export const intBitsToFloat64: FnN2 = (hi, lo) => { - IS_LE ? ((I32[1] = hi), (I32[0] = lo)) : ((I32[0] = hi), (I32[1] = lo)); - return F64[0]; + IS_LE ? ((I32[1] = hi), (I32[0] = lo)) : ((I32[0] = hi), (I32[1] = lo)); + return F64[0]; }; /** * Reverse op of {@link floatToUintBits64}. * - * @param hi - - * @param lo - + * @param hi - + * @param lo - */ export const uintBitsToFloat64: FnN2 = (hi, lo) => { - IS_LE ? ((U32[1] = hi), (U32[0] = lo)) : ((U32[0] = hi), (U32[1] = lo)); - return F64[0]; + IS_LE ? ((U32[1] = hi), (U32[0] = lo)) : ((U32[0] = hi), (U32[1] = lo)); + return F64[0]; }; /** @@ -70,9 +70,9 @@ export const uintBitsToFloat64: FnN2 = (hi, lo) => { * @param x - value to convert */ export const floatToSortableInt: FnN = (x) => { - if (x === -0) x = 0; - const i = floatToIntBits(x); - return x < 0 ? ~i | (1 << 31) : i; + if (x === -0) x = 0; + const i = floatToIntBits(x); + return x < 0 ? ~i | (1 << 31) : i; }; const clamp11: FnN = (x) => (x < -1 ? -1 : x > 1 ? 1 : x); @@ -80,61 +80,61 @@ const clamp11: FnN = (x) => (x < -1 ? -1 : x > 1 ? 1 : x); /** * Converts normalized float ([-1..1] range) to u8. * - * @param x - + * @param x - */ export const f32u8: FnN = (x) => (clamp11(x) * 0x7f) & 0xff; /** * Converts normalized float ([-1..1] range) to u16. * - * @param x - + * @param x - */ export const f32u16: FnN = (x) => (clamp11(x) * 0x7fff) & 0xffff; /** * Converts normalized float ([-1..1] range) to u24. * - * @param x - + * @param x - */ export const f32u24: FnN = (x) => (clamp11(x) * 0x7fffff) & 0xffffff; /** * Converts normalized float ([-1..1] range) to u32. * - * @param x - + * @param x - */ export const f32u32: FnN = (x) => (clamp11(x) * 0x7fffffff) >>> 0; /** * Reverse op of {@link f32u8}. * - * @param x - + * @param x - */ export const u8f32: FnN = (x) => ( - (x &= 0xff), (x | ((x >> 7) * 0xffffff00)) / 0x7f + (x &= 0xff), (x | ((x >> 7) * 0xffffff00)) / 0x7f ); /** * Reverse op of {@link f32u16}. * - * @param x - + * @param x - */ export const u16f32: FnN = (x) => ( - (x &= 0xffff), (x | ((x >> 15) * 0xffff0000)) / 0x7fff + (x &= 0xffff), (x | ((x >> 15) * 0xffff0000)) / 0x7fff ); /** * Reverse op of {@link f32u24}. * - * @param x - + * @param x - */ export const u24f32: FnN = (x) => ( - (x &= 0xffffff), (x | ((x >> 23) * 0xff000000)) / 0x7fffff + (x &= 0xffffff), (x | ((x >> 23) * 0xff000000)) / 0x7fffff ); /** * Reverse op of {@link f32u32}. * - * @param x - + * @param x - */ export const u32f32: FnN = (x) => (x | 0) / 0x7fffffff; diff --git a/packages/binary/src/gray.ts b/packages/binary/src/gray.ts index 0ec75b694d..3183761551 100644 --- a/packages/binary/src/gray.ts +++ b/packages/binary/src/gray.ts @@ -17,10 +17,10 @@ export const encodeGray32: FnN = (x) => (x ^ (x >>> 1)) >>> 0; * {@link https://en.wikipedia.org/wiki/Gray_code} */ export const decodeGray32: FnN = (x) => { - x = x ^ (x >>> 16); - x = x ^ (x >>> 8); - x = x ^ (x >>> 4); - x = x ^ (x >>> 2); - x = x ^ (x >>> 1); - return x >>> 0; + x = x ^ (x >>> 16); + x = x ^ (x >>> 8); + x = x ^ (x >>> 4); + x = x ^ (x >>> 2); + x = x ^ (x >>> 1); + return x >>> 0; }; diff --git a/packages/binary/src/logic.ts b/packages/binary/src/logic.ts index 199516f125..0a2536b187 100644 --- a/packages/binary/src/logic.ts +++ b/packages/binary/src/logic.ts @@ -28,8 +28,8 @@ export const bitOai22: FnN4 = (a, b, c, d) => ~((a | b) & (c | d)); export const bitMux: FnN3 = (a, b, s) => ((a & ~s) | (b & s)) >>> 0; export const bitDemux: FnU3 = (a, b, s) => [ - (a & ~s) >>> 0, - (b & s) >>> 0, + (a & ~s) >>> 0, + (b & s) >>> 0, ]; export const bitNotM: FnN2 = (n, x) => maskL(n, ~x); @@ -53,14 +53,14 @@ export const bitAoi21M: FnN4 = (n, a, b, c) => maskL(n, ~(a | (b & c))); export const bitOai21M: FnN4 = (n, a, b, c) => maskL(n, ~(a & (b | c))); export const bitAoi22M: FnN5 = (n, a, b, c, d) => - maskL(n, ~((a & b) | (c & d))); + maskL(n, ~((a & b) | (c & d))); export const bitOai22M: FnN5 = (n, a, b, c, d) => - maskL(n, ~((a | b) & (c | d))); + maskL(n, ~((a | b) & (c | d))); export const bitMuxM: FnN4 = (n, a, b, s) => maskL(n, (a & ~s) | (b & s)); export const bitDemuxM: FnU4 = (n, a, b, s) => [ - maskL(n, a & ~s), - maskL(n, b & s), + maskL(n, a & ~s), + maskL(n, b & s), ]; diff --git a/packages/binary/src/one-hot.ts b/packages/binary/src/one-hot.ts index 955a7c4075..12d0dd0910 100644 --- a/packages/binary/src/one-hot.ts +++ b/packages/binary/src/one-hot.ts @@ -7,7 +7,7 @@ import { clz32 } from "./count.js"; * @remarks * Reference: https://en.wikipedia.org/wiki/One-hot * - * @param x - + * @param x - */ export const binaryOneHot = (x: Range0_31) => (1 << x) >>> 0; @@ -17,6 +17,6 @@ export const binaryOneHot = (x: Range0_31) => (1 << x) >>> 0; * @remarks * Reference: https://en.wikipedia.org/wiki/One-hot * - * @param x - + * @param x - */ export const oneHotBinary = (x: number) => 31 - clz32(x); diff --git a/packages/binary/src/pow.ts b/packages/binary/src/pow.ts index 5b16f5c14c..d6bbe45c7d 100644 --- a/packages/binary/src/pow.ts +++ b/packages/binary/src/pow.ts @@ -6,21 +6,21 @@ import type { Pow2 } from "./api.js"; export const isPow2 = (x: number): x is Pow2 => !!x && !(x & (x - 1)); export const ceilPow2: FnN = (x) => { - x += (x === 0); - --x; - x |= x >>> 1; - x |= x >>> 2; - x |= x >>> 4; - x |= x >>> 8; - x |= x >>> 16; - return x + 1; + x += (x === 0); + --x; + x |= x >>> 1; + x |= x >>> 2; + x |= x >>> 4; + x |= x >>> 8; + x |= x >>> 16; + return x + 1; }; export const floorPow2: FnN = (x) => { - x |= x >>> 1; - x |= x >>> 2; - x |= x >>> 4; - x |= x >>> 8; - x |= x >>> 16; - return x - (x >>> 1); + x |= x >>> 1; + x |= x >>> 2; + x |= x >>> 4; + x |= x >>> 8; + x |= x >>> 16; + return x - (x >>> 1); }; diff --git a/packages/binary/src/rotate.ts b/packages/binary/src/rotate.ts index d877e0e415..c46c2741b5 100644 --- a/packages/binary/src/rotate.ts +++ b/packages/binary/src/rotate.ts @@ -7,7 +7,7 @@ import type { Bit } from "./api.js"; * @param n - rotation step */ export const rotateLeft = (x: number, n: Bit) => - ((x << n) | (x >>> (32 - n))) >>> 0; + ((x << n) | (x >>> (32 - n))) >>> 0; /** * Rotates `x` `n` bits to the right. @@ -16,13 +16,13 @@ export const rotateLeft = (x: number, n: Bit) => * @param n - rotation step */ export const rotateRight = (x: number, n: Bit) => - ((x >>> n) | (x << (32 - n))) >>> 0; + ((x >>> n) | (x << (32 - n))) >>> 0; /** * Shifts `x` by `n` bits left or right. If `n` >= 0, the value will be `>>>` * shifted to right, if `n` < 0 the value will be shifted left. * - * @param x - - * @param n - + * @param x - + * @param n - */ export const shiftRL = (x: number, n: number) => (n < 0 ? x << -n : x >>> n); diff --git a/packages/binary/src/splat.ts b/packages/binary/src/splat.ts index 8b2438eda0..d5e983fdc8 100644 --- a/packages/binary/src/splat.ts +++ b/packages/binary/src/splat.ts @@ -52,19 +52,19 @@ export const same8 = (x: number) => ((x >> 8) & 0xff) === (x & 0xff); /** * Expands 3x4bit value like `0xabc` to 24bits: `0xaabbcc` * - * @param x - + * @param x - */ export const interleave4_12_24 = (x: number) => - ((x & 0xf00) * 0x1100) | ((x & 0xf0) * 0x110) | ((x & 0xf) * 0x11); + ((x & 0xf00) * 0x1100) | ((x & 0xf0) * 0x110) | ((x & 0xf) * 0x11); /** * Expands 4x4bit value like `0xabcd` to 32bits: `0xaabbccdd` * - * @param x - + * @param x - */ export const interleave4_16_32 = (x: number) => - (((x & 0xf000) * 0x11000) | - ((x & 0xf00) * 0x1100) | - ((x & 0xf0) * 0x110) | - ((x & 0xf) * 0x11)) >>> - 0; + (((x & 0xf000) * 0x11000) | + ((x & 0xf00) * 0x1100) | + ((x & 0xf0) * 0x110) | + ((x & 0xf) * 0x11)) >>> + 0; diff --git a/packages/binary/src/swizzle.ts b/packages/binary/src/swizzle.ts index 68da2ed9aa..d596d62a98 100644 --- a/packages/binary/src/swizzle.ts +++ b/packages/binary/src/swizzle.ts @@ -12,7 +12,7 @@ import type { Lane16, Lane2, Lane4, Lane8 } from "./api.js"; * @param lane - lane ID enum */ export const lane16 = (x: number, lane: Lane16) => - (x >>> ((1 - lane) << 4)) & 0xffff; + (x >>> ((1 - lane) << 4)) & 0xffff; /** * Extracts 8-bit lane from given 32bit uint and returns as unsigned @@ -27,7 +27,7 @@ export const lane16 = (x: number, lane: Lane16) => * @param lane - lane ID enum */ export const lane8 = (x: number, lane: Lane8) => - (x >>> ((3 - lane) << 3)) & 0xff; + (x >>> ((3 - lane) << 3)) & 0xff; /** * Extracts 4-bit lane from given 32bit uint and returns as unsigned @@ -46,13 +46,13 @@ export const lane8 = (x: number, lane: Lane8) => * @param lane - lane ID enum */ export const lane4 = (x: number, lane: Lane4) => - (x >>> ((7 - lane) << 2)) & 0xf; + (x >>> ((7 - lane) << 2)) & 0xf; export const lane2 = (x: number, lane: Lane2) => - (x >>> ((15 - lane) << 1)) & 0x3; + (x >>> ((15 - lane) << 1)) & 0x3; export const setLane16 = (x: number, y: number, lane: Lane16) => - lane ? mux(x, y, 0xffff) : mux(x, y << 16, 0xffff0000); + lane ? mux(x, y, 0xffff) : mux(x, y << 16, 0xffff0000); /** * Sets 8-bit `lane` with value`y` in `x`. @@ -64,8 +64,8 @@ export const setLane16 = (x: number, y: number, lane: Lane16) => * @param lane - lane ID enum */ export const setLane8 = (x: number, y: number, lane: Lane8) => { - const l = (3 - lane) << 3; - return ((~(0xff << l) & x) | ((y & 0xff) << l)) >>> 0; + const l = (3 - lane) << 3; + return ((~(0xff << l) & x) | ((y & 0xff) << l)) >>> 0; }; /** @@ -78,8 +78,8 @@ export const setLane8 = (x: number, y: number, lane: Lane8) => { * @param lane - lane ID enum */ export const setLane4 = (x: number, y: number, lane: Lane4) => { - const l = (7 - lane) << 2; - return ((~(0xf << l) & x) | ((y & 0xf) << l)) >>> 0; + const l = (7 - lane) << 2; + return ((~(0xf << l) & x) | ((y & 0xf) << l)) >>> 0; }; /** @@ -92,8 +92,8 @@ export const setLane4 = (x: number, y: number, lane: Lane4) => { * @param lane - lane ID enum */ export const setLane2 = (x: number, y: number, lane: Lane2) => { - const l = (15 - lane) << 1; - return ((~(0x3 << l) & x) | ((y & 0x3) << l)) >>> 0; + const l = (15 - lane) << 1; + return ((~(0x3 << l) & x) | ((y & 0x3) << l)) >>> 0; }; /** @@ -113,11 +113,11 @@ export const setLane2 = (x: number, y: number, lane: Lane2) => { * @param d - lane ID enum */ export const swizzle8 = (x: number, a: Lane8, b: Lane8, c: Lane8, d: Lane8) => - ((lane8(x, a) << 24) | - (lane8(x, b) << 16) | - (lane8(x, c) << 8) | - lane8(x, d)) >>> - 0; + ((lane8(x, a) << 24) | + (lane8(x, b) << 16) | + (lane8(x, c) << 8) | + lane8(x, d)) >>> + 0; /** * @@ -132,25 +132,25 @@ export const swizzle8 = (x: number, a: Lane8, b: Lane8, c: Lane8, d: Lane8) => * @param h - lane ID enum */ export const swizzle4 = ( - x: number, - a: Lane4, - b: Lane4, - c: Lane4, - d: Lane4, - e: Lane4, - f: Lane4, - g: Lane4, - h: Lane4 + x: number, + a: Lane4, + b: Lane4, + c: Lane4, + d: Lane4, + e: Lane4, + f: Lane4, + g: Lane4, + h: Lane4 ) => - ((lane4(x, a) << 28) | - (lane4(x, b) << 24) | - (lane4(x, c) << 20) | - (lane4(x, d) << 16) | - (lane4(x, e) << 12) | - (lane4(x, f) << 8) | - (lane4(x, g) << 4) | - lane4(x, h)) >>> - 0; + ((lane4(x, a) << 28) | + (lane4(x, b) << 24) | + (lane4(x, c) << 20) | + (lane4(x, d) << 16) | + (lane4(x, e) << 12) | + (lane4(x, f) << 8) | + (lane4(x, g) << 4) | + lane4(x, h)) >>> + 0; /** * Merges bits of `a` and `b`, selecting bits from `b` where `mask` bits @@ -165,9 +165,9 @@ export const swizzle4 = ( * // 0x12345555 * ``` * - * @param a - - * @param b - - * @param mask - + * @param a - + * @param b - + * @param mask - */ export const mux: FnN3 = (a, b, mask) => (~mask & a) | (mask & b); @@ -177,7 +177,7 @@ export const mux: FnN3 = (a, b, mask) => (~mask & a) | (mask & b); * @param x - */ export const flip8: FnN = (x) => - ((x >>> 24) | ((x >> 8) & 0xff00) | ((x & 0xff00) << 8) | (x << 24)) >>> 0; + ((x >>> 24) | ((x >> 8) & 0xff00) | ((x & 0xff00) << 8) | (x << 24)) >>> 0; /** * Swaps the highest & lowest 16 bits in `x`. @@ -188,7 +188,7 @@ export const flip8: FnN = (x) => * // 0x56781234 * ``` * - * @param x - + * @param x - */ export const flip16: FnN = (x) => mux(x << 16, x >>> 16, 0xffff); @@ -200,15 +200,15 @@ export const flipBytes = flip8; /** * Swaps bytes lanes 0 & 2 (i.e. bits 24-31 with bits 8-15) * - * @param x - + * @param x - */ export const swapLane02: FnN = (x) => - ((x & 0xff00) << 16) | ((x >>> 16) & 0xff00) | (x & 0x00ff00ff); + ((x & 0xff00) << 16) | ((x >>> 16) & 0xff00) | (x & 0x00ff00ff); /** * Swaps bytes lanes 1 & 3 (i.e. bits 16-23 with bits 0-7) * - * @param x - + * @param x - */ export const swapLane13: FnN = (x) => - ((x & 0xff) << 16) | ((x >> 16) & 0xff) | (x & 0xff00ff00); + ((x & 0xff) << 16) | ((x >> 16) & 0xff) | (x & 0xff00ff00); diff --git a/packages/binary/tsconfig.json b/packages/binary/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/binary/tsconfig.json +++ b/packages/binary/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/bitfield/api-extractor.json b/packages/bitfield/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/bitfield/api-extractor.json +++ b/packages/bitfield/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/bitfield/package.json b/packages/bitfield/package.json index c95f9967a3..7962427011 100644 --- a/packages/bitfield/package.json +++ b/packages/bitfield/package.json @@ -1,89 +1,89 @@ { - "name": "@thi.ng/bitfield", - "version": "2.2.0", - "description": "1D / 2D bit field implementations", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/bitfield#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/binary": "^3.3.0", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/strings": "^3.3.6" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "1d", - "2d", - "array", - "binary", - "bitfield", - "bitwise", - "datastructure", - "matrix", - "typedarray", - "typescript", - "vector" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./bitfield": { - "default": "./bitfield.js" - }, - "./bitmatrix": { - "default": "./bitmatrix.js" - } - }, - "thi.ng": { - "related": [ - "adjacency" - ] - } + "name": "@thi.ng/bitfield", + "version": "2.2.0", + "description": "1D / 2D bit field implementations", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/bitfield#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/binary": "^3.3.0", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/strings": "^3.3.6" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "1d", + "2d", + "array", + "binary", + "bitfield", + "bitwise", + "datastructure", + "matrix", + "typedarray", + "typescript", + "vector" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./bitfield": { + "default": "./bitfield.js" + }, + "./bitmatrix": { + "default": "./bitmatrix.js" + } + }, + "thi.ng": { + "related": [ + "adjacency" + ] + } } diff --git a/packages/bitfield/src/bitfield.ts b/packages/bitfield/src/bitfield.ts index 26b11a9659..bea340e807 100644 --- a/packages/bitfield/src/bitfield.ts +++ b/packages/bitfield/src/bitfield.ts @@ -10,177 +10,177 @@ import { binOp, toString } from "./util.js"; * up to a multiple of 32. */ export class BitField implements IClear, ICopy, ILength { - data: Uint32Array; - n: number; - - constructor(bits: number | string | ArrayLike) { - const isNumber = typeof bits === "number"; - this.n = align(isNumber ? bits : (bits).length, 32); - this.data = new Uint32Array(this.n >>> 5); - !isNumber && this.setRange(0, bits); - } - - get length() { - return this.n; - } - - /** - * Yields iterator of the field's individual bits. - */ - *[Symbol.iterator]() { - const { data, n } = this; - for (let i = 0; i < n; i++) { - yield data[i >>> 5] & (1 << (~i & 31)) ? 1 : 0; - } - } - - /** - * Yields iterator of positions/indices of all set bits only. - */ - *positions() { - const { data, n } = this; - for (let i = 0; i < n; i++) { - if (data[i >>> 5] & (1 << (~i & 31))) yield i; - } - } - - clear() { - this.data.fill(0); - } - - copy() { - const dest = new BitField(this.n); - dest.data.set(this.data); - return dest; - } - - /** - * Resizes bitfield to new size given (rounded up to multiples of - * 32). - * - * @param n - new size - */ - resize(n: number) { - n = align(n, 32); - if (n === this.n) return this; - const dest = new Uint32Array(n >>> 5); - dest.set(this.data.slice(0, dest.length)); - this.data = dest; - this.n = n; - return this; - } - - /** - * Returns a non-zero value if bit `n` is enabled. No bounds - * checking. - * - * @param n - bit number - */ - at(n: number) { - return this.data[n >>> 5] & (1 << (~n & 31)); - } - - /** - * Enables or disables bit `n`. Returns a non-zero value if the bit - * was previously enabled. No bounds checking. - * - * @param n - bit number - * @param v - new bit value - */ - setAt(n: number, v: boolean | number = true) { - const id = n >>> 5; - const mask = 1 << (~n & 31); - const r = this.data[id] & mask; - if (v) { - this.data[id] |= mask; - } else { - this.data[id] &= ~mask; - } - return r; - } - - /** - * Sets bits from `start` index with given `values`. No bounds - * checking. - * - * @param start - - * @param vals - - */ - setRange(start: number, vals: string | ArrayLike) { - const isString = typeof vals === "string"; - for (let i = 0, n = Math.min(this.n, i + vals.length); i < n; i++) { - this.setAt( - start + i, - isString - ? (vals)[i] === "1" - : (>vals)[i] - ); - } - } - - /** - * Inverts bit `n`. Returns a non-zero value if the bit was - * previously enabled. No bounds checking. - * - * @param n - bit number - */ - toggleAt(n: number) { - const id = n >>> 5; - const mask = 1 << (~n & 31); - const r = this.data[id] & mask; - if (r) { - this.data[id] &= ~mask; - } else { - this.data[id] |= mask; - } - return r; - } - - /** - * Returns number of set bits (1's) in the bitfield. - */ - popCount() { - return popCountArray(this.data); - } - - /** - * Same as {@link BitField.popCount}, but as normalized ratio/percentage. - */ - density() { - return this.popCount() / this.n; - } - - and(field: BitField) { - return this.binOp(field, bitAnd); - } - - or(field: BitField) { - return this.binOp(field, bitOr); - } - - xor(field: BitField) { - return this.binOp(field, bitXor); - } - - not() { - return this.binOp(this, bitNot); - } - - toString() { - return toString(this.data); - } - - protected binOp(field: BitField, op: Fn2) { - this.ensureSize(field); - binOp(this.data, field.data, op); - return this; - } - - protected ensureSize(field: BitField) { - assert(field.n === this.n, `fields must be same size`); - } + data: Uint32Array; + n: number; + + constructor(bits: number | string | ArrayLike) { + const isNumber = typeof bits === "number"; + this.n = align(isNumber ? bits : (bits).length, 32); + this.data = new Uint32Array(this.n >>> 5); + !isNumber && this.setRange(0, bits); + } + + get length() { + return this.n; + } + + /** + * Yields iterator of the field's individual bits. + */ + *[Symbol.iterator]() { + const { data, n } = this; + for (let i = 0; i < n; i++) { + yield data[i >>> 5] & (1 << (~i & 31)) ? 1 : 0; + } + } + + /** + * Yields iterator of positions/indices of all set bits only. + */ + *positions() { + const { data, n } = this; + for (let i = 0; i < n; i++) { + if (data[i >>> 5] & (1 << (~i & 31))) yield i; + } + } + + clear() { + this.data.fill(0); + } + + copy() { + const dest = new BitField(this.n); + dest.data.set(this.data); + return dest; + } + + /** + * Resizes bitfield to new size given (rounded up to multiples of + * 32). + * + * @param n - new size + */ + resize(n: number) { + n = align(n, 32); + if (n === this.n) return this; + const dest = new Uint32Array(n >>> 5); + dest.set(this.data.slice(0, dest.length)); + this.data = dest; + this.n = n; + return this; + } + + /** + * Returns a non-zero value if bit `n` is enabled. No bounds + * checking. + * + * @param n - bit number + */ + at(n: number) { + return this.data[n >>> 5] & (1 << (~n & 31)); + } + + /** + * Enables or disables bit `n`. Returns a non-zero value if the bit + * was previously enabled. No bounds checking. + * + * @param n - bit number + * @param v - new bit value + */ + setAt(n: number, v: boolean | number = true) { + const id = n >>> 5; + const mask = 1 << (~n & 31); + const r = this.data[id] & mask; + if (v) { + this.data[id] |= mask; + } else { + this.data[id] &= ~mask; + } + return r; + } + + /** + * Sets bits from `start` index with given `values`. No bounds + * checking. + * + * @param start - + * @param vals - + */ + setRange(start: number, vals: string | ArrayLike) { + const isString = typeof vals === "string"; + for (let i = 0, n = Math.min(this.n, i + vals.length); i < n; i++) { + this.setAt( + start + i, + isString + ? (vals)[i] === "1" + : (>vals)[i] + ); + } + } + + /** + * Inverts bit `n`. Returns a non-zero value if the bit was + * previously enabled. No bounds checking. + * + * @param n - bit number + */ + toggleAt(n: number) { + const id = n >>> 5; + const mask = 1 << (~n & 31); + const r = this.data[id] & mask; + if (r) { + this.data[id] &= ~mask; + } else { + this.data[id] |= mask; + } + return r; + } + + /** + * Returns number of set bits (1's) in the bitfield. + */ + popCount() { + return popCountArray(this.data); + } + + /** + * Same as {@link BitField.popCount}, but as normalized ratio/percentage. + */ + density() { + return this.popCount() / this.n; + } + + and(field: BitField) { + return this.binOp(field, bitAnd); + } + + or(field: BitField) { + return this.binOp(field, bitOr); + } + + xor(field: BitField) { + return this.binOp(field, bitXor); + } + + not() { + return this.binOp(this, bitNot); + } + + toString() { + return toString(this.data); + } + + protected binOp(field: BitField, op: Fn2) { + this.ensureSize(field); + binOp(this.data, field.data, op); + return this; + } + + protected ensureSize(field: BitField) { + assert(field.n === this.n, `fields must be same size`); + } } export const defBitField = ( - bits: number | string | ArrayLike + bits: number | string | ArrayLike ) => new BitField(bits); diff --git a/packages/bitfield/src/bitmatrix.ts b/packages/bitfield/src/bitmatrix.ts index f772b02116..78a4d2ba48 100644 --- a/packages/bitfield/src/bitmatrix.ts +++ b/packages/bitfield/src/bitmatrix.ts @@ -12,198 +12,198 @@ import { binOp, toString } from "./util.js"; * (number of columns) is always rounded up to a multiple of 32. */ export class BitMatrix implements IClear, ICopy { - data: Uint32Array; - stride: number; - m: number; - n: number; - - constructor(rows: number, cols = rows) { - this.m = rows; - this.n = cols = align(cols, 32); - this.stride = cols >>> 5; - this.data = new Uint32Array(rows * this.stride); - } - - get length() { - return this.m * this.n; - } - - clear() { - this.data.fill(0); - } - - copy() { - const dest = new BitMatrix(this.m, this.n); - dest.data.set(this.data); - return dest; - } - - /** - * Resizes matrix to new size given (width always rounded up to - * multiples of 32). - * - * @param m - new number of rows - * @param n - new number of cols - */ - resize(m: number, n = m) { - n = align(n, 32); - if (m === this.m && n === this.n) return this; - const dstride = n >>> 5; - const sstride = this.stride; - const w = Math.min(dstride, sstride); - const src = this.data; - const dest = new Uint32Array(m * dstride); - for ( - let i = Math.min(m, this.m) - 1, si = i * sstride, di = i * dstride; - i >= 0; - i--, si -= sstride, di -= dstride - ) { - dest.set(src.slice(si, si + w), di); - } - this.m = m; - this.n = n; - this.stride = dstride; - this.data = dest; - return this; - } - - /** - * Returns a non-zero value if bit at `m,n` is enabled (row major). - * No bounds checking. - * - * @param m - row - * @param n - column - */ - at(m: number, n: number) { - return this.data[(n >>> 5) + m * this.stride] & (1 << (~n & 31)); - } - - /** - * Enables or disables bit at `m,n` (row major). Returns a non-zero - * value if the bit was previously enabled. No bounds checking. - * . - * @param m - row - * @param n - column - * @param v - bit value - */ - setAt(m: number, n: number, v: boolean | number = true) { - const id = (n >>> 5) + m * this.stride; - const mask = 1 << (~n & 31); - const r = this.data[id] & mask; - if (v) { - this.data[id] |= mask; - } else { - this.data[id] &= ~mask; - } - return r; - } - - /** - * Inverts bit at `m,n` (row major). Returns a non-zero value if the - * bit was previously enabled. No bounds checking. - * - * @param m - row - * @param n - column - */ - toggleAt(m: number, n: number) { - const id = (n >>> 5) + m * this.stride; - const mask = 1 << (~n & 31); - const r = this.data[id] & mask; - if (r) { - this.data[id] &= ~mask; - } else { - this.data[id] |= mask; - } - return r; - } - - and(mat: BitMatrix) { - return this.binOp(mat, bitAnd); - } - - or(mat: BitMatrix) { - return this.binOp(mat, bitOr); - } - - xor(mat: BitMatrix) { - return this.binOp(mat, bitXor); - } - - not() { - return this.binOp(this, bitNot); - } - - /** - * Returns number of set bits (1's) in the matrix. - */ - popCount() { - return popCountArray(this.data); - } - - popCountRow(m: number) { - ensureIndex(m, 0, this.m); - m *= this.stride; - return popCountArray(this.data.subarray(m, m + this.stride)); - } - - popCountColumn(n: number) { - ensureIndex(n, 0, this.n); - const { data, stride, m } = this; - const mask = 1 << (~n & 31); - let res = 0; - for (let i = n >>> 5, j = 0; j < m; i += stride, j++) { - data[i] & mask && res++; - } - return res; - } - - /** - * Same as {@link BitMatrix.popCount}, but as normalized ratio/percentage. - */ - density() { - return this.popCount() / this.length; - } - - row(m: number) { - ensureIndex(m, 0, this.m); - const row = new BitField(this.n); - m *= this.stride; - row.data.set(this.data.subarray(m, m + this.stride)); - return row; - } - - column(n: number) { - ensureIndex(n, 0, this.n); - const { data, stride, m } = this; - const column = new BitField(m); - const mask = 1 << (~n & 31); - for (let i = n >>> 5, j = 0; j < m; i += stride, j++) { - data[i] & mask && column.setAt(j); - } - return column; - } - - toString() { - const res: string[] = []; - for (let i = 0, j = 0, s = this.stride; i < this.m; i++, j += s) { - res.push(toString(this.data.subarray(j, j + s))); - } - return res.join("\n"); - } - - protected binOp(field: BitMatrix, op: Fn2) { - this.ensureSize(field); - binOp(this.data, field.data, op); - return this; - } - - protected ensureSize(field: BitMatrix) { - assert( - field.m === this.m && field.n === this.n, - `matrices must be same size` - ); - } + data: Uint32Array; + stride: number; + m: number; + n: number; + + constructor(rows: number, cols = rows) { + this.m = rows; + this.n = cols = align(cols, 32); + this.stride = cols >>> 5; + this.data = new Uint32Array(rows * this.stride); + } + + get length() { + return this.m * this.n; + } + + clear() { + this.data.fill(0); + } + + copy() { + const dest = new BitMatrix(this.m, this.n); + dest.data.set(this.data); + return dest; + } + + /** + * Resizes matrix to new size given (width always rounded up to + * multiples of 32). + * + * @param m - new number of rows + * @param n - new number of cols + */ + resize(m: number, n = m) { + n = align(n, 32); + if (m === this.m && n === this.n) return this; + const dstride = n >>> 5; + const sstride = this.stride; + const w = Math.min(dstride, sstride); + const src = this.data; + const dest = new Uint32Array(m * dstride); + for ( + let i = Math.min(m, this.m) - 1, si = i * sstride, di = i * dstride; + i >= 0; + i--, si -= sstride, di -= dstride + ) { + dest.set(src.slice(si, si + w), di); + } + this.m = m; + this.n = n; + this.stride = dstride; + this.data = dest; + return this; + } + + /** + * Returns a non-zero value if bit at `m,n` is enabled (row major). + * No bounds checking. + * + * @param m - row + * @param n - column + */ + at(m: number, n: number) { + return this.data[(n >>> 5) + m * this.stride] & (1 << (~n & 31)); + } + + /** + * Enables or disables bit at `m,n` (row major). Returns a non-zero + * value if the bit was previously enabled. No bounds checking. + * . + * @param m - row + * @param n - column + * @param v - bit value + */ + setAt(m: number, n: number, v: boolean | number = true) { + const id = (n >>> 5) + m * this.stride; + const mask = 1 << (~n & 31); + const r = this.data[id] & mask; + if (v) { + this.data[id] |= mask; + } else { + this.data[id] &= ~mask; + } + return r; + } + + /** + * Inverts bit at `m,n` (row major). Returns a non-zero value if the + * bit was previously enabled. No bounds checking. + * + * @param m - row + * @param n - column + */ + toggleAt(m: number, n: number) { + const id = (n >>> 5) + m * this.stride; + const mask = 1 << (~n & 31); + const r = this.data[id] & mask; + if (r) { + this.data[id] &= ~mask; + } else { + this.data[id] |= mask; + } + return r; + } + + and(mat: BitMatrix) { + return this.binOp(mat, bitAnd); + } + + or(mat: BitMatrix) { + return this.binOp(mat, bitOr); + } + + xor(mat: BitMatrix) { + return this.binOp(mat, bitXor); + } + + not() { + return this.binOp(this, bitNot); + } + + /** + * Returns number of set bits (1's) in the matrix. + */ + popCount() { + return popCountArray(this.data); + } + + popCountRow(m: number) { + ensureIndex(m, 0, this.m); + m *= this.stride; + return popCountArray(this.data.subarray(m, m + this.stride)); + } + + popCountColumn(n: number) { + ensureIndex(n, 0, this.n); + const { data, stride, m } = this; + const mask = 1 << (~n & 31); + let res = 0; + for (let i = n >>> 5, j = 0; j < m; i += stride, j++) { + data[i] & mask && res++; + } + return res; + } + + /** + * Same as {@link BitMatrix.popCount}, but as normalized ratio/percentage. + */ + density() { + return this.popCount() / this.length; + } + + row(m: number) { + ensureIndex(m, 0, this.m); + const row = new BitField(this.n); + m *= this.stride; + row.data.set(this.data.subarray(m, m + this.stride)); + return row; + } + + column(n: number) { + ensureIndex(n, 0, this.n); + const { data, stride, m } = this; + const column = new BitField(m); + const mask = 1 << (~n & 31); + for (let i = n >>> 5, j = 0; j < m; i += stride, j++) { + data[i] & mask && column.setAt(j); + } + return column; + } + + toString() { + const res: string[] = []; + for (let i = 0, j = 0, s = this.stride; i < this.m; i++, j += s) { + res.push(toString(this.data.subarray(j, j + s))); + } + return res.join("\n"); + } + + protected binOp(field: BitMatrix, op: Fn2) { + this.ensureSize(field); + binOp(this.data, field.data, op); + return this; + } + + protected ensureSize(field: BitMatrix) { + assert( + field.m === this.m && field.n === this.n, + `matrices must be same size` + ); + } } export const defBitMatrix = (rows: number, cols = rows) => - new BitMatrix(rows, cols); + new BitMatrix(rows, cols); diff --git a/packages/bitfield/src/util.ts b/packages/bitfield/src/util.ts index cb96c1bb68..18f8326a9f 100644 --- a/packages/bitfield/src/util.ts +++ b/packages/bitfield/src/util.ts @@ -19,9 +19,9 @@ export const toString = (data: Uint32Array) => [...data].map(B32).join(""); * @internal */ export const binOp = ( - dest: Uint32Array, - src: Uint32Array, - op: Fn2 + dest: Uint32Array, + src: Uint32Array, + op: Fn2 ) => { - for (let i = src.length; i-- > 0; ) dest[i] = op(src[i], dest[i]); + for (let i = src.length; i-- > 0; ) dest[i] = op(src[i], dest[i]); }; diff --git a/packages/bitfield/tsconfig.json b/packages/bitfield/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/bitfield/tsconfig.json +++ b/packages/bitfield/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/bitstream/api-extractor.json b/packages/bitstream/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/bitstream/api-extractor.json +++ b/packages/bitstream/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/bitstream/package.json b/packages/bitstream/package.json index 821867d583..a398f00dc8 100644 --- a/packages/bitstream/package.json +++ b/packages/bitstream/package.json @@ -1,84 +1,84 @@ { - "name": "@thi.ng/bitstream", - "version": "2.2.1", - "description": "ES6 iterator based read/write bit streams with support for variable word widths", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/bitstream#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/errors": "^2.1.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "binary", - "datastructure", - "iterator", - "stream", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./input": { - "default": "./input.js" - }, - "./output": { - "default": "./output.js" - }, - "./simple": { - "default": "./simple.js" - } - }, - "thi.ng": { - "related": [ - "range-coder", - "rle-pack" - ] - } + "name": "@thi.ng/bitstream", + "version": "2.2.1", + "description": "ES6 iterator based read/write bit streams with support for variable word widths", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/bitstream#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/errors": "^2.1.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "binary", + "datastructure", + "iterator", + "stream", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./input": { + "default": "./input.js" + }, + "./output": { + "default": "./output.js" + }, + "./simple": { + "default": "./simple.js" + } + }, + "thi.ng": { + "related": [ + "range-coder", + "rle-pack" + ] + } } diff --git a/packages/bitstream/src/input.ts b/packages/bitstream/src/input.ts index 363e9117fc..342f7804b1 100644 --- a/packages/bitstream/src/input.ts +++ b/packages/bitstream/src/input.ts @@ -4,125 +4,125 @@ import { illegalState } from "@thi.ng/errors/illegal-state"; const U32 = Math.pow(2, 32); export class BitInputStream { - buffer: Uint8Array; - protected start: number; - protected limit: number; - protected pos!: number; - protected bitPos!: number; - protected bit!: number; + buffer: Uint8Array; + protected start: number; + protected limit: number; + protected pos!: number; + protected bitPos!: number; + protected bit!: number; - constructor(buffer: Uint8Array, offset = 0, limit = buffer.length << 3) { - this.buffer = buffer; - this.start = offset; - this.limit = limit; - this.seek(offset); - } + constructor(buffer: Uint8Array, offset = 0, limit = buffer.length << 3) { + this.buffer = buffer; + this.start = offset; + this.limit = limit; + this.seek(offset); + } - *[Symbol.iterator]() { - let j = this.start; - let i = j >>> 3; - let b = 7 - (j & 0x7); - while (j < this.limit) { - yield (this.buffer[i] >>> b) & 1; - if (--b < 0) { - i++; - b = 7; - } - j++; - } - } + *[Symbol.iterator]() { + let j = this.start; + let i = j >>> 3; + let b = 7 - (j & 0x7); + while (j < this.limit) { + yield (this.buffer[i] >>> b) & 1; + if (--b < 0) { + i++; + b = 7; + } + j++; + } + } - get length() { - return this.limit; - } + get length() { + return this.limit; + } - get position() { - return this.bitPos; - } + get position() { + return this.bitPos; + } - seek(pos: number): BitInputStream { - if (pos < this.start || pos >= this.limit) { - illegalArgs(`seek pos out of bounds: ${pos}`); - } - this.pos = pos >>> 3; - this.bit = 8 - (pos & 0x7); - this.bitPos = pos; - return this; - } + seek(pos: number): BitInputStream { + if (pos < this.start || pos >= this.limit) { + illegalArgs(`seek pos out of bounds: ${pos}`); + } + this.pos = pos >>> 3; + this.bit = 8 - (pos & 0x7); + this.bitPos = pos; + return this; + } - read(wordSize = 1): number { - if (wordSize > 32) { - return this.read(wordSize - 32) * U32 + this.read(32); - } else if (wordSize > 8) { - let out = 0; - let n = wordSize & -8; - let msb = wordSize - n; - if (msb > 0) { - out = this._read(msb); - } - while (n > 0) { - out = ((out << 8) | this._read(8)) >>> 0; - n -= 8; - } - return out; - } else { - return this._read(wordSize); - } - } + read(wordSize = 1): number { + if (wordSize > 32) { + return this.read(wordSize - 32) * U32 + this.read(32); + } else if (wordSize > 8) { + let out = 0; + let n = wordSize & -8; + let msb = wordSize - n; + if (msb > 0) { + out = this._read(msb); + } + while (n > 0) { + out = ((out << 8) | this._read(8)) >>> 0; + n -= 8; + } + return out; + } else { + return this._read(wordSize); + } + } - readFields(fields: number[]) { - return fields.map((word) => this.read(word)); - } + readFields(fields: number[]) { + return fields.map((word) => this.read(word)); + } - readWords(n: number, wordSize = 8) { - let out = []; - while (n-- > 0) { - out.push(this.read(wordSize)); - } - return out; - } + readWords(n: number, wordSize = 8) { + let out = []; + while (n-- > 0) { + out.push(this.read(wordSize)); + } + return out; + } - readStruct(fields: [string, number][]) { - return fields.reduce((acc: any, [id, word]) => { - return (acc[id] = this.read(word)), acc; - }, {}); - } + readStruct(fields: [string, number][]) { + return fields.reduce((acc: any, [id, word]) => { + return (acc[id] = this.read(word)), acc; + }, {}); + } - readBit() { - this.checkLimit(1); - this.bit--; - this.bitPos++; - let out = (this.buffer[this.pos] >>> this.bit) & 1; - if (this.bit === 0) { - this.pos++; - this.bit = 8; - } - return out; - } + readBit() { + this.checkLimit(1); + this.bit--; + this.bitPos++; + let out = (this.buffer[this.pos] >>> this.bit) & 1; + if (this.bit === 0) { + this.pos++; + this.bit = 8; + } + return out; + } - protected _read(wordSize: number) { - this.checkLimit(wordSize); - let l = this.bit - wordSize, - out: number; - if (l >= 0) { - this.bit = l; - out = (this.buffer[this.pos] >>> l) & ((1 << wordSize) - 1); - if (l === 0) { - this.pos++; - this.bit = 8; - } - } else { - out = (this.buffer[this.pos++] & ((1 << this.bit) - 1)) << -l; - this.bit = 8 + l; - out = out | (this.buffer[this.pos] >>> this.bit); - } - this.bitPos += wordSize; - return out; - } + protected _read(wordSize: number) { + this.checkLimit(wordSize); + let l = this.bit - wordSize, + out: number; + if (l >= 0) { + this.bit = l; + out = (this.buffer[this.pos] >>> l) & ((1 << wordSize) - 1); + if (l === 0) { + this.pos++; + this.bit = 8; + } + } else { + out = (this.buffer[this.pos++] & ((1 << this.bit) - 1)) << -l; + this.bit = 8 + l; + out = out | (this.buffer[this.pos] >>> this.bit); + } + this.bitPos += wordSize; + return out; + } - protected checkLimit(requested: number) { - if (this.bitPos + requested > this.limit) { - illegalState(`can't read past EOF`); - } - } + protected checkLimit(requested: number) { + if (this.bitPos + requested > this.limit) { + illegalState(`can't read past EOF`); + } + } } diff --git a/packages/bitstream/src/output.ts b/packages/bitstream/src/output.ts index 76c4b2de05..e97421b2ff 100644 --- a/packages/bitstream/src/output.ts +++ b/packages/bitstream/src/output.ts @@ -5,122 +5,122 @@ const DEFAULT_BUF_SIZE = 0x10; const U32 = Math.pow(2, 32); export class BitOutputStream { - buffer: Uint8Array; - protected start: number; - protected pos!: number; - protected bit!: number; - protected bitPos!: number; + buffer: Uint8Array; + protected start: number; + protected pos!: number; + protected bit!: number; + protected bitPos!: number; - constructor(buffer?: number | Uint8Array, offset = 0) { - this.buffer = - typeof buffer === "undefined" - ? new Uint8Array(DEFAULT_BUF_SIZE) - : typeof buffer === "number" - ? new Uint8Array(buffer) - : buffer; - this.start = offset; - this.seek(offset); - this.buffer[this.pos] &= ~((1 << this.bit) - 1); - } + constructor(buffer?: number | Uint8Array, offset = 0) { + this.buffer = + typeof buffer === "undefined" + ? new Uint8Array(DEFAULT_BUF_SIZE) + : typeof buffer === "number" + ? new Uint8Array(buffer) + : buffer; + this.start = offset; + this.seek(offset); + this.buffer[this.pos] &= ~((1 << this.bit) - 1); + } - get position() { - return this.bitPos; - } + get position() { + return this.bitPos; + } - seek(pos: number): BitOutputStream { - if (pos < this.start || pos >= this.buffer.length << 3) { - illegalArgs(`seek pos out of bounds: ${pos}`); - } - this.pos = pos >>> 3; - this.bit = 8 - (pos & 0x7); - this.bitPos = pos; - return this; - } + seek(pos: number): BitOutputStream { + if (pos < this.start || pos >= this.buffer.length << 3) { + illegalArgs(`seek pos out of bounds: ${pos}`); + } + this.pos = pos >>> 3; + this.bit = 8 - (pos & 0x7); + this.bitPos = pos; + return this; + } - bytes() { - return this.buffer.slice(0, this.pos + (this.bit & 7 ? 1 : 0)); - } + bytes() { + return this.buffer.slice(0, this.pos + (this.bit & 7 ? 1 : 0)); + } - reader(from = 0) { - return new BitInputStream(this.buffer, from, this.position); - } + reader(from = 0) { + return new BitInputStream(this.buffer, from, this.position); + } - write(x: number, wordSize = 1) { - if (wordSize > 32) { - let hi = Math.floor(x / U32); - this.write(hi, wordSize - 32); - this.write(x - hi * U32, 32); - } else if (wordSize > 8) { - let n = wordSize & -8; - let msb = wordSize - n; - if (msb > 0) { - this._write(x >>> n, msb); - } - n -= 8; - while (n >= 0) { - this._write(x >>> n, 8); - n -= 8; - } - } else { - this._write(x, wordSize); - } - return this; - } + write(x: number, wordSize = 1) { + if (wordSize > 32) { + let hi = Math.floor(x / U32); + this.write(hi, wordSize - 32); + this.write(x - hi * U32, 32); + } else if (wordSize > 8) { + let n = wordSize & -8; + let msb = wordSize - n; + if (msb > 0) { + this._write(x >>> n, msb); + } + n -= 8; + while (n >= 0) { + this._write(x >>> n, 8); + n -= 8; + } + } else { + this._write(x, wordSize); + } + return this; + } - writeWords(input: Iterable, wordSize = 8) { - let iter = input[Symbol.iterator](); - let v: IteratorResult; - while (((v = iter.next()), !v.done)) { - this.write(v.value, wordSize); - } - } + writeWords(input: Iterable, wordSize = 8) { + let iter = input[Symbol.iterator](); + let v: IteratorResult; + while (((v = iter.next()), !v.done)) { + this.write(v.value, wordSize); + } + } - writeBit(x: number) { - this.bit--; - this.buffer[this.pos] = - (this.buffer[this.pos] & ~(1 << this.bit)) | (x << this.bit); - if (this.bit === 0) { - this.ensureSize(); - //this.buffer[this.pos] = 0; - this.bit = 8; - } - this.bitPos++; - return this; - } + writeBit(x: number) { + this.bit--; + this.buffer[this.pos] = + (this.buffer[this.pos] & ~(1 << this.bit)) | (x << this.bit); + if (this.bit === 0) { + this.ensureSize(); + //this.buffer[this.pos] = 0; + this.bit = 8; + } + this.bitPos++; + return this; + } - protected _write(x: number, wordSize: number) { - x &= (1 << wordSize) - 1; - let buf = this.buffer; - let pos = this.pos; - let bit = this.bit; - let b = bit - wordSize; - let m = bit < 8 ? ~((1 << bit) - 1) : 0; - if (b >= 0) { - m |= (1 << b) - 1; - buf[pos] = (buf[pos] & m) | ((x << b) & ~m); - if (b === 0) { - this.ensureSize(); - this.bit = 8; - } else { - this.bit = b; - } - } else { - this.bit = bit = 8 + b; - buf[pos] = (buf[pos] & m) | ((x >>> -b) & ~m); - this.ensureSize(); - this.buffer[this.pos] = - (this.buffer[this.pos] & ((1 << bit) - 1)) | - ((x << bit) & 0xff); - } - this.bitPos += wordSize; - return this; - } + protected _write(x: number, wordSize: number) { + x &= (1 << wordSize) - 1; + let buf = this.buffer; + let pos = this.pos; + let bit = this.bit; + let b = bit - wordSize; + let m = bit < 8 ? ~((1 << bit) - 1) : 0; + if (b >= 0) { + m |= (1 << b) - 1; + buf[pos] = (buf[pos] & m) | ((x << b) & ~m); + if (b === 0) { + this.ensureSize(); + this.bit = 8; + } else { + this.bit = b; + } + } else { + this.bit = bit = 8 + b; + buf[pos] = (buf[pos] & m) | ((x >>> -b) & ~m); + this.ensureSize(); + this.buffer[this.pos] = + (this.buffer[this.pos] & ((1 << bit) - 1)) | + ((x << bit) & 0xff); + } + this.bitPos += wordSize; + return this; + } - protected ensureSize() { - if (++this.pos === this.buffer.length) { - let b = new Uint8Array(this.buffer.length << 1); - b.set(this.buffer); - this.buffer = b; - } - } + protected ensureSize() { + if (++this.pos === this.buffer.length) { + let b = new Uint8Array(this.buffer.length << 1); + b.set(this.buffer); + this.buffer = b; + } + } } diff --git a/packages/bitstream/src/simple.ts b/packages/bitstream/src/simple.ts index f261d82e49..05998637ec 100644 --- a/packages/bitstream/src/simple.ts +++ b/packages/bitstream/src/simple.ts @@ -12,42 +12,42 @@ * @param capacity - initial capacity */ export const bitWriter = (capacity = 16) => { - let buf = new Uint8Array(capacity); - let pos = 0; - let bit = 8; + let buf = new Uint8Array(capacity); + let pos = 0; + let bit = 8; - const ensure = () => { - if (++pos === buf.length) { - let b = new Uint8Array(buf.length << 1); - b.set(buf); - buf = b; - } - }; + const ensure = () => { + if (++pos === buf.length) { + let b = new Uint8Array(buf.length << 1); + b.set(buf); + buf = b; + } + }; - return { - write: (x: number, n = 1) => { - x &= (1 << n) - 1; - let b = bit - n; - let m = bit < 8 ? ~((1 << bit) - 1) : 0; - if (b >= 0) { - m |= (1 << b) - 1; - buf[pos] = (buf[pos] & m) | ((x << b) & ~m); - if (b === 0) { - ensure(); - bit = 8; - } else { - bit = b; - } - } else { - bit = 8 + b; - buf[pos] = (buf[pos] & m) | ((x >>> -b) & ~m); - ensure(); - buf[pos] = (buf[pos] & ((1 << bit) - 1)) | ((x << bit) & 0xff); - } - }, + return { + write: (x: number, n = 1) => { + x &= (1 << n) - 1; + let b = bit - n; + let m = bit < 8 ? ~((1 << bit) - 1) : 0; + if (b >= 0) { + m |= (1 << b) - 1; + buf[pos] = (buf[pos] & m) | ((x << b) & ~m); + if (b === 0) { + ensure(); + bit = 8; + } else { + bit = b; + } + } else { + bit = 8 + b; + buf[pos] = (buf[pos] & m) | ((x >>> -b) & ~m); + ensure(); + buf[pos] = (buf[pos] & ((1 << bit) - 1)) | ((x << bit) & 0xff); + } + }, - bytes: () => buf.slice(0, pos + (bit & 7 ? 1 : 0)), - }; + bytes: () => buf.slice(0, pos + (bit & 7 ? 1 : 0)), + }; }; /** @@ -58,24 +58,24 @@ export const bitWriter = (capacity = 16) => { * @param buf */ export const bitReader = (buf: Uint8Array | number[]) => { - let p = 0; - let b = 8; + let p = 0; + let b = 8; - return (n = 1) => { - let l = b - n; - let out; - if (l >= 0) { - b = l; - out = (buf[p] >>> l) & ((1 << n) - 1); - if (!l) { - p++; - b = 8; - } - } else { - out = (buf[p++] & ((1 << b) - 1)) << -l; - b = 8 + l; - out = out | (buf[p] >>> b); - } - return out; - }; + return (n = 1) => { + let l = b - n; + let out; + if (l >= 0) { + b = l; + out = (buf[p] >>> l) & ((1 << n) - 1); + if (!l) { + p++; + b = 8; + } + } else { + out = (buf[p++] & ((1 << b) - 1)) << -l; + b = 8 + l; + out = out | (buf[p] >>> b); + } + return out; + }; }; diff --git a/packages/bitstream/test/index.ts b/packages/bitstream/test/index.ts index b0003c1e8a..ddfdb360af 100644 --- a/packages/bitstream/test/index.ts +++ b/packages/bitstream/test/index.ts @@ -1,182 +1,182 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import * as bits from "../src/index.js" +import * as bits from "../src/index.js"; let src = new Uint8Array([ - 0xbe, 0xef, 0xde, 0xca, 0xfb, 0xad, 0xf0, 0x0b, 0xaa, + 0xbe, 0xef, 0xde, 0xca, 0xfb, 0xad, 0xf0, 0x0b, 0xaa, ]); group("BitInputStream", { - iterator: () => { - assert.strictEqual( - [...new bits.BitInputStream(src)].join(""), - "101111101110111111011110110010101111101110101101111100000000101110101010", - "all" - ); - assert.strictEqual( - [...new bits.BitInputStream(src, 0, 18)].join(""), - "101111101110111111", - "0-18" - ); - assert.strictEqual( - [...new bits.BitInputStream(src, 49)].join(""), - "11100000000101110101010", - "49-..." - ); - assert.strictEqual( - [...new bits.BitInputStream(src, 49, 49 + 12)].join(""), - "111000000001", - "49+12" - ); - assert.throws( - () => new bits.BitInputStream(new Uint8Array(0)), - "empty input" - ); - }, + iterator: () => { + assert.strictEqual( + [...new bits.BitInputStream(src)].join(""), + "101111101110111111011110110010101111101110101101111100000000101110101010", + "all" + ); + assert.strictEqual( + [...new bits.BitInputStream(src, 0, 18)].join(""), + "101111101110111111", + "0-18" + ); + assert.strictEqual( + [...new bits.BitInputStream(src, 49)].join(""), + "11100000000101110101010", + "49-..." + ); + assert.strictEqual( + [...new bits.BitInputStream(src, 49, 49 + 12)].join(""), + "111000000001", + "49+12" + ); + assert.throws( + () => new bits.BitInputStream(new Uint8Array(0)), + "empty input" + ); + }, - read: () => { - let i = new bits.BitInputStream(src); - assert.strictEqual(i.read(4), 0xb, "b"); - assert.strictEqual(i.read(8), 0xee, "ee"); - assert.strictEqual(i.read(1), 0x1, "1"); - assert.strictEqual(i.read(3), 0x7, "7"); - assert.strictEqual(i.read(44), 15310211702528, "0xdecafbadf00"); - assert.strictEqual(i.read(5), 0x17, "0x17"); - assert.strictEqual(i.read(7), 0x2a, "0x2a"); - assert.throws(() => i.read(1), "EOF"); - }, + read: () => { + let i = new bits.BitInputStream(src); + assert.strictEqual(i.read(4), 0xb, "b"); + assert.strictEqual(i.read(8), 0xee, "ee"); + assert.strictEqual(i.read(1), 0x1, "1"); + assert.strictEqual(i.read(3), 0x7, "7"); + assert.strictEqual(i.read(44), 15310211702528, "0xdecafbadf00"); + assert.strictEqual(i.read(5), 0x17, "0x17"); + assert.strictEqual(i.read(7), 0x2a, "0x2a"); + assert.throws(() => i.read(1), "EOF"); + }, - readBit: () => { - let i = new bits.BitInputStream(new Uint8Array([0xaa, 0xf0]), 4, 12); - assert.strictEqual(i.readBit(), 1, "4"); - assert.strictEqual(i.readBit(), 0, "5"); - assert.strictEqual(i.readBit(), 1, "6"); - assert.strictEqual(i.readBit(), 0, "7"); - assert.strictEqual(i.readBit(), 1, "8"); - assert.strictEqual(i.readBit(), 1, "9"); - assert.strictEqual(i.readBit(), 1, "10"); - assert.strictEqual(i.readBit(), 1, "11"); - assert.throws(() => i.readBit(), "EOF"); - }, + readBit: () => { + let i = new bits.BitInputStream(new Uint8Array([0xaa, 0xf0]), 4, 12); + assert.strictEqual(i.readBit(), 1, "4"); + assert.strictEqual(i.readBit(), 0, "5"); + assert.strictEqual(i.readBit(), 1, "6"); + assert.strictEqual(i.readBit(), 0, "7"); + assert.strictEqual(i.readBit(), 1, "8"); + assert.strictEqual(i.readBit(), 1, "9"); + assert.strictEqual(i.readBit(), 1, "10"); + assert.strictEqual(i.readBit(), 1, "11"); + assert.throws(() => i.readBit(), "EOF"); + }, - mapBitfields: () => { - assert.deepStrictEqual( - new bits.BitInputStream(src) - .readFields([4, 8, 1, 3, 44, 5, 7]) - .map((x) => x.toString(16)), - ["b", "ee", "1", "7", "decafbadf00", "17", "2a"], - "map hex" - ); - }, + mapBitfields: () => { + assert.deepStrictEqual( + new bits.BitInputStream(src) + .readFields([4, 8, 1, 3, 44, 5, 7]) + .map((x) => x.toString(16)), + ["b", "ee", "1", "7", "decafbadf00", "17", "2a"], + "map hex" + ); + }, - position: () => { - let src = new Uint8Array(2); - let i = new bits.BitInputStream(src); - assert.strictEqual((i.read(15), i.position), 15, "15"); - assert.doesNotThrow(() => i.read(1), "not EOF"); - assert.strictEqual(i.position, 16, "16"); - assert.throws(() => i.read(1), "EOF"); - assert.strictEqual(i.position, 16, "16 (2)"); - assert.throws(() => new bits.BitInputStream(src, 16), "seek EOF"); - assert.throws(() => i.read(1), "EOF2"); - }, + position: () => { + let src = new Uint8Array(2); + let i = new bits.BitInputStream(src); + assert.strictEqual((i.read(15), i.position), 15, "15"); + assert.doesNotThrow(() => i.read(1), "not EOF"); + assert.strictEqual(i.position, 16, "16"); + assert.throws(() => i.read(1), "EOF"); + assert.strictEqual(i.position, 16, "16 (2)"); + assert.throws(() => new bits.BitInputStream(src, 16), "seek EOF"); + assert.throws(() => i.read(1), "EOF2"); + }, }); group("BitOutputStream", { - write: () => { - let o = new bits.BitOutputStream(8); - assert.deepStrictEqual( - [...o.write(0xff, 1).buffer], - [0x80, 0, 0, 0, 0, 0, 0, 0], - "1" - ); - assert.deepStrictEqual( - [...o.write(0xff, 2).buffer], - [0xe0, 0, 0, 0, 0, 0, 0, 0], - "2" - ); - assert.deepStrictEqual( - [...o.write(0xff, 4).buffer], - [0xfe, 0, 0, 0, 0, 0, 0, 0], - "4" - ); - assert.deepStrictEqual( - [...o.write(0xff, 8).buffer], - [0xff, 0xfe, 0, 0, 0, 0, 0, 0], - "8" - ); - assert.deepStrictEqual( - [...o.write(0, 1).buffer], - [0xff, 0xfe, 0, 0, 0, 0, 0, 0], - "1 zero" - ); - assert.deepStrictEqual( - [...o.write(0xdecafbad, 16).buffer], - [0xff, 0xfe, 0xfb, 0xad, 0, 0, 0, 0], - "16" - ); - assert.deepStrictEqual( - [...o.write(0xdecafbad, 32).buffer.slice(0, 8)], - [0xff, 0xfe, 0xfb, 0xad, 0xde, 0xca, 0xfb, 0xad], - "32" - ); - o = new bits.BitOutputStream(8, 4); - assert.deepStrictEqual( - [...o.write(0xf00baaf00b, 40).buffer], - [0x0f, 0x0, 0xba, 0xaf, 0x00, 0xb0, 0, 0], - "40" - ); - }, + write: () => { + let o = new bits.BitOutputStream(8); + assert.deepStrictEqual( + [...o.write(0xff, 1).buffer], + [0x80, 0, 0, 0, 0, 0, 0, 0], + "1" + ); + assert.deepStrictEqual( + [...o.write(0xff, 2).buffer], + [0xe0, 0, 0, 0, 0, 0, 0, 0], + "2" + ); + assert.deepStrictEqual( + [...o.write(0xff, 4).buffer], + [0xfe, 0, 0, 0, 0, 0, 0, 0], + "4" + ); + assert.deepStrictEqual( + [...o.write(0xff, 8).buffer], + [0xff, 0xfe, 0, 0, 0, 0, 0, 0], + "8" + ); + assert.deepStrictEqual( + [...o.write(0, 1).buffer], + [0xff, 0xfe, 0, 0, 0, 0, 0, 0], + "1 zero" + ); + assert.deepStrictEqual( + [...o.write(0xdecafbad, 16).buffer], + [0xff, 0xfe, 0xfb, 0xad, 0, 0, 0, 0], + "16" + ); + assert.deepStrictEqual( + [...o.write(0xdecafbad, 32).buffer.slice(0, 8)], + [0xff, 0xfe, 0xfb, 0xad, 0xde, 0xca, 0xfb, 0xad], + "32" + ); + o = new bits.BitOutputStream(8, 4); + assert.deepStrictEqual( + [...o.write(0xf00baaf00b, 40).buffer], + [0x0f, 0x0, 0xba, 0xaf, 0x00, 0xb0, 0, 0], + "40" + ); + }, - writeBit: () => { - let o = new bits.BitOutputStream(1); - assert.deepStrictEqual([...o.writeBit(1).buffer], [0x80], "1"); - assert.deepStrictEqual([...o.writeBit(1).buffer], [0xc0], "2"); - assert.deepStrictEqual([...o.writeBit(1).buffer], [0xe0], "3"); - assert.deepStrictEqual([...o.writeBit(0).buffer], [0xe0], "4"); - assert.deepStrictEqual([...o.writeBit(1).buffer], [0xe8], "5"); - assert.deepStrictEqual([...o.writeBit(1).buffer], [0xec], "6"); - assert.deepStrictEqual([...o.writeBit(1).buffer], [0xee], "7"); - assert.deepStrictEqual([...o.writeBit(1).buffer], [0xef, 0x00], "8"); - assert.strictEqual(o.buffer.length, 2, "len"); - assert.deepStrictEqual([...o.writeBit(1).buffer], [0xef, 0x80], "9"); - assert.deepStrictEqual( - [...o.seek(0).writeBit(0).buffer], - [0x6f, 0x80], - "seek 0" - ); - assert.deepStrictEqual( - [...o.seek(0).writeBit(1).buffer], - [0xef, 0x80], - "seek 0 1" - ); - assert.deepStrictEqual( - [...o.write(0, 4).buffer], - [0x87, 0x80], - "write 4" - ); - }, + writeBit: () => { + let o = new bits.BitOutputStream(1); + assert.deepStrictEqual([...o.writeBit(1).buffer], [0x80], "1"); + assert.deepStrictEqual([...o.writeBit(1).buffer], [0xc0], "2"); + assert.deepStrictEqual([...o.writeBit(1).buffer], [0xe0], "3"); + assert.deepStrictEqual([...o.writeBit(0).buffer], [0xe0], "4"); + assert.deepStrictEqual([...o.writeBit(1).buffer], [0xe8], "5"); + assert.deepStrictEqual([...o.writeBit(1).buffer], [0xec], "6"); + assert.deepStrictEqual([...o.writeBit(1).buffer], [0xee], "7"); + assert.deepStrictEqual([...o.writeBit(1).buffer], [0xef, 0x00], "8"); + assert.strictEqual(o.buffer.length, 2, "len"); + assert.deepStrictEqual([...o.writeBit(1).buffer], [0xef, 0x80], "9"); + assert.deepStrictEqual( + [...o.seek(0).writeBit(0).buffer], + [0x6f, 0x80], + "seek 0" + ); + assert.deepStrictEqual( + [...o.seek(0).writeBit(1).buffer], + [0xef, 0x80], + "seek 0 1" + ); + assert.deepStrictEqual( + [...o.write(0, 4).buffer], + [0x87, 0x80], + "write 4" + ); + }, - bytes: () => { - assert.deepStrictEqual( - [...new bits.BitOutputStream().bytes()], - [], - "empty" - ); - assert.deepStrictEqual( - [...new bits.BitOutputStream(1, 7).bytes()], - [0], - "7" - ); - assert.deepStrictEqual( - [...new bits.BitOutputStream(2, 8).bytes()], - [0], - "8" - ); - assert.deepStrictEqual( - [...new bits.BitOutputStream(2, 9).bytes()], - [0, 0], - "9" - ); - }, + bytes: () => { + assert.deepStrictEqual( + [...new bits.BitOutputStream().bytes()], + [], + "empty" + ); + assert.deepStrictEqual( + [...new bits.BitOutputStream(1, 7).bytes()], + [0], + "7" + ); + assert.deepStrictEqual( + [...new bits.BitOutputStream(2, 8).bytes()], + [0], + "8" + ); + assert.deepStrictEqual( + [...new bits.BitOutputStream(2, 9).bytes()], + [0, 0], + "9" + ); + }, }); diff --git a/packages/bitstream/tsconfig.json b/packages/bitstream/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/bitstream/tsconfig.json +++ b/packages/bitstream/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/cache/api-extractor.json b/packages/cache/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/cache/api-extractor.json +++ b/packages/cache/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/cache/package.json b/packages/cache/package.json index c2e0cba8a7..ed155232f2 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -1,93 +1,93 @@ { - "name": "@thi.ng/cache", - "version": "2.1.12", - "description": "In-memory cache implementations with ES6 Map-like API and different eviction strategies", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/cache#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/dcons": "^3.2.7", - "@thi.ng/transducers": "^8.3.7" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "acceleration", - "cache", - "datastructure", - "lru", - "map", - "mru", - "tlru", - "ttl", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./lru": { - "default": "./lru.js" - }, - "./mru": { - "default": "./mru.js" - }, - "./tlru": { - "default": "./tlru.js" - } - }, - "thi.ng": { - "related": [ - "associative" - ], - "year": 2018 - } + "name": "@thi.ng/cache", + "version": "2.1.12", + "description": "In-memory cache implementations with ES6 Map-like API and different eviction strategies", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/cache#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/dcons": "^3.2.7", + "@thi.ng/transducers": "^8.3.7" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "acceleration", + "cache", + "datastructure", + "lru", + "map", + "mru", + "tlru", + "ttl", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./lru": { + "default": "./lru.js" + }, + "./mru": { + "default": "./mru.js" + }, + "./tlru": { + "default": "./tlru.js" + } + }, + "thi.ng": { + "related": [ + "associative" + ], + "year": 2018 + } } diff --git a/packages/cache/src/api.ts b/packages/cache/src/api.ts index 341cda53fa..a025047637 100644 --- a/packages/cache/src/api.ts +++ b/packages/cache/src/api.ts @@ -1,43 +1,43 @@ import type { - Fn, - Fn0, - Fn2, - ICopy, - IEmpty, - ILength, - IRelease, + Fn, + Fn0, + Fn2, + ICopy, + IEmpty, + ILength, + IRelease, } from "@thi.ng/api"; export interface ICache - extends Iterable]>>, - ICopy>, - IEmpty>, - ILength, - IRelease { - readonly size: number; + extends Iterable]>>, + ICopy>, + IEmpty>, + ILength, + IRelease { + readonly size: number; - has(key: K): boolean; - get(key: K, notFound?: V): V; - set(key: K, val: V): V; - getSet(key: K, fn: Fn0>): Promise; - delete(key: K): boolean; + has(key: K): boolean; + get(key: K, notFound?: V): V; + set(key: K, val: V): V; + getSet(key: K, fn: Fn0>): Promise; + delete(key: K): boolean; - entries(): IterableIterator]>>; - keys(): IterableIterator>; - values(): IterableIterator>; + entries(): IterableIterator]>>; + keys(): IterableIterator>; + values(): IterableIterator>; } export interface CacheOpts { - ksize: Fn; - vsize: Fn; - release: Fn2; - map: Fn0>; - maxlen: number; - maxsize: number; + ksize: Fn; + vsize: Fn; + release: Fn2; + map: Fn0>; + maxlen: number; + maxsize: number; } export interface CacheEntry { - k: K; - v: V; - s: number; + k: K; + v: V; + s: number; } diff --git a/packages/cache/src/lru.ts b/packages/cache/src/lru.ts index 23841cf39f..2962ea7e2d 100644 --- a/packages/cache/src/lru.ts +++ b/packages/cache/src/lru.ts @@ -5,173 +5,173 @@ import { map } from "@thi.ng/transducers/map"; import type { CacheEntry, CacheOpts, ICache } from "./api.js"; export class LRUCache implements ICache { - protected map: Map>>; - protected items: DCons>; - protected opts: CacheOpts; - protected _size: number; - - constructor( - pairs?: Iterable<[K, V]> | null, - opts?: Partial> - ) { - const _opts = >Object.assign( - { - maxlen: Infinity, - maxsize: Infinity, - map: () => new Map(), - ksize: () => 0, - vsize: () => 0, - }, - opts - ); - this.map = _opts.map(); - this.items = new DCons>(); - this._size = 0; - this.opts = _opts; - if (pairs) { - this.into(pairs); - } - } - - get length() { - return this.items.length; - } - - get size() { - return this._size; - } - - [Symbol.iterator]() { - return this.entries(); - } - - entries(): IterableIterator]>> { - return map((e) => <[K, CacheEntry]>[e.k, e], this.items); - } - - keys(): IterableIterator> { - return map((e) => e.k, this.items); - } - - values(): IterableIterator> { - return map((e) => e.v, this.items); - } - - copy(): ICache { - const c = this.empty(); - c.items = this.items.copy(); - let cell = c.items.head; - while (cell) { - c.map.set(cell.value.k, cell); - cell = cell.next; - } - return c; - } - - empty(): LRUCache { - return new LRUCache(null, this.opts); - } - - release() { - this._size = 0; - this.map.clear(); - const release = this.opts.release; - if (release) { - let e; - while ((e = this.items.drop())) { - release(e.k, e.v); - } - return true; - } - return this.items.release(); - } - - has(key: K): boolean { - return this.map.has(key); - } - - get(key: K, notFound?: any) { - const e = this.map.get(key); - if (e) { - return this.resetEntry(e); - } - return notFound; - } - - set(key: K, value: V) { - const size = this.opts.ksize(key) + this.opts.vsize(value); - const e = this.map.get(key); - this._size += Math.max(0, size - (e ? e.value.s : 0)); - this.ensureSize() && this.doSetEntry(e, key, value, size); - return value; - } - - into(pairs: Iterable<[K, V]>) { - for (let p of pairs) { - this.set(p[0], p[1]); - } - return this; - } - - getSet(key: K, retrieve: Fn0>): Promise { - const e = this.map.get(key); - if (e) { - return Promise.resolve(this.resetEntry(e)); - } - return retrieve().then((v) => this.set(key, v)); - } - - delete(key: K): boolean { - const e = this.map.get(key); - if (e) { - this.removeEntry(e); - return true; - } - return false; - } - - protected resetEntry(e: ConsCell>) { - this.items.asTail(e); - return e.value.v; - } - - protected ensureSize() { - const release = this.opts.release; - const maxs = this.opts.maxsize; - const maxl = this.opts.maxlen; - while (this._size > maxs || this.length >= maxl) { - const e = this.items.drop(); - if (!e) { - return false; - } - this.map.delete(e.k); - release && release(e.k, e.v); - this._size -= e.s; - } - return true; - } - - protected removeEntry(e: ConsCell>) { - const ee = e.value; - this.map.delete(ee.k); - this.items.remove(e); - this.opts.release && this.opts.release(ee.k, ee.v); - this._size -= ee.s; - } - - protected doSetEntry( - e: ConsCell> | undefined, - k: K, - v: V, - s: number - ) { - if (e) { - e.value.v = v; - e.value.s = s; - this.items.asTail(e); - } else { - this.items.push({ k, v, s }); - this.map.set(k, this.items.tail!); - } - } + protected map: Map>>; + protected items: DCons>; + protected opts: CacheOpts; + protected _size: number; + + constructor( + pairs?: Iterable<[K, V]> | null, + opts?: Partial> + ) { + const _opts = >Object.assign( + { + maxlen: Infinity, + maxsize: Infinity, + map: () => new Map(), + ksize: () => 0, + vsize: () => 0, + }, + opts + ); + this.map = _opts.map(); + this.items = new DCons>(); + this._size = 0; + this.opts = _opts; + if (pairs) { + this.into(pairs); + } + } + + get length() { + return this.items.length; + } + + get size() { + return this._size; + } + + [Symbol.iterator]() { + return this.entries(); + } + + entries(): IterableIterator]>> { + return map((e) => <[K, CacheEntry]>[e.k, e], this.items); + } + + keys(): IterableIterator> { + return map((e) => e.k, this.items); + } + + values(): IterableIterator> { + return map((e) => e.v, this.items); + } + + copy(): ICache { + const c = this.empty(); + c.items = this.items.copy(); + let cell = c.items.head; + while (cell) { + c.map.set(cell.value.k, cell); + cell = cell.next; + } + return c; + } + + empty(): LRUCache { + return new LRUCache(null, this.opts); + } + + release() { + this._size = 0; + this.map.clear(); + const release = this.opts.release; + if (release) { + let e; + while ((e = this.items.drop())) { + release(e.k, e.v); + } + return true; + } + return this.items.release(); + } + + has(key: K): boolean { + return this.map.has(key); + } + + get(key: K, notFound?: any) { + const e = this.map.get(key); + if (e) { + return this.resetEntry(e); + } + return notFound; + } + + set(key: K, value: V) { + const size = this.opts.ksize(key) + this.opts.vsize(value); + const e = this.map.get(key); + this._size += Math.max(0, size - (e ? e.value.s : 0)); + this.ensureSize() && this.doSetEntry(e, key, value, size); + return value; + } + + into(pairs: Iterable<[K, V]>) { + for (let p of pairs) { + this.set(p[0], p[1]); + } + return this; + } + + getSet(key: K, retrieve: Fn0>): Promise { + const e = this.map.get(key); + if (e) { + return Promise.resolve(this.resetEntry(e)); + } + return retrieve().then((v) => this.set(key, v)); + } + + delete(key: K): boolean { + const e = this.map.get(key); + if (e) { + this.removeEntry(e); + return true; + } + return false; + } + + protected resetEntry(e: ConsCell>) { + this.items.asTail(e); + return e.value.v; + } + + protected ensureSize() { + const release = this.opts.release; + const maxs = this.opts.maxsize; + const maxl = this.opts.maxlen; + while (this._size > maxs || this.length >= maxl) { + const e = this.items.drop(); + if (!e) { + return false; + } + this.map.delete(e.k); + release && release(e.k, e.v); + this._size -= e.s; + } + return true; + } + + protected removeEntry(e: ConsCell>) { + const ee = e.value; + this.map.delete(ee.k); + this.items.remove(e); + this.opts.release && this.opts.release(ee.k, ee.v); + this._size -= ee.s; + } + + protected doSetEntry( + e: ConsCell> | undefined, + k: K, + v: V, + s: number + ) { + if (e) { + e.value.v = v; + e.value.s = s; + this.items.asTail(e); + } else { + this.items.push({ k, v, s }); + this.map.set(k, this.items.tail!); + } + } } diff --git a/packages/cache/src/mru.ts b/packages/cache/src/mru.ts index f23799ec36..bd836e0306 100644 --- a/packages/cache/src/mru.ts +++ b/packages/cache/src/mru.ts @@ -3,35 +3,35 @@ import type { CacheEntry, CacheOpts } from "./api.js"; import { LRUCache } from "./lru.js"; export class MRUCache extends LRUCache { - constructor( - pairs?: Iterable<[K, V]> | null, - opts?: Partial> - ) { - super(pairs, opts); - } + constructor( + pairs?: Iterable<[K, V]> | null, + opts?: Partial> + ) { + super(pairs, opts); + } - empty(): MRUCache { - return new MRUCache(null, this.opts); - } + empty(): MRUCache { + return new MRUCache(null, this.opts); + } - protected resetEntry(e: ConsCell>) { - this.items.asHead(e); - return e.value.v; - } + protected resetEntry(e: ConsCell>) { + this.items.asHead(e); + return e.value.v; + } - protected doSetEntry( - e: ConsCell> | undefined, - k: K, - v: V, - s: number - ) { - if (e) { - e.value.v = v; - e.value.s = s; - this.items.asHead(e); - } else { - this.items.prepend({ k, v, s }); - this.map.set(k, this.items.head!); - } - } + protected doSetEntry( + e: ConsCell> | undefined, + k: K, + v: V, + s: number + ) { + if (e) { + e.value.v = v; + e.value.s = s; + this.items.asHead(e); + } else { + this.items.prepend({ k, v, s }); + this.map.set(k, this.items.head!); + } + } } diff --git a/packages/cache/src/tlru.ts b/packages/cache/src/tlru.ts index eef57e3eb4..c9713f82f4 100644 --- a/packages/cache/src/tlru.ts +++ b/packages/cache/src/tlru.ts @@ -4,11 +4,11 @@ import type { CacheEntry, CacheOpts } from "./api.js"; import { LRUCache } from "./lru.js"; export interface TLRUCacheOpts extends CacheOpts { - ttl: number; + ttl: number; } export interface TLRUCacheEntry extends CacheEntry { - t: number; + t: number; } /** @@ -26,91 +26,91 @@ export interface TLRUCacheEntry extends CacheEntry { * arg). If no instance TTL is given, TTL defaults to 1 hour. */ export class TLRUCache extends LRUCache { - protected declare opts: TLRUCacheOpts; - protected declare map: Map>>; - protected declare items: DCons>; + protected declare opts: TLRUCacheOpts; + protected declare map: Map>>; + protected declare items: DCons>; - constructor( - pairs?: Iterable<[K, V]> | null, - opts?: Partial> - ) { - opts = Object.assign({ ttl: 60 * 60 * 1000 }, opts); - super(pairs, opts); - } + constructor( + pairs?: Iterable<[K, V]> | null, + opts?: Partial> + ) { + opts = Object.assign({ ttl: 60 * 60 * 1000 }, opts); + super(pairs, opts); + } - empty(): TLRUCache { - return new TLRUCache(null, this.opts); - } + empty(): TLRUCache { + return new TLRUCache(null, this.opts); + } - has(key: K) { - return this.get(key) !== undefined; - } + has(key: K) { + return this.get(key) !== undefined; + } - get(key: K, notFound?: any) { - const e = this.map.get(key); - if (e) { - if (e.value.t >= Date.now()) { - return this.resetEntry(e); - } - this.removeEntry(e); - } - return notFound; - } + get(key: K, notFound?: any) { + const e = this.map.get(key); + if (e) { + if (e.value.t >= Date.now()) { + return this.resetEntry(e); + } + this.removeEntry(e); + } + return notFound; + } - set(key: K, value: V, ttl = this.opts.ttl) { - const size = this.opts.ksize(key) + this.opts.vsize(value); - const e = this.map.get(key); - this._size += Math.max(0, size - (e ? e.value.s : 0)); - if (this.ensureSize()) { - const t = Date.now() + ttl; - if (e) { - e.value.v = value; - e.value.s = size; - e.value.t = t; - this.items.asTail(e); - } else { - this.items.push({ - k: key, - v: value, - s: size, - t, - }); - this.map.set(key, this.items.tail!); - } - } - return value; - } + set(key: K, value: V, ttl = this.opts.ttl) { + const size = this.opts.ksize(key) + this.opts.vsize(value); + const e = this.map.get(key); + this._size += Math.max(0, size - (e ? e.value.s : 0)); + if (this.ensureSize()) { + const t = Date.now() + ttl; + if (e) { + e.value.v = value; + e.value.s = size; + e.value.t = t; + this.items.asTail(e); + } else { + this.items.push({ + k: key, + v: value, + s: size, + t, + }); + this.map.set(key, this.items.tail!); + } + } + return value; + } - getSet(key: K, retrieve: Fn0>, ttl = this.opts.ttl): Promise { - const e = this.get(key); - if (e) { - return Promise.resolve(e); - } - return retrieve().then((v) => this.set(key, v, ttl)); - } + getSet(key: K, retrieve: Fn0>, ttl = this.opts.ttl): Promise { + const e = this.get(key); + if (e) { + return Promise.resolve(e); + } + return retrieve().then((v) => this.set(key, v, ttl)); + } - prune() { - const now = Date.now(); - let cell = this.items.head; - while (cell) { - if (cell.value.t < now) { - this.removeEntry(cell); - } - cell = cell.next; - } - } + prune() { + const now = Date.now(); + let cell = this.items.head; + while (cell) { + if (cell.value.t < now) { + this.removeEntry(cell); + } + cell = cell.next; + } + } - protected ensureSize() { - const maxs = this.opts.maxsize; - const maxl = this.opts.maxlen; - const now = Date.now(); - let cell = this.items.head; - while (cell && (this._size > maxs || this.length >= maxl)) { - if (cell.value.t < now) { - this.removeEntry(cell); - } - cell = cell.next; - } - return super.ensureSize(); - } + protected ensureSize() { + const maxs = this.opts.maxsize; + const maxl = this.opts.maxlen; + const now = Date.now(); + let cell = this.items.head; + while (cell && (this._size > maxs || this.length >= maxl)) { + if (cell.value.t < now) { + this.removeEntry(cell); + } + cell = cell.next; + } + return super.ensureSize(); + } } diff --git a/packages/cache/test/lru.ts b/packages/cache/test/lru.ts index 90b8e51cbe..3c93d97175 100644 --- a/packages/cache/test/lru.ts +++ b/packages/cache/test/lru.ts @@ -1,48 +1,48 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { LRUCache } from "../src/index.js" +import { LRUCache } from "../src/index.js"; let c: LRUCache; let evicts: any[]; group( - "LRU", - { - "max length": () => { - assert.strictEqual(c.length, 3); - c.set("d", 4); - assert.strictEqual(c.length, 4); - c.set("e", 5); - assert.strictEqual(c.length, 4); - assert.deepStrictEqual(evicts, [["a", 1]]); - }, + "LRU", + { + "max length": () => { + assert.strictEqual(c.length, 3); + c.set("d", 4); + assert.strictEqual(c.length, 4); + c.set("e", 5); + assert.strictEqual(c.length, 4); + assert.deepStrictEqual(evicts, [["a", 1]]); + }, - get: () => { - assert.strictEqual(c.get("a"), 1); - assert.strictEqual(c.get("b"), 2); - assert.deepStrictEqual([...c.keys()], ["c", "a", "b"]); - c.set("d", 4); - assert.deepStrictEqual([...c.keys()], ["c", "a", "b", "d"]); - c.set("e", 5); - assert.deepStrictEqual([...c.keys()], ["a", "b", "d", "e"]); - assert.deepStrictEqual([...c.values()], [1, 2, 4, 5]); - assert.deepStrictEqual(evicts, [["c", 3]]); - }, - }, - { - beforeEach: () => { - evicts = []; - c = new LRUCache( - [ - ["a", 1], - ["b", 2], - ["c", 3], - ], - { - maxlen: 4, - release: (k, v) => evicts.push([k, v]), - } - ); - }, - } + get: () => { + assert.strictEqual(c.get("a"), 1); + assert.strictEqual(c.get("b"), 2); + assert.deepStrictEqual([...c.keys()], ["c", "a", "b"]); + c.set("d", 4); + assert.deepStrictEqual([...c.keys()], ["c", "a", "b", "d"]); + c.set("e", 5); + assert.deepStrictEqual([...c.keys()], ["a", "b", "d", "e"]); + assert.deepStrictEqual([...c.values()], [1, 2, 4, 5]); + assert.deepStrictEqual(evicts, [["c", 3]]); + }, + }, + { + beforeEach: () => { + evicts = []; + c = new LRUCache( + [ + ["a", 1], + ["b", 2], + ["c", 3], + ], + { + maxlen: 4, + release: (k, v) => evicts.push([k, v]), + } + ); + }, + } ); diff --git a/packages/cache/test/mru.ts b/packages/cache/test/mru.ts index 95bea5a565..12d33dafb4 100644 --- a/packages/cache/test/mru.ts +++ b/packages/cache/test/mru.ts @@ -1,48 +1,48 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { MRUCache } from "../src/index.js" +import { MRUCache } from "../src/index.js"; let c: MRUCache; let evicts: any[]; group( - "MRU", - { - "max length": () => { - assert.strictEqual(c.length, 3); - c.set("d", 4); - assert.strictEqual(c.length, 4); - c.set("e", 5); - assert.strictEqual(c.length, 4); - assert.deepStrictEqual(evicts, [["d", 4]]); - }, + "MRU", + { + "max length": () => { + assert.strictEqual(c.length, 3); + c.set("d", 4); + assert.strictEqual(c.length, 4); + c.set("e", 5); + assert.strictEqual(c.length, 4); + assert.deepStrictEqual(evicts, [["d", 4]]); + }, - get: () => { - assert.strictEqual(c.get("a"), 1); - assert.strictEqual(c.get("b"), 2); - assert.deepStrictEqual([...c.keys()], ["b", "a", "c"]); - c.set("d", 4); - assert.deepStrictEqual([...c.keys()], ["d", "b", "a", "c"]); - c.set("e", 5); - assert.deepStrictEqual([...c.keys()], ["e", "b", "a", "c"]); - assert.deepStrictEqual([...c.values()], [5, 2, 1, 3]); - assert.deepStrictEqual(evicts, [["d", 4]]); - }, - }, - { - beforeEach: () => { - evicts = []; - c = new MRUCache( - [ - ["a", 1], - ["b", 2], - ["c", 3], - ], - { - maxlen: 4, - release: (k, v) => evicts.push([k, v]), - } - ); - }, - } + get: () => { + assert.strictEqual(c.get("a"), 1); + assert.strictEqual(c.get("b"), 2); + assert.deepStrictEqual([...c.keys()], ["b", "a", "c"]); + c.set("d", 4); + assert.deepStrictEqual([...c.keys()], ["d", "b", "a", "c"]); + c.set("e", 5); + assert.deepStrictEqual([...c.keys()], ["e", "b", "a", "c"]); + assert.deepStrictEqual([...c.values()], [5, 2, 1, 3]); + assert.deepStrictEqual(evicts, [["d", 4]]); + }, + }, + { + beforeEach: () => { + evicts = []; + c = new MRUCache( + [ + ["a", 1], + ["b", 2], + ["c", 3], + ], + { + maxlen: 4, + release: (k, v) => evicts.push([k, v]), + } + ); + }, + } ); diff --git a/packages/cache/test/tlru.ts b/packages/cache/test/tlru.ts index 206993e404..078b20b2e2 100644 --- a/packages/cache/test/tlru.ts +++ b/packages/cache/test/tlru.ts @@ -1,86 +1,86 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { TLRUCache } from "../src/index.js" +import { TLRUCache } from "../src/index.js"; let c: TLRUCache; let evicts: any[]; group( - "TLRU", - { - "max length": () => { - assert.strictEqual(c.length, 3); - c.set("d", 4); - assert.strictEqual(c.length, 4); - c.set("e", 5); - assert.strictEqual(c.length, 4); - assert.deepStrictEqual(evicts, [["a", 1]]); - }, + "TLRU", + { + "max length": () => { + assert.strictEqual(c.length, 3); + c.set("d", 4); + assert.strictEqual(c.length, 4); + c.set("e", 5); + assert.strictEqual(c.length, 4); + assert.deepStrictEqual(evicts, [["a", 1]]); + }, - "get lru": () => { - assert.strictEqual(c.get("a"), 1); - assert.strictEqual(c.get("b"), 2); - assert.deepStrictEqual([...c.keys()], ["c", "a", "b"]); - c.set("d", 4); - assert.deepStrictEqual([...c.keys()], ["c", "a", "b", "d"]); - c.set("e", 5); - assert.deepStrictEqual([...c.keys()], ["a", "b", "d", "e"]); - assert.deepStrictEqual([...c.values()], [1, 2, 4, 5]); - assert.deepStrictEqual(evicts, [["c", 3]]); - }, + "get lru": () => { + assert.strictEqual(c.get("a"), 1); + assert.strictEqual(c.get("b"), 2); + assert.deepStrictEqual([...c.keys()], ["c", "a", "b"]); + c.set("d", 4); + assert.deepStrictEqual([...c.keys()], ["c", "a", "b", "d"]); + c.set("e", 5); + assert.deepStrictEqual([...c.keys()], ["a", "b", "d", "e"]); + assert.deepStrictEqual([...c.values()], [1, 2, 4, 5]); + assert.deepStrictEqual(evicts, [["c", 3]]); + }, - "get ttl": ({ done }) => { - assert.strictEqual(c.set("a", 10, 100), 10); - setTimeout(() => { - assert.ok(!c.has("b")); - assert.ok(!c.has("c")); - assert.deepStrictEqual(evicts, [ - ["b", 2], - ["c", 3], - ]); - assert.deepStrictEqual([...c.keys()], ["a"]); - done(); - }, 20); - }, + "get ttl": ({ done }) => { + assert.strictEqual(c.set("a", 10, 100), 10); + setTimeout(() => { + assert.ok(!c.has("b")); + assert.ok(!c.has("c")); + assert.deepStrictEqual(evicts, [ + ["b", 2], + ["c", 3], + ]); + assert.deepStrictEqual([...c.keys()], ["a"]); + done(); + }, 20); + }, - "getSet ttl": ({ done }) => { - setTimeout(() => { - c.getSet("a", () => Promise.resolve(10)) - .then((v) => { - assert.strictEqual(v, 10); - assert.ok(!c.has("b")); - assert.ok(!c.has("c")); - assert.deepStrictEqual( - [...evicts], - [ - ["a", 1], - ["b", 2], - ["c", 3], - ] - ); - assert.deepStrictEqual([...c.keys()], ["a"]); - assert.deepStrictEqual([...c.values()], [10]); - done(); - }) - .catch(done); - }, 20); - }, - }, - { - beforeEach: () => { - evicts = []; - c = new TLRUCache( - [ - ["a", 1], - ["b", 2], - ["c", 3], - ], - { - maxlen: 4, - ttl: 10, - release: (k, v) => evicts.push([k, v]), - } - ); - }, - } + "getSet ttl": ({ done }) => { + setTimeout(() => { + c.getSet("a", () => Promise.resolve(10)) + .then((v) => { + assert.strictEqual(v, 10); + assert.ok(!c.has("b")); + assert.ok(!c.has("c")); + assert.deepStrictEqual( + [...evicts], + [ + ["a", 1], + ["b", 2], + ["c", 3], + ] + ); + assert.deepStrictEqual([...c.keys()], ["a"]); + assert.deepStrictEqual([...c.values()], [10]); + done(); + }) + .catch(done); + }, 20); + }, + }, + { + beforeEach: () => { + evicts = []; + c = new TLRUCache( + [ + ["a", 1], + ["b", 2], + ["c", 3], + ], + { + maxlen: 4, + ttl: 10, + release: (k, v) => evicts.push([k, v]), + } + ); + }, + } ); diff --git a/packages/cache/tsconfig.json b/packages/cache/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/cache/tsconfig.json +++ b/packages/cache/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/cellular/api-extractor.json b/packages/cellular/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/cellular/api-extractor.json +++ b/packages/cellular/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/cellular/package.json b/packages/cellular/package.json index d4ffceff4f..a914653168 100644 --- a/packages/cellular/package.json +++ b/packages/cellular/package.json @@ -1,95 +1,95 @@ { - "name": "@thi.ng/cellular", - "version": "0.2.4", - "description": "Highly customizable 1D cellular automata, shared env, multiple rules, arbitrary sized/shaped neighborhoods, short term memory, cell states etc.", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/cellular#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/random": "^3.3.3", - "@thi.ng/transducers": "^8.3.7" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "1d", - "automata", - "bigint", - "cellular", - "generative", - "neighborhood", - "pixel", - "rulebased", - "simulation", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=14" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./1d": { - "default": "./1d.js" - }, - "./api": { - "default": "./api.js" - } - }, - "thi.ng": { - "related": [ - "lsys", - "pixel" - ], - "year": 2022 - } + "name": "@thi.ng/cellular", + "version": "0.2.4", + "description": "Highly customizable 1D cellular automata, shared env, multiple rules, arbitrary sized/shaped neighborhoods, short term memory, cell states etc.", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/cellular#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/random": "^3.3.3", + "@thi.ng/transducers": "^8.3.7" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "1d", + "automata", + "bigint", + "cellular", + "generative", + "neighborhood", + "pixel", + "rulebased", + "simulation", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=14" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./1d": { + "default": "./1d.js" + }, + "./api": { + "default": "./api.js" + } + }, + "thi.ng": { + "related": [ + "lsys", + "pixel" + ], + "year": 2022 + } } diff --git a/packages/cellular/src/1d.ts b/packages/cellular/src/1d.ts index e6098eff47..99fbc8c147 100644 --- a/packages/cellular/src/1d.ts +++ b/packages/cellular/src/1d.ts @@ -11,12 +11,12 @@ import { pluck } from "@thi.ng/transducers/pluck"; import { repeatedly } from "@thi.ng/transducers/repeatedly"; import { transduce } from "@thi.ng/transducers/transduce"; import type { - CAConfig1D, - CASpec1D, - Kernel, - Target, - UpdateBufferOpts, - UpdateImageOpts1D, + CAConfig1D, + CASpec1D, + Kernel, + Target, + UpdateBufferOpts, + UpdateImageOpts1D, } from "./api.js"; const $0 = BigInt(0); @@ -27,9 +27,9 @@ const $32 = BigInt(32); * Standard Wolfram automata 3-neighborhood (no history) */ export const WOLFRAM3: Kernel = [ - [-1, 0], - [0, 0], - [1, 0], + [-1, 0], + [0, 0], + [1, 0], ]; /** @@ -164,261 +164,261 @@ export const WOLFRAM7: Kernel = [[-3, 0], ...WOLFRAM5, [3, 0]]; * ``` */ export class MultiCA1D implements IClear { - configs: CAConfig1D[]; - rows: number; - numStates: number; - mask!: Uint8Array; - gens!: Uint8Array[]; - prob!: Float32Array; + configs: CAConfig1D[]; + rows: number; + numStates: number; + mask!: Uint8Array; + gens!: Uint8Array[]; + prob!: Float32Array; - constructor(configs: CASpec1D[], public width: number, public wrap = true) { - this.configs = configs.map(__compileSpec); - this.rows = - transduce( - mapcat((c) => map((k) => k[1], c.kernel)), - max(), - configs - ) + 1; - this.numStates = transduce(pluck("states"), max(), configs); - assert( - this.numStates >= 2 && this.numStates <= 256, - "num states must be in [2..256] range" - ); - this.resize(width); - } + constructor(configs: CASpec1D[], public width: number, public wrap = true) { + this.configs = configs.map(__compileSpec); + this.rows = + transduce( + mapcat((c) => map((k) => k[1], c.kernel)), + max(), + configs + ) + 1; + this.numStates = transduce(pluck("states"), max(), configs); + assert( + this.numStates >= 2 && this.numStates <= 256, + "num states must be in [2..256] range" + ); + this.resize(width); + } - get current() { - return this.gens[1]; - } + get current() { + return this.gens[1]; + } - get previous() { - return this.gens[2 % this.gens.length]; - } + get previous() { + return this.gens[2 % this.gens.length]; + } - clear() { - this.gens.forEach((g) => g.fill(0)); - this.mask.fill(0); - this.prob.fill(1); - } + clear() { + this.gens.forEach((g) => g.fill(0)); + this.mask.fill(0); + this.prob.fill(1); + } - clearTarget(target: Target) { - this._getTarget(target)[0].fill(target === "prob" ? 1 : 0); - } + clearTarget(target: Target) { + this._getTarget(target)[0].fill(target === "prob" ? 1 : 0); + } - resize(width: number) { - this.width = width; - this.mask = new Uint8Array(width); - this.gens = [...repeatedly(() => new Uint8Array(width), this.rows + 1)]; - this.prob = new Float32Array(width).fill(1); - } + resize(width: number) { + this.width = width; + this.mask = new Uint8Array(width); + this.gens = [...repeatedly(() => new Uint8Array(width), this.rows + 1)]; + this.prob = new Float32Array(width).fill(1); + } - /** - * Sets a parametric pattern in the current generation or mask array. - * - * @param target - target buffer ID to apply pattern - * @param width - number of consecutive cells per segment - * @param stride - number of cells between each pattern segment - * @param val - start cell value per segment - * @param inc - cell value increment - * @param offset - start cell offset - */ - setPattern( - target: Target, - width: number, - stride: number, - val = 1, - inc = 0, - offset = 0 - ) { - const [dest, num] = this._getTarget(target); - for (let x = offset, w = this.width; x < w; x += stride) { - for (let k = 0, v = val; k < width; k++, v += inc) { - dest[x + k] = v % num; - } - } - return this; - } + /** + * Sets a parametric pattern in the current generation or mask array. + * + * @param target - target buffer ID to apply pattern + * @param width - number of consecutive cells per segment + * @param stride - number of cells between each pattern segment + * @param val - start cell value per segment + * @param inc - cell value increment + * @param offset - start cell offset + */ + setPattern( + target: Target, + width: number, + stride: number, + val = 1, + inc = 0, + offset = 0 + ) { + const [dest, num] = this._getTarget(target); + for (let x = offset, w = this.width; x < w; x += stride) { + for (let k = 0, v = val; k < width; k++, v += inc) { + dest[x + k] = v % num; + } + } + return this; + } - /** - * Sets cells in current generation array to a random state using given - * `probability` and optional PRNG ({@link @thi.ng/random#IRandom} instance). - * - * @param target - * @param prob - * @param rnd - */ - setNoise(target: Target, prob = 0.5, rnd: IRandom = SYSTEM) { - const [dest, num] = this._getTarget(target); - const fn = - target === "prob" ? () => rnd.float() : () => rnd.int() % num; - for (let x = 0, width = this.width; x < width; x++) { - if (rnd.float() < prob) dest[x] = fn(); - } - return this; - } + /** + * Sets cells in current generation array to a random state using given + * `probability` and optional PRNG ({@link @thi.ng/random#IRandom} instance). + * + * @param target + * @param prob + * @param rnd + */ + setNoise(target: Target, prob = 0.5, rnd: IRandom = SYSTEM) { + const [dest, num] = this._getTarget(target); + const fn = + target === "prob" ? () => rnd.float() : () => rnd.int() % num; + for (let x = 0, width = this.width; x < width; x++) { + if (rnd.float() < prob) dest[x] = fn(); + } + return this; + } - /** - * Computes a single new generation using current cell states and mask only - * (no consideration for cell update probabilities, use - * {@link MultiCA1D.updateProbabilistic} for that instead). Als see - * {@link MultiCA1D.updateImage} for batch updates. - */ - update() { - const { width, gens, configs, mask } = this; - const [next, curr] = gens; - for (let x = 0; x < width; x++) { - next[x] = this.computeCell(configs[mask[x]], x, curr[x]); - } - gens.unshift(gens.pop()!); - } + /** + * Computes a single new generation using current cell states and mask only + * (no consideration for cell update probabilities, use + * {@link MultiCA1D.updateProbabilistic} for that instead). Als see + * {@link MultiCA1D.updateImage} for batch updates. + */ + update() { + const { width, gens, configs, mask } = this; + const [next, curr] = gens; + for (let x = 0; x < width; x++) { + next[x] = this.computeCell(configs[mask[x]], x, curr[x]); + } + gens.unshift(gens.pop()!); + } - /** - * Same as {@link MultiCA1D.update}, but also considering cell update - * probabilities stored in the {@link MultiCA1D.prob} array. - * - * @param rnd - */ - updateProbabilistic(rnd: IRandom = SYSTEM) { - const { width, prob, gens, configs, mask } = this; - const [next, curr] = gens; - for (let x = 0; x < width; x++) { - next[x] = - rnd.float() < prob[x] - ? this.computeCell(configs[mask[x]], x, curr[x]) - : curr[x]; - } - gens.unshift(gens.pop()!); - } + /** + * Same as {@link MultiCA1D.update}, but also considering cell update + * probabilities stored in the {@link MultiCA1D.prob} array. + * + * @param rnd + */ + updateProbabilistic(rnd: IRandom = SYSTEM) { + const { width, prob, gens, configs, mask } = this; + const [next, curr] = gens; + for (let x = 0; x < width; x++) { + next[x] = + rnd.float() < prob[x] + ? this.computeCell(configs[mask[x]], x, curr[x]) + : curr[x]; + } + gens.unshift(gens.pop()!); + } - /** - * Computes (but doesn't apply) the new state for a single cell. - * - * @param config - CA configuration - * @param x - cell index - * @param val - current cell value - */ - computeCell( - { rule, kernel, weights, fn }: CAConfig1D, - x: number, - val: number - ) { - const { width, gens, wrap } = this; - let sum = $0; - for (let i = 0, n = kernel.length; i < n; i++) { - const k = kernel[i]; - let xx = x + k[0]; - if (wrap) { - if (xx < 0) xx += width; - else if (xx >= width) xx -= width; - } else if (xx < 0 || xx >= width) continue; - const y = k[1]; - if (y >= 0 && gens[1 + y][xx] !== 0) sum += weights[i]; - } - return rule & ($1 << sum) ? fn(val) : 0; - } + /** + * Computes (but doesn't apply) the new state for a single cell. + * + * @param config - CA configuration + * @param x - cell index + * @param val - current cell value + */ + computeCell( + { rule, kernel, weights, fn }: CAConfig1D, + x: number, + val: number + ) { + const { width, gens, wrap } = this; + let sum = $0; + for (let i = 0, n = kernel.length; i < n; i++) { + const k = kernel[i]; + let xx = x + k[0]; + if (wrap) { + if (xx < 0) xx += width; + else if (xx >= width) xx -= width; + } else if (xx < 0 || xx >= width) continue; + const y = k[1]; + if (y >= 0 && gens[1 + y][xx] !== 0) sum += weights[i]; + } + return rule & ($1 << sum) ? fn(val) : 0; + } - /** - * Batch version of {@link MultiCA1D.update} to compute an entire image of - * given `height` (and assumed to be the same width as this CA instance has - * been configured to). Fills given `pixels` array with consecutive - * generations. - * - * @remarks - * Via the provided options object, per-generation & per-cell perturbance - * settings can be provided for cell states, mask and cell update - * probabilities. The latter are only considered if the - * {@link UpdateImageOpts1D.probabilistic} option is enabled. This can be - * helpful to sporadically introduce noise into the sim, break constant - * patterns and/or produce more varied/complex outputs. - * - * See {@link UpdateImageOpts1D} for further options. - * - * @param pixels - * @param height - * @param opts - */ - updateImage( - pixels: UIntArray, - height: number, - opts: Partial = {} - ) { - assert( - pixels.length >= this.width * height, - "target pixel buffer too small" - ); - const { cells, mask, prob, probabilistic, rnd, onupdate } = { - probabilistic: false, - rnd: SYSTEM, - ...opts, - }; - const $ = (id: Target, conf?: Partial) => { - conf && - conf.perturb && - rnd.float() < conf.perturb && - this.setNoise(id, conf.density || 0.05, rnd); - }; - for (let y = 0; y < height; y++) { - $("cells", cells); - $("mask", mask); - $("prob", prob); - probabilistic ? this.updateProbabilistic(rnd) : this.update(); - onupdate && onupdate(this, y); - pixels.set(this.current, y * this.width); - } - } + /** + * Batch version of {@link MultiCA1D.update} to compute an entire image of + * given `height` (and assumed to be the same width as this CA instance has + * been configured to). Fills given `pixels` array with consecutive + * generations. + * + * @remarks + * Via the provided options object, per-generation & per-cell perturbance + * settings can be provided for cell states, mask and cell update + * probabilities. The latter are only considered if the + * {@link UpdateImageOpts1D.probabilistic} option is enabled. This can be + * helpful to sporadically introduce noise into the sim, break constant + * patterns and/or produce more varied/complex outputs. + * + * See {@link UpdateImageOpts1D} for further options. + * + * @param pixels + * @param height + * @param opts + */ + updateImage( + pixels: UIntArray, + height: number, + opts: Partial = {} + ) { + assert( + pixels.length >= this.width * height, + "target pixel buffer too small" + ); + const { cells, mask, prob, probabilistic, rnd, onupdate } = { + probabilistic: false, + rnd: SYSTEM, + ...opts, + }; + const $ = (id: Target, conf?: Partial) => { + conf && + conf.perturb && + rnd.float() < conf.perturb && + this.setNoise(id, conf.density || 0.05, rnd); + }; + for (let y = 0; y < height; y++) { + $("cells", cells); + $("mask", mask); + $("prob", prob); + probabilistic ? this.updateProbabilistic(rnd) : this.update(); + onupdate && onupdate(this, y); + pixels.set(this.current, y * this.width); + } + } - rotate(target: Target | "all", dir: number) { - if (target === "all") { - __rotate(this.current, dir); - __rotate(this.mask, dir); - __rotate(this.prob, dir); - } else { - __rotate(this._getTarget(target)[0], dir); - } - } + rotate(target: Target | "all", dir: number) { + if (target === "all") { + __rotate(this.current, dir); + __rotate(this.mask, dir); + __rotate(this.prob, dir); + } else { + __rotate(this._getTarget(target)[0], dir); + } + } - protected _getTarget(target: Target): [TypedArray, number] { - return target === "cells" - ? [this.current, this.numStates] - : target === "mask" - ? [this.mask, this.configs.length] - : [this.prob, 1]; - } + protected _getTarget(target: Target): [TypedArray, number] { + return target === "cells" + ? [this.current, this.numStates] + : target === "mask" + ? [this.mask, this.configs.length] + : [this.prob, 1]; + } } const __compileSpec = ({ - rule, - kernel, - positional, - states, - reset, + rule, + kernel, + positional, + states, + reset, }: CASpec1D) => { - const max = states - 1; - return { - kernel, - states, - rule: isBigInt(rule) ? rule : BigInt(rule), - weights: - positional !== false - ? kernel.map((_, i) => BigInt(2) ** BigInt(i)) - : [...repeat($1, kernel.length)], - fn: - reset !== false - ? (y) => (++y >= states ? 0 : y) - : (y) => (++y >= max ? max : y), - }; + const max = states - 1; + return { + kernel, + states, + rule: isBigInt(rule) ? rule : BigInt(rule), + weights: + positional !== false + ? kernel.map((_, i) => BigInt(2) ** BigInt(i)) + : [...repeat($1, kernel.length)], + fn: + reset !== false + ? (y) => (++y >= states ? 0 : y) + : (y) => (++y >= max ? max : y), + }; }; const __rotate = (buf: TypedArray, dir: number) => { - if (dir < 0) { - const tmp = buf.slice(0, -dir); - buf.copyWithin(0, -dir); - buf.set(tmp, buf.length + dir); - } else if (dir > 0) { - const tmp = buf.slice(buf.length - dir); - buf.copyWithin(dir, 0); - buf.set(tmp, 0); - } + if (dir < 0) { + const tmp = buf.slice(0, -dir); + buf.copyWithin(0, -dir); + buf.set(tmp, buf.length + dir); + } else if (dir > 0) { + const tmp = buf.slice(buf.length - dir); + buf.copyWithin(dir, 0); + buf.set(tmp, 0); + } }; /** @@ -429,13 +429,13 @@ const __rotate = (buf: TypedArray, dir: number) => { * @param rnd */ export const randomRule1D = (kernelSize: number, rnd: IRandom = SYSTEM) => { - const n = BigInt(2 ** kernelSize); - let id = $0; - for (let i = $0; i < n; i += $32) { - id <<= $32; - let mask = n - i; - if (mask > $32) mask = $32; - id |= BigInt(rnd.int()) & (($1 << mask) - $1); - } - return id; + const n = BigInt(2 ** kernelSize); + let id = $0; + for (let i = $0; i < n; i += $32) { + id <<= $32; + let mask = n - i; + if (mask > $32) mask = $32; + id |= BigInt(rnd.int()) & (($1 << mask) - $1); + } + return id; }; diff --git a/packages/cellular/src/api.ts b/packages/cellular/src/api.ts index 95ec5eb86f..c6e8ef42ea 100644 --- a/packages/cellular/src/api.ts +++ b/packages/cellular/src/api.ts @@ -7,116 +7,116 @@ export type Target = "cells" | "mask" | "prob"; export type Kernel = NumericArray[]; export interface CAConfig1D { - /** - * Same as {@link CASpec1D.kernel}. - */ - kernel: Kernel; - /** - * Weight factors for each kernel offset. If {@link CASpec1D.positional} is - * true, these weights will all be `1 << i` where `i` is the index of each - * kernel offset vector. If `positional` is false, all weights will be set - * to 1. - */ - weights: bigint[]; - /** - * Same as {@link CASpec1D.rule}, but always a bigint. - */ - rule: bigint; - /** - * Same as {@link CASpec1D.states}. - */ - states: number; - /** - * Cell state update function/behavior. Takes a current cell state, returns - * new one. - */ - fn: FnN; + /** + * Same as {@link CASpec1D.kernel}. + */ + kernel: Kernel; + /** + * Weight factors for each kernel offset. If {@link CASpec1D.positional} is + * true, these weights will all be `1 << i` where `i` is the index of each + * kernel offset vector. If `positional` is false, all weights will be set + * to 1. + */ + weights: bigint[]; + /** + * Same as {@link CASpec1D.rule}, but always a bigint. + */ + rule: bigint; + /** + * Same as {@link CASpec1D.states}. + */ + states: number; + /** + * Cell state update function/behavior. Takes a current cell state, returns + * new one. + */ + fn: FnN; } export interface CASpec1D { - /** - * Array of 2D offset vectors `[x, y]` defining the automata's neighborhood. - * `x` coordinates are horizontal offsets and positive `y` coordinates are - * used to refer to previous generations (e.g. 0 = current gen, 1 = T-1, 2 = - * T-2 etc.) and thereby providing a form of short term memory for that - * specific automata. Negative `y` coords will lead to cells being ignored. - * - * Unless {@link CASpec1D.positional} is false (default: true), the order of - * offsets _is_ important: Whenever the offset relates to a non-zero cell in the - * neighborhood, it will contribute a specific bit value to encode the - * overall state of the neighborhood, i.e. 2^k, where `k` is the array index - * of the corresponding kernel offset. - */ - kernel: Kernel; - /** - * If false (default: true), the order of kernel offsets is irrelevant and - * only the count of non-zero cells in the neighborhood is used to check a - * relevant bit in the `rule` ID. E.g. if count = 3, then the 3rd LSB will - * be checked. - */ - positional?: boolean; - /** - * CA replication rules encoded as bigint. The overall magnitude of these - * rule IDs depends on the size of the neighborhood kernel and will be 2^n - * bits, where `n` is the kernel size. E.g. A 5-neighborhood will offer 2^32 - * = 4 billion possibilities. A 7-neighborhood corresponds to a 2^7 = 128 - * bit large rule space (~10^38 possibilities!). - */ - rule: bigint | number | string; - /** - * Max number of cell states (aka cell age). Note: MUST be <= 256. For - * "standard" Wolfram automata, states = 2. - */ - states: number; - /** - * If true (default), cells will reset to zero once their max. age has been - * reached. Should be set to `false` for "standard" 2-state Wolfram - * automata. - */ - reset?: boolean; + /** + * Array of 2D offset vectors `[x, y]` defining the automata's neighborhood. + * `x` coordinates are horizontal offsets and positive `y` coordinates are + * used to refer to previous generations (e.g. 0 = current gen, 1 = T-1, 2 = + * T-2 etc.) and thereby providing a form of short term memory for that + * specific automata. Negative `y` coords will lead to cells being ignored. + * + * Unless {@link CASpec1D.positional} is false (default: true), the order of + * offsets _is_ important: Whenever the offset relates to a non-zero cell in the + * neighborhood, it will contribute a specific bit value to encode the + * overall state of the neighborhood, i.e. 2^k, where `k` is the array index + * of the corresponding kernel offset. + */ + kernel: Kernel; + /** + * If false (default: true), the order of kernel offsets is irrelevant and + * only the count of non-zero cells in the neighborhood is used to check a + * relevant bit in the `rule` ID. E.g. if count = 3, then the 3rd LSB will + * be checked. + */ + positional?: boolean; + /** + * CA replication rules encoded as bigint. The overall magnitude of these + * rule IDs depends on the size of the neighborhood kernel and will be 2^n + * bits, where `n` is the kernel size. E.g. A 5-neighborhood will offer 2^32 + * = 4 billion possibilities. A 7-neighborhood corresponds to a 2^7 = 128 + * bit large rule space (~10^38 possibilities!). + */ + rule: bigint | number | string; + /** + * Max number of cell states (aka cell age). Note: MUST be <= 256. For + * "standard" Wolfram automata, states = 2. + */ + states: number; + /** + * If true (default), cells will reset to zero once their max. age has been + * reached. Should be set to `false` for "standard" 2-state Wolfram + * automata. + */ + reset?: boolean; } export interface UpdateBufferOpts { - /** - * Per-generation perturbance probability. Default: 0% - */ - perturb: number; - /** - * Per-cell perturbance probability. Default: 5% (only used if `perturb > - * 0`) - */ - density: number; + /** + * Per-generation perturbance probability. Default: 0% + */ + perturb: number; + /** + * Per-cell perturbance probability. Default: 5% (only used if `perturb > + * 0`) + */ + density: number; } export interface UpdateImageOpts1D { - /** - * Per-generation perturbance options for cell states array - */ - cells: Partial; - /** - * Per-generation perturbance options for cell mask array - */ - mask: Partial; - /** - * Per-generation perturbance options for cell update probability array. - * Only used if {@link UpdateImageOpts1D.probabilistic} is true. - */ - prob: Partial; - /** - * If true, each new generation will be updated via - * {@link MultiCA1D.updateProbabilistic} instead of - * {@link MultiCA1D.update}. - */ - probabilistic: boolean; - /** - * PRNG instance to use for perturbance. Default: - * {@link @thi.ng/random#SYSTEM} aka `Math.random`. - */ - rnd: IRandom; - /** - * User handler function called immediatedly after each update (computation - * of a new generation). The arguments passed are the {@link MultiCA1D} - * instance and pixel row index. - */ - onupdate: Fn2; + /** + * Per-generation perturbance options for cell states array + */ + cells: Partial; + /** + * Per-generation perturbance options for cell mask array + */ + mask: Partial; + /** + * Per-generation perturbance options for cell update probability array. + * Only used if {@link UpdateImageOpts1D.probabilistic} is true. + */ + prob: Partial; + /** + * If true, each new generation will be updated via + * {@link MultiCA1D.updateProbabilistic} instead of + * {@link MultiCA1D.update}. + */ + probabilistic: boolean; + /** + * PRNG instance to use for perturbance. Default: + * {@link @thi.ng/random#SYSTEM} aka `Math.random`. + */ + rnd: IRandom; + /** + * User handler function called immediatedly after each update (computation + * of a new generation). The arguments passed are the {@link MultiCA1D} + * instance and pixel row index. + */ + onupdate: Fn2; } diff --git a/packages/cellular/tsconfig.json b/packages/cellular/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/cellular/tsconfig.json +++ b/packages/cellular/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/checks/api-extractor.json b/packages/checks/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/checks/api-extractor.json +++ b/packages/checks/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/checks/package.json b/packages/checks/package.json index 7e8bd572bd..75b565d4b9 100644 --- a/packages/checks/package.json +++ b/packages/checks/package.json @@ -1,272 +1,272 @@ { - "name": "@thi.ng/checks", - "version": "3.2.2", - "description": "Collection of 70+ type, feature & value checks", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/checks#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "tslib": "^2.4.0" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "@types/node": "^17.0.41", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "detect", - "feature", - "reflect", - "typescript", - "validate" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./exists-not-null": { - "default": "./exists-not-null.js" - }, - "./exists": { - "default": "./exists.js" - }, - "./has-bigint": { - "default": "./has-bigint.js" - }, - "./has-crypto": { - "default": "./has-crypto.js" - }, - "./has-max-length": { - "default": "./has-max-length.js" - }, - "./has-min-length": { - "default": "./has-min-length.js" - }, - "./has-performance": { - "default": "./has-performance.js" - }, - "./has-wasm": { - "default": "./has-wasm.js" - }, - "./has-webgl": { - "default": "./has-webgl.js" - }, - "./has-websocket": { - "default": "./has-websocket.js" - }, - "./implements-function": { - "default": "./implements-function.js" - }, - "./is-alphanum": { - "default": "./is-alphanum.js" - }, - "./is-array": { - "default": "./is-array.js" - }, - "./is-arraylike": { - "default": "./is-arraylike.js" - }, - "./is-ascii": { - "default": "./is-ascii.js" - }, - "./is-async-iterable": { - "default": "./is-async-iterable.js" - }, - "./is-bigint": { - "default": "./is-bigint.js" - }, - "./is-blob": { - "default": "./is-blob.js" - }, - "./is-boolean": { - "default": "./is-boolean.js" - }, - "./is-chrome": { - "default": "./is-chrome.js" - }, - "./is-data-url": { - "default": "./is-data-url.js" - }, - "./is-date": { - "default": "./is-date.js" - }, - "./is-even": { - "default": "./is-even.js" - }, - "./is-false": { - "default": "./is-false.js" - }, - "./is-file": { - "default": "./is-file.js" - }, - "./is-firefox": { - "default": "./is-firefox.js" - }, - "./is-float-string": { - "default": "./is-float-string.js" - }, - "./is-function": { - "default": "./is-function.js" - }, - "./is-hex-color": { - "default": "./is-hex-color.js" - }, - "./is-hex": { - "default": "./is-hex.js" - }, - "./is-ie": { - "default": "./is-ie.js" - }, - "./is-in-range": { - "default": "./is-in-range.js" - }, - "./is-int-string": { - "default": "./is-int-string.js" - }, - "./is-int32": { - "default": "./is-int32.js" - }, - "./is-iterable": { - "default": "./is-iterable.js" - }, - "./is-map": { - "default": "./is-map.js" - }, - "./is-mobile": { - "default": "./is-mobile.js" - }, - "./is-nan": { - "default": "./is-nan.js" - }, - "./is-negative": { - "default": "./is-negative.js" - }, - "./is-nil": { - "default": "./is-nil.js" - }, - "./is-node": { - "default": "./is-node.js" - }, - "./is-not-string-iterable": { - "default": "./is-not-string-iterable.js" - }, - "./is-null": { - "default": "./is-null.js" - }, - "./is-number": { - "default": "./is-number.js" - }, - "./is-numeric": { - "default": "./is-numeric.js" - }, - "./is-object": { - "default": "./is-object.js" - }, - "./is-odd": { - "default": "./is-odd.js" - }, - "./is-plain-object": { - "default": "./is-plain-object.js" - }, - "./is-positive": { - "default": "./is-positive.js" - }, - "./is-primitive": { - "default": "./is-primitive.js" - }, - "./is-promise": { - "default": "./is-promise.js" - }, - "./is-promiselike": { - "default": "./is-promiselike.js" - }, - "./is-proto-path": { - "default": "./is-proto-path.js" - }, - "./is-regexp": { - "default": "./is-regexp.js" - }, - "./is-safari": { - "default": "./is-safari.js" - }, - "./is-set": { - "default": "./is-set.js" - }, - "./is-string": { - "default": "./is-string.js" - }, - "./is-symbol": { - "default": "./is-symbol.js" - }, - "./is-transferable": { - "default": "./is-transferable.js" - }, - "./is-true": { - "default": "./is-true.js" - }, - "./is-typedarray": { - "default": "./is-typedarray.js" - }, - "./is-uint32": { - "default": "./is-uint32.js" - }, - "./is-undefined": { - "default": "./is-undefined.js" - }, - "./is-uuid": { - "default": "./is-uuid.js" - }, - "./is-uuid4": { - "default": "./is-uuid4.js" - }, - "./is-zero": { - "default": "./is-zero.js" - } - } + "name": "@thi.ng/checks", + "version": "3.2.2", + "description": "Collection of 70+ type, feature & value checks", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/checks#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "tslib": "^2.4.0" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "@types/node": "^17.0.41", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "detect", + "feature", + "reflect", + "typescript", + "validate" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./exists-not-null": { + "default": "./exists-not-null.js" + }, + "./exists": { + "default": "./exists.js" + }, + "./has-bigint": { + "default": "./has-bigint.js" + }, + "./has-crypto": { + "default": "./has-crypto.js" + }, + "./has-max-length": { + "default": "./has-max-length.js" + }, + "./has-min-length": { + "default": "./has-min-length.js" + }, + "./has-performance": { + "default": "./has-performance.js" + }, + "./has-wasm": { + "default": "./has-wasm.js" + }, + "./has-webgl": { + "default": "./has-webgl.js" + }, + "./has-websocket": { + "default": "./has-websocket.js" + }, + "./implements-function": { + "default": "./implements-function.js" + }, + "./is-alphanum": { + "default": "./is-alphanum.js" + }, + "./is-array": { + "default": "./is-array.js" + }, + "./is-arraylike": { + "default": "./is-arraylike.js" + }, + "./is-ascii": { + "default": "./is-ascii.js" + }, + "./is-async-iterable": { + "default": "./is-async-iterable.js" + }, + "./is-bigint": { + "default": "./is-bigint.js" + }, + "./is-blob": { + "default": "./is-blob.js" + }, + "./is-boolean": { + "default": "./is-boolean.js" + }, + "./is-chrome": { + "default": "./is-chrome.js" + }, + "./is-data-url": { + "default": "./is-data-url.js" + }, + "./is-date": { + "default": "./is-date.js" + }, + "./is-even": { + "default": "./is-even.js" + }, + "./is-false": { + "default": "./is-false.js" + }, + "./is-file": { + "default": "./is-file.js" + }, + "./is-firefox": { + "default": "./is-firefox.js" + }, + "./is-float-string": { + "default": "./is-float-string.js" + }, + "./is-function": { + "default": "./is-function.js" + }, + "./is-hex-color": { + "default": "./is-hex-color.js" + }, + "./is-hex": { + "default": "./is-hex.js" + }, + "./is-ie": { + "default": "./is-ie.js" + }, + "./is-in-range": { + "default": "./is-in-range.js" + }, + "./is-int-string": { + "default": "./is-int-string.js" + }, + "./is-int32": { + "default": "./is-int32.js" + }, + "./is-iterable": { + "default": "./is-iterable.js" + }, + "./is-map": { + "default": "./is-map.js" + }, + "./is-mobile": { + "default": "./is-mobile.js" + }, + "./is-nan": { + "default": "./is-nan.js" + }, + "./is-negative": { + "default": "./is-negative.js" + }, + "./is-nil": { + "default": "./is-nil.js" + }, + "./is-node": { + "default": "./is-node.js" + }, + "./is-not-string-iterable": { + "default": "./is-not-string-iterable.js" + }, + "./is-null": { + "default": "./is-null.js" + }, + "./is-number": { + "default": "./is-number.js" + }, + "./is-numeric": { + "default": "./is-numeric.js" + }, + "./is-object": { + "default": "./is-object.js" + }, + "./is-odd": { + "default": "./is-odd.js" + }, + "./is-plain-object": { + "default": "./is-plain-object.js" + }, + "./is-positive": { + "default": "./is-positive.js" + }, + "./is-primitive": { + "default": "./is-primitive.js" + }, + "./is-promise": { + "default": "./is-promise.js" + }, + "./is-promiselike": { + "default": "./is-promiselike.js" + }, + "./is-proto-path": { + "default": "./is-proto-path.js" + }, + "./is-regexp": { + "default": "./is-regexp.js" + }, + "./is-safari": { + "default": "./is-safari.js" + }, + "./is-set": { + "default": "./is-set.js" + }, + "./is-string": { + "default": "./is-string.js" + }, + "./is-symbol": { + "default": "./is-symbol.js" + }, + "./is-transferable": { + "default": "./is-transferable.js" + }, + "./is-true": { + "default": "./is-true.js" + }, + "./is-typedarray": { + "default": "./is-typedarray.js" + }, + "./is-uint32": { + "default": "./is-uint32.js" + }, + "./is-undefined": { + "default": "./is-undefined.js" + }, + "./is-uuid": { + "default": "./is-uuid.js" + }, + "./is-uuid4": { + "default": "./is-uuid4.js" + }, + "./is-zero": { + "default": "./is-zero.js" + } + } } diff --git a/packages/checks/src/exists-not-null.ts b/packages/checks/src/exists-not-null.ts index d1a3264940..225a5a070c 100644 --- a/packages/checks/src/exists-not-null.ts +++ b/packages/checks/src/exists-not-null.ts @@ -1,2 +1,2 @@ export const existsAndNotNull = (x: T | null | undefined): x is T => - x != null; + x != null; diff --git a/packages/checks/src/has-crypto.ts b/packages/checks/src/has-crypto.ts index de0564fd01..9f1474d4db 100644 --- a/packages/checks/src/has-crypto.ts +++ b/packages/checks/src/has-crypto.ts @@ -1,2 +1,2 @@ export const hasCrypto = () => - typeof window !== "undefined" && window["crypto"] !== undefined; + typeof window !== "undefined" && window["crypto"] !== undefined; diff --git a/packages/checks/src/has-max-length.ts b/packages/checks/src/has-max-length.ts index 4ab340b4b6..4a750fbe24 100644 --- a/packages/checks/src/has-max-length.ts +++ b/packages/checks/src/has-max-length.ts @@ -1,2 +1,2 @@ export const hasMaxLength = (len: number, x: ArrayLike) => - x != null && x.length <= len; + x != null && x.length <= len; diff --git a/packages/checks/src/has-min-length.ts b/packages/checks/src/has-min-length.ts index 50e86572f1..7bb79eaaea 100644 --- a/packages/checks/src/has-min-length.ts +++ b/packages/checks/src/has-min-length.ts @@ -1,2 +1,2 @@ export const hasMinLength = (len: number, x: ArrayLike) => - x != null && x.length >= len; + x != null && x.length >= len; diff --git a/packages/checks/src/has-performance.ts b/packages/checks/src/has-performance.ts index 6931050446..e2517e25dd 100644 --- a/packages/checks/src/has-performance.ts +++ b/packages/checks/src/has-performance.ts @@ -1,4 +1,4 @@ import { isFunction } from "./is-function.js"; export const hasPerformance = () => - typeof performance !== "undefined" && isFunction(performance.now); + typeof performance !== "undefined" && isFunction(performance.now); diff --git a/packages/checks/src/has-wasm.ts b/packages/checks/src/has-wasm.ts index 61402e6819..1d94640770 100644 --- a/packages/checks/src/has-wasm.ts +++ b/packages/checks/src/has-wasm.ts @@ -1,5 +1,5 @@ export const hasWASM = () => - (typeof window !== "undefined" && - typeof (window)["WebAssembly"] !== "undefined") || - (typeof global !== "undefined" && - typeof (global)["WebAssembly"] !== "undefined"); + (typeof window !== "undefined" && + typeof (window)["WebAssembly"] !== "undefined") || + (typeof global !== "undefined" && + typeof (global)["WebAssembly"] !== "undefined"); diff --git a/packages/checks/src/has-webgl.ts b/packages/checks/src/has-webgl.ts index d45368380f..4966af6678 100644 --- a/packages/checks/src/has-webgl.ts +++ b/packages/checks/src/has-webgl.ts @@ -1,8 +1,8 @@ export const hasWebGL = () => { - try { - document.createElement("canvas").getContext("webgl"); - return true; - } catch (e) { - return false; - } + try { + document.createElement("canvas").getContext("webgl"); + return true; + } catch (e) { + return false; + } }; diff --git a/packages/checks/src/implements-function.ts b/packages/checks/src/implements-function.ts index 79e80e5b42..4db7ed1dc6 100644 --- a/packages/checks/src/implements-function.ts +++ b/packages/checks/src/implements-function.ts @@ -1,4 +1,4 @@ export const implementsFunction = ( - x: any, - fn: K + x: any, + fn: K ): x is Pick => x != null && typeof x[fn] === "function"; diff --git a/packages/checks/src/is-arraylike.ts b/packages/checks/src/is-arraylike.ts index d73f9ecade..01d04b7385 100644 --- a/packages/checks/src/is-arraylike.ts +++ b/packages/checks/src/is-arraylike.ts @@ -1,2 +1,2 @@ export const isArrayLike = (x: any): x is ArrayLike => - x != null && typeof x !== "function" && x.length !== undefined; + x != null && typeof x !== "function" && x.length !== undefined; diff --git a/packages/checks/src/is-ascii.ts b/packages/checks/src/is-ascii.ts index 0c539e3367..4dc2059973 100644 --- a/packages/checks/src/is-ascii.ts +++ b/packages/checks/src/is-ascii.ts @@ -1,13 +1,13 @@ /** * Returns true iff all chars are in ASCII range [0x00 .. 0x7f] * - * @param x - + * @param x - */ export const isASCII = (x: string) => /^[\x00-\x7f]+$/.test(x); /** * Returns true iff all chars are in printable ASCII range [0x20 .. 0x7e] * - * @param x - + * @param x - */ export const isPrintableASCII = (x: string) => /^[\x20-\x7e]+$/.test(x); diff --git a/packages/checks/src/is-async-iterable.ts b/packages/checks/src/is-async-iterable.ts index cd8e93984e..ec30c12e47 100644 --- a/packages/checks/src/is-async-iterable.ts +++ b/packages/checks/src/is-async-iterable.ts @@ -1,2 +1,2 @@ export const isAsyncIterable = (x: any): x is AsyncIterable => - x != null && typeof x[Symbol.asyncIterator] === "function"; + x != null && typeof x[Symbol.asyncIterator] === "function"; diff --git a/packages/checks/src/is-chrome.ts b/packages/checks/src/is-chrome.ts index cdd17b3797..9655762c6a 100644 --- a/packages/checks/src/is-chrome.ts +++ b/packages/checks/src/is-chrome.ts @@ -1,2 +1,2 @@ export const isChrome = () => - typeof window !== "undefined" && !!(window)["chrome"]; + typeof window !== "undefined" && !!(window)["chrome"]; diff --git a/packages/checks/src/is-firefox.ts b/packages/checks/src/is-firefox.ts index 68a2ae5d53..2ab547648c 100644 --- a/packages/checks/src/is-firefox.ts +++ b/packages/checks/src/is-firefox.ts @@ -1,2 +1,2 @@ export const isFirefox = () => - typeof window !== "undefined" && !!(window)["InstallTrigger"]; + typeof window !== "undefined" && !!(window)["InstallTrigger"]; diff --git a/packages/checks/src/is-ie.ts b/packages/checks/src/is-ie.ts index 802b557d65..ee8038c055 100644 --- a/packages/checks/src/is-ie.ts +++ b/packages/checks/src/is-ie.ts @@ -1,4 +1,4 @@ export const isIE = () => - typeof document !== "undefined" && - (typeof (document)["documentMode"] !== "undefined" || - navigator.userAgent.indexOf("MSIE") > 0); + typeof document !== "undefined" && + (typeof (document)["documentMode"] !== "undefined" || + navigator.userAgent.indexOf("MSIE") > 0); diff --git a/packages/checks/src/is-in-range.ts b/packages/checks/src/is-in-range.ts index 8a9cf31e2d..a301b8c8ce 100644 --- a/packages/checks/src/is-in-range.ts +++ b/packages/checks/src/is-in-range.ts @@ -1,2 +1,2 @@ export const isInRange = (min: number, max: number, x: number) => - x >= min && x <= max; + x >= min && x <= max; diff --git a/packages/checks/src/is-int32.ts b/packages/checks/src/is-int32.ts index 2ad79dcc1a..45d1dbac24 100644 --- a/packages/checks/src/is-int32.ts +++ b/packages/checks/src/is-int32.ts @@ -1,2 +1,2 @@ export const isInt32 = (x: any): x is number => - typeof x === "number" && (x | 0) === x; + typeof x === "number" && (x | 0) === x; diff --git a/packages/checks/src/is-iterable.ts b/packages/checks/src/is-iterable.ts index a2695dfc68..dd08074a10 100644 --- a/packages/checks/src/is-iterable.ts +++ b/packages/checks/src/is-iterable.ts @@ -1,2 +1,2 @@ export const isIterable = (x: any): x is Iterable => - x != null && typeof x[Symbol.iterator] === "function"; + x != null && typeof x[Symbol.iterator] === "function"; diff --git a/packages/checks/src/is-mobile.ts b/packages/checks/src/is-mobile.ts index a54d48b107..5049e8fb1c 100644 --- a/packages/checks/src/is-mobile.ts +++ b/packages/checks/src/is-mobile.ts @@ -1,5 +1,5 @@ export const isMobile = () => - typeof navigator !== "undefined" && - /mobile|tablet|ip(ad|hone|od)|android|silk|crios/i.test( - navigator.userAgent - ); + typeof navigator !== "undefined" && + /mobile|tablet|ip(ad|hone|od)|android|silk|crios/i.test( + navigator.userAgent + ); diff --git a/packages/checks/src/is-negative.ts b/packages/checks/src/is-negative.ts index 1cd834dd4d..897458b0fc 100644 --- a/packages/checks/src/is-negative.ts +++ b/packages/checks/src/is-negative.ts @@ -1,2 +1,2 @@ export const isNegative = (x: any): x is number => - typeof x === "number" && x < 0; + typeof x === "number" && x < 0; diff --git a/packages/checks/src/is-node.ts b/packages/checks/src/is-node.ts index e8e5850190..eb9a3736f9 100644 --- a/packages/checks/src/is-node.ts +++ b/packages/checks/src/is-node.ts @@ -1,6 +1,6 @@ declare var process: any; export const isNode = () => - typeof process === "object" && - typeof process.versions === "object" && - typeof process.versions.node !== "undefined"; + typeof process === "object" && + typeof process.versions === "object" && + typeof process.versions.node !== "undefined"; diff --git a/packages/checks/src/is-not-string-iterable.ts b/packages/checks/src/is-not-string-iterable.ts index 6d8660d4e0..cd24b9589f 100644 --- a/packages/checks/src/is-not-string-iterable.ts +++ b/packages/checks/src/is-not-string-iterable.ts @@ -1,4 +1,4 @@ export const isNotStringAndIterable = (x: any): x is Iterable => - x != null && - typeof x !== "string" && - typeof x[Symbol.iterator] === "function"; + x != null && + typeof x !== "string" && + typeof x[Symbol.iterator] === "function"; diff --git a/packages/checks/src/is-numeric.ts b/packages/checks/src/is-numeric.ts index eaf14f8a9d..a4a50f0a09 100644 --- a/packages/checks/src/is-numeric.ts +++ b/packages/checks/src/is-numeric.ts @@ -2,7 +2,7 @@ * Returns true if given string contains only digits, and optionally, a sign * prefix. * - * @param x - + * @param x - */ export const isNumericInt = (x: string) => /^[-+]?\d+$/.test(x); @@ -10,7 +10,7 @@ export const isNumericInt = (x: string) => /^[-+]?\d+$/.test(x); * Returns true if given string only contains an integer or floating point * number, optionally in scientific notiation (e.g. `-123.45e-6`). * - * @param x - + * @param x - */ export const isNumericFloat = (x: string) => - /^[-+]?\d*\.?\d+(e[-+]?\d+)?$/i.test(x); + /^[-+]?\d*\.?\d+(e[-+]?\d+)?$/i.test(x); diff --git a/packages/checks/src/is-object.ts b/packages/checks/src/is-object.ts index 45f78212fa..5de57b300b 100644 --- a/packages/checks/src/is-object.ts +++ b/packages/checks/src/is-object.ts @@ -1,2 +1,2 @@ export const isObject = (x: any): x is Object => - x !== null && typeof x === "object"; + x !== null && typeof x === "object"; diff --git a/packages/checks/src/is-plain-object.ts b/packages/checks/src/is-plain-object.ts index 20c9fd4179..3dd2f8435a 100644 --- a/packages/checks/src/is-plain-object.ts +++ b/packages/checks/src/is-plain-object.ts @@ -7,10 +7,10 @@ const OBJP = Object.getPrototypeOf; * @param x - */ export const isPlainObject = (x: any): x is Record => { - let p; - return ( - x != null && - typeof x === "object" && - ((p = OBJP(x)) === null || OBJP(p) === null) - ); + let p; + return ( + x != null && + typeof x === "object" && + ((p = OBJP(x)) === null || OBJP(p) === null) + ); }; diff --git a/packages/checks/src/is-positive.ts b/packages/checks/src/is-positive.ts index 0cceb3781c..52547f7677 100644 --- a/packages/checks/src/is-positive.ts +++ b/packages/checks/src/is-positive.ts @@ -1,2 +1,2 @@ export const isPositive = (x: any): x is number => - typeof x === "number" && x > 0; + typeof x === "number" && x > 0; diff --git a/packages/checks/src/is-primitive.ts b/packages/checks/src/is-primitive.ts index d36d06a65e..8ac9c878ad 100644 --- a/packages/checks/src/is-primitive.ts +++ b/packages/checks/src/is-primitive.ts @@ -1,9 +1,9 @@ /** * Returns true if `x` is a string, number or boolean. * - * @param x - + * @param x - */ export const isPrimitive = (x: any): x is string | number | boolean => { - const t = typeof x; - return t === "string" || t === "number" || t === "boolean"; + const t = typeof x; + return t === "string" || t === "number" || t === "boolean"; }; diff --git a/packages/checks/src/is-promiselike.ts b/packages/checks/src/is-promiselike.ts index 35bf895b8e..1bbe54e651 100644 --- a/packages/checks/src/is-promiselike.ts +++ b/packages/checks/src/is-promiselike.ts @@ -1,5 +1,5 @@ import { implementsFunction } from "./implements-function.js"; export const isPromiseLike = (x: any): x is Promise => - x instanceof Promise || - (implementsFunction(x, "then") && implementsFunction(x, "catch")); + x instanceof Promise || + (implementsFunction(x, "then") && implementsFunction(x, "catch")); diff --git a/packages/checks/src/is-proto-path.ts b/packages/checks/src/is-proto-path.ts index 5be6d112af..3bd707367a 100644 --- a/packages/checks/src/is-proto-path.ts +++ b/packages/checks/src/is-proto-path.ts @@ -9,7 +9,7 @@ const ILLEGAL_KEYS = new Set(["__proto__", "prototype", "constructor"]); * * @see {@link isProtoPath} for more details * - * @param x - + * @param x - */ export const isIllegalKey = (x: any) => ILLEGAL_KEYS.has(x); @@ -25,15 +25,15 @@ export const isIllegalKey = (x: any) => ILLEGAL_KEYS.has(x); * Original discussion here, implementation updated to be more encompassing: * https://github.com/thi-ng/umbrella/pull/273 * - * @param path - + * @param path - */ export const isProtoPath = ( - path: string | number | readonly (string | number)[] + path: string | number | readonly (string | number)[] ) => - isArray(path) - ? path.some(isIllegalKey) - : isString(path) - ? path.indexOf(".") !== -1 - ? path.split(".").some(isIllegalKey) - : isIllegalKey(path) - : false; + isArray(path) + ? path.some(isIllegalKey) + : isString(path) + ? path.indexOf(".") !== -1 + ? path.split(".").some(isIllegalKey) + : isIllegalKey(path) + : false; diff --git a/packages/checks/src/is-safari.ts b/packages/checks/src/is-safari.ts index 7ce707878d..6eeb047d1e 100644 --- a/packages/checks/src/is-safari.ts +++ b/packages/checks/src/is-safari.ts @@ -1,6 +1,6 @@ import { isChrome } from "./is-chrome.js"; export const isSafari = () => - typeof navigator !== "undefined" && - /Safari/.test(navigator.userAgent) && - !isChrome(); + typeof navigator !== "undefined" && + /Safari/.test(navigator.userAgent) && + !isChrome(); diff --git a/packages/checks/src/is-transferable.ts b/packages/checks/src/is-transferable.ts index 473be2ccfa..c036f30940 100644 --- a/packages/checks/src/is-transferable.ts +++ b/packages/checks/src/is-transferable.ts @@ -1,7 +1,7 @@ declare var SharedArrayBuffer: any; export const isTransferable = (x: any) => - x instanceof ArrayBuffer || - (typeof SharedArrayBuffer !== "undefined" && - x instanceof SharedArrayBuffer) || - (typeof MessagePort !== "undefined" && x instanceof MessagePort); + x instanceof ArrayBuffer || + (typeof SharedArrayBuffer !== "undefined" && + x instanceof SharedArrayBuffer) || + (typeof MessagePort !== "undefined" && x instanceof MessagePort); diff --git a/packages/checks/src/is-typedarray.ts b/packages/checks/src/is-typedarray.ts index 2cc3348c67..1ff9e91d1d 100644 --- a/packages/checks/src/is-typedarray.ts +++ b/packages/checks/src/is-typedarray.ts @@ -1,22 +1,22 @@ type TypedArray = - | Float32Array - | Float64Array - | Int8Array - | Int16Array - | Int32Array - | Uint8Array - | Uint8ClampedArray - | Uint16Array - | Uint32Array; + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array; export const isTypedArray = (x: any): x is TypedArray => - x && - (x instanceof Float32Array || - x instanceof Float64Array || - x instanceof Uint32Array || - x instanceof Int32Array || - x instanceof Uint8Array || - x instanceof Int8Array || - x instanceof Uint16Array || - x instanceof Int16Array || - x instanceof Uint8ClampedArray); + x && + (x instanceof Float32Array || + x instanceof Float64Array || + x instanceof Uint32Array || + x instanceof Int32Array || + x instanceof Uint8Array || + x instanceof Int8Array || + x instanceof Uint16Array || + x instanceof Int16Array || + x instanceof Uint8ClampedArray); diff --git a/packages/checks/src/is-uint32.ts b/packages/checks/src/is-uint32.ts index 8d54ee556c..8383a13e36 100644 --- a/packages/checks/src/is-uint32.ts +++ b/packages/checks/src/is-uint32.ts @@ -1,2 +1,2 @@ export const isUint32 = (x: any): x is number => - typeof x === "number" && x >>> 0 === x; + typeof x === "number" && x >>> 0 === x; diff --git a/packages/checks/src/is-uuid4.ts b/packages/checks/src/is-uuid4.ts index 43bc282ba9..2729ee1b65 100644 --- a/packages/checks/src/is-uuid4.ts +++ b/packages/checks/src/is-uuid4.ts @@ -1,3 +1,4 @@ -const RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; +const RE = + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; export const isUUIDv4 = (x: string) => RE.test(x); diff --git a/packages/checks/test/index.ts b/packages/checks/test/index.ts index a5f1575d57..40feb1da54 100644 --- a/packages/checks/test/index.ts +++ b/packages/checks/test/index.ts @@ -2,212 +2,212 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; import * as vm from "vm"; import { - existsAndNotNull, - implementsFunction, - isArray, - isArrayLike, - isFunction, - isHexColor, - isNil, - isObject, - isPlainObject, - isProtoPath, - isString, - isSymbol, - isTransferable, - isTypedArray, + existsAndNotNull, + implementsFunction, + isArray, + isArrayLike, + isFunction, + isHexColor, + isNil, + isObject, + isPlainObject, + isProtoPath, + isString, + isSymbol, + isTransferable, + isTypedArray, } from "../src/index.js"; group("checks", { - existsAndNotNull: () => { - assert.ok(existsAndNotNull([]), "empty array"); - assert.ok(existsAndNotNull(new Uint8Array(1)), "typedarray"); - assert.ok(existsAndNotNull({}), "obj"); - assert.ok(existsAndNotNull("[]"), "string"); - assert.ok(existsAndNotNull(0), "zero"); - assert.ok(!existsAndNotNull(({})["foobar"]), "prop"); - assert.ok(!existsAndNotNull(null), "null"); - assert.ok(!existsAndNotNull(undefined), "null"); - }, - - isArray: () => { - assert.ok(isArray([]), "empty array"); - assert.ok(!isArray(new Uint8Array(1)), "typedarray"); - assert.ok(!isArray({}), "obj"); - assert.ok(!isArray("[]"), "string"); - assert.ok(!isArray(0), "zero"); - assert.ok(!isArray(null), "null"); - assert.ok(!isArray(undefined), "null"); - }, - - isTypedArray: () => { - assert.ok(isTypedArray(new Uint8Array(1)), "u8"); - assert.ok(isTypedArray(new Uint8ClampedArray(1)), "u8c"); - assert.ok(isTypedArray(new Uint16Array(1)), "u16"); - assert.ok(isTypedArray(new Uint32Array(1)), "u32"); - assert.ok(isTypedArray(new Int8Array(1)), "i8"); - assert.ok(isTypedArray(new Int16Array(1)), "i16"); - assert.ok(isTypedArray(new Int32Array(1)), "i32"); - assert.ok(isTypedArray(new Float32Array(1)), "f32"); - assert.ok(isTypedArray(new Float64Array(1)), "f64"); - assert.ok(!isTypedArray([]), "empty array"); - assert.ok(!isTypedArray({}), "obj"); - assert.ok(!isTypedArray("[]"), "string"); - assert.ok(!isTypedArray(0), "zero"); - assert.ok(!isTypedArray(null), "null"); - assert.ok(!isTypedArray(undefined), "null"); - }, - - isArrayLike: () => { - assert.ok(isArrayLike([]), "empty array"); - assert.ok(isArrayLike(new Uint8Array(1)), "typedarray"); - assert.ok(isArrayLike({ length: 1 }), "obj.length"); - assert.ok(isArrayLike("[]"), "string"); - assert.ok(!isArrayLike({}), "empty obj"); - assert.ok(!isArrayLike(0), "zero"); - assert.ok(!isArrayLike(null), "null"); - assert.ok(!isArrayLike(undefined), "null"); - assert.ok(!isArrayLike((x: any, y: any) => x + y), "null"); - }, - - isObject: () => { - class Foo {} - assert.ok(isObject([]), "empty array"); - assert.ok(isObject(new Uint8Array(1)), "typedarray"); - assert.ok(isObject({}), "obj"); - assert.ok(isObject(new Foo()), "class"); - assert.ok(!isObject(Foo), "fn"); - assert.ok(!isObject("[]"), "string"); - assert.ok(!isObject(0), "zero"); - assert.ok(!isObject(null), "null"); - assert.ok(!isObject(undefined), "null"); - }, - - isPlainObject: () => { - const ctxClass = vm.runInNewContext("class A {}; new A();"); - const ctxObj = vm.runInNewContext("({})"); - - class Foo {} - - assert.ok(isPlainObject({}), "obj"); - assert.ok(isPlainObject(Object.create(null)), "obj"); - assert.ok(isPlainObject(new Object()), "obj ctor"); - assert.ok(!isPlainObject(Foo), "fn"); - assert.ok( - !isPlainObject((function* (): IterableIterator {})()), - "generator" - ); - assert.ok(!isPlainObject(new Foo()), "class"); - assert.ok(!isPlainObject([]), "empty array"); - assert.ok(!isPlainObject(new Uint8Array(1)), "typedarray"); - assert.ok(!isPlainObject("[]"), "string"); - assert.ok(!isPlainObject(0), "zero"); - assert.ok(!isPlainObject(null), "null"); - assert.ok(!isPlainObject(undefined), "null"); - assert.ok(isPlainObject(ctxObj), "vm ctx obj"); - assert.ok(!isPlainObject(ctxClass), "vm ctx class"); - }, - - isString: () => { - assert.ok(isString(""), "empty string"); - assert.ok(isString("a"), "empty string"); - assert.ok(!isString({}), "obj"); - assert.ok(!isString([]), "array"); - assert.ok(!isString(new Uint8Array(1)), "typedarray"); - assert.ok(!isString(0), "zero"); - assert.ok(!isString(null), "null"); - assert.ok(!isString(undefined), "null"); - }, - - isFunction: () => { - assert.ok( - isFunction((_: any) => {}), - "fn" - ); - assert.ok(isFunction(Uint8Array), "ctor"); - assert.ok(isFunction("a".toString), "toString"); - assert.ok(!isFunction("a"), "empty string"); - assert.ok(!isFunction({}), "obj"); - assert.ok(!isFunction([]), "array"); - assert.ok(!isFunction(new Uint8Array(1)), "typedarray"); - assert.ok(!isFunction(0), "zero"); - assert.ok(!isFunction(null), "null"); - assert.ok(!isFunction(undefined), "undefined"); - }, - - implementsFunction: () => { - assert.ok(implementsFunction({ a: () => true }, "a"), "obj"); - assert.ok(implementsFunction([], Symbol.iterator), "arr iterator"); - assert.ok(implementsFunction("", Symbol.iterator), "string iterator"); - assert.ok(!implementsFunction(0, Symbol.iterator), "zero"); - assert.ok(!implementsFunction(null, Symbol.iterator), "null"); - assert.ok(!implementsFunction(undefined, Symbol.iterator), "undefined"); - }, - - isSymbol: () => { - assert.ok(isSymbol(Symbol.iterator), "iterator"); - assert.ok(!isSymbol("iterator"), "string"); - assert.ok(!isFunction(0), "zero"); - assert.ok(!isFunction(null), "null"); - assert.ok(!isFunction(undefined), "undefined"); - }, - - isTransferable: () => { - assert.ok(isTransferable(new ArrayBuffer(4)), "arraybuffer"); - assert.ok(!isTransferable(new Uint8Array(4)), "typedarray"); - assert.ok(!isTransferable([]), "array"); - assert.ok(!isTransferable("a"), "string"); - assert.ok(!isTransferable(0), "zero"); - assert.ok(!isTransferable(null), "null"); - assert.ok(!isTransferable(undefined), "undefined"); - }, - - isNil: () => { - assert.ok(isNil(undefined), "undefined"); - assert.ok(isNil(null), "null"); - assert.ok(!isNil("foo"), "string"); - assert.ok(!isNil({}), "empty object"); - assert.ok(!isNil([]), "empty array"); - assert.ok(!isNil(""), "empty string"); - assert.ok(!isNil(false), "false"); - assert.ok(!isNil(true), "true"); - assert.ok(!isNil(() => {}), "function"); - }, - - isHexColor: () => { - assert.ok(isHexColor("#123"), "valid 3 digits rgb"); - assert.ok(isHexColor("#ff3300"), "valid 6 digits rrggbb"); - assert.ok(isHexColor("#f30f"), "valid 4 digits rgba"); - assert.ok(isHexColor("#ff3300ff"), "valid 8 digits rrggbbaa"); - assert.ok(!isHexColor(undefined), "undefined"); - assert.ok(!isHexColor(null), "null"); - assert.ok(!isHexColor(""), "empty string"); - assert.ok(!isHexColor("foo"), "invalid: foo"); - assert.ok(!isHexColor("123"), "invalid: 123"); - assert.ok(!isHexColor("#12."), "invalid: #12."); - assert.ok(!isHexColor("#j23"), "invalid: #j23"); - assert.ok(!isHexColor("#jf3300"), "invalid: #jf3300"); - assert.ok(!isHexColor("#j30f"), "invalid: #j30f"); - assert.ok(!isHexColor("#jf3300ff"), "invalid: #jf3300ff"); - assert.ok(!isHexColor("hi #123"), "invalid: hi #123"); - assert.ok(!isHexColor("#ff3300 hi"), "invalid: #ff3300 hi"); - assert.ok(!isHexColor("hi #ff3300 hi"), "invalid: hi #ff3300 hi"); - assert.ok(!isHexColor("#123 #123"), "invalid: #123 #123"); - }, - - isProtoPath: () => { - assert.ok(!isProtoPath("foo.__proto.bar"), "0"); - assert.ok(!isProtoPath("foo.bar"), "1"); - assert.ok(!isProtoPath(""), "2"); - assert.ok(isProtoPath("__proto__"), "3"); - assert.ok(isProtoPath("prototype"), "4"); - assert.ok(isProtoPath("constructor"), "5"); - assert.ok(isProtoPath("foo.__proto__.bar"), "6"); - assert.ok(!isProtoPath([]), "7"); - assert.ok(!isProtoPath([""]), "8"); - assert.ok(!isProtoPath(["foo", 23]), "9"); - assert.ok(!isProtoPath(["prototype.foo"]), "10"); - assert.ok(isProtoPath(["__proto__"]), "11"); - assert.ok(isProtoPath(["foo", "__proto__", "bar"]), "12"); - }, + existsAndNotNull: () => { + assert.ok(existsAndNotNull([]), "empty array"); + assert.ok(existsAndNotNull(new Uint8Array(1)), "typedarray"); + assert.ok(existsAndNotNull({}), "obj"); + assert.ok(existsAndNotNull("[]"), "string"); + assert.ok(existsAndNotNull(0), "zero"); + assert.ok(!existsAndNotNull(({})["foobar"]), "prop"); + assert.ok(!existsAndNotNull(null), "null"); + assert.ok(!existsAndNotNull(undefined), "null"); + }, + + isArray: () => { + assert.ok(isArray([]), "empty array"); + assert.ok(!isArray(new Uint8Array(1)), "typedarray"); + assert.ok(!isArray({}), "obj"); + assert.ok(!isArray("[]"), "string"); + assert.ok(!isArray(0), "zero"); + assert.ok(!isArray(null), "null"); + assert.ok(!isArray(undefined), "null"); + }, + + isTypedArray: () => { + assert.ok(isTypedArray(new Uint8Array(1)), "u8"); + assert.ok(isTypedArray(new Uint8ClampedArray(1)), "u8c"); + assert.ok(isTypedArray(new Uint16Array(1)), "u16"); + assert.ok(isTypedArray(new Uint32Array(1)), "u32"); + assert.ok(isTypedArray(new Int8Array(1)), "i8"); + assert.ok(isTypedArray(new Int16Array(1)), "i16"); + assert.ok(isTypedArray(new Int32Array(1)), "i32"); + assert.ok(isTypedArray(new Float32Array(1)), "f32"); + assert.ok(isTypedArray(new Float64Array(1)), "f64"); + assert.ok(!isTypedArray([]), "empty array"); + assert.ok(!isTypedArray({}), "obj"); + assert.ok(!isTypedArray("[]"), "string"); + assert.ok(!isTypedArray(0), "zero"); + assert.ok(!isTypedArray(null), "null"); + assert.ok(!isTypedArray(undefined), "null"); + }, + + isArrayLike: () => { + assert.ok(isArrayLike([]), "empty array"); + assert.ok(isArrayLike(new Uint8Array(1)), "typedarray"); + assert.ok(isArrayLike({ length: 1 }), "obj.length"); + assert.ok(isArrayLike("[]"), "string"); + assert.ok(!isArrayLike({}), "empty obj"); + assert.ok(!isArrayLike(0), "zero"); + assert.ok(!isArrayLike(null), "null"); + assert.ok(!isArrayLike(undefined), "null"); + assert.ok(!isArrayLike((x: any, y: any) => x + y), "null"); + }, + + isObject: () => { + class Foo {} + assert.ok(isObject([]), "empty array"); + assert.ok(isObject(new Uint8Array(1)), "typedarray"); + assert.ok(isObject({}), "obj"); + assert.ok(isObject(new Foo()), "class"); + assert.ok(!isObject(Foo), "fn"); + assert.ok(!isObject("[]"), "string"); + assert.ok(!isObject(0), "zero"); + assert.ok(!isObject(null), "null"); + assert.ok(!isObject(undefined), "null"); + }, + + isPlainObject: () => { + const ctxClass = vm.runInNewContext("class A {}; new A();"); + const ctxObj = vm.runInNewContext("({})"); + + class Foo {} + + assert.ok(isPlainObject({}), "obj"); + assert.ok(isPlainObject(Object.create(null)), "obj"); + assert.ok(isPlainObject(new Object()), "obj ctor"); + assert.ok(!isPlainObject(Foo), "fn"); + assert.ok( + !isPlainObject((function* (): IterableIterator {})()), + "generator" + ); + assert.ok(!isPlainObject(new Foo()), "class"); + assert.ok(!isPlainObject([]), "empty array"); + assert.ok(!isPlainObject(new Uint8Array(1)), "typedarray"); + assert.ok(!isPlainObject("[]"), "string"); + assert.ok(!isPlainObject(0), "zero"); + assert.ok(!isPlainObject(null), "null"); + assert.ok(!isPlainObject(undefined), "null"); + assert.ok(isPlainObject(ctxObj), "vm ctx obj"); + assert.ok(!isPlainObject(ctxClass), "vm ctx class"); + }, + + isString: () => { + assert.ok(isString(""), "empty string"); + assert.ok(isString("a"), "empty string"); + assert.ok(!isString({}), "obj"); + assert.ok(!isString([]), "array"); + assert.ok(!isString(new Uint8Array(1)), "typedarray"); + assert.ok(!isString(0), "zero"); + assert.ok(!isString(null), "null"); + assert.ok(!isString(undefined), "null"); + }, + + isFunction: () => { + assert.ok( + isFunction((_: any) => {}), + "fn" + ); + assert.ok(isFunction(Uint8Array), "ctor"); + assert.ok(isFunction("a".toString), "toString"); + assert.ok(!isFunction("a"), "empty string"); + assert.ok(!isFunction({}), "obj"); + assert.ok(!isFunction([]), "array"); + assert.ok(!isFunction(new Uint8Array(1)), "typedarray"); + assert.ok(!isFunction(0), "zero"); + assert.ok(!isFunction(null), "null"); + assert.ok(!isFunction(undefined), "undefined"); + }, + + implementsFunction: () => { + assert.ok(implementsFunction({ a: () => true }, "a"), "obj"); + assert.ok(implementsFunction([], Symbol.iterator), "arr iterator"); + assert.ok(implementsFunction("", Symbol.iterator), "string iterator"); + assert.ok(!implementsFunction(0, Symbol.iterator), "zero"); + assert.ok(!implementsFunction(null, Symbol.iterator), "null"); + assert.ok(!implementsFunction(undefined, Symbol.iterator), "undefined"); + }, + + isSymbol: () => { + assert.ok(isSymbol(Symbol.iterator), "iterator"); + assert.ok(!isSymbol("iterator"), "string"); + assert.ok(!isFunction(0), "zero"); + assert.ok(!isFunction(null), "null"); + assert.ok(!isFunction(undefined), "undefined"); + }, + + isTransferable: () => { + assert.ok(isTransferable(new ArrayBuffer(4)), "arraybuffer"); + assert.ok(!isTransferable(new Uint8Array(4)), "typedarray"); + assert.ok(!isTransferable([]), "array"); + assert.ok(!isTransferable("a"), "string"); + assert.ok(!isTransferable(0), "zero"); + assert.ok(!isTransferable(null), "null"); + assert.ok(!isTransferable(undefined), "undefined"); + }, + + isNil: () => { + assert.ok(isNil(undefined), "undefined"); + assert.ok(isNil(null), "null"); + assert.ok(!isNil("foo"), "string"); + assert.ok(!isNil({}), "empty object"); + assert.ok(!isNil([]), "empty array"); + assert.ok(!isNil(""), "empty string"); + assert.ok(!isNil(false), "false"); + assert.ok(!isNil(true), "true"); + assert.ok(!isNil(() => {}), "function"); + }, + + isHexColor: () => { + assert.ok(isHexColor("#123"), "valid 3 digits rgb"); + assert.ok(isHexColor("#ff3300"), "valid 6 digits rrggbb"); + assert.ok(isHexColor("#f30f"), "valid 4 digits rgba"); + assert.ok(isHexColor("#ff3300ff"), "valid 8 digits rrggbbaa"); + assert.ok(!isHexColor(undefined), "undefined"); + assert.ok(!isHexColor(null), "null"); + assert.ok(!isHexColor(""), "empty string"); + assert.ok(!isHexColor("foo"), "invalid: foo"); + assert.ok(!isHexColor("123"), "invalid: 123"); + assert.ok(!isHexColor("#12."), "invalid: #12."); + assert.ok(!isHexColor("#j23"), "invalid: #j23"); + assert.ok(!isHexColor("#jf3300"), "invalid: #jf3300"); + assert.ok(!isHexColor("#j30f"), "invalid: #j30f"); + assert.ok(!isHexColor("#jf3300ff"), "invalid: #jf3300ff"); + assert.ok(!isHexColor("hi #123"), "invalid: hi #123"); + assert.ok(!isHexColor("#ff3300 hi"), "invalid: #ff3300 hi"); + assert.ok(!isHexColor("hi #ff3300 hi"), "invalid: hi #ff3300 hi"); + assert.ok(!isHexColor("#123 #123"), "invalid: #123 #123"); + }, + + isProtoPath: () => { + assert.ok(!isProtoPath("foo.__proto.bar"), "0"); + assert.ok(!isProtoPath("foo.bar"), "1"); + assert.ok(!isProtoPath(""), "2"); + assert.ok(isProtoPath("__proto__"), "3"); + assert.ok(isProtoPath("prototype"), "4"); + assert.ok(isProtoPath("constructor"), "5"); + assert.ok(isProtoPath("foo.__proto__.bar"), "6"); + assert.ok(!isProtoPath([]), "7"); + assert.ok(!isProtoPath([""]), "8"); + assert.ok(!isProtoPath(["foo", 23]), "9"); + assert.ok(!isProtoPath(["prototype.foo"]), "10"); + assert.ok(isProtoPath(["__proto__"]), "11"); + assert.ok(isProtoPath(["foo", "__proto__", "bar"]), "12"); + }, }); diff --git a/packages/checks/tsconfig.json b/packages/checks/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/checks/tsconfig.json +++ b/packages/checks/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/color-palettes/api-extractor.json b/packages/color-palettes/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/color-palettes/api-extractor.json +++ b/packages/color-palettes/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/color-palettes/package.json b/packages/color-palettes/package.json index 2afedab4c7..de0e1c18b4 100644 --- a/packages/color-palettes/package.json +++ b/packages/color-palettes/package.json @@ -1,74 +1,74 @@ { - "name": "@thi.ng/color-palettes", - "version": "0.8.4", - "description": "Collection of 176 image based color palettes", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/color-palettes#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test", - "tool:swatches": "tools:node-esm tools/index.ts" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "css", - "color", - "dominant", - "image", - "palette", - "rgb", - "theme", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/color", - "year": 2021 - } + "name": "@thi.ng/color-palettes", + "version": "0.8.4", + "description": "Collection of 176 image based color palettes", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/color-palettes#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test", + "tool:swatches": "tools:node-esm tools/index.ts" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "css", + "color", + "dominant", + "image", + "palette", + "rgb", + "theme", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/color", + "year": 2021 + } } diff --git a/packages/color-palettes/src/index.ts b/packages/color-palettes/src/index.ts index c13a63c890..ce84c2bdaf 100644 --- a/packages/color-palettes/src/index.ts +++ b/packages/color-palettes/src/index.ts @@ -1,1411 +1,1411 @@ export const THEMES = { - "00ORLwKeosxtEeZxq": [ - "#18132c", - "#492f5e", - "#775692", - "#bf89bb", - "#ecb1d0", - "#fbd0c8", - ], - "00OkEXVdMQmQ1oQTp": [ - "#dbb98e", - "#9e8f52", - "#607630", - "#2c4c1c", - "#0d6176", - "#589cb4", - ], - "00OqoPfvP6y1EmXZQ": [ - "#0d1e63", - "#4e49a7", - "#1890cc", - "#9a8a94", - "#c6c3d2", - "#efeef4", - ], - "00OwGARqASN2zngyg": [ - "#4e351d", - "#6c5133", - "#896a46", - "#b18959", - "#c9bca8", - "#f7efe4", - ], - "00PBWZvgJvi4gdxGj": [ - "#070b04", - "#2e3911", - "#646e32", - "#949d85", - "#b2baac", - "#d9ddd9", - ], - "00PhEgF9AlI5PTrLB": [ - "#150a23", - "#3c0f3f", - "#591962", - "#493b92", - "#5465af", - "#6175b8", - ], - "00PhGSfzpeU5Bkh47a": [ - "#440807", - "#6c0f0b", - "#910711", - "#8c4c2a", - "#b87d4e", - "#d9b995", - ], - "00Q9Yxm7DrZXqTkyS": [ - "#2f1864", - "#e40302", - "#f25c22", - "#d987bd", - "#44b6e7", - "#e3dadd", - ], - "00QLNHv6JW1bL031O": [ - "#d03835", - "#fc4340", - "#c2756a", - "#cf998d", - "#f2c7b9", - "#f2dcd5", - ], - "00QLNIKFhjs5rJCjr": [ - "#f77908", - "#ba9623", - "#c6a489", - "#bbc3bb", - "#9dc9be", - "#668c92", - ], - "00QLNIQXmbJgghRXg": [ - "#502527", - "#366974", - "#d93d0f", - "#fc8b1e", - "#efa481", - "#f2ebe5", - ], - "00QLNIVe1AkurVqYj": [ - "#4f4c63", - "#7288a5", - "#91a5be", - "#c36b7e", - "#f34452", - "#b5172a", - ], - "00QLcP0k2fPAz0KHR": [ - "#814232", - "#b87931", - "#dfa531", - "#e3d1be", - "#d7b776", - "#a69e85", - ], - "00QLfPj2trTnyJHAg": [ - "#15253c", - "#552f34", - "#8f1f21", - "#7a6574", - "#b0a0b3", - "#d1e6ed", - ], - "00QLfPkuNx1jE7sMm": [ - "#1c161c", - "#413a42", - "#6f6166", - "#9f8b8c", - "#d7c4c3", - "#fbeae4", - ], - "00QLfPm4UfkMiaicq": [ - "#3e3e25", - "#664d1c", - "#a2510d", - "#ce881d", - "#f6cb53", - "#fefce8", - ], - "00QLfPtu33OAdSN4Y": [ - "#60281b", - "#a34236", - "#f1612f", - "#cb9876", - "#ebd8be", - "#788e26", - ], - "00QLfPusSpy3uFOpb": [ - "#282819", - "#4e572f", - "#787972", - "#aaaba7", - "#b4e614", - "#d5ff3f", - ], - "00QLj3F8heV6QT4YG": [ - "#b5704e", - "#5a4940", - "#282820", - "#0fac9d", - "#badcd7", - "#6d8484", - ], - "00QLj3PylHkh8qY2R": [ - "#411c20", - "#b71022", - "#f63a3a", - "#c1c3d1", - "#8a858e", - "#5c555d", - ], - "00QLj3Yj1pUNnTKVY": [ - "#7c7575", - "#743d28", - "#e88b46", - "#caa78d", - "#acd4d4", - "#1d2544", - ], - "00QLj3dmHiT8Ep1Un": [ - "#1a51aa", - "#167418", - "#5d8937", - "#95a566", - "#b9b296", - "#e0dab5", - ], - "00QLj3fsFErIl5W0b": [ - "#763a20", - "#c78f67", - "#f27f14", - "#86949b", - "#37516c", - "#1c222f", - ], - "00QLn7BH2doQfh4Yw": [ - "#401918", - "#9f1d1f", - "#ab6868", - "#d1d3d3", - "#39b7b6", - "#336b6b", - ], - "00QLn7G59JhKtuUs0": [ - "#0a0e40", - "#715589", - "#c7264b", - "#3fa1cc", - "#fa7b0e", - "#e8b7ac", - ], - "00QLn7Lr9V98YD0zY": [ - "#ab3051", - "#e31f4b", - "#c68575", - "#f3bda5", - "#652b55", - "#834270", - ], - "00QLn7XfPKvMKCrzO": [ - "#73272f", - "#e65633", - "#e5af98", - "#f2dfcd", - "#43b6ad", - "#181240", - ], - "00QLn7oETUQZKKr34": [ - "#392325", - "#504d54", - "#89462c", - "#da5c74", - "#908a92", - "#e6cbce", - ], - "00QLqYLjs4H7kdHrp": [ - "#3d4d20", - "#73854d", - "#aeb87f", - "#6e9db5", - "#4c6f8e", - "#1c4861", - ], - "00QLqYQ3HE1oy3ByI": [ - "#97867e", - "#894e29", - "#d7a31a", - "#f2d95b", - "#dcdedb", - "#92b1cb", - ], - "00QLqYS2ZOgkMeMAE": [ - "#3b4227", - "#255c50", - "#2d7663", - "#788664", - "#b3ad81", - "#e3d6ad", - ], - "00QLslJAGNA8YREKh": [ - "#ffd8d2", - "#fbc6b7", - "#fd9b79", - "#ece8e8", - "#c2bab6", - "#888078", - ], - "00QMA8RhpXdgwPtLn": [ - "#4d3621", - "#806b52", - "#9b9685", - "#acb8af", - "#bfd4d0", - "#60acb2", - ], - "00QMA8SkIwjW5KEch": [ - "#f0181f", - "#b51c1c", - "#b4a8a2", - "#dcd4db", - "#75787a", - "#3c373b", - ], - "00QMA8h2BH69zyEk0": [ - "#0a0320", - "#260d6c", - "#613dda", - "#b4b6ac", - "#f6e019", - "#eef5fa", - ], - "00QMA8tedClukB7IL": [ - "#6f402c", - "#ac3038", - "#ad796b", - "#c9b4a2", - "#cbcbc2", - "#ebe3d8", - ], - "00QMEZVdDC6dMYpjp": [ - "#df4341", - "#e8817e", - "#b1a789", - "#d6d1ba", - "#b1b828", - "#c1ed7f", - ], - "00QMEZazmZG85DaWw": [ - "#1b2434", - "#47423d", - "#6a614a", - "#8b8262", - "#d9d3c7", - "#fae7dc", - ], - "00QMEZb0EorAC0k7y": [ - "#2f112d", - "#520330", - "#7f0233", - "#c52b3e", - "#fc714b", - "#fedac0", - ], - "00QMEZgMsSVZavaNd": [ - "#ddf0c3", - "#b3eed4", - "#94e6da", - "#78d1e7", - "#a3d5ed", - "#c8c3ec", - ], - "00QMEZl7ulP3f2WaX": [ - "#adda3d", - "#d0f4a9", - "#f0f5f9", - "#6db6e8", - "#2296dc", - "#0b73d1", - ], - "00QMEZzTrk06iVgaK": [ - "#e2361c", - "#f36958", - "#cdd2df", - "#a6a4af", - "#807377", - "#41372f", - ], - "00QMKP9yh8XlJYcgM": [ - "#584d36", - "#978a7c", - "#cfccc9", - "#f8fc85", - "#dfe245", - "#a9a108", - ], - "00QMSRoDq0SDYprak": [ - "#2d1e22", - "#a90f0c", - "#ee1d2b", - "#ba8476", - "#dacbc6", - "#f7f3f7", - ], - "00QMSRqFlj9B8ayZW": [ - "#db4a55", - "#f5c625", - "#408aa3", - "#b0cadb", - "#163b64", - "#744881", - ], - "00QMSSCjsJpojbpYz": [ - "#a7333a", - "#ea723e", - "#f1aa74", - "#aae39b", - "#5da773", - "#42cfbb", - ], - "00QMSSCjsJpojbpYza": [ - "#3f322f", - "#d06f4c", - "#d4b588", - "#bbe3d5", - "#71a99a", - "#4d7370", - ], - "00QMSSEJXJOUdMoSj": [ - "#ed9d5f", - "#e7caac", - "#4da1c2", - "#3c3664", - "#6c5c82", - "#ad7a8d", - ], - "00QMSSK2AEOwErS5M": [ - "#e1a895", - "#eb946b", - "#7db5d9", - "#1d4480", - "#edadcb", - "#a17588", - ], - "00QMSSK2AEOwErS5Ma": [ - "#c4575f", - "#f5976d", - "#0ba4d7", - "#0e3077", - "#f38db2", - "#746fb9", - ], - "00QMxescYuh8eYT39": [ - "#12182f", - "#4f2431", - "#844036", - "#b1663e", - "#de9548", - "#d1c6b5", - ], - "00QMxexuJYMe6enbZ": [ - "#0a122f", - "#3d2233", - "#73383a", - "#ac5541", - "#e5804f", - "#f8b778", - ], - "00QN31G5AB2FTftCe": [ - "#0e2222", - "#244c4f", - "#56888b", - "#84afb3", - "#aacbcc", - "#c8dfe0", - ], - "00QN49h9BAkHHyKJh": [ - "#ab312c", - "#d57f1c", - "#f4d42b", - "#3199cc", - "#c4c9e0", - "#1c284b", - ], - "00RB4I684QFqc2HAM": [ - "#453f38", - "#746b5d", - "#b39777", - "#c1c2b2", - "#e3dccf", - "#f1ede7", - ], - "00RB4I6NRn6CF3oS0": [ - "#4e3427", - "#7f5a47", - "#b68d73", - "#e3ccb7", - "#064857", - "#077d95", - ], - "00RB4I89XiwNSlobH": [ - "#c2936c", - "#795d45", - "#463524", - "#587479", - "#6c848c", - "#bed0de", - ], - "00RB4IFxoIYII3Cqi": [ - "#556087", - "#6e78aa", - "#adb1e6", - "#9c5d78", - "#f1a7c0", - "#c97a8e", - ], - "00RGly9mkWt6i3suL": [ - "#160e0a", - "#573014", - "#9f6135", - "#cf935c", - "#ecc18c", - "#f9eada", - ], - "00RGly9mkWt6i3suLa": [ - "#8d4a09", - "#a85f31", - "#c3764a", - "#c1861f", - "#e6ae42", - "#f9d267", - ], - "00RGlyFVinQl5cj21": [ - "#461b10", - "#95573a", - "#dfb79b", - "#75b5c7", - "#33748f", - "#0a2c42", - ], - "00RGlyJwY9q4Sh0tQ": [ - "#182d4f", - "#2e629a", - "#78a9d4", - "#8a8c99", - "#bac0cd", - "#f2f2f3", - ], - "00RGlyLkqOmPmbuX6": [ - "#3c1b18", - "#5d2328", - "#922849", - "#685256", - "#918289", - "#b2b1bd", - ], - "00RTRDAqmTEarmyeR": [ - "#241644", - "#7e4743", - "#9a614f", - "#b78265", - "#d5a584", - "#eddbd4", - ], - "00RTRDIRDbnErAXHV": [ - "#381c1a", - "#793b21", - "#b7673d", - "#e8aa7c", - "#47a0ce", - "#186086", - ], - "00RTRDIzyUse2qJ9N": [ - "#a0293c", - "#3c1e18", - "#88675f", - "#61392d", - "#5e788c", - "#a4a4a4", - ], - "00RTRDNL7MjHkDys4": [ - "#5e1b11", - "#8a4b26", - "#036a65", - "#336d50", - "#17849b", - "#b79944", - ], - "00RTRDOtbFF8KFNH9": [ - "#4c2a2b", - "#85503b", - "#a47c63", - "#c57929", - "#ceb38d", - "#e2e8c6", - ], - "00RYyJ25i8zFwOJGh": [ - "#47210d", - "#724621", - "#4d6e80", - "#9c7857", - "#c47619", - "#d5b48a", - ], - "00RYyJ4jHYfMF6yfy": [ - "#33341f", - "#61532f", - "#9b6230", - "#cc8646", - "#b3a68a", - "#f0f5f3", - ], - "00RYyJDsTKzsBBgCO": [ - "#845a14", - "#b07314", - "#d69527", - "#f4c03b", - "#758734", - "#406010", - ], - "00RlTJrw94KFOF3zL": [ - "#231927", - "#323445", - "#610d21", - "#97061d", - "#d56a76", - "#eebcb2", - ], - "00RlTJxJZ16ivgKBw": [ - "#2b340e", - "#615022", - "#9b6f3c", - "#c49560", - "#e2bb8b", - "#f3dbb6", - ], - "00RnZfzWN7Ewml76I": [ - "#563063", - "#905c70", - "#b17e84", - "#c89896", - "#d1bbb6", - "#ebe1d8", - ], - "00T2A6qx5VScIQ9bU": [ - "#341740", - "#67415f", - "#a52e45", - "#da3549", - "#d4796c", - "#ebd0b6", - ], - "00UWNvBioJ6ZSjDix": [ - "#0c0b0a", - "#482518", - "#835132", - "#a47855", - "#c4a58a", - "#efd0bc", - ], - "00UWNvEkTsOF0aQHh": [ - "#874416", - "#b56f49", - "#e69f29", - "#f3d28a", - "#c5cda5", - "#549cba", - ], - "00Yi9btvr8RY4NROm": [ - "#1c2e5e", - "#52506d", - "#9f6771", - "#d28c80", - "#c3b39d", - "#eec6a0", - ], - "00Yi9c5ifk9eXO5XX": [ - "#291e0a", - "#424127", - "#565f44", - "#765c2b", - "#988552", - "#e1c790", - ], - "00YxFeFYOuMCD0qWe": [ - "#13102b", - "#67493b", - "#887757", - "#9fa785", - "#9bc1af", - "#779a8d", - ], - "00b7Az8pDWEjBLMHs": [ - "#583824", - "#95643f", - "#bb9369", - "#5a9179", - "#85c0b1", - "#b4dfde", - ], - "00bYah8QOLZNgszuV": [ - "#6b6c56", - "#888c81", - "#b3bab9", - "#3f8aac", - "#f19028", - "#f6b661", - ], - "00bYahDYU6E7wDJwL": [ - "#cf4e23", - "#8a6b56", - "#af9682", - "#dfd5bd", - "#79a199", - "#40555a", - ], - "00bYahKZ8LbVgx54y": [ - "#88390a", - "#a9571b", - "#c77932", - "#bba333", - "#9f8b16", - "#837609", - ], - "00bYcivY8Jqx8nsiR": [ - "#3f2615", - "#67492d", - "#b7954b", - "#e0e2a1", - "#2f5f4f", - "#5c8c89", - ], - "00bYcixZkofJhLJ8w": [ - "#1f4d7b", - "#2f1013", - "#660f09", - "#a13d10", - "#684640", - "#b0947f", - ], - "00bYcj0VC9wmmKNm5": [ - "#798931", - "#e3cc1c", - "#cac7a4", - "#838da6", - "#475b7a", - "#1d3143", - ], - "00bYcj4uvi0NIlqyw": [ - "#072d29", - "#045049", - "#077667", - "#5c9d74", - "#a8ce9e", - "#effacd", - ], - "00bYcjDCylnf3k1aB": [ - "#322324", - "#92462f", - "#c56928", - "#eb5e44", - "#f49669", - "#f5aa2f", - ], - "00bYiYyzA6ODPIC8V": [ - "#322930", - "#0e6c9a", - "#e56227", - "#8998a0", - "#c9d1d5", - "#ebf1f5", - ], - "00bYiZ8YHLsLMA7j3": [ - "#372a2b", - "#ca8a51", - "#b8c9d0", - "#7ea0c5", - "#6786ac", - "#555f6f", - ], - "00f5whlJFUwx7AaEe": [ - "#50302b", - "#805a54", - "#9d776a", - "#bc9f89", - "#d7d7c1", - "#f0f2d8", - ], - "00g3Jv9zydyJs2QlX": [ - "#171214", - "#061844", - "#062460", - "#493127", - "#675e58", - "#e5ddd9", - ], - "00g3JvJ0ZydpXXvEC": [ - "#9f7374", - "#d3ac98", - "#edd2bd", - "#c4ddf0", - "#9aadcf", - "#354372", - ], - "00gSFutQrW4MxihX7": [ - "#284c5a", - "#3e5c64", - "#714a37", - "#976950", - "#a19081", - "#b2aea5", - ], - "00i0fT276sz5H8vMy": [ - "#806364", - "#c48f81", - "#f1bbb8", - "#607f3c", - "#355337", - "#052b36", - ], - "00i0iHNQPUpX6Jzsb": [ - "#686565", - "#888986", - "#56110d", - "#92342a", - "#d87f6e", - "#f4cbb0", - ], - "00i0iHUSVMHOENQof": [ - "#d5e9f6", - "#91bedb", - "#5299c4", - "#166ea4", - "#2a4866", - "#1b232e", - ], - "00i0iHWHzzEoacpfQ": [ - "#e76274", - "#d71440", - "#033a73", - "#051f41", - "#bfa2ba", - "#90497f", - ], - "00i0iHXq6xPzf1pt0": [ - "#2e361c", - "#707814", - "#ada713", - "#c1d2cd", - "#7ba7b5", - "#3c758f", - ], - "00iGNImQqINC9iQ1D": [ - "#72564a", - "#a29aa2", - "#129dab", - "#f78915", - "#e0a657", - "#f3d1a9", - ], - "00iGNInM4y8vS5Aon": [ - "#2a3c6f", - "#8c5788", - "#b6762d", - "#a8928a", - "#ebbc44", - "#e0cdb8", - ], - "00qAPJWAuZtV1JQeL": [ - "#102024", - "#6b7438", - "#a7a632", - "#cccc67", - "#e4c80e", - "#d5dfbf", - ], - "00qAPJlU7mDXqCkiN": [ - "#392418", - "#6d2c23", - "#ac2d25", - "#d8ada6", - "#f4ca49", - "#f4d9a4", - ], - "00qAPJU0vXnWXxf1k": [ - "#be1f1c", - "#73160c", - "#d28874", - "#ecdfda", - "#746647", - "#262412", - ], - "00qAPJp5JxBfA0TbH": [ - "#d6bbb5", - "#9d7d78", - "#e78a7f", - "#e14337", - "#813436", - "#2d2124", - ], - "00qAPJgQvoDkRkQTN": [ - "#181f15", - "#2e4846", - "#571b10", - "#991912", - "#c15a36", - "#eba56c", - ], - "00qAPJgiiIyY8shVo": [ - "#101012", - "#973828", - "#556150", - "#8d9883", - "#b1bca6", - "#d6dac2", - ], - "00qAZjyY6S1ycz77W": [ - "#49332d", - "#715650", - "#9e8178", - "#e2cdb5", - "#d1b38b", - "#ebc02d", - ], - "00qAZjzBDFZPMdKr3": [ - "#e6cec2", - "#f1825c", - "#d5a34c", - "#a16818", - "#6e535e", - "#3e2830", - ], - "00qAZjpCY9A2fnaKR": [ - "#d07c14", - "#e2a220", - "#e1ccbd", - "#d0af89", - "#a7836d", - "#7e4114", - ], - "00qAZjs8gQNFggT0M": [ - "#898885", - "#b3b7bd", - "#dfe0e1", - "#ede14b", - "#a6a814", - "#44432e", - ], - "00qAZk070SYGij58C": [ - "#857b84", - "#b1a7b0", - "#d0c7d0", - "#e7e0e8", - "#faeceb", - "#e4e9fa", - ], - "00qAgiXIRZqDXJsI8": [ - "#2d342c", - "#25634c", - "#6f513d", - "#80765d", - "#b3a586", - "#d8ddc3", - ], - "00qHifB285rfalpIo": [ - "#200a0e", - "#73263e", - "#8a84a0", - "#06739b", - "#10abca", - "#8dccdc", - ], - "00qHkUndEk4MzLo6H": [ - "#2f1a0f", - "#d9662e", - "#a87958", - "#c0b5a9", - "#e5e1e1", - "#879293", - ], - "00qHekhPJJlwTDcMi": [ - "#301912", - "#dc1221", - "#6c6250", - "#b69571", - "#d8d7cd", - "#94afb4", - ], - "00qHekwxmgUGlfAWi": [ - "#140e0d", - "#794121", - "#cc8c3e", - "#c3af92", - "#7e7e77", - "#2d3d3a", - ], - "00qHo5P8w5tq00yLM": [ - "#17565c", - "#3e8c8f", - "#8fccda", - "#ffeecb", - "#e4cfa5", - "#b3a07b", - ], - "00qHekwhYwYf6QJGD": [ - "#241912", - "#7e1c13", - "#d95832", - "#525e62", - "#b9a68c", - "#e3d7c0", - ], - "00qHxct5IvpT7kl9p": [ - "#1c1319", - "#463e3b", - "#095347", - "#7a3325", - "#e3773d", - "#f4d19b", - ], - "00qHzDGNbubSeZXrS": [ - "#09194e", - "#233585", - "#895943", - "#c08d72", - "#c0b5ad", - "#e7e7e6", - ], - "00QMA8iIbk7Biajay": [ - "#103148", - "#fc9128", - "#fdd33c", - "#97dcdb", - "#dcdddd", - "#f0f0ef", - ], - "00QMxewR6IS5vyatN": [ - "#111a26", - "#313238", - "#614e40", - "#a69075", - "#e08609", - "#9d611a", - ], - "00QMEZhEMNGnZZxL4": [ - "#44060c", - "#8f040a", - "#ce060d", - "#768695", - "#3a3d45", - "#110b0e", - ], - "00QMEZSYbyUNt0041": [ - "#081d4f", - "#0a3e83", - "#5385a6", - "#485966", - "#aeab9e", - "#ebe1c7", - ], - "00qIEl4NwoLkiinhc": [ - "#0f3e49", - "#47555c", - "#798388", - "#1fb7c1", - "#a1cad1", - "#e8ecef", - ], - "00qIG1KwWOlIJkB81": [ - "#825e46", - "#e69f36", - "#3c7aa5", - "#2c3f51", - "#aaa2b7", - "#f1ecf2", - ], - "00qIIS5LLApenMByv": [ - "#bc4a0f", - "#e9762e", - "#f2d0c3", - "#c18b6c", - "#8a5c3d", - "#46240e", - ], - "00qILiVShgC82zOHm": [ - "#b28c80", - "#fac547", - "#fdb684", - "#fbdabf", - "#4d6c80", - "#283e41", - "#45551f", - ], - "00qIPBCHil9FW2idX": [ - "#3b3b27", - "#737674", - "#aaaeba", - "#d1b860", - "#cea820", - "#7e792c", - ], - "00qIR1FROo31bNduE": [ - "#b01136", - "#f30b68", - "#db8495", - "#dfb3b4", - "#e4d8d2", - "#f2f4eb", - ], - "00sz5Uxo4ByGDH6tQ": [ - "#091b0f", - "#1d4219", - "#4a5c1e", - "#6cb7cf", - "#a6dae0", - "#e8bc7f", - ], - "00sz7CFkl88HCRtUW": [ - "#2f0b0e", - "#5e282a", - "#ad3a3f", - "#e55a54", - "#f9c3d5", - "#f89b6a", - ], - "00sz9mhhmiZmgR9r6": [ - "#f29269", - "#e8c1ac", - "#856412", - "#3e2e0c", - "#436779", - "#acc9e6", - ], - "00t03UwzLJ4HO8u5o": [ - "#674ca2", - "#8bbbd9", - "#736a62", - "#e3d9d0", - "#d9bb84", - "#f25041", - ], - "00tAmMznURg0qH4jp": [ - "#0e0b0c", - "#697289", - "#634e4b", - "#772809", - "#d75314", - "#e7dfd6", - ], - "00tAnftt5xsJlMhDZ": [ - "#083034", - "#2d4d4d", - "#9d988e", - "#cfd0ce", - "#fbc47f", - "#d98f4d", - ], - "00tAq6qSD1erL65L8": [ - "#161212", - "#03515f", - "#884027", - "#d87945", - "#ad9d92", - "#d1d2d2", - ], - "00tAsz6uflx4ciwdA": [ - "#1f2332", - "#47363b", - "#716476", - "#9d8f92", - "#f06b14", - "#feb627", - ], - "00tAuOG3R9ivBX2gO": [ - "#7a0c04", - "#bb2605", - "#eb600c", - "#faaa27", - "#1b1e1a", - "#414a47", - ], - "00tAvqx4Pm26XfKhx": [ - "#34240b", - "#7c7e77", - "#cf8f17", - "#b2daed", - "#2ca6d6", - "#0c4f6a", - ], - "00uKBokFLvI0Epxqx": [ - "#1f2844", - "#556881", - "#7f411d", - "#f66811", - "#e9a371", - "#fbf4e1", - ], - "00uOogzjozpSchnrp": [ - "#00334b", - "#6699bb", - "#7a0011", - "#c21122", - "#e5bf99", - "#fef2d8", - ], - "00uOqryM4SOXgd7S0": [ - "#001580", - "#00779a", - "#00afbf", - "#f07167", - "#fed9b7", - "#fdfcdc", - ], - "00uOuzh5czMDF0xUE": [ - "#552c5c", - "#945b9d", - "#de7f81", - "#f2ac69", - "#e7c1a3", - "#ffede1", - ], - "00uP6PKyOf1h08rEq": [ - "#990a21", - "#e71d3c", - "#ff9519", - "#fafff7", - "#2ec4ad", - "#01182b", - ], - "00uPLAKIHr3vJ0xUB": [ - "#03223e", - "#0f4272", - "#fdf0cc", - "#f9d860", - "#f49a4d", - "#f24c2c", - ], - "00uPN5nCkNSzg7ri4": [ - "#5f00ff", - "#9f37ff", - "#0a99cc", - "#17e8ff", - "#c0c1cf", - "#f0f2f8", - ], - "00uPNyZwgGQhZDUaQ": [ - "#401447", - "#077085", - "#1fe1bb", - "#adfc3a", - "#c9ffbc", - "#f2ffec", - ], - "013j2ZZYIvwFGp4yL": [ - "#551818", - "#9b2c1b", - "#e15847", - "#efa995", - "#c8bcb5", - "#f0e9e3", - ], - "013j3Yj2Vj9r3oaql": [ - "#322b30", - "#ef2e20", - "#bcd6c6", - "#7dadaf", - "#59878b", - "#0b7d90", - ], - "014c40MQuHMbjhkDu": [ - "#1a0e24", - "#6a8aa6", - "#aedff2", - "#fabd05", - "#bf7e04", - "#734743", - ], - "014c5Q2Qc8D1tNdAn": [ - "#0d0d0d", - "#0c4451", - "#61bf36", - "#f2c029", - "#dd9a25", - "#f2f2f4", - ], - "014c6avJ50DRl2NSK": [ - "#252426", - "#ad0401", - "#e90408", - "#fc9518", - "#62c3d9", - "#b6e7f2", - ], - "014c7dTQjU2TA8F68": [ - "#010d00", - "#400d01", - "#595859", - "#bfb4aa", - "#f1c8d8", - "#f2f2f2", - ], - "014c8uRpFlkiHSo9k": [ - "#0b0d0c", - "#025940", - "#16998c", - "#85a0a6", - "#a67c84", - "#f2858e", - ], - "014cACEjxmYUHo9fx": [ - "#1e2e46", - "#8d6747", - "#aa987e", - "#da7347", - "#eea789", - "#f5bfc2", - ], - "014cBhci3KKThfPSX": [ - "#463114", - "#976e2e", - "#bbab49", - "#a7a99a", - "#437c84", - "#144f66", - ], - "014cCBZzmDcqRiRMv": [ - "#d73215", - "#b2a78e", - "#f2f3f6", - "#bbd6c0", - "#799d91", - "#193d2c", - ], - "014cMCdTh3bwH8wVB": [ - "#f2b93a", - "#494f31", - "#01596e", - "#cfd4f4", - "#ba78d4", - "#d802b8", - ], - "014cNjiiOwqOhSKJS": [ - "#12355a", - "#640ea8", - "#a21dd0", - "#0ba67e", - "#81d0a3", - "#d0e4d3", - ], - "014qFZbOZZ3YOqfdS": [ - "#16274b", - "#237388", - "#8593a9", - "#6d135b", - "#e62984", - "#e7b0c7", - ], - "014r4557GBXovXsQ2": [ - "#f57a3f", - "#f59637", - "#f2bd2a", - "#efd94d", - "#dfe18a", - "#ddf0bf", - ], - "014r6uHUtjyoZvMXw": [ - "#fc65a0", - "#eb13a3", - "#603423", - "#b6875c", - "#c2a9a5", - "#d0d9d4", - ], - "014rzaZCKrmbLLuJu": [ - "#29110a", - "#603427", - "#f23b2c", - "#c8bcbe", - "#e8dadb", - "#fff5f5", - ], - "014whoAyxl5gzJwC1": [ - "#7e95a0", - "#7da8b0", - "#abc6c7", - "#9ba7a1", - "#c1bbad", - "#d0d5d1", - ], - "014wjGaMtVyXGNzL6": [ - "#172328", - "#2a454b", - "#516f75", - "#7f9b9f", - "#afc4c6", - "#543e31", - ], - "014wjKhdj0Me4MQvc": [ - "#272817", - "#484223", - "#9d7a20", - "#ed9120", - "#bcb69b", - "#4e5c66", - ], - "014wkzZloB6Lgkluy": [ - "#56261b", - "#9b5847", - "#e28f6d", - "#e3c4b5", - "#fadaae", - "#e0eae8", - ], - "014wmwCkvNXm2qCVW": [ - "#0a041b", - "#2a117e", - "#240cf4", - "#7d2ff4", - "#eb72fc", - "#fceffc", - ], - "014wozXMrzwiMiM3x": [ - "#2d6253", - "#62aec8", - "#f16844", - "#f3b436", - "#d6ad9c", - "#efe0cf", - ], - "014wxZ7JcSYlbNfxL": [ - "#182d3a", - "#65493b", - "#897969", - "#b4a692", - "#d47827", - "#facb8d", - ], - "014xmJC3rdNVkG5Ke": [ - "#021436", - "#0d2f73", - "#0150a5", - "#6e6249", - "#dfd2ba", - "#eba21a", - ], - "014xtY7NR86Aa2AMH": [ - "#e2f4eb", - "#bedbde", - "#88bada", - "#2190d8", - "#8672ae", - "#5f1c5d", - ], - "014xxHPGJc41M07kL": [ - "#1d070d", - "#3b1625", - "#711c44", - "#ab1934", - "#858b98", - "#404252", - ], + "00ORLwKeosxtEeZxq": [ + "#18132c", + "#492f5e", + "#775692", + "#bf89bb", + "#ecb1d0", + "#fbd0c8", + ], + "00OkEXVdMQmQ1oQTp": [ + "#dbb98e", + "#9e8f52", + "#607630", + "#2c4c1c", + "#0d6176", + "#589cb4", + ], + "00OqoPfvP6y1EmXZQ": [ + "#0d1e63", + "#4e49a7", + "#1890cc", + "#9a8a94", + "#c6c3d2", + "#efeef4", + ], + "00OwGARqASN2zngyg": [ + "#4e351d", + "#6c5133", + "#896a46", + "#b18959", + "#c9bca8", + "#f7efe4", + ], + "00PBWZvgJvi4gdxGj": [ + "#070b04", + "#2e3911", + "#646e32", + "#949d85", + "#b2baac", + "#d9ddd9", + ], + "00PhEgF9AlI5PTrLB": [ + "#150a23", + "#3c0f3f", + "#591962", + "#493b92", + "#5465af", + "#6175b8", + ], + "00PhGSfzpeU5Bkh47a": [ + "#440807", + "#6c0f0b", + "#910711", + "#8c4c2a", + "#b87d4e", + "#d9b995", + ], + "00Q9Yxm7DrZXqTkyS": [ + "#2f1864", + "#e40302", + "#f25c22", + "#d987bd", + "#44b6e7", + "#e3dadd", + ], + "00QLNHv6JW1bL031O": [ + "#d03835", + "#fc4340", + "#c2756a", + "#cf998d", + "#f2c7b9", + "#f2dcd5", + ], + "00QLNIKFhjs5rJCjr": [ + "#f77908", + "#ba9623", + "#c6a489", + "#bbc3bb", + "#9dc9be", + "#668c92", + ], + "00QLNIQXmbJgghRXg": [ + "#502527", + "#366974", + "#d93d0f", + "#fc8b1e", + "#efa481", + "#f2ebe5", + ], + "00QLNIVe1AkurVqYj": [ + "#4f4c63", + "#7288a5", + "#91a5be", + "#c36b7e", + "#f34452", + "#b5172a", + ], + "00QLcP0k2fPAz0KHR": [ + "#814232", + "#b87931", + "#dfa531", + "#e3d1be", + "#d7b776", + "#a69e85", + ], + "00QLfPj2trTnyJHAg": [ + "#15253c", + "#552f34", + "#8f1f21", + "#7a6574", + "#b0a0b3", + "#d1e6ed", + ], + "00QLfPkuNx1jE7sMm": [ + "#1c161c", + "#413a42", + "#6f6166", + "#9f8b8c", + "#d7c4c3", + "#fbeae4", + ], + "00QLfPm4UfkMiaicq": [ + "#3e3e25", + "#664d1c", + "#a2510d", + "#ce881d", + "#f6cb53", + "#fefce8", + ], + "00QLfPtu33OAdSN4Y": [ + "#60281b", + "#a34236", + "#f1612f", + "#cb9876", + "#ebd8be", + "#788e26", + ], + "00QLfPusSpy3uFOpb": [ + "#282819", + "#4e572f", + "#787972", + "#aaaba7", + "#b4e614", + "#d5ff3f", + ], + "00QLj3F8heV6QT4YG": [ + "#b5704e", + "#5a4940", + "#282820", + "#0fac9d", + "#badcd7", + "#6d8484", + ], + "00QLj3PylHkh8qY2R": [ + "#411c20", + "#b71022", + "#f63a3a", + "#c1c3d1", + "#8a858e", + "#5c555d", + ], + "00QLj3Yj1pUNnTKVY": [ + "#7c7575", + "#743d28", + "#e88b46", + "#caa78d", + "#acd4d4", + "#1d2544", + ], + "00QLj3dmHiT8Ep1Un": [ + "#1a51aa", + "#167418", + "#5d8937", + "#95a566", + "#b9b296", + "#e0dab5", + ], + "00QLj3fsFErIl5W0b": [ + "#763a20", + "#c78f67", + "#f27f14", + "#86949b", + "#37516c", + "#1c222f", + ], + "00QLn7BH2doQfh4Yw": [ + "#401918", + "#9f1d1f", + "#ab6868", + "#d1d3d3", + "#39b7b6", + "#336b6b", + ], + "00QLn7G59JhKtuUs0": [ + "#0a0e40", + "#715589", + "#c7264b", + "#3fa1cc", + "#fa7b0e", + "#e8b7ac", + ], + "00QLn7Lr9V98YD0zY": [ + "#ab3051", + "#e31f4b", + "#c68575", + "#f3bda5", + "#652b55", + "#834270", + ], + "00QLn7XfPKvMKCrzO": [ + "#73272f", + "#e65633", + "#e5af98", + "#f2dfcd", + "#43b6ad", + "#181240", + ], + "00QLn7oETUQZKKr34": [ + "#392325", + "#504d54", + "#89462c", + "#da5c74", + "#908a92", + "#e6cbce", + ], + "00QLqYLjs4H7kdHrp": [ + "#3d4d20", + "#73854d", + "#aeb87f", + "#6e9db5", + "#4c6f8e", + "#1c4861", + ], + "00QLqYQ3HE1oy3ByI": [ + "#97867e", + "#894e29", + "#d7a31a", + "#f2d95b", + "#dcdedb", + "#92b1cb", + ], + "00QLqYS2ZOgkMeMAE": [ + "#3b4227", + "#255c50", + "#2d7663", + "#788664", + "#b3ad81", + "#e3d6ad", + ], + "00QLslJAGNA8YREKh": [ + "#ffd8d2", + "#fbc6b7", + "#fd9b79", + "#ece8e8", + "#c2bab6", + "#888078", + ], + "00QMA8RhpXdgwPtLn": [ + "#4d3621", + "#806b52", + "#9b9685", + "#acb8af", + "#bfd4d0", + "#60acb2", + ], + "00QMA8SkIwjW5KEch": [ + "#f0181f", + "#b51c1c", + "#b4a8a2", + "#dcd4db", + "#75787a", + "#3c373b", + ], + "00QMA8h2BH69zyEk0": [ + "#0a0320", + "#260d6c", + "#613dda", + "#b4b6ac", + "#f6e019", + "#eef5fa", + ], + "00QMA8tedClukB7IL": [ + "#6f402c", + "#ac3038", + "#ad796b", + "#c9b4a2", + "#cbcbc2", + "#ebe3d8", + ], + "00QMEZVdDC6dMYpjp": [ + "#df4341", + "#e8817e", + "#b1a789", + "#d6d1ba", + "#b1b828", + "#c1ed7f", + ], + "00QMEZazmZG85DaWw": [ + "#1b2434", + "#47423d", + "#6a614a", + "#8b8262", + "#d9d3c7", + "#fae7dc", + ], + "00QMEZb0EorAC0k7y": [ + "#2f112d", + "#520330", + "#7f0233", + "#c52b3e", + "#fc714b", + "#fedac0", + ], + "00QMEZgMsSVZavaNd": [ + "#ddf0c3", + "#b3eed4", + "#94e6da", + "#78d1e7", + "#a3d5ed", + "#c8c3ec", + ], + "00QMEZl7ulP3f2WaX": [ + "#adda3d", + "#d0f4a9", + "#f0f5f9", + "#6db6e8", + "#2296dc", + "#0b73d1", + ], + "00QMEZzTrk06iVgaK": [ + "#e2361c", + "#f36958", + "#cdd2df", + "#a6a4af", + "#807377", + "#41372f", + ], + "00QMKP9yh8XlJYcgM": [ + "#584d36", + "#978a7c", + "#cfccc9", + "#f8fc85", + "#dfe245", + "#a9a108", + ], + "00QMSRoDq0SDYprak": [ + "#2d1e22", + "#a90f0c", + "#ee1d2b", + "#ba8476", + "#dacbc6", + "#f7f3f7", + ], + "00QMSRqFlj9B8ayZW": [ + "#db4a55", + "#f5c625", + "#408aa3", + "#b0cadb", + "#163b64", + "#744881", + ], + "00QMSSCjsJpojbpYz": [ + "#a7333a", + "#ea723e", + "#f1aa74", + "#aae39b", + "#5da773", + "#42cfbb", + ], + "00QMSSCjsJpojbpYza": [ + "#3f322f", + "#d06f4c", + "#d4b588", + "#bbe3d5", + "#71a99a", + "#4d7370", + ], + "00QMSSEJXJOUdMoSj": [ + "#ed9d5f", + "#e7caac", + "#4da1c2", + "#3c3664", + "#6c5c82", + "#ad7a8d", + ], + "00QMSSK2AEOwErS5M": [ + "#e1a895", + "#eb946b", + "#7db5d9", + "#1d4480", + "#edadcb", + "#a17588", + ], + "00QMSSK2AEOwErS5Ma": [ + "#c4575f", + "#f5976d", + "#0ba4d7", + "#0e3077", + "#f38db2", + "#746fb9", + ], + "00QMxescYuh8eYT39": [ + "#12182f", + "#4f2431", + "#844036", + "#b1663e", + "#de9548", + "#d1c6b5", + ], + "00QMxexuJYMe6enbZ": [ + "#0a122f", + "#3d2233", + "#73383a", + "#ac5541", + "#e5804f", + "#f8b778", + ], + "00QN31G5AB2FTftCe": [ + "#0e2222", + "#244c4f", + "#56888b", + "#84afb3", + "#aacbcc", + "#c8dfe0", + ], + "00QN49h9BAkHHyKJh": [ + "#ab312c", + "#d57f1c", + "#f4d42b", + "#3199cc", + "#c4c9e0", + "#1c284b", + ], + "00RB4I684QFqc2HAM": [ + "#453f38", + "#746b5d", + "#b39777", + "#c1c2b2", + "#e3dccf", + "#f1ede7", + ], + "00RB4I6NRn6CF3oS0": [ + "#4e3427", + "#7f5a47", + "#b68d73", + "#e3ccb7", + "#064857", + "#077d95", + ], + "00RB4I89XiwNSlobH": [ + "#c2936c", + "#795d45", + "#463524", + "#587479", + "#6c848c", + "#bed0de", + ], + "00RB4IFxoIYII3Cqi": [ + "#556087", + "#6e78aa", + "#adb1e6", + "#9c5d78", + "#f1a7c0", + "#c97a8e", + ], + "00RGly9mkWt6i3suL": [ + "#160e0a", + "#573014", + "#9f6135", + "#cf935c", + "#ecc18c", + "#f9eada", + ], + "00RGly9mkWt6i3suLa": [ + "#8d4a09", + "#a85f31", + "#c3764a", + "#c1861f", + "#e6ae42", + "#f9d267", + ], + "00RGlyFVinQl5cj21": [ + "#461b10", + "#95573a", + "#dfb79b", + "#75b5c7", + "#33748f", + "#0a2c42", + ], + "00RGlyJwY9q4Sh0tQ": [ + "#182d4f", + "#2e629a", + "#78a9d4", + "#8a8c99", + "#bac0cd", + "#f2f2f3", + ], + "00RGlyLkqOmPmbuX6": [ + "#3c1b18", + "#5d2328", + "#922849", + "#685256", + "#918289", + "#b2b1bd", + ], + "00RTRDAqmTEarmyeR": [ + "#241644", + "#7e4743", + "#9a614f", + "#b78265", + "#d5a584", + "#eddbd4", + ], + "00RTRDIRDbnErAXHV": [ + "#381c1a", + "#793b21", + "#b7673d", + "#e8aa7c", + "#47a0ce", + "#186086", + ], + "00RTRDIzyUse2qJ9N": [ + "#a0293c", + "#3c1e18", + "#88675f", + "#61392d", + "#5e788c", + "#a4a4a4", + ], + "00RTRDNL7MjHkDys4": [ + "#5e1b11", + "#8a4b26", + "#036a65", + "#336d50", + "#17849b", + "#b79944", + ], + "00RTRDOtbFF8KFNH9": [ + "#4c2a2b", + "#85503b", + "#a47c63", + "#c57929", + "#ceb38d", + "#e2e8c6", + ], + "00RYyJ25i8zFwOJGh": [ + "#47210d", + "#724621", + "#4d6e80", + "#9c7857", + "#c47619", + "#d5b48a", + ], + "00RYyJ4jHYfMF6yfy": [ + "#33341f", + "#61532f", + "#9b6230", + "#cc8646", + "#b3a68a", + "#f0f5f3", + ], + "00RYyJDsTKzsBBgCO": [ + "#845a14", + "#b07314", + "#d69527", + "#f4c03b", + "#758734", + "#406010", + ], + "00RlTJrw94KFOF3zL": [ + "#231927", + "#323445", + "#610d21", + "#97061d", + "#d56a76", + "#eebcb2", + ], + "00RlTJxJZ16ivgKBw": [ + "#2b340e", + "#615022", + "#9b6f3c", + "#c49560", + "#e2bb8b", + "#f3dbb6", + ], + "00RnZfzWN7Ewml76I": [ + "#563063", + "#905c70", + "#b17e84", + "#c89896", + "#d1bbb6", + "#ebe1d8", + ], + "00T2A6qx5VScIQ9bU": [ + "#341740", + "#67415f", + "#a52e45", + "#da3549", + "#d4796c", + "#ebd0b6", + ], + "00UWNvBioJ6ZSjDix": [ + "#0c0b0a", + "#482518", + "#835132", + "#a47855", + "#c4a58a", + "#efd0bc", + ], + "00UWNvEkTsOF0aQHh": [ + "#874416", + "#b56f49", + "#e69f29", + "#f3d28a", + "#c5cda5", + "#549cba", + ], + "00Yi9btvr8RY4NROm": [ + "#1c2e5e", + "#52506d", + "#9f6771", + "#d28c80", + "#c3b39d", + "#eec6a0", + ], + "00Yi9c5ifk9eXO5XX": [ + "#291e0a", + "#424127", + "#565f44", + "#765c2b", + "#988552", + "#e1c790", + ], + "00YxFeFYOuMCD0qWe": [ + "#13102b", + "#67493b", + "#887757", + "#9fa785", + "#9bc1af", + "#779a8d", + ], + "00b7Az8pDWEjBLMHs": [ + "#583824", + "#95643f", + "#bb9369", + "#5a9179", + "#85c0b1", + "#b4dfde", + ], + "00bYah8QOLZNgszuV": [ + "#6b6c56", + "#888c81", + "#b3bab9", + "#3f8aac", + "#f19028", + "#f6b661", + ], + "00bYahDYU6E7wDJwL": [ + "#cf4e23", + "#8a6b56", + "#af9682", + "#dfd5bd", + "#79a199", + "#40555a", + ], + "00bYahKZ8LbVgx54y": [ + "#88390a", + "#a9571b", + "#c77932", + "#bba333", + "#9f8b16", + "#837609", + ], + "00bYcivY8Jqx8nsiR": [ + "#3f2615", + "#67492d", + "#b7954b", + "#e0e2a1", + "#2f5f4f", + "#5c8c89", + ], + "00bYcixZkofJhLJ8w": [ + "#1f4d7b", + "#2f1013", + "#660f09", + "#a13d10", + "#684640", + "#b0947f", + ], + "00bYcj0VC9wmmKNm5": [ + "#798931", + "#e3cc1c", + "#cac7a4", + "#838da6", + "#475b7a", + "#1d3143", + ], + "00bYcj4uvi0NIlqyw": [ + "#072d29", + "#045049", + "#077667", + "#5c9d74", + "#a8ce9e", + "#effacd", + ], + "00bYcjDCylnf3k1aB": [ + "#322324", + "#92462f", + "#c56928", + "#eb5e44", + "#f49669", + "#f5aa2f", + ], + "00bYiYyzA6ODPIC8V": [ + "#322930", + "#0e6c9a", + "#e56227", + "#8998a0", + "#c9d1d5", + "#ebf1f5", + ], + "00bYiZ8YHLsLMA7j3": [ + "#372a2b", + "#ca8a51", + "#b8c9d0", + "#7ea0c5", + "#6786ac", + "#555f6f", + ], + "00f5whlJFUwx7AaEe": [ + "#50302b", + "#805a54", + "#9d776a", + "#bc9f89", + "#d7d7c1", + "#f0f2d8", + ], + "00g3Jv9zydyJs2QlX": [ + "#171214", + "#061844", + "#062460", + "#493127", + "#675e58", + "#e5ddd9", + ], + "00g3JvJ0ZydpXXvEC": [ + "#9f7374", + "#d3ac98", + "#edd2bd", + "#c4ddf0", + "#9aadcf", + "#354372", + ], + "00gSFutQrW4MxihX7": [ + "#284c5a", + "#3e5c64", + "#714a37", + "#976950", + "#a19081", + "#b2aea5", + ], + "00i0fT276sz5H8vMy": [ + "#806364", + "#c48f81", + "#f1bbb8", + "#607f3c", + "#355337", + "#052b36", + ], + "00i0iHNQPUpX6Jzsb": [ + "#686565", + "#888986", + "#56110d", + "#92342a", + "#d87f6e", + "#f4cbb0", + ], + "00i0iHUSVMHOENQof": [ + "#d5e9f6", + "#91bedb", + "#5299c4", + "#166ea4", + "#2a4866", + "#1b232e", + ], + "00i0iHWHzzEoacpfQ": [ + "#e76274", + "#d71440", + "#033a73", + "#051f41", + "#bfa2ba", + "#90497f", + ], + "00i0iHXq6xPzf1pt0": [ + "#2e361c", + "#707814", + "#ada713", + "#c1d2cd", + "#7ba7b5", + "#3c758f", + ], + "00iGNImQqINC9iQ1D": [ + "#72564a", + "#a29aa2", + "#129dab", + "#f78915", + "#e0a657", + "#f3d1a9", + ], + "00iGNInM4y8vS5Aon": [ + "#2a3c6f", + "#8c5788", + "#b6762d", + "#a8928a", + "#ebbc44", + "#e0cdb8", + ], + "00qAPJWAuZtV1JQeL": [ + "#102024", + "#6b7438", + "#a7a632", + "#cccc67", + "#e4c80e", + "#d5dfbf", + ], + "00qAPJlU7mDXqCkiN": [ + "#392418", + "#6d2c23", + "#ac2d25", + "#d8ada6", + "#f4ca49", + "#f4d9a4", + ], + "00qAPJU0vXnWXxf1k": [ + "#be1f1c", + "#73160c", + "#d28874", + "#ecdfda", + "#746647", + "#262412", + ], + "00qAPJp5JxBfA0TbH": [ + "#d6bbb5", + "#9d7d78", + "#e78a7f", + "#e14337", + "#813436", + "#2d2124", + ], + "00qAPJgQvoDkRkQTN": [ + "#181f15", + "#2e4846", + "#571b10", + "#991912", + "#c15a36", + "#eba56c", + ], + "00qAPJgiiIyY8shVo": [ + "#101012", + "#973828", + "#556150", + "#8d9883", + "#b1bca6", + "#d6dac2", + ], + "00qAZjyY6S1ycz77W": [ + "#49332d", + "#715650", + "#9e8178", + "#e2cdb5", + "#d1b38b", + "#ebc02d", + ], + "00qAZjzBDFZPMdKr3": [ + "#e6cec2", + "#f1825c", + "#d5a34c", + "#a16818", + "#6e535e", + "#3e2830", + ], + "00qAZjpCY9A2fnaKR": [ + "#d07c14", + "#e2a220", + "#e1ccbd", + "#d0af89", + "#a7836d", + "#7e4114", + ], + "00qAZjs8gQNFggT0M": [ + "#898885", + "#b3b7bd", + "#dfe0e1", + "#ede14b", + "#a6a814", + "#44432e", + ], + "00qAZk070SYGij58C": [ + "#857b84", + "#b1a7b0", + "#d0c7d0", + "#e7e0e8", + "#faeceb", + "#e4e9fa", + ], + "00qAgiXIRZqDXJsI8": [ + "#2d342c", + "#25634c", + "#6f513d", + "#80765d", + "#b3a586", + "#d8ddc3", + ], + "00qHifB285rfalpIo": [ + "#200a0e", + "#73263e", + "#8a84a0", + "#06739b", + "#10abca", + "#8dccdc", + ], + "00qHkUndEk4MzLo6H": [ + "#2f1a0f", + "#d9662e", + "#a87958", + "#c0b5a9", + "#e5e1e1", + "#879293", + ], + "00qHekhPJJlwTDcMi": [ + "#301912", + "#dc1221", + "#6c6250", + "#b69571", + "#d8d7cd", + "#94afb4", + ], + "00qHekwxmgUGlfAWi": [ + "#140e0d", + "#794121", + "#cc8c3e", + "#c3af92", + "#7e7e77", + "#2d3d3a", + ], + "00qHo5P8w5tq00yLM": [ + "#17565c", + "#3e8c8f", + "#8fccda", + "#ffeecb", + "#e4cfa5", + "#b3a07b", + ], + "00qHekwhYwYf6QJGD": [ + "#241912", + "#7e1c13", + "#d95832", + "#525e62", + "#b9a68c", + "#e3d7c0", + ], + "00qHxct5IvpT7kl9p": [ + "#1c1319", + "#463e3b", + "#095347", + "#7a3325", + "#e3773d", + "#f4d19b", + ], + "00qHzDGNbubSeZXrS": [ + "#09194e", + "#233585", + "#895943", + "#c08d72", + "#c0b5ad", + "#e7e7e6", + ], + "00QMA8iIbk7Biajay": [ + "#103148", + "#fc9128", + "#fdd33c", + "#97dcdb", + "#dcdddd", + "#f0f0ef", + ], + "00QMxewR6IS5vyatN": [ + "#111a26", + "#313238", + "#614e40", + "#a69075", + "#e08609", + "#9d611a", + ], + "00QMEZhEMNGnZZxL4": [ + "#44060c", + "#8f040a", + "#ce060d", + "#768695", + "#3a3d45", + "#110b0e", + ], + "00QMEZSYbyUNt0041": [ + "#081d4f", + "#0a3e83", + "#5385a6", + "#485966", + "#aeab9e", + "#ebe1c7", + ], + "00qIEl4NwoLkiinhc": [ + "#0f3e49", + "#47555c", + "#798388", + "#1fb7c1", + "#a1cad1", + "#e8ecef", + ], + "00qIG1KwWOlIJkB81": [ + "#825e46", + "#e69f36", + "#3c7aa5", + "#2c3f51", + "#aaa2b7", + "#f1ecf2", + ], + "00qIIS5LLApenMByv": [ + "#bc4a0f", + "#e9762e", + "#f2d0c3", + "#c18b6c", + "#8a5c3d", + "#46240e", + ], + "00qILiVShgC82zOHm": [ + "#b28c80", + "#fac547", + "#fdb684", + "#fbdabf", + "#4d6c80", + "#283e41", + "#45551f", + ], + "00qIPBCHil9FW2idX": [ + "#3b3b27", + "#737674", + "#aaaeba", + "#d1b860", + "#cea820", + "#7e792c", + ], + "00qIR1FROo31bNduE": [ + "#b01136", + "#f30b68", + "#db8495", + "#dfb3b4", + "#e4d8d2", + "#f2f4eb", + ], + "00sz5Uxo4ByGDH6tQ": [ + "#091b0f", + "#1d4219", + "#4a5c1e", + "#6cb7cf", + "#a6dae0", + "#e8bc7f", + ], + "00sz7CFkl88HCRtUW": [ + "#2f0b0e", + "#5e282a", + "#ad3a3f", + "#e55a54", + "#f9c3d5", + "#f89b6a", + ], + "00sz9mhhmiZmgR9r6": [ + "#f29269", + "#e8c1ac", + "#856412", + "#3e2e0c", + "#436779", + "#acc9e6", + ], + "00t03UwzLJ4HO8u5o": [ + "#674ca2", + "#8bbbd9", + "#736a62", + "#e3d9d0", + "#d9bb84", + "#f25041", + ], + "00tAmMznURg0qH4jp": [ + "#0e0b0c", + "#697289", + "#634e4b", + "#772809", + "#d75314", + "#e7dfd6", + ], + "00tAnftt5xsJlMhDZ": [ + "#083034", + "#2d4d4d", + "#9d988e", + "#cfd0ce", + "#fbc47f", + "#d98f4d", + ], + "00tAq6qSD1erL65L8": [ + "#161212", + "#03515f", + "#884027", + "#d87945", + "#ad9d92", + "#d1d2d2", + ], + "00tAsz6uflx4ciwdA": [ + "#1f2332", + "#47363b", + "#716476", + "#9d8f92", + "#f06b14", + "#feb627", + ], + "00tAuOG3R9ivBX2gO": [ + "#7a0c04", + "#bb2605", + "#eb600c", + "#faaa27", + "#1b1e1a", + "#414a47", + ], + "00tAvqx4Pm26XfKhx": [ + "#34240b", + "#7c7e77", + "#cf8f17", + "#b2daed", + "#2ca6d6", + "#0c4f6a", + ], + "00uKBokFLvI0Epxqx": [ + "#1f2844", + "#556881", + "#7f411d", + "#f66811", + "#e9a371", + "#fbf4e1", + ], + "00uOogzjozpSchnrp": [ + "#00334b", + "#6699bb", + "#7a0011", + "#c21122", + "#e5bf99", + "#fef2d8", + ], + "00uOqryM4SOXgd7S0": [ + "#001580", + "#00779a", + "#00afbf", + "#f07167", + "#fed9b7", + "#fdfcdc", + ], + "00uOuzh5czMDF0xUE": [ + "#552c5c", + "#945b9d", + "#de7f81", + "#f2ac69", + "#e7c1a3", + "#ffede1", + ], + "00uP6PKyOf1h08rEq": [ + "#990a21", + "#e71d3c", + "#ff9519", + "#fafff7", + "#2ec4ad", + "#01182b", + ], + "00uPLAKIHr3vJ0xUB": [ + "#03223e", + "#0f4272", + "#fdf0cc", + "#f9d860", + "#f49a4d", + "#f24c2c", + ], + "00uPN5nCkNSzg7ri4": [ + "#5f00ff", + "#9f37ff", + "#0a99cc", + "#17e8ff", + "#c0c1cf", + "#f0f2f8", + ], + "00uPNyZwgGQhZDUaQ": [ + "#401447", + "#077085", + "#1fe1bb", + "#adfc3a", + "#c9ffbc", + "#f2ffec", + ], + "013j2ZZYIvwFGp4yL": [ + "#551818", + "#9b2c1b", + "#e15847", + "#efa995", + "#c8bcb5", + "#f0e9e3", + ], + "013j3Yj2Vj9r3oaql": [ + "#322b30", + "#ef2e20", + "#bcd6c6", + "#7dadaf", + "#59878b", + "#0b7d90", + ], + "014c40MQuHMbjhkDu": [ + "#1a0e24", + "#6a8aa6", + "#aedff2", + "#fabd05", + "#bf7e04", + "#734743", + ], + "014c5Q2Qc8D1tNdAn": [ + "#0d0d0d", + "#0c4451", + "#61bf36", + "#f2c029", + "#dd9a25", + "#f2f2f4", + ], + "014c6avJ50DRl2NSK": [ + "#252426", + "#ad0401", + "#e90408", + "#fc9518", + "#62c3d9", + "#b6e7f2", + ], + "014c7dTQjU2TA8F68": [ + "#010d00", + "#400d01", + "#595859", + "#bfb4aa", + "#f1c8d8", + "#f2f2f2", + ], + "014c8uRpFlkiHSo9k": [ + "#0b0d0c", + "#025940", + "#16998c", + "#85a0a6", + "#a67c84", + "#f2858e", + ], + "014cACEjxmYUHo9fx": [ + "#1e2e46", + "#8d6747", + "#aa987e", + "#da7347", + "#eea789", + "#f5bfc2", + ], + "014cBhci3KKThfPSX": [ + "#463114", + "#976e2e", + "#bbab49", + "#a7a99a", + "#437c84", + "#144f66", + ], + "014cCBZzmDcqRiRMv": [ + "#d73215", + "#b2a78e", + "#f2f3f6", + "#bbd6c0", + "#799d91", + "#193d2c", + ], + "014cMCdTh3bwH8wVB": [ + "#f2b93a", + "#494f31", + "#01596e", + "#cfd4f4", + "#ba78d4", + "#d802b8", + ], + "014cNjiiOwqOhSKJS": [ + "#12355a", + "#640ea8", + "#a21dd0", + "#0ba67e", + "#81d0a3", + "#d0e4d3", + ], + "014qFZbOZZ3YOqfdS": [ + "#16274b", + "#237388", + "#8593a9", + "#6d135b", + "#e62984", + "#e7b0c7", + ], + "014r4557GBXovXsQ2": [ + "#f57a3f", + "#f59637", + "#f2bd2a", + "#efd94d", + "#dfe18a", + "#ddf0bf", + ], + "014r6uHUtjyoZvMXw": [ + "#fc65a0", + "#eb13a3", + "#603423", + "#b6875c", + "#c2a9a5", + "#d0d9d4", + ], + "014rzaZCKrmbLLuJu": [ + "#29110a", + "#603427", + "#f23b2c", + "#c8bcbe", + "#e8dadb", + "#fff5f5", + ], + "014whoAyxl5gzJwC1": [ + "#7e95a0", + "#7da8b0", + "#abc6c7", + "#9ba7a1", + "#c1bbad", + "#d0d5d1", + ], + "014wjGaMtVyXGNzL6": [ + "#172328", + "#2a454b", + "#516f75", + "#7f9b9f", + "#afc4c6", + "#543e31", + ], + "014wjKhdj0Me4MQvc": [ + "#272817", + "#484223", + "#9d7a20", + "#ed9120", + "#bcb69b", + "#4e5c66", + ], + "014wkzZloB6Lgkluy": [ + "#56261b", + "#9b5847", + "#e28f6d", + "#e3c4b5", + "#fadaae", + "#e0eae8", + ], + "014wmwCkvNXm2qCVW": [ + "#0a041b", + "#2a117e", + "#240cf4", + "#7d2ff4", + "#eb72fc", + "#fceffc", + ], + "014wozXMrzwiMiM3x": [ + "#2d6253", + "#62aec8", + "#f16844", + "#f3b436", + "#d6ad9c", + "#efe0cf", + ], + "014wxZ7JcSYlbNfxL": [ + "#182d3a", + "#65493b", + "#897969", + "#b4a692", + "#d47827", + "#facb8d", + ], + "014xmJC3rdNVkG5Ke": [ + "#021436", + "#0d2f73", + "#0150a5", + "#6e6249", + "#dfd2ba", + "#eba21a", + ], + "014xtY7NR86Aa2AMH": [ + "#e2f4eb", + "#bedbde", + "#88bada", + "#2190d8", + "#8672ae", + "#5f1c5d", + ], + "014xxHPGJc41M07kL": [ + "#1d070d", + "#3b1625", + "#711c44", + "#ab1934", + "#858b98", + "#404252", + ], }; diff --git a/packages/color-palettes/tools/index.ts b/packages/color-palettes/tools/index.ts index b52c2f1714..4f24af96e2 100644 --- a/packages/color-palettes/tools/index.ts +++ b/packages/color-palettes/tools/index.ts @@ -4,24 +4,24 @@ import { serialize } from "@thi.ng/hiccup"; import { svg } from "@thi.ng/hiccup-svg"; import { table } from "@thi.ng/markdown-table"; import { - assocObj, - groupByMap, - map, - mapIndexed, - minMax, - pairs, - pluck, - range2d, - transduce, - vals, + assocObj, + groupByMap, + map, + mapIndexed, + minMax, + pairs, + pluck, + range2d, + transduce, + vals, } from "@thi.ng/transducers"; import { writeFileSync } from "fs"; import { THEMES } from "../src/index.js"; const BASE_URL = - process.argv[2] !== "--local" - ? "https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/color-palettes" - : "."; + process.argv[2] !== "--local" + ? "https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/color-palettes" + : "."; const R = 24; const D = R * 2; @@ -31,79 +31,79 @@ const cellW = (width - 2 * GAP) / 3; const yOff = D + GAP; const DOTS = [ - [0.3, 0.25, 0.2], - [0.6, 0.45, 0.3], - [0.35, 0.7, 0.25], - [0.7, 0.7, 0.2], - [0.6, 0.5, 0.125], + [0.3, 0.25, 0.2], + [0.6, 0.45, 0.3], + [0.35, 0.7, 0.25], + [0.7, 0.7, 0.2], + [0.6, 0.5, 0.125], ]; const col = (theme: string[], i: number) => theme[i % theme.length]; const composition = (theme: string[], i: number) => [ - ["rect", { fill: theme[i] }, [0, 0], cellW, cellW], - ...DOTS.map(([x, y, r], j) => [ - "circle", - { fill: col(theme, i + j + 1) }, - [x * cellW, y * cellW], - r * cellW, - ]), + ["rect", { fill: theme[i] }, [0, 0], cellW, cellW], + ...DOTS.map(([x, y, r], j) => [ + "circle", + { fill: col(theme, i + j + 1) }, + [x * cellW, y * cellW], + r * cellW, + ]), ]; type ThemeStat = { id: string; sortKey: number; key: number; theme: string[] }; const themeStats = transduce( - map(([id, theme]) => { - const lchTheme = THEMES[id].map((x) => lch(x)); - const [_, maxC] = transduce(pluck("c"), minMax(), lchTheme); - // const meanC = transduce(pluck("c"), mean(), lchTheme); - // const hue = transduce(pluck("h"), mean(), lchTheme); - const median = [...pluck("c", lchTheme)].sort()[3]; - const key = median < 0.22 ? 0 : median < 0.33 ? 1 : 2; - return [id, { id, key, sortKey: maxC, theme }]; - }), - assocObj(), - pairs(THEMES) + map(([id, theme]) => { + const lchTheme = THEMES[id].map((x) => lch(x)); + const [_, maxC] = transduce(pluck("c"), minMax(), lchTheme); + // const meanC = transduce(pluck("c"), mean(), lchTheme); + // const hue = transduce(pluck("h"), mean(), lchTheme); + const median = [...pluck("c", lchTheme)].sort()[3]; + const key = median < 0.22 ? 0 : median < 0.33 ? 1 : 2; + return [id, { id, key, sortKey: maxC, theme }]; + }), + assocObj(), + pairs(THEMES) ); const grouped: Map = groupByMap( - { - key: ({ key }: ThemeStat) => key, - }, - vals(themeStats) + { + key: ({ key }: ThemeStat) => key, + }, + vals(themeStats) ); const sections: string[] = []; for (let gid of [...grouped.keys()].sort(compareNumDesc)) { - sections.push(`### ${["Soft", "Medium", "Strong"][gid]}`); - const rows: string[][] = []; - const themes = grouped - .get(gid)! - .sort(compareByKey("sortKey", compareNumDesc)); - const cw = cellW + GAP; - for (let { id, theme } of themes) { - const doc = serialize( - svg( - { convert: true, width, height: yOff + cellW * 2 + GAP }, - dotsH(theme, R, GAP, { translate: [R, R] }), - ...mapIndexed( - (i, [x, y]) => [ - "g", - { - translate: [cw * x, yOff + cw * y], - }, - composition(theme, i), - ], - range2d(3, 2) - ) - ) - ); - writeFileSync(`export/${id}.svg`, doc); + sections.push(`### ${["Soft", "Medium", "Strong"][gid]}`); + const rows: string[][] = []; + const themes = grouped + .get(gid)! + .sort(compareByKey("sortKey", compareNumDesc)); + const cw = cellW + GAP; + for (let { id, theme } of themes) { + const doc = serialize( + svg( + { convert: true, width, height: yOff + cellW * 2 + GAP }, + dotsH(theme, R, GAP, { translate: [R, R] }), + ...mapIndexed( + (i, [x, y]) => [ + "g", + { + translate: [cw * x, yOff + cw * y], + }, + composition(theme, i), + ], + range2d(3, 2) + ) + ) + ); + writeFileSync(`export/${id}.svg`, doc); - rows.push([`\`${id}\``, `![](${BASE_URL}/${id}.svg)`]); - } - sections.push(table(["Preset", "Swatches"], rows)); + rows.push([`\`${id}\``, `![](${BASE_URL}/${id}.svg)`]); + } + sections.push(table(["Preset", "Swatches"], rows)); } writeFileSync(`export/table.md`, sections.join("\n\n")); @@ -111,11 +111,11 @@ writeFileSync(`export/table.md`, sections.join("\n\n")); const RECENT_ID = "014xxHPGJc41M07kL"; const recents = Object.keys(THEMES) - .sort((a, b) => (a < b ? 1 : a > b ? -1 : 0)) - .filter((x) => x > RECENT_ID) - .map((id) => [`\`${id}\``, `![](${BASE_URL}/${id}.svg)`]); + .sort((a, b) => (a < b ? 1 : a > b ? -1 : 0)) + .filter((x) => x > RECENT_ID) + .map((id) => [`\`${id}\``, `![](${BASE_URL}/${id}.svg)`]); writeFileSync( - `export/table-recents.md`, - table(["Preset", "Swatches"], recents) + `export/table-recents.md`, + table(["Preset", "Swatches"], recents) ); diff --git a/packages/color-palettes/tsconfig.json b/packages/color-palettes/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/color-palettes/tsconfig.json +++ b/packages/color-palettes/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/color/api-extractor.json b/packages/color/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/color/api-extractor.json +++ b/packages/color/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/color/package.json b/packages/color/package.json index 0d7a2b35fa..42aff0d093 100644 --- a/packages/color/package.json +++ b/packages/color/package.json @@ -1,424 +1,424 @@ { - "name": "@thi.ng/color", - "version": "5.1.4", - "description": "Array-based color types, CSS parsing, conversions, transformations, declarative theme generation, gradients, presets", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/color#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc api css hcy hsi hsl hsv int internal lab lch oklab rgb srgb xyy xyz ycc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test", - "tool:maxchroma": "tools:node-esm tools/max-chroma.ts", - "tool:swatches": "tools:node-esm tools/index.ts" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/arrays": "^2.3.1", - "@thi.ng/binary": "^3.3.0", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/compare": "^2.1.8", - "@thi.ng/compose": "^2.1.8", - "@thi.ng/defmulti": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/math": "^5.3.4", - "@thi.ng/random": "^3.3.3", - "@thi.ng/strings": "^3.3.6", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "color", - "conversion", - "cosine", - "css", - "D50", - "D65", - "distance", - "filter", - "gamma", - "generator", - "gradient", - "hcy", - "hsi", - "hsl", - "hsv", - "interpolation", - "iterator", - "lab", - "lch", - "matrix", - "oklab", - "random", - "rgb", - "sort", - "srgb", - "swatches", - "theme", - "typescript", - "xyy", - "xyz", - "ycbcr", - "ycc" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "api", - "css", - "hcy", - "hsi", - "hsl", - "hsv", - "int", - "internal", - "lab", - "lch", - "oklab", - "rgb", - "srgb", - "xyy", - "xyz", - "ycc" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./alpha": { - "default": "./alpha.js" - }, - "./analog": { - "default": "./analog.js" - }, - "./api/constants": { - "default": "./api/constants.js" - }, - "./api/gradients": { - "default": "./api/gradients.js" - }, - "./api/names": { - "default": "./api/names.js" - }, - "./api/ranges": { - "default": "./api/ranges.js" - }, - "./api/system": { - "default": "./api/system.js" - }, - "./api": { - "default": "./api.js" - }, - "./clamp": { - "default": "./clamp.js" - }, - "./closest-hue": { - "default": "./closest-hue.js" - }, - "./color-range": { - "default": "./color-range.js" - }, - "./color": { - "default": "./color.js" - }, - "./convert": { - "default": "./convert.js" - }, - "./cosine-gradients": { - "default": "./cosine-gradients.js" - }, - "./css/css": { - "default": "./css/css.js" - }, - "./css/parse-css": { - "default": "./css/parse-css.js" - }, - "./defcolor": { - "default": "./defcolor.js" - }, - "./distance": { - "default": "./distance.js" - }, - "./gradients": { - "default": "./gradients.js" - }, - "./hcy/hcy-rgb": { - "default": "./hcy/hcy-rgb.js" - }, - "./hcy/hcy": { - "default": "./hcy/hcy.js" - }, - "./hsi/hsi-rgb": { - "default": "./hsi/hsi-rgb.js" - }, - "./hsi/hsi": { - "default": "./hsi/hsi.js" - }, - "./hsl/hsl-css": { - "default": "./hsl/hsl-css.js" - }, - "./hsl/hsl-hsv": { - "default": "./hsl/hsl-hsv.js" - }, - "./hsl/hsl-rgb": { - "default": "./hsl/hsl-rgb.js" - }, - "./hsl/hsl": { - "default": "./hsl/hsl.js" - }, - "./hsv/hsv-css": { - "default": "./hsv/hsv-css.js" - }, - "./hsv/hsv-hsl": { - "default": "./hsv/hsv-hsl.js" - }, - "./hsv/hsv-rgb": { - "default": "./hsv/hsv-rgb.js" - }, - "./hsv/hsv": { - "default": "./hsv/hsv.js" - }, - "./int/int-css": { - "default": "./int/int-css.js" - }, - "./int/int-int": { - "default": "./int/int-int.js" - }, - "./int/int-rgb": { - "default": "./int/int-rgb.js" - }, - "./int/int-srgb": { - "default": "./int/int-srgb.js" - }, - "./int/int": { - "default": "./int/int.js" - }, - "./invert": { - "default": "./invert.js" - }, - "./is-black": { - "default": "./is-black.js" - }, - "./is-gamut": { - "default": "./is-gamut.js" - }, - "./is-gray": { - "default": "./is-gray.js" - }, - "./is-white": { - "default": "./is-white.js" - }, - "./lab/lab-css": { - "default": "./lab/lab-css.js" - }, - "./lab/lab-lab": { - "default": "./lab/lab-lab.js" - }, - "./lab/lab-lch": { - "default": "./lab/lab-lch.js" - }, - "./lab/lab-rgb": { - "default": "./lab/lab-rgb.js" - }, - "./lab/lab-xyz": { - "default": "./lab/lab-xyz.js" - }, - "./lab/lab50": { - "default": "./lab/lab50.js" - }, - "./lab/lab65": { - "default": "./lab/lab65.js" - }, - "./lch/lch-css": { - "default": "./lch/lch-css.js" - }, - "./lch/lch": { - "default": "./lch/lch.js" - }, - "./lighten": { - "default": "./lighten.js" - }, - "./linear": { - "default": "./linear.js" - }, - "./luminance-rgb": { - "default": "./luminance-rgb.js" - }, - "./luminance": { - "default": "./luminance.js" - }, - "./max-chroma": { - "default": "./max-chroma.js" - }, - "./mix": { - "default": "./mix.js" - }, - "./oklab/oklab-rgb": { - "default": "./oklab/oklab-rgb.js" - }, - "./oklab/oklab-xyz": { - "default": "./oklab/oklab-xyz.js" - }, - "./oklab/oklab": { - "default": "./oklab/oklab.js" - }, - "./rgb/hue-rgb": { - "default": "./rgb/hue-rgb.js" - }, - "./rgb/kelvin-rgba": { - "default": "./rgb/kelvin-rgba.js" - }, - "./rgb/rgb-css": { - "default": "./rgb/rgb-css.js" - }, - "./rgb/rgb-hcv": { - "default": "./rgb/rgb-hcv.js" - }, - "./rgb/rgb-hcy": { - "default": "./rgb/rgb-hcy.js" - }, - "./rgb/rgb-hsi": { - "default": "./rgb/rgb-hsi.js" - }, - "./rgb/rgb-hsl": { - "default": "./rgb/rgb-hsl.js" - }, - "./rgb/rgb-hsv": { - "default": "./rgb/rgb-hsv.js" - }, - "./rgb/rgb-lab": { - "default": "./rgb/rgb-lab.js" - }, - "./rgb/rgb-oklab": { - "default": "./rgb/rgb-oklab.js" - }, - "./rgb/rgb-srgb": { - "default": "./rgb/rgb-srgb.js" - }, - "./rgb/rgb-xyz": { - "default": "./rgb/rgb-xyz.js" - }, - "./rgb/rgb-ycc": { - "default": "./rgb/rgb-ycc.js" - }, - "./rgb/rgb": { - "default": "./rgb/rgb.js" - }, - "./rotate": { - "default": "./rotate.js" - }, - "./sort": { - "default": "./sort.js" - }, - "./srgb/srgb-css": { - "default": "./srgb/srgb-css.js" - }, - "./srgb/srgb-int": { - "default": "./srgb/srgb-int.js" - }, - "./srgb/srgb-rgb": { - "default": "./srgb/srgb-rgb.js" - }, - "./srgb/srgb": { - "default": "./srgb/srgb.js" - }, - "./strategies": { - "default": "./strategies.js" - }, - "./swatches": { - "default": "./swatches.js" - }, - "./tint": { - "default": "./tint.js" - }, - "./transform": { - "default": "./transform.js" - }, - "./variations": { - "default": "./variations.js" - }, - "./xyy/xyy-xyz": { - "default": "./xyy/xyy-xyz.js" - }, - "./xyy/xyy": { - "default": "./xyy/xyy.js" - }, - "./xyz/wavelength-xyz": { - "default": "./xyz/wavelength-xyz.js" - }, - "./xyz/xyz-lab": { - "default": "./xyz/xyz-lab.js" - }, - "./xyz/xyz-oklab": { - "default": "./xyz/xyz-oklab.js" - }, - "./xyz/xyz-rgb": { - "default": "./xyz/xyz-rgb.js" - }, - "./xyz/xyz-xyy": { - "default": "./xyz/xyz-xyy.js" - }, - "./xyz/xyz-xyz": { - "default": "./xyz/xyz-xyz.js" - }, - "./xyz/xyz50": { - "default": "./xyz/xyz50.js" - }, - "./xyz/xyz65": { - "default": "./xyz/xyz65.js" - }, - "./ycc/ycc-rgb": { - "default": "./ycc/ycc-rgb.js" - }, - "./ycc/ycc": { - "default": "./ycc/ycc.js" - } - }, - "thi.ng": { - "related": [ - "pixel", - "vectors" - ] - } + "name": "@thi.ng/color", + "version": "5.1.4", + "description": "Array-based color types, CSS parsing, conversions, transformations, declarative theme generation, gradients, presets", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/color#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc api css hcy hsi hsl hsv int internal lab lch oklab rgb srgb xyy xyz ycc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test", + "tool:maxchroma": "tools:node-esm tools/max-chroma.ts", + "tool:swatches": "tools:node-esm tools/index.ts" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/arrays": "^2.3.1", + "@thi.ng/binary": "^3.3.0", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/compare": "^2.1.8", + "@thi.ng/compose": "^2.1.8", + "@thi.ng/defmulti": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/math": "^5.3.4", + "@thi.ng/random": "^3.3.3", + "@thi.ng/strings": "^3.3.6", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "color", + "conversion", + "cosine", + "css", + "D50", + "D65", + "distance", + "filter", + "gamma", + "generator", + "gradient", + "hcy", + "hsi", + "hsl", + "hsv", + "interpolation", + "iterator", + "lab", + "lch", + "matrix", + "oklab", + "random", + "rgb", + "sort", + "srgb", + "swatches", + "theme", + "typescript", + "xyy", + "xyz", + "ycbcr", + "ycc" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "api", + "css", + "hcy", + "hsi", + "hsl", + "hsv", + "int", + "internal", + "lab", + "lch", + "oklab", + "rgb", + "srgb", + "xyy", + "xyz", + "ycc" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./alpha": { + "default": "./alpha.js" + }, + "./analog": { + "default": "./analog.js" + }, + "./api/constants": { + "default": "./api/constants.js" + }, + "./api/gradients": { + "default": "./api/gradients.js" + }, + "./api/names": { + "default": "./api/names.js" + }, + "./api/ranges": { + "default": "./api/ranges.js" + }, + "./api/system": { + "default": "./api/system.js" + }, + "./api": { + "default": "./api.js" + }, + "./clamp": { + "default": "./clamp.js" + }, + "./closest-hue": { + "default": "./closest-hue.js" + }, + "./color-range": { + "default": "./color-range.js" + }, + "./color": { + "default": "./color.js" + }, + "./convert": { + "default": "./convert.js" + }, + "./cosine-gradients": { + "default": "./cosine-gradients.js" + }, + "./css/css": { + "default": "./css/css.js" + }, + "./css/parse-css": { + "default": "./css/parse-css.js" + }, + "./defcolor": { + "default": "./defcolor.js" + }, + "./distance": { + "default": "./distance.js" + }, + "./gradients": { + "default": "./gradients.js" + }, + "./hcy/hcy-rgb": { + "default": "./hcy/hcy-rgb.js" + }, + "./hcy/hcy": { + "default": "./hcy/hcy.js" + }, + "./hsi/hsi-rgb": { + "default": "./hsi/hsi-rgb.js" + }, + "./hsi/hsi": { + "default": "./hsi/hsi.js" + }, + "./hsl/hsl-css": { + "default": "./hsl/hsl-css.js" + }, + "./hsl/hsl-hsv": { + "default": "./hsl/hsl-hsv.js" + }, + "./hsl/hsl-rgb": { + "default": "./hsl/hsl-rgb.js" + }, + "./hsl/hsl": { + "default": "./hsl/hsl.js" + }, + "./hsv/hsv-css": { + "default": "./hsv/hsv-css.js" + }, + "./hsv/hsv-hsl": { + "default": "./hsv/hsv-hsl.js" + }, + "./hsv/hsv-rgb": { + "default": "./hsv/hsv-rgb.js" + }, + "./hsv/hsv": { + "default": "./hsv/hsv.js" + }, + "./int/int-css": { + "default": "./int/int-css.js" + }, + "./int/int-int": { + "default": "./int/int-int.js" + }, + "./int/int-rgb": { + "default": "./int/int-rgb.js" + }, + "./int/int-srgb": { + "default": "./int/int-srgb.js" + }, + "./int/int": { + "default": "./int/int.js" + }, + "./invert": { + "default": "./invert.js" + }, + "./is-black": { + "default": "./is-black.js" + }, + "./is-gamut": { + "default": "./is-gamut.js" + }, + "./is-gray": { + "default": "./is-gray.js" + }, + "./is-white": { + "default": "./is-white.js" + }, + "./lab/lab-css": { + "default": "./lab/lab-css.js" + }, + "./lab/lab-lab": { + "default": "./lab/lab-lab.js" + }, + "./lab/lab-lch": { + "default": "./lab/lab-lch.js" + }, + "./lab/lab-rgb": { + "default": "./lab/lab-rgb.js" + }, + "./lab/lab-xyz": { + "default": "./lab/lab-xyz.js" + }, + "./lab/lab50": { + "default": "./lab/lab50.js" + }, + "./lab/lab65": { + "default": "./lab/lab65.js" + }, + "./lch/lch-css": { + "default": "./lch/lch-css.js" + }, + "./lch/lch": { + "default": "./lch/lch.js" + }, + "./lighten": { + "default": "./lighten.js" + }, + "./linear": { + "default": "./linear.js" + }, + "./luminance-rgb": { + "default": "./luminance-rgb.js" + }, + "./luminance": { + "default": "./luminance.js" + }, + "./max-chroma": { + "default": "./max-chroma.js" + }, + "./mix": { + "default": "./mix.js" + }, + "./oklab/oklab-rgb": { + "default": "./oklab/oklab-rgb.js" + }, + "./oklab/oklab-xyz": { + "default": "./oklab/oklab-xyz.js" + }, + "./oklab/oklab": { + "default": "./oklab/oklab.js" + }, + "./rgb/hue-rgb": { + "default": "./rgb/hue-rgb.js" + }, + "./rgb/kelvin-rgba": { + "default": "./rgb/kelvin-rgba.js" + }, + "./rgb/rgb-css": { + "default": "./rgb/rgb-css.js" + }, + "./rgb/rgb-hcv": { + "default": "./rgb/rgb-hcv.js" + }, + "./rgb/rgb-hcy": { + "default": "./rgb/rgb-hcy.js" + }, + "./rgb/rgb-hsi": { + "default": "./rgb/rgb-hsi.js" + }, + "./rgb/rgb-hsl": { + "default": "./rgb/rgb-hsl.js" + }, + "./rgb/rgb-hsv": { + "default": "./rgb/rgb-hsv.js" + }, + "./rgb/rgb-lab": { + "default": "./rgb/rgb-lab.js" + }, + "./rgb/rgb-oklab": { + "default": "./rgb/rgb-oklab.js" + }, + "./rgb/rgb-srgb": { + "default": "./rgb/rgb-srgb.js" + }, + "./rgb/rgb-xyz": { + "default": "./rgb/rgb-xyz.js" + }, + "./rgb/rgb-ycc": { + "default": "./rgb/rgb-ycc.js" + }, + "./rgb/rgb": { + "default": "./rgb/rgb.js" + }, + "./rotate": { + "default": "./rotate.js" + }, + "./sort": { + "default": "./sort.js" + }, + "./srgb/srgb-css": { + "default": "./srgb/srgb-css.js" + }, + "./srgb/srgb-int": { + "default": "./srgb/srgb-int.js" + }, + "./srgb/srgb-rgb": { + "default": "./srgb/srgb-rgb.js" + }, + "./srgb/srgb": { + "default": "./srgb/srgb.js" + }, + "./strategies": { + "default": "./strategies.js" + }, + "./swatches": { + "default": "./swatches.js" + }, + "./tint": { + "default": "./tint.js" + }, + "./transform": { + "default": "./transform.js" + }, + "./variations": { + "default": "./variations.js" + }, + "./xyy/xyy-xyz": { + "default": "./xyy/xyy-xyz.js" + }, + "./xyy/xyy": { + "default": "./xyy/xyy.js" + }, + "./xyz/wavelength-xyz": { + "default": "./xyz/wavelength-xyz.js" + }, + "./xyz/xyz-lab": { + "default": "./xyz/xyz-lab.js" + }, + "./xyz/xyz-oklab": { + "default": "./xyz/xyz-oklab.js" + }, + "./xyz/xyz-rgb": { + "default": "./xyz/xyz-rgb.js" + }, + "./xyz/xyz-xyy": { + "default": "./xyz/xyz-xyy.js" + }, + "./xyz/xyz-xyz": { + "default": "./xyz/xyz-xyz.js" + }, + "./xyz/xyz50": { + "default": "./xyz/xyz50.js" + }, + "./xyz/xyz65": { + "default": "./xyz/xyz65.js" + }, + "./ycc/ycc-rgb": { + "default": "./ycc/ycc-rgb.js" + }, + "./ycc/ycc": { + "default": "./ycc/ycc.js" + } + }, + "thi.ng": { + "related": [ + "pixel", + "vectors" + ] + } } diff --git a/packages/color/src/alpha.ts b/packages/color/src/alpha.ts index 79547ee156..3aa8b48a3b 100644 --- a/packages/color/src/alpha.ts +++ b/packages/color/src/alpha.ts @@ -2,10 +2,10 @@ import { setC4 } from "@thi.ng/vectors/setc"; import type { Color, ReadonlyColor } from "./api.js"; export const alpha = (src: ReadonlyColor) => - src[3] !== undefined ? src[3] : 1; + src[3] !== undefined ? src[3] : 1; export const setAlpha = ( - out: Color | null, - src: ReadonlyColor, - alpha: number + out: Color | null, + src: ReadonlyColor, + alpha: number ) => setC4(out || src, src[0], src[1], src[2], alpha); diff --git a/packages/color/src/analog.ts b/packages/color/src/analog.ts index e4794764c8..debc4916f4 100644 --- a/packages/color/src/analog.ts +++ b/packages/color/src/analog.ts @@ -11,35 +11,35 @@ import { __ensureAlpha } from "./internal/ensure.js"; /** @internal */ const analogU = (x: number, delta: number, rnd: IRandom) => - delta !== 0 ? x + rnd.norm(delta) : x; + delta !== 0 ? x + rnd.norm(delta) : x; /** @internal */ const analogN = (x: number, delta: number, rnd: IRandom, post: FnN = clamp01) => - delta !== 0 ? post(x + rnd.norm(delta)) : x; + delta !== 0 ? post(x + rnd.norm(delta)) : x; /** @internal */ const analogH = (x: number, delta: number, rnd: IRandom) => - analogN(x, delta, rnd, fract); + analogN(x, delta, rnd, fract); /** @internal */ const analogA = (a: number, delta: number, rnd: IRandom) => - delta !== 0 - ? clamp01((a !== undefined ? a : 1) + rnd.norm(delta)) - : __ensureAlpha(a); + delta !== 0 + ? clamp01((a !== undefined ? a : 1) + rnd.norm(delta)) + : __ensureAlpha(a); export const defAnalog: FnU3< - Fn3, - Fn4, number, IRandom | undefined, Color> + Fn3, + Fn4, number, IRandom | undefined, Color> > = - (x, y, z) => - (out, src, delta, rnd = SYSTEM) => - setC4( - out || src, - x(src[0], delta, rnd), - y(src[1], delta, rnd), - z(src[2], delta, rnd), - __ensureAlpha(src[3]) - ); + (x, y, z) => + (out, src, delta, rnd = SYSTEM) => + setC4( + out || src, + x(src[0], delta, rnd), + y(src[1], delta, rnd), + z(src[2], delta, rnd), + __ensureAlpha(src[3]) + ); /** @internal */ const analogHNN = defAnalog(analogH, analogN, analogN); @@ -56,25 +56,25 @@ const analogNUU = defAnalog(analogN, analogU, analogU); * provided {@link @thi.ng/random#IRandom} PRNG. */ export const analog = defmulti< - Color | null, - TypedColor, - number, - IRandom | undefined, - Color + Color | null, + TypedColor, + number, + IRandom | undefined, + Color >( - __dispatch1, - {}, - { - hcy: analogHNN, - hsi: analogHNN, - hsl: analogHNN, - hsv: analogHNN, - lab50: analogNUU, - lab65: analogNUU, - lch: defAnalog(analogN, analogN, analogH), - ycc: analogNUU, - [DEFAULT]: analogNNN, - } + __dispatch1, + {}, + { + hcy: analogHNN, + hsi: analogHNN, + hsl: analogHNN, + hsv: analogHNN, + lab50: analogNUU, + lab65: analogNUU, + lch: defAnalog(analogN, analogN, analogH), + ycc: analogNUU, + [DEFAULT]: analogNNN, + } ); /** @@ -89,30 +89,30 @@ export const analog = defmulti< * By default (unless `deltaS`, `deltaV`, `deltaA` are provided) only the hue of * the color will be modulated. * - * @param out - - * @param src - - * @param deltaH - - * @param deltaS - - * @param deltaV - - * @param deltaA - - * @param rnd - + * @param out - + * @param src - + * @param deltaH - + * @param deltaS - + * @param deltaV - + * @param deltaA - + * @param rnd - */ export const analogHsv = ( - out: Color | null, - src: ReadonlyColor, - deltaH: number, - deltaS = 0, - deltaV = 0, - deltaA = 0, - rnd: IRandom = SYSTEM + out: Color | null, + src: ReadonlyColor, + deltaH: number, + deltaS = 0, + deltaV = 0, + deltaA = 0, + rnd: IRandom = SYSTEM ) => - setC4( - out || src, - analogN(src[0], deltaH, rnd, fract), - analogN(src[1], deltaS, rnd), - analogN(src[2], deltaV, rnd), - analogA(src[3], deltaA, rnd) - ); + setC4( + out || src, + analogN(src[0], deltaH, rnd, fract), + analogN(src[1], deltaS, rnd), + analogN(src[2], deltaV, rnd), + analogA(src[3], deltaA, rnd) + ); /** * Similar to {@link analogHSV}. Returns an analog color based on given RGBA @@ -122,27 +122,27 @@ export const analogHsv = ( * @remarks * By default the green and blue channel variance will be the same as `deltaR`. * - * @param out - - * @param src - - * @param deltaR - - * @param deltaG - - * @param deltaB - - * @param deltaA - - * @param rnd - + * @param out - + * @param src - + * @param deltaR - + * @param deltaG - + * @param deltaB - + * @param deltaA - + * @param rnd - */ export const analogRgb = ( - out: Color | null, - src: ReadonlyColor, - deltaR: number, - deltaG = deltaR, - deltaB = deltaR, - deltaA = 0, - rnd: IRandom = SYSTEM + out: Color | null, + src: ReadonlyColor, + deltaR: number, + deltaG = deltaR, + deltaB = deltaR, + deltaA = 0, + rnd: IRandom = SYSTEM ) => - setC4( - out || src, - analogN(src[0], deltaR, rnd), - analogN(src[1], deltaG, rnd), - analogN(src[2], deltaB, rnd), - analogA(src[3], deltaA, rnd) - ); + setC4( + out || src, + analogN(src[0], deltaR, rnd), + analogN(src[1], deltaG, rnd), + analogN(src[2], deltaB, rnd), + analogA(src[3], deltaA, rnd) + ); diff --git a/packages/color/src/api.ts b/packages/color/src/api.ts index ff2269bfbf..7c38beadf1 100644 --- a/packages/color/src/api.ts +++ b/packages/color/src/api.ts @@ -1,10 +1,10 @@ import type { - FnU2, - IDeref, - IEqualsDelta, - NumericArray, - Range, - Tuple, + FnU2, + IDeref, + IEqualsDelta, + NumericArray, + Range, + Tuple, } from "@thi.ng/api"; import type { IRandom } from "@thi.ng/random"; import type { IVector, ReadonlyVec, Vec } from "@thi.ng/vectors"; @@ -13,219 +13,219 @@ export type Color = Vec; export type ReadonlyColor = ReadonlyVec; export type MaybeColor = - | TypedColor - | IParsedColor - | ReadonlyColor - | string - | number; + | TypedColor + | IParsedColor + | ReadonlyColor + | string + | number; export type ColorOp = (out: Color | null, src: ReadonlyColor) => Color; export type ColorMode = - | "argb32" - | "abgr32" - | "hcy" - | "hsi" - | "hsl" - | "hsv" - | "lab50" - | "lab65" - | "lch" - | "oklab" - | "rgb" - | "srgb" - | "xyy" - | "xyz50" - | "xyz65" - | "ycc"; + | "argb32" + | "abgr32" + | "hcy" + | "hsi" + | "hsl" + | "hsv" + | "lab50" + | "lab65" + | "lch" + | "oklab" + | "rgb" + | "srgb" + | "xyy" + | "xyz50" + | "xyz65" + | "ycc"; /** * Hue names in radial order, e.g. used by {@link namedHueRgb}. */ export enum Hue { - RED, - ORANGE, - YELLOW, - CHARTREUSE, - GREEN, - SPRING_GREEN, - CYAN, - AZURE, - BLUE, - VIOLET, - MAGENTA, - ROSE, + RED, + ORANGE, + YELLOW, + CHARTREUSE, + GREEN, + SPRING_GREEN, + CYAN, + AZURE, + BLUE, + VIOLET, + MAGENTA, + ROSE, } export interface IColor { - readonly mode: ColorMode; + readonly mode: ColorMode; } export interface ChannelSpec { - /** - * Acceptable value range for this channel. Used by {@link TypedColor.clamp}. - * - * @defaultValue [0,1] - */ - range?: Range; - /** - * If true, this channel is used to store normalized hue values. - * - * @defaultValue false - */ - hue?: boolean; + /** + * Acceptable value range for this channel. Used by {@link TypedColor.clamp}. + * + * @defaultValue [0,1] + */ + range?: Range; + /** + * If true, this channel is used to store normalized hue values. + * + * @defaultValue false + */ + hue?: boolean; } export interface ColorSpec { - mode: M; - /** - * Define additional per-channel constraints, information. Currently only - * used to define limits. - */ - channels?: Partial>; - /** - * Channel names in index order (used to define channel accessors). - */ - order: readonly K[]; - /** - * Conversions from source modes. `rgb` is mandatory, others optional. If a - * key specifies an array of functions, these will be applied to source - * color in LTR order. - */ - from: Partial>> & { - rgb: ColorOp; - }; - /** - * Mandatory conversion to RGB mode. Used as fallback solution if no other - * direct conversion path is defined (e.g. for CSS formatting). - */ - toRgb: ColorOp | Tuple; + mode: M; + /** + * Define additional per-channel constraints, information. Currently only + * used to define limits. + */ + channels?: Partial>; + /** + * Channel names in index order (used to define channel accessors). + */ + order: readonly K[]; + /** + * Conversions from source modes. `rgb` is mandatory, others optional. If a + * key specifies an array of functions, these will be applied to source + * color in LTR order. + */ + from: Partial>> & { + rgb: ColorOp; + }; + /** + * Mandatory conversion to RGB mode. Used as fallback solution if no other + * direct conversion path is defined (e.g. for CSS formatting). + */ + toRgb: ColorOp | Tuple; } export type Conversions = Partial> & { - rgb: ColorOp; + rgb: ColorOp; }; export interface ColorFactory> { - (col: MaybeColor, buf?: NumericArray, idx?: number, stride?: number): T; - (col?: Vec, idx?: number, stride?: number): T; - (a: number, b: number, c: number, ...xs: number[]): T; + (col: MaybeColor, buf?: NumericArray, idx?: number, stride?: number): T; + (col?: Vec, idx?: number, stride?: number): T; + (a: number, b: number, c: number, ...xs: number[]): T; - readonly class: TypedColorConstructor; + readonly class: TypedColorConstructor; - /** - * Returns a new random color, optionally backed by given memory. I.e. if - * `buf` is given, the returned color will wrap `buf` from given `index` - * (default: 0) and `stride` step size (default: 1). - * - * @param rnd - - * @param buf - - * @param index - - * @param stride - - */ - random( - rnd?: IRandom, - buf?: NumericArray, - index?: number, - stride?: number - ): T; + /** + * Returns a new random color, optionally backed by given memory. I.e. if + * `buf` is given, the returned color will wrap `buf` from given `index` + * (default: 0) and `stride` step size (default: 1). + * + * @param rnd - + * @param buf - + * @param index - + * @param stride - + */ + random( + rnd?: IRandom, + buf?: NumericArray, + index?: number, + stride?: number + ): T; - /** - * Same as {@link TypedColor.range}. - */ - readonly range: [ReadonlyColor, ReadonlyColor]; + /** + * Same as {@link TypedColor.range}. + */ + readonly range: [ReadonlyColor, ReadonlyColor]; - /** - * Returns array of memory mapped colors using given backing array and - * stride settings. - * - * @remarks - * The `cstride` is the step size between individual color components. - * `estride` is the step size between successive colors and will default to - * number of channels supported by given color type/space. This arrangement - * allows for different storage approaches, incl. SOA, AOS, striped / - * interleaved etc. - * - * @param buf - backing array - * @param num - num vectors (default: buf.length / numChannels) - * @param start - start index (default: 0) - * @param cstride - component stride (default: 1) - * @param estride - element stride (default: numChannels) - */ - mapBuffer( - buf: NumericArray, - num?: number, - start?: number, - cstride?: number, - estride?: number - ): T[]; + /** + * Returns array of memory mapped colors using given backing array and + * stride settings. + * + * @remarks + * The `cstride` is the step size between individual color components. + * `estride` is the step size between successive colors and will default to + * number of channels supported by given color type/space. This arrangement + * allows for different storage approaches, incl. SOA, AOS, striped / + * interleaved etc. + * + * @param buf - backing array + * @param num - num vectors (default: buf.length / numChannels) + * @param start - start index (default: 0) + * @param cstride - component stride (default: 1) + * @param estride - element stride (default: numChannels) + */ + mapBuffer( + buf: NumericArray, + num?: number, + start?: number, + cstride?: number, + estride?: number + ): T[]; } export interface TypedColorConstructor> { - new (buf?: NumericArray, offset?: number, stride?: number): T; + new (buf?: NumericArray, offset?: number, stride?: number): T; } export interface TypedColor - extends IColor, - IDeref, - IEqualsDelta, - IVector, - Iterable { - /** - * Backing array / memory - */ - buf: NumericArray; - /** - * Start index in array - */ - offset: number; - /** - * Step size between channels - */ - stride: number; + extends IColor, + IDeref, + IEqualsDelta, + IVector, + Iterable { + /** + * Backing array / memory + */ + buf: NumericArray; + /** + * Start index in array + */ + offset: number; + /** + * Step size between channels + */ + stride: number; - /** - * A tuple of `[min, max]` where both are vectors specifying the - * min/max channel ranges for this color type. - * - * @remarks - * Even though there're several color spaces which do not impose limits on - * at least some of their axes, the limits returned by this function - * indicate RGB "safe" colors and were determined by projecting all RGB - * colors into the color space used by this type. - */ - readonly range: [ReadonlyColor, ReadonlyColor]; + /** + * A tuple of `[min, max]` where both are vectors specifying the + * min/max channel ranges for this color type. + * + * @remarks + * Even though there're several color spaces which do not impose limits on + * at least some of their axes, the limits returned by this function + * indicate RGB "safe" colors and were determined by projecting all RGB + * colors into the color space used by this type. + */ + readonly range: [ReadonlyColor, ReadonlyColor]; - /** - * Clamps all color channels so that colors is inside RGB gamut. - * - * @remarks - * Note: This is not a 100% guarantee, due to each channel being clamped - * individually based on pre-determined limits. - */ - clamp(): this; + /** + * Clamps all color channels so that colors is inside RGB gamut. + * + * @remarks + * Note: This is not a 100% guarantee, due to each channel being clamped + * individually based on pre-determined limits. + */ + clamp(): this; - /** - * Randomizes all color channels based on channel ranges defined for this - * color type (usually [0..1] interval). Alpha channel will remain - * untouched. - * - * @param rnd - - */ - randomize(rnd?: IRandom): this; + /** + * Randomizes all color channels based on channel ranges defined for this + * color type (usually [0..1] interval). Alpha channel will remain + * untouched. + * + * @param rnd - + */ + randomize(rnd?: IRandom): this; - /** - * Copies `src` into this color's array. - * - * @param src - - */ - set(src: ReadonlyColor): this; + /** + * Copies `src` into this color's array. + * + * @param src - + */ + set(src: ReadonlyColor): this; - /** - * For memory mapped colors, this ensures only the elements used by this - * color are being serialized (as array) by `JSON.stringify()`. - */ - toJSON(): number[]; + /** + * For memory mapped colors, this ensures only the elements used by this + * color are being serialized (as array) by `JSON.stringify()`. + */ + toJSON(): number[]; } export interface IParsedColor extends IColor, IDeref {} @@ -235,11 +235,11 @@ export interface IParsedColor extends IColor, IDeref {} * array and color mode. */ export class ParsedColor implements IParsedColor { - constructor(public readonly mode: ColorMode, public value: Color) {} + constructor(public readonly mode: ColorMode, public value: Color) {} - deref() { - return this.value; - } + deref() { + return this.value; + } } /** @@ -250,8 +250,8 @@ export type ColorMatrix = Tuple; export type ColorDistance = FnU2; export type ColorMixFn = ( - out: Color | null, - a: T, - b: T, - t: number + out: Color | null, + a: T, + b: T, + t: number ) => Color; diff --git a/packages/color/src/api/constants.ts b/packages/color/src/api/constants.ts index 42b3101d63..3725005555 100644 --- a/packages/color/src/api/constants.ts +++ b/packages/color/src/api/constants.ts @@ -69,8 +69,8 @@ export const RGB_LUMINANCE_REC2020 = [0.2627, 0.678, 0.0593]; * http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html */ export const RGB_XYZ_D50 = [ - 0.4360747, 0.2225045, 0.0139322, 0.3850649, 0.7168786, 0.0971045, 0.1430804, - 0.0606169, 0.7141733, + 0.4360747, 0.2225045, 0.0139322, 0.3850649, 0.7168786, 0.0971045, 0.1430804, + 0.0606169, 0.7141733, ]; /** @@ -81,8 +81,8 @@ export const RGB_XYZ_D50 = [ * http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html */ export const XYZ_RGB_D50 = [ - 3.1338561, -0.9787684, 0.0719453, -1.6168667, 1.9161415, -0.2289914, - -0.4906146, 0.033454, 1.4052427, + 3.1338561, -0.9787684, 0.0719453, -1.6168667, 1.9161415, -0.2289914, + -0.4906146, 0.033454, 1.4052427, ]; /** @@ -93,8 +93,8 @@ export const XYZ_RGB_D50 = [ * http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html */ export const RGB_XYZ_D65 = [ - 0.4124564, 0.2126729, 0.0193339, 0.3575761, 0.7151522, 0.119192, 0.1804375, - 0.072175, 0.9503041, + 0.4124564, 0.2126729, 0.0193339, 0.3575761, 0.7151522, 0.119192, 0.1804375, + 0.072175, 0.9503041, ]; /** @@ -105,8 +105,8 @@ export const RGB_XYZ_D65 = [ * http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html */ export const XYZ_RGB_D65 = [ - 3.2404542, -0.969266, 0.0556434, -1.5371385, 1.8760108, -0.2040259, - -0.4985314, 0.041556, 1.0572252, + 3.2404542, -0.969266, 0.0556434, -1.5371385, 1.8760108, -0.2040259, + -0.4985314, 0.041556, 1.0572252, ]; /** @@ -117,8 +117,8 @@ export const XYZ_RGB_D65 = [ * http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html */ export const BRADFORD_D50_D65 = [ - 0.9555766, -0.0282895, 0.0122982, -0.0230393, 1.0099416, -0.020483, - 0.0631636, 0.0210077, 1.3299098, + 0.9555766, -0.0282895, 0.0122982, -0.0230393, 1.0099416, -0.020483, + 0.0631636, 0.0210077, 1.3299098, ]; /** @@ -129,8 +129,8 @@ export const BRADFORD_D50_D65 = [ * http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html */ export const BRADFORD_D65_D50 = [ - 1.0478112, 0.0295424, -0.0092345, 0.0228866, 0.9904844, 0.0150436, - -0.050127, -0.0170491, 0.7521316, + 1.0478112, 0.0295424, -0.0092345, 0.0228866, 0.9904844, 0.0150436, + -0.050127, -0.0170491, 0.7521316, ]; /** @@ -147,13 +147,13 @@ export const D50 = [0.96422, 1, 0.82521]; export const D65 = [0.95047, 1, 1.08883]; export const OKLAB_M1 = [ - 0.8189330101, 0.0329845436, 0.0482003018, 0.3618667424, 0.9293118715, - 0.2643662691, -0.1288597137, 0.0361456387, 0.633851707, + 0.8189330101, 0.0329845436, 0.0482003018, 0.3618667424, 0.9293118715, + 0.2643662691, -0.1288597137, 0.0361456387, 0.633851707, ]; export const OKLAB_M2 = [ - 0.2104542553, 1.9779984951, 0.0259040371, 0.793617785, -2.428592205, - 0.7827717662, -0.0040720468, 0.4505937099, -0.808675766, + 0.2104542553, 1.9779984951, 0.0259040371, 0.793617785, -2.428592205, + 0.7827717662, -0.0040720468, 0.4505937099, -0.808675766, ]; /** @@ -173,11 +173,11 @@ export let PC = percent(3); * Sets precision for CSS formatted values to `x` significant digits (default: * 3). * - * @param x - + * @param x - */ export const setPrecision = (x: number) => { - FF = float(x); - PC = percent(x); + FF = float(x); + PC = percent(x); }; export const INV8BIT = 1 / 0xff; diff --git a/packages/color/src/api/gradients.ts b/packages/color/src/api/gradients.ts index 4a98fe2d52..052603899e 100644 --- a/packages/color/src/api/gradients.ts +++ b/packages/color/src/api/gradients.ts @@ -2,28 +2,28 @@ import type { FnN, FnU, Tuple } from "@thi.ng/api"; import type { Color, ColorMixFn, ReadonlyColor } from "../api.js"; export type CosineGradientPreset = - | "blue-cyan" - | "blue-magenta-orange" - | "blue-white-red" - | "cyan-magenta" - | "green-blue-orange" - | "green-cyan" - | "green-magenta" - | "green-red" - | "heat1" - | "magenta-green" - | "orange-blue" - | "orange-magenta-blue" - | "purple-orange-cyan" - | "rainbow1" - | "rainbow2" - | "rainbow3" - | "rainbow4" - | "red-blue" - | "yellow-green-blue" - | "yellow-magenta-cyan" - | "yellow-purple-magenta" - | "yellow-red"; + | "blue-cyan" + | "blue-magenta-orange" + | "blue-white-red" + | "cyan-magenta" + | "green-blue-orange" + | "green-cyan" + | "green-magenta" + | "green-red" + | "heat1" + | "magenta-green" + | "orange-blue" + | "orange-magenta-blue" + | "purple-orange-cyan" + | "rainbow1" + | "rainbow2" + | "rainbow3" + | "rainbow4" + | "red-blue" + | "yellow-green-blue" + | "yellow-magenta-cyan" + | "yellow-purple-magenta" + | "yellow-red"; export type CosineCoeffs = Tuple; @@ -35,27 +35,27 @@ export type CosGradientSpec = Tuple; export type GradientColorStop = [number, T]; export interface GradientOpts { - /** - * Number of colors to generate - */ - num: number; - /** - * Gradient color stops, each a `[pos, color]` - */ - stops: GradientColorStop[]; - /** - * Interpolation function - */ - mix?: ColorMixFn; - /** - * Easing function - */ - easing?: FnN; + /** + * Number of colors to generate + */ + num: number; + /** + * Gradient color stops, each a `[pos, color]` + */ + stops: GradientColorStop[]; + /** + * Interpolation function + */ + mix?: ColorMixFn; + /** + * Easing function + */ + easing?: FnN; } export interface CosineGradientOpts extends GradientOpts { - /** - * Post transformation function for each color - */ - tx?: FnU; + /** + * Post transformation function for each color + */ + tx?: FnU; } diff --git a/packages/color/src/api/names.ts b/packages/color/src/api/names.ts index c8865d1f74..1bfcb8f607 100644 --- a/packages/color/src/api/names.ts +++ b/packages/color/src/api/names.ts @@ -1,304 +1,304 @@ export type CSSColorName = - | "aliceblue" - | "antiquewhite" - | "aqua" - | "aquamarine" - | "azure" - | "beige" - | "bisque" - | "black" - | "blanchedalmond" - | "blue" - | "blueviolet" - | "brown" - | "burlywood" - | "cadetblue" - | "chartreuse" - | "chocolate" - | "coral" - | "cornflowerblue" - | "cornsilk" - | "crimson" - | "cyan" - | "darkblue" - | "darkcyan" - | "darkgoldenrod" - | "darkgray" - | "darkgreen" - | "darkgrey" - | "darkkhaki" - | "darkmagenta" - | "darkolivegreen" - | "darkorange" - | "darkorchid" - | "darkred" - | "darksalmon" - | "darkseagreen" - | "darkslateblue" - | "darkslategray" - | "darkslategrey" - | "darkturquoise" - | "darkviolet" - | "deeppink" - | "deepskyblue" - | "dimgray" - | "dimgrey" - | "dodgerblue" - | "firebrick" - | "floralwhite" - | "forestgreen" - | "fuchsia" - | "gainsboro" - | "ghostwhite" - | "gold" - | "goldenrod" - | "gray" - | "grey" - | "green" - | "greenyellow" - | "honeydew" - | "hotpink" - | "indianred" - | "indigo" - | "ivory" - | "khaki" - | "lavender" - | "lavenderblush" - | "lawngreen" - | "lemonchiffon" - | "lightblue" - | "lightcoral" - | "lightcyan" - | "lightgoldenrodyellow" - | "lightgray" - | "lightgreen" - | "lightgrey" - | "lightpink" - | "lightsalmon" - | "lightseagreen" - | "lightskyblue" - | "lightslategray" - | "lightslategrey" - | "lightsteelblue" - | "lightyellow" - | "lime" - | "limegreen" - | "linen" - | "magenta" - | "maroon" - | "mediumaquamarine" - | "mediumblue" - | "mediumorchid" - | "mediumpurple" - | "mediumseagreen" - | "mediumslateblue" - | "mediumspringgreen" - | "mediumturquoise" - | "mediumvioletred" - | "midnightblue" - | "mintcream" - | "mistyrose" - | "moccasin" - | "navajowhite" - | "navy" - | "oldlace" - | "olive" - | "olivedrab" - | "orange" - | "orangered" - | "orchid" - | "palegoldenrod" - | "palegreen" - | "paleturquoise" - | "palevioletred" - | "papayawhip" - | "peachpuff" - | "peru" - | "pink" - | "plum" - | "powderblue" - | "purple" - | "red" - | "rosybrown" - | "royalblue" - | "saddlebrown" - | "salmon" - | "sandybrown" - | "seagreen" - | "seashell" - | "sienna" - | "silver" - | "skyblue" - | "slateblue" - | "slategray" - | "slategrey" - | "snow" - | "springgreen" - | "steelblue" - | "tan" - | "teal" - | "thistle" - | "tomato" - | "turquoise" - | "violet" - | "wheat" - | "white" - | "whitesmoke" - | "yellow" - | "yellowgreen" - // additions - | "rebeccapurple" - | "transparent"; + | "aliceblue" + | "antiquewhite" + | "aqua" + | "aquamarine" + | "azure" + | "beige" + | "bisque" + | "black" + | "blanchedalmond" + | "blue" + | "blueviolet" + | "brown" + | "burlywood" + | "cadetblue" + | "chartreuse" + | "chocolate" + | "coral" + | "cornflowerblue" + | "cornsilk" + | "crimson" + | "cyan" + | "darkblue" + | "darkcyan" + | "darkgoldenrod" + | "darkgray" + | "darkgreen" + | "darkgrey" + | "darkkhaki" + | "darkmagenta" + | "darkolivegreen" + | "darkorange" + | "darkorchid" + | "darkred" + | "darksalmon" + | "darkseagreen" + | "darkslateblue" + | "darkslategray" + | "darkslategrey" + | "darkturquoise" + | "darkviolet" + | "deeppink" + | "deepskyblue" + | "dimgray" + | "dimgrey" + | "dodgerblue" + | "firebrick" + | "floralwhite" + | "forestgreen" + | "fuchsia" + | "gainsboro" + | "ghostwhite" + | "gold" + | "goldenrod" + | "gray" + | "grey" + | "green" + | "greenyellow" + | "honeydew" + | "hotpink" + | "indianred" + | "indigo" + | "ivory" + | "khaki" + | "lavender" + | "lavenderblush" + | "lawngreen" + | "lemonchiffon" + | "lightblue" + | "lightcoral" + | "lightcyan" + | "lightgoldenrodyellow" + | "lightgray" + | "lightgreen" + | "lightgrey" + | "lightpink" + | "lightsalmon" + | "lightseagreen" + | "lightskyblue" + | "lightslategray" + | "lightslategrey" + | "lightsteelblue" + | "lightyellow" + | "lime" + | "limegreen" + | "linen" + | "magenta" + | "maroon" + | "mediumaquamarine" + | "mediumblue" + | "mediumorchid" + | "mediumpurple" + | "mediumseagreen" + | "mediumslateblue" + | "mediumspringgreen" + | "mediumturquoise" + | "mediumvioletred" + | "midnightblue" + | "mintcream" + | "mistyrose" + | "moccasin" + | "navajowhite" + | "navy" + | "oldlace" + | "olive" + | "olivedrab" + | "orange" + | "orangered" + | "orchid" + | "palegoldenrod" + | "palegreen" + | "paleturquoise" + | "palevioletred" + | "papayawhip" + | "peachpuff" + | "peru" + | "pink" + | "plum" + | "powderblue" + | "purple" + | "red" + | "rosybrown" + | "royalblue" + | "saddlebrown" + | "salmon" + | "sandybrown" + | "seagreen" + | "seashell" + | "sienna" + | "silver" + | "skyblue" + | "slateblue" + | "slategray" + | "slategrey" + | "snow" + | "springgreen" + | "steelblue" + | "tan" + | "teal" + | "thistle" + | "tomato" + | "turquoise" + | "violet" + | "wheat" + | "white" + | "whitesmoke" + | "yellow" + | "yellowgreen" + // additions + | "rebeccapurple" + | "transparent"; export const CSS_NAMES: Record = { - aliceblue: "f0f8ff", - antiquewhite: "faebd7", - aqua: "0ff", - aquamarine: "7fffd4", - azure: "f0ffff", - beige: "f5f5dc", - bisque: "ffe4c4", - black: "000", - blanchedalmond: "ffebcd", - blue: "00f", - blueviolet: "8a2be2", - brown: "a52a2a", - burlywood: "deb887", - cadetblue: "5f9ea0", - chartreuse: "7fff00", - chocolate: "d2691e", - coral: "ff7f50", - cornflowerblue: "6495ed", - cornsilk: "fff8dc", - crimson: "dc143c", - cyan: "0ff", - darkblue: "00008b", - darkcyan: "008b8b", - darkgoldenrod: "b8860b", - darkgray: "a9a9a9", - darkgreen: "006400", - darkgrey: "a9a9a9", - darkkhaki: "bdb76b", - darkmagenta: "8b008b", - darkolivegreen: "556b2f", - darkorange: "ff8c00", - darkorchid: "9932cc", - darkred: "8b0000", - darksalmon: "e9967a", - darkseagreen: "8fbc8f", - darkslateblue: "483d8b", - darkslategray: "2f4f4f", - darkslategrey: "2f4f4f", - darkturquoise: "00ced1", - darkviolet: "9400d3", - deeppink: "ff1493", - deepskyblue: "00bfff", - dimgray: "696969", - dimgrey: "696969", - dodgerblue: "1e90ff", - firebrick: "b22222", - floralwhite: "fffaf0", - forestgreen: "228b22", - fuchsia: "f0f", - gainsboro: "dcdcdc", - ghostwhite: "f8f8ff", - gold: "ffd700", - goldenrod: "daa520", - gray: "808080", - grey: "808080", - green: "008000", - greenyellow: "adff2f", - honeydew: "f0fff0", - hotpink: "ff69b4", - indianred: "cd5c5c", - indigo: "4b0082", - ivory: "fffff0", - khaki: "f0e68c", - lavender: "e6e6fa", - lavenderblush: "fff0f5", - lawngreen: "7cfc00", - lemonchiffon: "fffacd", - lightblue: "add8e6", - lightcoral: "f08080", - lightcyan: "e0ffff", - lightgoldenrodyellow: "fafad2", - lightgray: "d3d3d3", - lightgreen: "90ee90", - lightgrey: "d3d3d3", - lightpink: "ffb6c1", - lightsalmon: "ffa07a", - lightseagreen: "20b2aa", - lightskyblue: "87cefa", - lightslategray: "789", - lightslategrey: "789", - lightsteelblue: "b0c4de", - lightyellow: "ffffe0", - lime: "0f0", - limegreen: "32cd32", - linen: "faf0e6", - magenta: "f0f", - maroon: "800000", - mediumaquamarine: "66cdaa", - mediumblue: "0000cd", - mediumorchid: "ba55d3", - mediumpurple: "9370db", - mediumseagreen: "3cb371", - mediumslateblue: "7b68ee", - mediumspringgreen: "00fa9a", - mediumturquoise: "48d1cc", - mediumvioletred: "c71585", - midnightblue: "191970", - mintcream: "f5fffa", - mistyrose: "ffe4e1", - moccasin: "ffe4b5", - navajowhite: "ffdead", - navy: "000080", - oldlace: "fdf5e6", - olive: "808000", - olivedrab: "6b8e23", - orange: "ffa500", - orangered: "ff4500", - orchid: "da70d6", - palegoldenrod: "eee8aa", - palegreen: "98fb98", - paleturquoise: "afeeee", - palevioletred: "db7093", - papayawhip: "ffefd5", - peachpuff: "ffdab9", - peru: "cd853f", - pink: "ffc0cb", - plum: "dda0dd", - powderblue: "b0e0e6", - purple: "800080", - red: "f00", - rosybrown: "bc8f8f", - royalblue: "4169e1", - saddlebrown: "8b4513", - salmon: "fa8072", - sandybrown: "f4a460", - seagreen: "2e8b57", - seashell: "fff5ee", - sienna: "a0522d", - silver: "c0c0c0", - skyblue: "87ceeb", - slateblue: "6a5acd", - slategray: "708090", - slategrey: "708090", - snow: "fffafa", - springgreen: "00ff7f", - steelblue: "4682b4", - tan: "d2b48c", - teal: "008080", - thistle: "d8bfd8", - tomato: "ff6347", - turquoise: "40e0d0", - violet: "ee82ee", - wheat: "f5deb3", - white: "fff", - whitesmoke: "f5f5f5", - yellow: "ff0", - yellowgreen: "9acd32", - // additions - transparent: "0000", - rebeccapurple: "639", + aliceblue: "f0f8ff", + antiquewhite: "faebd7", + aqua: "0ff", + aquamarine: "7fffd4", + azure: "f0ffff", + beige: "f5f5dc", + bisque: "ffe4c4", + black: "000", + blanchedalmond: "ffebcd", + blue: "00f", + blueviolet: "8a2be2", + brown: "a52a2a", + burlywood: "deb887", + cadetblue: "5f9ea0", + chartreuse: "7fff00", + chocolate: "d2691e", + coral: "ff7f50", + cornflowerblue: "6495ed", + cornsilk: "fff8dc", + crimson: "dc143c", + cyan: "0ff", + darkblue: "00008b", + darkcyan: "008b8b", + darkgoldenrod: "b8860b", + darkgray: "a9a9a9", + darkgreen: "006400", + darkgrey: "a9a9a9", + darkkhaki: "bdb76b", + darkmagenta: "8b008b", + darkolivegreen: "556b2f", + darkorange: "ff8c00", + darkorchid: "9932cc", + darkred: "8b0000", + darksalmon: "e9967a", + darkseagreen: "8fbc8f", + darkslateblue: "483d8b", + darkslategray: "2f4f4f", + darkslategrey: "2f4f4f", + darkturquoise: "00ced1", + darkviolet: "9400d3", + deeppink: "ff1493", + deepskyblue: "00bfff", + dimgray: "696969", + dimgrey: "696969", + dodgerblue: "1e90ff", + firebrick: "b22222", + floralwhite: "fffaf0", + forestgreen: "228b22", + fuchsia: "f0f", + gainsboro: "dcdcdc", + ghostwhite: "f8f8ff", + gold: "ffd700", + goldenrod: "daa520", + gray: "808080", + grey: "808080", + green: "008000", + greenyellow: "adff2f", + honeydew: "f0fff0", + hotpink: "ff69b4", + indianred: "cd5c5c", + indigo: "4b0082", + ivory: "fffff0", + khaki: "f0e68c", + lavender: "e6e6fa", + lavenderblush: "fff0f5", + lawngreen: "7cfc00", + lemonchiffon: "fffacd", + lightblue: "add8e6", + lightcoral: "f08080", + lightcyan: "e0ffff", + lightgoldenrodyellow: "fafad2", + lightgray: "d3d3d3", + lightgreen: "90ee90", + lightgrey: "d3d3d3", + lightpink: "ffb6c1", + lightsalmon: "ffa07a", + lightseagreen: "20b2aa", + lightskyblue: "87cefa", + lightslategray: "789", + lightslategrey: "789", + lightsteelblue: "b0c4de", + lightyellow: "ffffe0", + lime: "0f0", + limegreen: "32cd32", + linen: "faf0e6", + magenta: "f0f", + maroon: "800000", + mediumaquamarine: "66cdaa", + mediumblue: "0000cd", + mediumorchid: "ba55d3", + mediumpurple: "9370db", + mediumseagreen: "3cb371", + mediumslateblue: "7b68ee", + mediumspringgreen: "00fa9a", + mediumturquoise: "48d1cc", + mediumvioletred: "c71585", + midnightblue: "191970", + mintcream: "f5fffa", + mistyrose: "ffe4e1", + moccasin: "ffe4b5", + navajowhite: "ffdead", + navy: "000080", + oldlace: "fdf5e6", + olive: "808000", + olivedrab: "6b8e23", + orange: "ffa500", + orangered: "ff4500", + orchid: "da70d6", + palegoldenrod: "eee8aa", + palegreen: "98fb98", + paleturquoise: "afeeee", + palevioletred: "db7093", + papayawhip: "ffefd5", + peachpuff: "ffdab9", + peru: "cd853f", + pink: "ffc0cb", + plum: "dda0dd", + powderblue: "b0e0e6", + purple: "800080", + red: "f00", + rosybrown: "bc8f8f", + royalblue: "4169e1", + saddlebrown: "8b4513", + salmon: "fa8072", + sandybrown: "f4a460", + seagreen: "2e8b57", + seashell: "fff5ee", + sienna: "a0522d", + silver: "c0c0c0", + skyblue: "87ceeb", + slateblue: "6a5acd", + slategray: "708090", + slategrey: "708090", + snow: "fffafa", + springgreen: "00ff7f", + steelblue: "4682b4", + tan: "d2b48c", + teal: "008080", + thistle: "d8bfd8", + tomato: "ff6347", + turquoise: "40e0d0", + violet: "ee82ee", + wheat: "f5deb3", + white: "fff", + whitesmoke: "f5f5f5", + yellow: "ff0", + yellowgreen: "9acd32", + // additions + transparent: "0000", + rebeccapurple: "639", }; diff --git a/packages/color/src/api/ranges.ts b/packages/color/src/api/ranges.ts index fc2ec01f56..ee5c82716d 100644 --- a/packages/color/src/api/ranges.ts +++ b/packages/color/src/api/ranges.ts @@ -4,99 +4,99 @@ import type { ReadonlyColor } from "../api.js"; import type { CSSColorName } from "./names.js"; export type ColorRangePreset = - | "light" - | "dark" - | "bright" - | "weak" - | "neutral" - | "fresh" - | "soft" - | "hard" - | "warm" - | "cool" - | "intense"; + | "light" + | "dark" + | "bright" + | "weak" + | "neutral" + | "fresh" + | "soft" + | "hard" + | "warm" + | "cool" + | "intense"; export interface ColorRange { - /** - * Hue ranges - */ - h?: Range[]; - /** - * Saturation ranges - */ - c?: Range[]; - /** - * Brightness ranges - */ - l?: Range[]; - /** - * Alpha ranges - */ - a?: Range[]; - /** - * Black point ranges - */ - b?: Range[]; - /** - * White point ranges - */ - w?: Range[]; + /** + * Hue ranges + */ + h?: Range[]; + /** + * Saturation ranges + */ + c?: Range[]; + /** + * Brightness ranges + */ + l?: Range[]; + /** + * Alpha ranges + */ + a?: Range[]; + /** + * Black point ranges + */ + b?: Range[]; + /** + * White point ranges + */ + w?: Range[]; } export interface ColorRangeOpts { - /** - * Nunber of result colors. - * - * @defaultValue ∞ - */ - num: number; - /** - * Base color. Either a {@link TypedColor} instance, {@link CSSColorName} or - * raw LCH tuple. Its hue will be used as bias to create a randomized - * variation (based on {@link ColorRangeOpts.variance}). - */ - base?: ReadonlyColor | CSSColorName; - /** - * Max. normalized & randomized hue shift for result colors. Only used if a - * base color is given. - * - * @defaultValue 0.025 (i.e. +/- 9 degrees) - */ - variance: number; - /** - * Tolerance for grayscale check (used for both saturation and brightness). - * - * @defaultValue 0.001 - */ - eps: number; - /** - * PRNG instance to use for randomized values - * - * @defaultValue {@link @thi.ng/random#SYSTEM} - */ - rnd: IRandom; + /** + * Nunber of result colors. + * + * @defaultValue ∞ + */ + num: number; + /** + * Base color. Either a {@link TypedColor} instance, {@link CSSColorName} or + * raw LCH tuple. Its hue will be used as bias to create a randomized + * variation (based on {@link ColorRangeOpts.variance}). + */ + base?: ReadonlyColor | CSSColorName; + /** + * Max. normalized & randomized hue shift for result colors. Only used if a + * base color is given. + * + * @defaultValue 0.025 (i.e. +/- 9 degrees) + */ + variance: number; + /** + * Tolerance for grayscale check (used for both saturation and brightness). + * + * @defaultValue 0.001 + */ + eps: number; + /** + * PRNG instance to use for randomized values + * + * @defaultValue {@link @thi.ng/random#SYSTEM} + */ + rnd: IRandom; } export interface ColorThemePart { - /** - * Color range spec to use - */ - range?: ColorRange | ColorRangePreset; - /** - * Base color. Either a {@link TypedColor} instance, {@link CSSColorName} or - * raw LCH tuple. - */ - base?: ReadonlyColor | CSSColorName; - /** - * Relative weight of this theme part - * - * @defaultValue 1.0 - */ - weight?: number; + /** + * Color range spec to use + */ + range?: ColorRange | ColorRangePreset; + /** + * Base color. Either a {@link TypedColor} instance, {@link CSSColorName} or + * raw LCH tuple. + */ + base?: ReadonlyColor | CSSColorName; + /** + * Relative weight of this theme part + * + * @defaultValue 1.0 + */ + weight?: number; } export type ColorThemePartTuple = - | [ColorRangePreset, CSSColorName, number?] - | [ColorRangePreset | CSSColorName, number?] - | ColorRangePreset - | CSSColorName; + | [ColorRangePreset, CSSColorName, number?] + | [ColorRangePreset | CSSColorName, number?] + | ColorRangePreset + | CSSColorName; diff --git a/packages/color/src/api/system.ts b/packages/color/src/api/system.ts index 0b934a9995..cdbc8c833f 100644 --- a/packages/color/src/api/system.ts +++ b/packages/color/src/api/system.ts @@ -3,68 +3,68 @@ * Reference: https://www.w3.org/TR/css-color-4/#typedef-system-color */ export interface SystemColors { - /** - * Background of application content or documents. - */ - canvas: string; - /** - * Text in application content or documents. - */ - canvastext: string; - /** - * Text in non-active, non-visited links. For light backgrounds, - * traditionally blue. - */ - linktext: string; - /** - * Text in visited links. For light backgrounds, traditionally purple. - */ - visitedtext: string; - /** - * Text in active links. For light backgrounds, traditionally red. - */ - activetext: string; - /** - * The face background color for push buttons. - */ - buttonface: string; - /** - * Text on push buttons. - */ - buttontext: string; - /** - * The base border color for push buttons. - */ - buttonborder: string; - /** - * Background of input fields. - */ - field: string; - /** - * Text in input fields. - */ - fieldtext: string; - /** - * Background of selected items/text. - */ - highlight: string; - /** - * Text of selected items/text. - */ - highlighttext: string; - /** - * Background of text that has been specially marked (such as by the HTML - * mark element). - */ - mark: string; - /** - * Text that has been specially marked (such as by the HTML mark element). - */ - marktext: string; - /** - * Disabled text. (Often, but not necessarily, gray.) - */ - graytext: string; + /** + * Background of application content or documents. + */ + canvas: string; + /** + * Text in application content or documents. + */ + canvastext: string; + /** + * Text in non-active, non-visited links. For light backgrounds, + * traditionally blue. + */ + linktext: string; + /** + * Text in visited links. For light backgrounds, traditionally purple. + */ + visitedtext: string; + /** + * Text in active links. For light backgrounds, traditionally red. + */ + activetext: string; + /** + * The face background color for push buttons. + */ + buttonface: string; + /** + * Text on push buttons. + */ + buttontext: string; + /** + * The base border color for push buttons. + */ + buttonborder: string; + /** + * Background of input fields. + */ + field: string; + /** + * Text in input fields. + */ + fieldtext: string; + /** + * Background of selected items/text. + */ + highlight: string; + /** + * Text of selected items/text. + */ + highlighttext: string; + /** + * Background of text that has been specially marked (such as by the HTML + * mark element). + */ + mark: string; + /** + * Text that has been specially marked (such as by the HTML mark element). + */ + marktext: string; + /** + * Disabled text. (Often, but not necessarily, gray.) + */ + graytext: string; } /** @@ -72,27 +72,27 @@ export interface SystemColors { * {@link setSystemColors} to provide custom defaults. */ export let CSS_SYSTEM_COLORS: SystemColors = { - canvas: "fff", - canvastext: "000", - linktext: "001ee4", - visitedtext: "4e2386", - activetext: "eb3323", - buttonface: "ddd", - buttontext: "000", - buttonborder: "000", - field: "fff", - fieldtext: "000", - highlight: "bbd5fb", - highlighttext: "000", - mark: "000", - marktext: "fff", - graytext: "808080", + canvas: "fff", + canvastext: "000", + linktext: "001ee4", + visitedtext: "4e2386", + activetext: "eb3323", + buttonface: "ddd", + buttontext: "000", + buttonborder: "000", + field: "fff", + fieldtext: "000", + highlight: "bbd5fb", + highlighttext: "000", + mark: "000", + marktext: "fff", + graytext: "808080", }; /** * Merges {@link CSS_SYSTEM_COLORS} w/ new values. * - * @param cols - + * @param cols - */ export const setSystemColors = (cols: Partial) => - Object.assign(CSS_SYSTEM_COLORS, cols); + Object.assign(CSS_SYSTEM_COLORS, cols); diff --git a/packages/color/src/clamp.ts b/packages/color/src/clamp.ts index 0e39232645..54f14a7dd3 100644 --- a/packages/color/src/clamp.ts +++ b/packages/color/src/clamp.ts @@ -14,13 +14,13 @@ import { __ensureAlpha } from "./internal/ensure.js"; * @param alpha - alpha value */ export const clamp = (out: Color | null, src: ReadonlyColor, alpha = 1) => - setC4( - out || src, - clamp01(src[0]), - clamp01(src[1]), - clamp01(src[2]), - __ensureAlpha(src[3], alpha) - ); + setC4( + out || src, + clamp01(src[0]), + clamp01(src[1]), + clamp01(src[2]), + __ensureAlpha(src[3], alpha) + ); /** * Similar to {@link clamp}, but calls `ensureHue` to fold (instead of @@ -31,10 +31,10 @@ export const clamp = (out: Color | null, src: ReadonlyColor, alpha = 1) => * @param alpha - alpha value */ export const clampH = (out: Color | null, src: ReadonlyColor, alpha = 1) => - setC4( - out || src, - fract(src[0]), - clamp01(src[1]), - clamp01(src[2]), - __ensureAlpha(src[3], alpha) - ); + setC4( + out || src, + fract(src[0]), + clamp01(src[1]), + clamp01(src[2]), + __ensureAlpha(src[3], alpha) + ); diff --git a/packages/color/src/color-range.ts b/packages/color/src/color-range.ts index bfba13ef11..b80a9b0484 100644 --- a/packages/color/src/color-range.ts +++ b/packages/color/src/color-range.ts @@ -10,11 +10,11 @@ import { coin } from "@thi.ng/random/coin"; import { SYSTEM } from "@thi.ng/random/system"; import { weightedRandom } from "@thi.ng/random/weighted-random"; import type { - ColorRange, - ColorRangeOpts, - ColorRangePreset, - ColorThemePart, - ColorThemePartTuple, + ColorRange, + ColorRangeOpts, + ColorRangePreset, + ColorThemePart, + ColorThemePartTuple, } from "./api/ranges.js"; import { parseCss } from "./css/parse-css.js"; import { __ensureAlpha } from "./internal/ensure.js"; @@ -29,91 +29,91 @@ import { isWhite } from "./is-white.js"; * {@link colorsFromTheme} etc. */ export const COLOR_RANGES: Record = { - light: { - c: [[0.3, 0.7]], - l: [[0.9, 1]], - b: [[0.35, 0.5]], - w: [[0.6, 1]], - }, - dark: { - c: [[0.7, 1]], - l: [[0.15, 0.4]], - b: [[0, 0.4]], - w: [[0.4, 0.6]], - }, - bright: { - c: [[0.75, 0.95]], - l: [[0.8, 1]], - }, - weak: { - c: [[0.15, 0.3]], - l: [[0.7, 1]], - b: [[0.4, 0.6]], - w: [[0.8, 1]], - }, - neutral: { - c: [[0.25, 0.35]], - l: [[0.3, 0.7]], - b: [[0.25, 0.4]], - w: [[0.9, 1]], - }, - fresh: { - c: [[0.4, 0.8]], - l: [[0.8, 1]], - b: [[0.05, 0.3]], - w: [[0.8, 1]], - }, - soft: { - c: [[0.2, 0.3]], - l: [[0.6, 0.9]], - b: [[0.05, 0.15]], - w: [[0.6, 0.9]], - }, - hard: { - c: [[0.85, 0.95]], - l: [[0.4, 1]], - }, - warm: { - c: [[0.6, 0.9]], - l: [[0.4, 0.9]], - b: [[0.2, 0.3]], - w: [[0.8, 1]], - }, - cool: { - c: [[0.05, 0.2]], - l: [[0.9, 1]], - b: [[0, 0.95]], - w: [[0.95, 1]], - }, - intense: { - c: [[0.9, 1]], - l: [ - [0.2, 0.35], - [0.8, 1], - ], - }, + light: { + c: [[0.3, 0.7]], + l: [[0.9, 1]], + b: [[0.35, 0.5]], + w: [[0.6, 1]], + }, + dark: { + c: [[0.7, 1]], + l: [[0.15, 0.4]], + b: [[0, 0.4]], + w: [[0.4, 0.6]], + }, + bright: { + c: [[0.75, 0.95]], + l: [[0.8, 1]], + }, + weak: { + c: [[0.15, 0.3]], + l: [[0.7, 1]], + b: [[0.4, 0.6]], + w: [[0.8, 1]], + }, + neutral: { + c: [[0.25, 0.35]], + l: [[0.3, 0.7]], + b: [[0.25, 0.4]], + w: [[0.9, 1]], + }, + fresh: { + c: [[0.4, 0.8]], + l: [[0.8, 1]], + b: [[0.05, 0.3]], + w: [[0.8, 1]], + }, + soft: { + c: [[0.2, 0.3]], + l: [[0.6, 0.9]], + b: [[0.05, 0.15]], + w: [[0.6, 0.9]], + }, + hard: { + c: [[0.85, 0.95]], + l: [[0.4, 1]], + }, + warm: { + c: [[0.6, 0.9]], + l: [[0.4, 0.9]], + b: [[0.2, 0.3]], + w: [[0.8, 1]], + }, + cool: { + c: [[0.05, 0.2]], + l: [[0.9, 1]], + b: [[0, 0.95]], + w: [[0.95, 1]], + }, + intense: { + c: [[0.9, 1]], + l: [ + [0.2, 0.35], + [0.8, 1], + ], + }, }; const FULL: Range[] = [[0, 1]]; const DEFAULT_RANGE: ColorRange = { - h: FULL, - c: FULL, - l: FULL, - b: FULL, - w: FULL, - a: [[1, 1]], + h: FULL, + c: FULL, + l: FULL, + b: FULL, + w: FULL, + a: [[1, 1]], }; const DEFAULT_OPTS: ColorRangeOpts = { - num: Infinity, - variance: 0.025, - eps: 1e-3, - rnd: SYSTEM, + num: Infinity, + variance: 0.025, + eps: 1e-3, + rnd: SYSTEM, }; const $rnd = (ranges: Range[], rnd: IRandom) => - rnd.minmax(...ranges[rnd.int() % ranges.length]); + rnd.minmax(...ranges[rnd.int() % ranges.length]); /** * Takes a {@link ColorRange} and options to produce a single new result color. @@ -132,48 +132,48 @@ const $rnd = (ranges: Range[], rnd: IRandom) => * * A custom PRNG can be defined via the `rnd` option (default: `Math.random`). * - * @param range - - * @param opts - + * @param range - + * @param opts - */ export const colorFromRange = ( - range: ColorRange | keyof typeof COLOR_RANGES, - opts?: Partial> + range: ColorRange | keyof typeof COLOR_RANGES, + opts?: Partial> ): LCH => { - range = { - ...DEFAULT_RANGE, - ...(isString(range) ? COLOR_RANGES[range] : range), - }; - const { base, variance, rnd, eps } = { ...DEFAULT_OPTS, ...opts }; - let h: number; - let c: number | undefined; - let l: number | undefined; - let a: number; - if (base) { - const col = lch(base); - h = col[2]; - a = __ensureAlpha(col[3]); - if (isBlack(col, eps)) { - c = 0; - l = $rnd(range.b!, rnd); - } else if (isWhite(col, eps)) { - c = 0; - l = $rnd(range.w!, rnd); - } else if (isGray(col, eps)) { - c = 0; - l = $rnd(coin(rnd) ? range.b! : range.w!, rnd); - } else { - h = fract(h + rnd.norm(variance)); - } - } else { - h = $rnd(range.h!, rnd); - a = $rnd(range.a!, rnd); - } - return lch([ - l != undefined ? l : $rnd(range.l!, rnd), - c !== undefined ? c : $rnd(range.c!, rnd), - h, - a, - ]); + range = { + ...DEFAULT_RANGE, + ...(isString(range) ? COLOR_RANGES[range] : range), + }; + const { base, variance, rnd, eps } = { ...DEFAULT_OPTS, ...opts }; + let h: number; + let c: number | undefined; + let l: number | undefined; + let a: number; + if (base) { + const col = lch(base); + h = col[2]; + a = __ensureAlpha(col[3]); + if (isBlack(col, eps)) { + c = 0; + l = $rnd(range.b!, rnd); + } else if (isWhite(col, eps)) { + c = 0; + l = $rnd(range.w!, rnd); + } else if (isGray(col, eps)) { + c = 0; + l = $rnd(coin(rnd) ? range.b! : range.w!, rnd); + } else { + h = fract(h + rnd.norm(variance)); + } + } else { + h = $rnd(range.h!, rnd); + a = $rnd(range.a!, rnd); + } + return lch([ + l != undefined ? l : $rnd(range.l!, rnd), + c !== undefined ? c : $rnd(range.c!, rnd), + h, + a, + ]); }; /** @@ -181,67 +181,67 @@ export const colorFromRange = ( * sequence of random colors based on given range, base color (optional) and * other opts. Use `num` option to limit number of results. * - * @param range - - * @param opts - + * @param range - + * @param opts - */ export function* colorsFromRange( - range: ColorRange | keyof typeof COLOR_RANGES, - opts: Partial = {} + range: ColorRange | keyof typeof COLOR_RANGES, + opts: Partial = {} ) { - let num = opts.num != undefined ? opts.num : Infinity; - while (num-- > 0) yield colorFromRange(range, opts); + let num = opts.num != undefined ? opts.num : Infinity; + while (num-- > 0) yield colorFromRange(range, opts); } /** @internal */ const compileThemePart = ( - part: ColorThemePart | ColorThemePartTuple, - opts: Partial + part: ColorThemePart | ColorThemePartTuple, + opts: Partial ) => { - let spec: ColorThemePart; - if (isArray(part)) { - spec = themePartFromTuple(part); - } else if (isString(part)) { - spec = themePartFromString(part); - } else { - spec = { ...part }; - spec.weight == null && (spec.weight = 1); - } - isString(spec.range) && (spec.range = COLOR_RANGES[spec.range]); - isString(spec.base) && (spec.base = lch(parseCss(spec.base))); - if (spec.base !== undefined) { - opts = { ...opts, base: spec.base }; - } - return { spec, opts }; + let spec: ColorThemePart; + if (isArray(part)) { + spec = themePartFromTuple(part); + } else if (isString(part)) { + spec = themePartFromString(part); + } else { + spec = { ...part }; + spec.weight == null && (spec.weight = 1); + } + isString(spec.range) && (spec.range = COLOR_RANGES[spec.range]); + isString(spec.base) && (spec.base = lch(parseCss(spec.base))); + if (spec.base !== undefined) { + opts = { ...opts, base: spec.base }; + } + return { spec, opts }; }; /** @internal */ const themePartFromTuple = (part: ColorThemePartTuple) => { - let weight: number; - const [range, ...xs] = part; - if (isNumber(peek(xs))) { - weight = peek(xs); - xs.pop(); - } else { - weight = 1; - } - return ( - (xs.length === 1 - ? { range, base: xs[0], weight } - : xs.length === 0 - ? COLOR_RANGES[range] - ? { range, weight } - : { base: range, weight } - : illegalArgs(`invalid theme part: "${part}"`)) - ); + let weight: number; + const [range, ...xs] = part; + if (isNumber(peek(xs))) { + weight = peek(xs); + xs.pop(); + } else { + weight = 1; + } + return ( + (xs.length === 1 + ? { range, base: xs[0], weight } + : xs.length === 0 + ? COLOR_RANGES[range] + ? { range, weight } + : { base: range, weight } + : illegalArgs(`invalid theme part: "${part}"`)) + ); }; /** @internal */ const themePartFromString = (part: string) => - ( - (COLOR_RANGES[part] - ? { range: part, weight: 1 } - : { base: part, weight: 1 }) - ); + ( + (COLOR_RANGES[part] + ? { range: part, weight: 1 } + : { base: part, weight: 1 }) + ); /** * Probabilistic color theme generator. Yield randomized colors based on given @@ -268,26 +268,26 @@ const themePartFromString = (part: string) => * )] * ``` * - * @param parts - - * @param opts - + * @param parts - + * @param opts - */ export function* colorsFromTheme( - parts: (ColorThemePart | ColorThemePartTuple)[], - opts: Partial> = {} + parts: (ColorThemePart | ColorThemePartTuple)[], + opts: Partial> = {} ) { - let { num, variance, rnd } = { ...DEFAULT_OPTS, ...opts }; - const theme = parts.map((p) => compileThemePart(p, opts)); - const choice = weightedRandom( - theme, - theme.map((x) => x.spec.weight!), - rnd - ); - while (--num! >= 0) { - const { spec, opts } = choice(); - if (spec.range) { - yield colorFromRange(spec.range, opts); - } else if (spec.base) { - yield analog(lch(), lch(spec.base), variance!, rnd); - } - } + let { num, variance, rnd } = { ...DEFAULT_OPTS, ...opts }; + const theme = parts.map((p) => compileThemePart(p, opts)); + const choice = weightedRandom( + theme, + theme.map((x) => x.spec.weight!), + rnd + ); + while (--num! >= 0) { + const { spec, opts } = choice(); + if (spec.range) { + yield colorFromRange(spec.range, opts); + } else if (spec.base) { + yield analog(lch(), lch(spec.base), variance!, rnd); + } + } } diff --git a/packages/color/src/color.ts b/packages/color/src/color.ts index a3259347e3..99eb635a12 100644 --- a/packages/color/src/color.ts +++ b/packages/color/src/color.ts @@ -1,10 +1,10 @@ import { isString } from "@thi.ng/checks/is-string"; import type { - Color, - ColorFactory, - ColorMode, - ParsedColor, - TypedColor, + Color, + ColorFactory, + ColorMode, + ParsedColor, + TypedColor, } from "./api.js"; import { hcy } from "./hcy/hcy.js"; import { hsi } from "./hsi/hsi.js"; @@ -23,47 +23,47 @@ import { xyzD65 } from "./xyz/xyz65.js"; import { ycc } from "./ycc/ycc.js"; const FACTORIES: Record> = { - argb32, - abgr32, - hcy, - hsi, - hsl, - hsv, - lab50: labD50, - lab65: labD65, - lch, - oklab, - rgb, - srgb, - xyy, - xyz50: xyzD50, - xyz65: xyzD65, - ycc, + argb32, + abgr32, + hcy, + hsi, + hsl, + hsv, + lab50: labD50, + lab65: labD65, + lch, + oklab, + rgb, + srgb, + xyy, + xyz50: xyzD50, + xyz65: xyzD65, + ycc, }; export function color( - src: ParsedColor, - buf?: Color, - idx?: number, - stride?: number + src: ParsedColor, + buf?: Color, + idx?: number, + stride?: number ): TypedColor; export function color( - mode: ColorMode, - buf: Color, - idx?: number, - stride?: number + mode: ColorMode, + buf: Color, + idx?: number, + stride?: number ): TypedColor; export function color( - src: any, - buf?: any, - idx?: number, - stride?: number + src: any, + buf?: any, + idx?: number, + stride?: number ): TypedColor { - if (isString(src)) return FACTORIES[src](buf, idx, stride); - if (buf) { - const res = FACTORIES[(src).mode](buf, idx, stride); - res.set(src.deref()); - return res; - } - return FACTORIES[(src).mode](src.deref()); + if (isString(src)) return FACTORIES[src](buf, idx, stride); + if (buf) { + const res = FACTORIES[(src).mode](buf, idx, stride); + res.set(src.deref()); + return res; + } + return FACTORIES[(src).mode](src.deref()); } diff --git a/packages/color/src/convert.ts b/packages/color/src/convert.ts index 564ded0187..a408518157 100644 --- a/packages/color/src/convert.ts +++ b/packages/color/src/convert.ts @@ -2,11 +2,11 @@ import { isArray } from "@thi.ng/checks/is-array"; import { assert } from "@thi.ng/errors/assert"; import { unsupported } from "@thi.ng/errors/unsupported"; import type { - Color, - ColorMode, - ColorSpec, - Conversions, - ReadonlyColor, + Color, + ColorMode, + ColorSpec, + Conversions, + ReadonlyColor, } from "./api.js"; export const CONVERSIONS: Partial> = {}; @@ -15,41 +15,41 @@ export const CONVERSIONS: Partial> = {}; * Registers conversions for given {@link ColorSpec}. Called by * {@link defColor}. * - * @param spec - + * @param spec - * * @internal */ export const defConversions = ( - mode: ColorMode, - spec: ColorSpec["from"] + mode: ColorMode, + spec: ColorSpec["from"] ) => { - for (let id in spec) { - const val = spec[id]; - if (isArray(val)) { - const [a, b, c, d] = val; - spec[id] = - val.length === 2 - ? (out, src) => b(out, a(out, src)) - : val.length === 3 - ? (out, src) => c!(out, b(out, a(out, src))) - : (out, src) => d!(out, c!(out, b(out, a(out, src)))); - } - } - CONVERSIONS[mode] = { ...CONVERSIONS[mode], ...(spec) }; + for (let id in spec) { + const val = spec[id]; + if (isArray(val)) { + const [a, b, c, d] = val; + spec[id] = + val.length === 2 + ? (out, src) => b(out, a(out, src)) + : val.length === 3 + ? (out, src) => c!(out, b(out, a(out, src))) + : (out, src) => d!(out, c!(out, b(out, a(out, src)))); + } + } + CONVERSIONS[mode] = { ...CONVERSIONS[mode], ...(spec) }; }; export const convert = ( - res: T | null, - src: ReadonlyColor, - destMode: ColorMode, - srcMode: ColorMode + res: T | null, + src: ReadonlyColor, + destMode: ColorMode, + srcMode: ColorMode ): T => { - const spec = CONVERSIONS[destMode]; - assert(!!spec, `no conversions available for ${destMode}`); - let $convert = spec![srcMode]; - return $convert - ? $convert(res, src) - : CONVERSIONS.rgb![srcMode] - ? spec!.rgb(res, CONVERSIONS.rgb![srcMode]!([], src)) - : unsupported(`can't convert: ${srcMode} -> ${destMode}`); + const spec = CONVERSIONS[destMode]; + assert(!!spec, `no conversions available for ${destMode}`); + let $convert = spec![srcMode]; + return $convert + ? $convert(res, src) + : CONVERSIONS.rgb![srcMode] + ? spec!.rgb(res, CONVERSIONS.rgb![srcMode]!([], src)) + : unsupported(`can't convert: ${srcMode} -> ${destMode}`); }; diff --git a/packages/color/src/cosine-gradients.ts b/packages/color/src/cosine-gradients.ts index 43400c422d..86b54d6d15 100644 --- a/packages/color/src/cosine-gradients.ts +++ b/packages/color/src/cosine-gradients.ts @@ -13,10 +13,10 @@ import { zip } from "@thi.ng/transducers/zip"; import { setS4 } from "@thi.ng/vectors/sets"; import type { Color, ReadonlyColor } from "./api.js"; import type { - CosGradientSpec, - CosineCoeffs, - CosineGradientOpts, - CosineGradientPreset, + CosGradientSpec, + CosineCoeffs, + CosineGradientOpts, + CosineGradientPreset, } from "./api/gradients.js"; import { clamp } from "./clamp.js"; @@ -30,156 +30,156 @@ import { clamp } from "./clamp.js"; * alpha channel is configured to always be 1.0) */ export const COSINE_GRADIENTS: Record = { - "blue-cyan": [ - [0, 0.5, 0.5, 1], - [0, 0.5, 0.5, 0], - [0, 0.5, 0.3333, 0], - [0, 0.5, 0.6666, 0], - ], - "blue-magenta-orange": [ - [0.938, 0.328, 0.718, 1], - [0.659, 0.438, 0.328, 0], - [0.388, 0.388, 0.296, 0], - [2.538, 2.478, 0.168, 0], - ], - "blue-white-red": [ - [0.66, 0.56, 0.68, 1], - [0.718, 0.438, 0.72, 0], - [0.52, 0.8, 0.52, 0], - [-0.43, -0.397, -0.083, 0], - ], - "cyan-magenta": [ - [0.61, 0.498, 0.65, 1], - [0.388, 0.498, 0.35, 0], - [0.53, 0.498, 0.62, 0], - [3.438, 3.012, 4.025, 0], - ], - "green-blue-orange": [ - [0.892, 0.725, 0, 1], - [0.878, 0.278, 0.725, 0], - [0.332, 0.518, 0.545, 0], - [2.44, 5.043, 0.732, 0], - ], - "green-cyan": [ - [0, 0.5, 0.5, 1], - [0, 0.5, 0.5, 0], - [0, 0.3333, 0.5, 0], - [0, 0.6666, 0.5, 0], - ], - "green-magenta": [ - [0.6666, 0.5, 0.5, 1], - [0.5, 0.6666, 0.5, 0], - [0.6666, 0.666, 0.5, 0], - [0.2, 0, 0.5, 0], - ], - "green-red": [ - [0.5, 0.5, 0, 1], - [0.5, 0.5, 0, 0], - [0.5, 0.5, 0, 0], - [0.5, 0, 0, 0], - ], - heat1: [ - [0.5, 0.4, 0.25, 1], - [0.5, 0.5, 0.666, 0], - [0.5, 0.666, 0.8, 0], - [0.5, 0.666, 0.8, 0], - ], - "magenta-green": [ - [0.59, 0.811, 0.12, 1], - [0.41, 0.392, 0.59, 0], - [0.94, 0.548, 0.278, 0], - [-4.242, -6.611, -4.045, 0], - ], - "orange-blue": [ - [0.5, 0.5, 0.5, 1], - [0.5, 0.5, 0.5, 0], - [0.8, 0.8, 0.5, 0], - [0, 0.2, 0.5, 0], - ], - "orange-magenta-blue": [ - [0.821, 0.328, 0.242, 1], - [0.659, 0.481, 0.896, 0], - [0.612, 0.34, 0.296, 0], - [2.82, 3.026, -0.273, 0], - ], - "purple-orange-cyan": [ - [0.5, 0.5, 0.5, 1], - [0.5, 0.5, 0.5, 0], - [0.5, 0.5, 1, 0], - [-0.25, 0.5, 1, 0], - ], - rainbow1: [ - [0.5, 0.5, 0.5, 1], - [0.5, 0.5, 0.5, 0], - [1.0, 1.0, 1.0, 0], - [0, 0.3333, 0.6666, 0], - ], - rainbow2: [ - [0.5, 0.5, 0.5, 1], - [0.666, 0.666, 0.666, 0], - [1.0, 1.0, 1.0, 0], - [0, 0.3333, 0.6666, 0], - ], - rainbow3: [ - [0.5, 0.5, 0.5, 1], - [0.75, 0.75, 0.75, 0], - [1.0, 1.0, 1.0, 0], - [0, 0.3333, 0.6666, 0], - ], - rainbow4: [ - [0.5, 0.5, 0.5, 1], - [1, 1, 1, 0], - [1.0, 1.0, 1.0, 0], - [0, 0.3333, 0.6666, 0], - ], - "red-blue": [ - [0.5, 0, 0.5, 1], - [0.5, 0, 0.5, 0], - [0.5, 0, 0.5, 0], - [0, 0, 0.5, 0], - ], - "yellow-green-blue": [ - [0.65, 0.5, 0.31, 1], - [-0.65, 0.5, 0.6, 0], - [0.333, 0.278, 0.278, 0], - [0.66, 0, 0.667, 0], - ], - "yellow-magenta-cyan": [ - [1, 0.5, 0.5, 1], - [0.5, 0.5, 0.5, 0], - [0.75, 1.0, 0.6666, 0], - [0.8, 1.0, 0.3333, 0], - ], - "yellow-purple-magenta": [ - [0.731, 1.098, 0.192, 1], - [0.358, 1.09, 0.657, 0], - [1.077, 0.36, 0.328, 0], - [0.965, 2.265, 0.837, 0], - ], - "yellow-red": [ - [0.5, 0.5, 0, 1], - [0.5, 0.5, 0, 0], - [0.1, 0.5, 0, 0], - [0, 0, 0, 0], - ], + "blue-cyan": [ + [0, 0.5, 0.5, 1], + [0, 0.5, 0.5, 0], + [0, 0.5, 0.3333, 0], + [0, 0.5, 0.6666, 0], + ], + "blue-magenta-orange": [ + [0.938, 0.328, 0.718, 1], + [0.659, 0.438, 0.328, 0], + [0.388, 0.388, 0.296, 0], + [2.538, 2.478, 0.168, 0], + ], + "blue-white-red": [ + [0.66, 0.56, 0.68, 1], + [0.718, 0.438, 0.72, 0], + [0.52, 0.8, 0.52, 0], + [-0.43, -0.397, -0.083, 0], + ], + "cyan-magenta": [ + [0.61, 0.498, 0.65, 1], + [0.388, 0.498, 0.35, 0], + [0.53, 0.498, 0.62, 0], + [3.438, 3.012, 4.025, 0], + ], + "green-blue-orange": [ + [0.892, 0.725, 0, 1], + [0.878, 0.278, 0.725, 0], + [0.332, 0.518, 0.545, 0], + [2.44, 5.043, 0.732, 0], + ], + "green-cyan": [ + [0, 0.5, 0.5, 1], + [0, 0.5, 0.5, 0], + [0, 0.3333, 0.5, 0], + [0, 0.6666, 0.5, 0], + ], + "green-magenta": [ + [0.6666, 0.5, 0.5, 1], + [0.5, 0.6666, 0.5, 0], + [0.6666, 0.666, 0.5, 0], + [0.2, 0, 0.5, 0], + ], + "green-red": [ + [0.5, 0.5, 0, 1], + [0.5, 0.5, 0, 0], + [0.5, 0.5, 0, 0], + [0.5, 0, 0, 0], + ], + heat1: [ + [0.5, 0.4, 0.25, 1], + [0.5, 0.5, 0.666, 0], + [0.5, 0.666, 0.8, 0], + [0.5, 0.666, 0.8, 0], + ], + "magenta-green": [ + [0.59, 0.811, 0.12, 1], + [0.41, 0.392, 0.59, 0], + [0.94, 0.548, 0.278, 0], + [-4.242, -6.611, -4.045, 0], + ], + "orange-blue": [ + [0.5, 0.5, 0.5, 1], + [0.5, 0.5, 0.5, 0], + [0.8, 0.8, 0.5, 0], + [0, 0.2, 0.5, 0], + ], + "orange-magenta-blue": [ + [0.821, 0.328, 0.242, 1], + [0.659, 0.481, 0.896, 0], + [0.612, 0.34, 0.296, 0], + [2.82, 3.026, -0.273, 0], + ], + "purple-orange-cyan": [ + [0.5, 0.5, 0.5, 1], + [0.5, 0.5, 0.5, 0], + [0.5, 0.5, 1, 0], + [-0.25, 0.5, 1, 0], + ], + rainbow1: [ + [0.5, 0.5, 0.5, 1], + [0.5, 0.5, 0.5, 0], + [1.0, 1.0, 1.0, 0], + [0, 0.3333, 0.6666, 0], + ], + rainbow2: [ + [0.5, 0.5, 0.5, 1], + [0.666, 0.666, 0.666, 0], + [1.0, 1.0, 1.0, 0], + [0, 0.3333, 0.6666, 0], + ], + rainbow3: [ + [0.5, 0.5, 0.5, 1], + [0.75, 0.75, 0.75, 0], + [1.0, 1.0, 1.0, 0], + [0, 0.3333, 0.6666, 0], + ], + rainbow4: [ + [0.5, 0.5, 0.5, 1], + [1, 1, 1, 0], + [1.0, 1.0, 1.0, 0], + [0, 0.3333, 0.6666, 0], + ], + "red-blue": [ + [0.5, 0, 0.5, 1], + [0.5, 0, 0.5, 0], + [0.5, 0, 0.5, 0], + [0, 0, 0.5, 0], + ], + "yellow-green-blue": [ + [0.65, 0.5, 0.31, 1], + [-0.65, 0.5, 0.6, 0], + [0.333, 0.278, 0.278, 0], + [0.66, 0, 0.667, 0], + ], + "yellow-magenta-cyan": [ + [1, 0.5, 0.5, 1], + [0.5, 0.5, 0.5, 0], + [0.75, 1.0, 0.6666, 0], + [0.8, 1.0, 0.3333, 0], + ], + "yellow-purple-magenta": [ + [0.731, 1.098, 0.192, 1], + [0.358, 1.09, 0.657, 0], + [1.077, 0.36, 0.328, 0], + [0.965, 2.265, 0.837, 0], + ], + "yellow-red": [ + [0.5, 0.5, 0, 1], + [0.5, 0.5, 0, 0], + [0.1, 0.5, 0, 0], + [0, 0, 0, 0], + ], }; /** * Computes a single linear RGBA color for given gradient spec and normalized * position `t` (in [0..1] interval). * - * @param spec - - * @param t - + * @param spec - + * @param t - */ export const cosineColor = (spec: CosGradientSpec, t: number): Color => - transduce( - map(([a, b, c, d]) => - clamp01(a + b * Math.cos(TAU * (c * t + d))) - ), - push(), - // @ts-ignore - zip(...spec) - ); + transduce( + map(([a, b, c, d]) => + clamp01(a + b * Math.cos(TAU * (c * t + d))) + ), + push(), + // @ts-ignore + zip(...spec) + ); /** * Computes a full cosine gradient and returns an array of `n` sampled linear @@ -190,20 +190,20 @@ export const cosineColor = (spec: CosGradientSpec, t: number): Color => * For CSS/SVG use cases you could use {@link srgba} as transformation function * to not convert, but reinterpret & wrap raw color values as SRGBA. * - * @param n - - * @param spec - - * @param tx - + * @param n - + * @param spec - + * @param tx - */ export const cosineGradient = ( - n: number, - spec: CosGradientSpec, - tx?: FnU + n: number, + spec: CosGradientSpec, + tx?: FnU ): Color[] => - transduce( - comp(map(partial(cosineColor, spec)), tx ? map(tx) : noop()), - push(), - normRange(n - 1) - ); + transduce( + comp(map(partial(cosineColor, spec)), tx ? map(tx) : noop()), + push(), + normRange(n - 1) + ); /** * Similar to {@link cosineGradient}, but writes results into `buffer` from @@ -221,18 +221,18 @@ export const cosineGradient = ( * @param estride - element stride (default: 4) */ export const cosineGradientBuffer = ( - n: number, - spec: CosGradientSpec, - buffer: NumericArray = [], - offset = 0, - cstride = 1, - estride = 4 + n: number, + spec: CosGradientSpec, + buffer: NumericArray = [], + offset = 0, + cstride = 1, + estride = 4 ) => { - for (let t of normRange(n - 1)) { - setS4(buffer, cosineColor(spec, t), offset, 0, cstride); - offset += estride; - } - return buffer; + for (let t of normRange(n - 1)) { + setS4(buffer, cosineColor(spec, t), offset, 0, cstride); + offset += estride; + } + return buffer; }; /** @@ -243,18 +243,18 @@ export const cosineGradientBuffer = ( * @param to - end color */ export const cosineCoeffs: FnU2 = ( - from, - to + from, + to ) => { - from = clamp([], from); - to = clamp([], to); - const amp = [...map(([a, b]) => 0.5 * (a - b), zip(from, to))]; - return [ - [...map(([s, a]) => s - a, zip(from, amp))], - amp, - [-0.5, -0.5, -0.5, -0.5], - [0, 0, 0, 0], - ]; + from = clamp([], from); + to = clamp([], to); + const amp = [...map(([a, b]) => 0.5 * (a - b), zip(from, to))]; + return [ + [...map(([s, a]) => s - a, zip(from, amp))], + amp, + [-0.5, -0.5, -0.5, -0.5], + [0, 0, 0, 0], + ]; }; /** @@ -279,16 +279,16 @@ export const cosineCoeffs: FnU2 = ( * @param stops - gradient stops */ export const multiCosineGradient = (opts: CosineGradientOpts): Color[] => - transduce( - opts.tx ? map(opts.tx) : noop(), - push(), - tween({ - num: opts.num, - stops: opts.stops, - easing: opts.easing, - min: 0, - max: 1, - init: cosineCoeffs, - mix: cosineColor, - }) - ); + transduce( + opts.tx ? map(opts.tx) : noop(), + push(), + tween({ + num: opts.num, + stops: opts.stops, + easing: opts.easing, + min: 0, + max: 1, + init: cosineCoeffs, + mix: cosineColor, + }) + ); diff --git a/packages/color/src/css/css.ts b/packages/color/src/css/css.ts index 9ffb735e3c..715045d878 100644 --- a/packages/color/src/css/css.ts +++ b/packages/color/src/css/css.ts @@ -2,10 +2,10 @@ import type { Fn } from "@thi.ng/api"; import { isNumber } from "@thi.ng/checks/is-number"; import { isString } from "@thi.ng/checks/is-string"; import type { - ColorMode, - IParsedColor, - MaybeColor, - TypedColor, + ColorMode, + IParsedColor, + MaybeColor, + TypedColor, } from "../api.js"; import { convert } from "../convert.js"; import { hslCss } from "../hsl/hsl-css.js"; @@ -20,20 +20,20 @@ import { srgbCss } from "../srgb/srgb-css.js"; /** @internal */ const CSS_CONVERSIONS: Partial>> = { - abgr32: (x) => intArgb32Css(intAbgr32Argb32(x[0])), - argb32: (x) => intArgb32Css(x[0]), - hsl: hslCss, - hsv: hsvCss, - // TODO temporarily disabled until CSS L4 is officially supported in browsers - // currently serializing as sRGB CSS - // lab50: labCss, - // lab65: (x) => labCss(labLabD65_50([], x)), - // lch: lchCss, - lab50: (src) => srgbCss(rgbSrgb(null, labRgb([], src))), - lab65: (src) => srgbCss(rgbSrgb(null, labRgbD65([], src))), - lch: (src) => srgbCss(rgbSrgb(null, labRgb(null, lchLab([], src)))), - rgb: rgbCss, - srgb: srgbCss, + abgr32: (x) => intArgb32Css(intAbgr32Argb32(x[0])), + argb32: (x) => intArgb32Css(x[0]), + hsl: hslCss, + hsv: hsvCss, + // TODO temporarily disabled until CSS L4 is officially supported in browsers + // currently serializing as sRGB CSS + // lab50: labCss, + // lab65: (x) => labCss(labLabD65_50([], x)), + // lch: lchCss, + lab50: (src) => srgbCss(rgbSrgb(null, labRgb([], src))), + lab65: (src) => srgbCss(rgbSrgb(null, labRgbD65([], src))), + lch: (src) => srgbCss(rgbSrgb(null, labRgb(null, lchLab([], src)))), + rgb: rgbCss, + srgb: srgbCss, }; /** @@ -48,16 +48,16 @@ const CSS_CONVERSIONS: Partial>> = { * @param col - source color */ export const css = (src: Exclude) => { - let asCss: Fn | undefined; - return isString(src) - ? src - : isNumber(src) - ? intArgb32Css(src) - : (>src).mode - ? (asCss = CSS_CONVERSIONS[(>src).mode]) - ? asCss(src) - : CSS_CONVERSIONS.rgb!( - convert([], src, "rgb", (>src).mode) - ) - : srgbCss(src); + let asCss: Fn | undefined; + return isString(src) + ? src + : isNumber(src) + ? intArgb32Css(src) + : (>src).mode + ? (asCss = CSS_CONVERSIONS[(>src).mode]) + ? asCss(src) + : CSS_CONVERSIONS.rgb!( + convert([], src, "rgb", (>src).mode) + ) + : srgbCss(src); }; diff --git a/packages/color/src/css/parse-css.ts b/packages/color/src/css/parse-css.ts index dd923dfe04..abe39e3501 100644 --- a/packages/color/src/css/parse-css.ts +++ b/packages/color/src/css/parse-css.ts @@ -45,108 +45,108 @@ import { intArgb32Srgb } from "../int/int-srgb.js"; * and {@link srgb} for non-linear (gamma encoded) RGB colors (CSS uses sRGB by * default). * - * @param src - + * @param src - */ export const parseCss = (src: string | IDeref): IParsedColor => { - src = (isString(src) ? src : src.deref()).toLowerCase(); - const named = (CSS_NAMES)[src] || (CSS_SYSTEM_COLORS)[src]; - if (named || src[0] === "#") - return new ParsedColor( - "srgb", - intArgb32Srgb([], parseHex(named || src)) - ); - const parts = src.split(/[(),/ ]+/); - const [mode, a, b, c, d] = parts; - assert( - parts.length === 5 || parts.length === 6, - `invalid ${mode} color: ${src}` - ); - switch (mode) { - case "rgb": - case "rgba": - return new ParsedColor("srgb", [ - parseNumOrPercent(a), - parseNumOrPercent(b), - parseNumOrPercent(c), - parseAlpha(d), - ]); - case "hsl": - case "hsla": - return new ParsedColor("hsl", [ - parseHue(a), - parsePercent(b), - parsePercent(c), - parseAlpha(d), - ]); - case "lab": - return new ParsedColor("lab50", [ - parsePercent(a, false), - parseNumber(b) * 0.01, - parseNumber(c) * 0.01, - parseAlpha(d), - ]); - case "lch": - return new ParsedColor("lch", [ - parsePercent(a, false), - parseNumber(b) * 0.01, - parseHue(c), - parseAlpha(d), - ]); - default: - unsupported(`color mode: ${mode}`); - } + src = (isString(src) ? src : src.deref()).toLowerCase(); + const named = (CSS_NAMES)[src] || (CSS_SYSTEM_COLORS)[src]; + if (named || src[0] === "#") + return new ParsedColor( + "srgb", + intArgb32Srgb([], parseHex(named || src)) + ); + const parts = src.split(/[(),/ ]+/); + const [mode, a, b, c, d] = parts; + assert( + parts.length === 5 || parts.length === 6, + `invalid ${mode} color: ${src}` + ); + switch (mode) { + case "rgb": + case "rgba": + return new ParsedColor("srgb", [ + parseNumOrPercent(a), + parseNumOrPercent(b), + parseNumOrPercent(c), + parseAlpha(d), + ]); + case "hsl": + case "hsla": + return new ParsedColor("hsl", [ + parseHue(a), + parsePercent(b), + parsePercent(c), + parseAlpha(d), + ]); + case "lab": + return new ParsedColor("lab50", [ + parsePercent(a, false), + parseNumber(b) * 0.01, + parseNumber(c) * 0.01, + parseAlpha(d), + ]); + case "lch": + return new ParsedColor("lch", [ + parsePercent(a, false), + parseNumber(b) * 0.01, + parseHue(c), + parseAlpha(d), + ]); + default: + unsupported(`color mode: ${mode}`); + } }; const HUE_NORMS: Record = { - rad: TAU, - grad: 400, - turn: 1, - deg: 360, - undefined: 360, + rad: TAU, + grad: 400, + turn: 1, + deg: 360, + undefined: 360, }; const parseHue = (x: string) => { - const match = /^(-?[0-9.]+)(deg|rad|grad|turn)?$/.exec(x); - assert(!!match, `expected hue, got: ${x}`); - return fract(parseFloat(match![1]) / HUE_NORMS[match![2]]); + const match = /^(-?[0-9.]+)(deg|rad|grad|turn)?$/.exec(x); + assert(!!match, `expected hue, got: ${x}`); + return fract(parseFloat(match![1]) / HUE_NORMS[match![2]]); }; const parseAlpha = (x?: string) => (x ? parseNumOrPercent(x, 1) : 1); const parsePercent = (x: string, clamp = true) => { - assert(/^([0-9.]+)%$/.test(x), `expected percentage, got: ${x}`); - const res = parseFloat(x) / 100; - return clamp ? clamp01(res) : res; + assert(/^([0-9.]+)%$/.test(x), `expected percentage, got: ${x}`); + const res = parseFloat(x) / 100; + return clamp ? clamp01(res) : res; }; const parseNumber = (x: string) => { - assert(/^-?[0-9.]+$/.test(x), `expected number, got: ${x}`); - return parseFloat(x); + assert(/^-?[0-9.]+$/.test(x), `expected number, got: ${x}`); + return parseFloat(x); }; const parseNumOrPercent = (x: string, norm = 255, clamp = true) => { - assert(/^-?[0-9.]+%?$/.test(x), `expected number or percentage, got: ${x}`); - const res = parseFloat(x) / (x.endsWith("%") ? 100 : norm); - return clamp ? clamp01(res) : res; + assert(/^-?[0-9.]+%?$/.test(x), `expected number or percentage, got: ${x}`); + const res = parseFloat(x) / (x.endsWith("%") ? 100 : norm); + return clamp ? clamp01(res) : res; }; export const parseHex = (src: string): number => { - const match = /^#?([0-9a-f]{3,8})$/i.exec(src); - if (match) { - const hex = match[1]; - switch (hex.length) { - case 3: - return ( - (interleave4_12_24(parseInt(hex, 16)) | 0xff000000) >>> 0 - ); - case 4: - return interleave4_16_32(parseInt(hex, 16)) >>> 0; - case 6: - return (parseInt(hex, 16) | 0xff000000) >>> 0; - case 8: - return parseInt(hex, 16) >>> 0; - default: - } - } - return illegalArgs(`invalid hex color: "${src}"`); + const match = /^#?([0-9a-f]{3,8})$/i.exec(src); + if (match) { + const hex = match[1]; + switch (hex.length) { + case 3: + return ( + (interleave4_12_24(parseInt(hex, 16)) | 0xff000000) >>> 0 + ); + case 4: + return interleave4_16_32(parseInt(hex, 16)) >>> 0; + case 6: + return (parseInt(hex, 16) | 0xff000000) >>> 0; + case 8: + return parseInt(hex, 16) >>> 0; + default: + } + } + return illegalArgs(`invalid hex color: "${src}"`); }; diff --git a/packages/color/src/defcolor.ts b/packages/color/src/defcolor.ts index 38b714129c..26228993d0 100644 --- a/packages/color/src/defcolor.ts +++ b/packages/color/src/defcolor.ts @@ -16,14 +16,14 @@ import { stridedValues } from "@thi.ng/vectors/iterator"; import { randMinMax } from "@thi.ng/vectors/random"; import { set4 } from "@thi.ng/vectors/set"; import type { - ChannelSpec, - ColorFactory, - ColorMode, - ColorSpec, - IColor, - MaybeColor, - ReadonlyColor, - TypedColor, + ChannelSpec, + ColorFactory, + ColorMode, + ColorSpec, + IColor, + MaybeColor, + ReadonlyColor, + TypedColor, } from "./api.js"; import { convert, defConversions } from "./convert.js"; import { parseCss } from "./css/parse-css.js"; @@ -31,160 +31,160 @@ import { intArgb32Rgb } from "./int/int-rgb.js"; import { __ensureArgs } from "./internal/ensure.js"; type $DefColor = { - [k in K]: number; + [k in K]: number; } & { - readonly mode: M; - random(rnd?: IRandom): $DefColor; - set(src: ReadonlyColor): $DefColor; - toJSON(): number[]; + readonly mode: M; + random(rnd?: IRandom): $DefColor; + set(src: ReadonlyColor): $DefColor; + toJSON(): number[]; } & TypedColor<$DefColor>; export const defColor = ( - spec: ColorSpec + spec: ColorSpec ) => { - const channels: Partial> = spec.channels || {}; - const order = spec.order; - const numChannels = order.length; - order.reduce((acc, id) => { - acc[id] = { - range: [0, 1], - ...channels[id], - }; - return acc; - }, channels); - const min = order.map((id) => channels[id]!.range![0]); - const max = order.map((id) => channels[id]!.range![1]); - // fix alpha channel for randomize() - const minR = set4([], min); - const maxR = set4([], max); - minR[numChannels - 1] = 1; - - const hueChanID = order.findIndex((id) => !!channels[id]!.hue); - - const $Color = class implements TypedColor<$DefColor> { - buf: NumericArray; - [id: number]: number; - - constructor(buf?: NumericArray, public offset = 0, public stride = 1) { - this.buf = buf || [0, 0, 0, 0]; - this.offset = offset; - this.stride = stride; - } - - get mode() { - return spec.mode; - } - - get length() { - return numChannels; - } - - get range(): [ReadonlyColor, ReadonlyColor] { - return [min, max]; - } - - get [Symbol.toStringTag]() { - return spec.mode; - } - - [Symbol.iterator]() { - return stridedValues( - this.buf, - this.length, - this.offset, - this.stride - ); - } - - copy(): $DefColor { - return new $Color(this.deref()); - } - - copyView(): $DefColor { - return new $Color(this.buf, this.offset, this.stride); - } - - empty(): $DefColor { - return new $Color(); - } - - deref() { - return [this[0], this[1], this[2], this[3]]; - } - - set(src: ReadonlyColor) { - return set4(this, src); - } - - clamp() { - hueChanID >= 0 && (this[hueChanID] = fract(this[hueChanID])); - clamp4(null, this, min, max); - return this; - } - - eqDelta(o: $DefColor, eps = EPS): boolean { - return eqDelta4(this, o, eps); - } - - randomize(rnd?: IRandom): this { - return randMinMax(this, minR, maxR, rnd); - } - - toJSON() { - return this.deref(); - } - - toString() { - return vector(4, 4)(this); - } - }; - - declareIndices($Color.prototype, order); - defConversions(spec.mode, spec.from); - defConversions("rgb", { [spec.mode]: spec.toRgb }); - - const fromColor = (src: ReadonlyColor, mode: ColorMode, xs: any[]): any => { - const res = new $Color(...xs); - return mode !== spec.mode - ? convert(res, src, spec.mode, mode) - : res.set(src); - }; - - const factory = (src?: MaybeColor, ...xs: any[]): $DefColor => - src == null - ? new $Color() - : isString(src) - ? factory(parseCss(src), ...xs) - : isArrayLike(src) - ? isString((src).mode) - ? fromColor(src, (src).mode, xs) - : new $Color(src, ...xs) - : implementsFunction(src, "deref") - ? fromColor(src.deref(), (src).mode, xs) - : isNumber(src) - ? xs.length && xs.every(isNumber) - ? new $Color(...__ensureArgs([src, ...xs])) - : fromColor(intArgb32Rgb([], src), "rgb", xs) - : illegalArgs(`can't create a ${spec.mode} color from: ${src}`); - - factory.class = $Color; - - factory.range = <[ReadonlyColor, ReadonlyColor]>[min, max]; - - factory.random = ( - rnd?: IRandom, - buf?: NumericArray, - idx?: number, - stride?: number - ) => new $Color(buf, idx, stride).randomize(rnd); - - factory.mapBuffer = ( - buf: NumericArray, - num = (buf.length / numChannels) | 0, - start = 0, - cstride = 1, - estride = numChannels - ) => mapStridedBuffer($Color, buf, num, start, cstride, estride); - - return >>factory; + const channels: Partial> = spec.channels || {}; + const order = spec.order; + const numChannels = order.length; + order.reduce((acc, id) => { + acc[id] = { + range: [0, 1], + ...channels[id], + }; + return acc; + }, channels); + const min = order.map((id) => channels[id]!.range![0]); + const max = order.map((id) => channels[id]!.range![1]); + // fix alpha channel for randomize() + const minR = set4([], min); + const maxR = set4([], max); + minR[numChannels - 1] = 1; + + const hueChanID = order.findIndex((id) => !!channels[id]!.hue); + + const $Color = class implements TypedColor<$DefColor> { + buf: NumericArray; + [id: number]: number; + + constructor(buf?: NumericArray, public offset = 0, public stride = 1) { + this.buf = buf || [0, 0, 0, 0]; + this.offset = offset; + this.stride = stride; + } + + get mode() { + return spec.mode; + } + + get length() { + return numChannels; + } + + get range(): [ReadonlyColor, ReadonlyColor] { + return [min, max]; + } + + get [Symbol.toStringTag]() { + return spec.mode; + } + + [Symbol.iterator]() { + return stridedValues( + this.buf, + this.length, + this.offset, + this.stride + ); + } + + copy(): $DefColor { + return new $Color(this.deref()); + } + + copyView(): $DefColor { + return new $Color(this.buf, this.offset, this.stride); + } + + empty(): $DefColor { + return new $Color(); + } + + deref() { + return [this[0], this[1], this[2], this[3]]; + } + + set(src: ReadonlyColor) { + return set4(this, src); + } + + clamp() { + hueChanID >= 0 && (this[hueChanID] = fract(this[hueChanID])); + clamp4(null, this, min, max); + return this; + } + + eqDelta(o: $DefColor, eps = EPS): boolean { + return eqDelta4(this, o, eps); + } + + randomize(rnd?: IRandom): this { + return randMinMax(this, minR, maxR, rnd); + } + + toJSON() { + return this.deref(); + } + + toString() { + return vector(4, 4)(this); + } + }; + + declareIndices($Color.prototype, order); + defConversions(spec.mode, spec.from); + defConversions("rgb", { [spec.mode]: spec.toRgb }); + + const fromColor = (src: ReadonlyColor, mode: ColorMode, xs: any[]): any => { + const res = new $Color(...xs); + return mode !== spec.mode + ? convert(res, src, spec.mode, mode) + : res.set(src); + }; + + const factory = (src?: MaybeColor, ...xs: any[]): $DefColor => + src == null + ? new $Color() + : isString(src) + ? factory(parseCss(src), ...xs) + : isArrayLike(src) + ? isString((src).mode) + ? fromColor(src, (src).mode, xs) + : new $Color(src, ...xs) + : implementsFunction(src, "deref") + ? fromColor(src.deref(), (src).mode, xs) + : isNumber(src) + ? xs.length && xs.every(isNumber) + ? new $Color(...__ensureArgs([src, ...xs])) + : fromColor(intArgb32Rgb([], src), "rgb", xs) + : illegalArgs(`can't create a ${spec.mode} color from: ${src}`); + + factory.class = $Color; + + factory.range = <[ReadonlyColor, ReadonlyColor]>[min, max]; + + factory.random = ( + rnd?: IRandom, + buf?: NumericArray, + idx?: number, + stride?: number + ) => new $Color(buf, idx, stride).randomize(rnd); + + factory.mapBuffer = ( + buf: NumericArray, + num = (buf.length / numChannels) | 0, + start = 0, + cstride = 1, + estride = numChannels + ) => mapStridedBuffer($Color, buf, num, start, cstride, estride); + + return >>factory; }; diff --git a/packages/color/src/distance.ts b/packages/color/src/distance.ts index 930c35ad0b..9d980b6852 100644 --- a/packages/color/src/distance.ts +++ b/packages/color/src/distance.ts @@ -13,39 +13,39 @@ const { abs, cos, hypot, sin, sqrt } = Math; * Higher order function. Returns {@link ColorDistance} function for given color * channel ID. * - * @param id - + * @param id - */ export const distChannel = - (id: number): ColorDistance => - (a, b) => - abs(a[id] - b[id]); + (id: number): ColorDistance => + (a, b) => + abs(a[id] - b[id]); /** * Computes distance between two HSV colors, i.e. the eucledian distance between * points in a cyclinder. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const distHsv: ColorDistance = (a, b) => { - const aa = cossin(a[0] * TAU, a[1]); - const bb = cossin(b[0] * TAU, b[1]); - return hypot(aa[0] - bb[0], aa[1] - bb[1], a[2] - b[2]); + const aa = cossin(a[0] * TAU, a[1]); + const bb = cossin(b[0] * TAU, b[1]); + return hypot(aa[0] - bb[0], aa[1] - bb[1], a[2] - b[2]); }; /** * Computes difference in saturation between two HSV colors. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const distHsvSat = distChannel(1); /** * Computes difference in brightness between two HSV or two HSL colors. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const distHsvLuma = distChannel(2); @@ -53,8 +53,8 @@ export const distHsvLuma = distChannel(2); * Computes eucledian distance between two colors. Only the first 3 color * channels will be considered. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const distEucledian3: ColorDistance = dist3; @@ -63,36 +63,36 @@ export const distEucledian4: ColorDistance = dist4; /** * Computes difference in luminance between two RGB colors. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const distRgbLuma: ColorDistance = (a, b) => - abs(luminanceRgb(a) - luminanceRgb(b)); + abs(luminanceRgb(a) - luminanceRgb(b)); export const distSrgbLuma: ColorDistance = (a, b) => - abs(luminanceSrgb(a) - luminanceSrgb(b)); + abs(luminanceSrgb(a) - luminanceSrgb(b)); /** * Computes red difference between two RGB colors. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const distRgbRed = distChannel(0); /** * Computes green difference between two RGB colors. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const distRgbGreen = distChannel(1); /** * Computes blue difference between two RGB colors. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const distRgbBlue = distChannel(1); @@ -117,70 +117,70 @@ const H275 = 275 * DEG2RAD; * @param b - Lab color */ export const distCIEDE2000 = - (weights: ReadonlyVec = ONE3): ColorDistance => - (a, b) => { - let { 0: l1, 1: a1, 2: b1 } = labD50(a); - let { 0: l2, 1: a2, 2: b2 } = labD50(b); - l1 *= 100; - a1 *= 100; - b1 *= 100; - l2 *= 100; - a2 *= 100; - b2 *= 100; - const c1ab = hypot(a1, b1); - const c2ab = hypot(a2, b2); - const cab = (c1ab + c2ab) * 0.5; - const g = 1 + 0.5 * (1 - c7Coeff(cab)); - a1 *= g; - a2 *= g; - const c1 = hypot(a1, b1); - const c2 = hypot(a2, b2); - const cmean = (c1 + c2) * 0.5; - const { deltaH, H } = computeDeltaH(a1, b1, a2, b2, c1, c2); - const T = - 1 - - 0.17 * cos(H - SIXTH_PI) + - 0.24 * cos(2 * H) + - 0.32 * cos(3 * H + H6) - - 0.2 * cos(4 * H - H63); - const Rt = - -2 * - c7Coeff(cmean) * - sin(THIRD_PI * Math.exp(-(((H - H275) / H25) ** 2))); - const L50 = ((l1 + l2) * 0.5 - 50) ** 2; - const Sl = 1 + (0.015 * L50) / sqrt(20 + L50); - const Sc = 1 + 0.045 * cmean; - const Sh = 1 + 0.015 * cmean * T; - const termL = (l2 - l1) / (weights[0] * Sl); - const termC = (c2 - c1) / (weights[1] * Sc); - const termH = deltaH / (weights[2] * Sh); - return sqrt(termL ** 2 + termC ** 2 + termH ** 2 + Rt * termC * termH); - }; + (weights: ReadonlyVec = ONE3): ColorDistance => + (a, b) => { + let { 0: l1, 1: a1, 2: b1 } = labD50(a); + let { 0: l2, 1: a2, 2: b2 } = labD50(b); + l1 *= 100; + a1 *= 100; + b1 *= 100; + l2 *= 100; + a2 *= 100; + b2 *= 100; + const c1ab = hypot(a1, b1); + const c2ab = hypot(a2, b2); + const cab = (c1ab + c2ab) * 0.5; + const g = 1 + 0.5 * (1 - c7Coeff(cab)); + a1 *= g; + a2 *= g; + const c1 = hypot(a1, b1); + const c2 = hypot(a2, b2); + const cmean = (c1 + c2) * 0.5; + const { deltaH, H } = computeDeltaH(a1, b1, a2, b2, c1, c2); + const T = + 1 - + 0.17 * cos(H - SIXTH_PI) + + 0.24 * cos(2 * H) + + 0.32 * cos(3 * H + H6) - + 0.2 * cos(4 * H - H63); + const Rt = + -2 * + c7Coeff(cmean) * + sin(THIRD_PI * Math.exp(-(((H - H275) / H25) ** 2))); + const L50 = ((l1 + l2) * 0.5 - 50) ** 2; + const Sl = 1 + (0.015 * L50) / sqrt(20 + L50); + const Sc = 1 + 0.045 * cmean; + const Sh = 1 + 0.015 * cmean * T; + const termL = (l2 - l1) / (weights[0] * Sl); + const termC = (c2 - c1) / (weights[1] * Sc); + const termH = deltaH / (weights[2] * Sh); + return sqrt(termL ** 2 + termC ** 2 + termH ** 2 + Rt * termC * termH); + }; const c7Coeff = (c: number) => { - c = c ** 7; - return sqrt(c / (c + 25 ** 7)); + c = c ** 7; + return sqrt(c / (c + 25 ** 7)); }; const computeDeltaH = ( - a1: number, - b1: number, - a2: number, - b2: number, - c1: number, - c2: number, - eps = 1e-3 + a1: number, + b1: number, + a2: number, + b2: number, + c1: number, + c2: number, + eps = 1e-3 ) => { - const h1 = atan2Abs(b1, a1); - const h2 = atan2Abs(b2, a2); - if (c1 <= eps || c2 <= eps) return { deltaH: 0, H: h1 + h2 }; - let dh = h2 - h1; - const sumH = h1 + h2; - const absH = abs(dh); - dh = absH <= PI ? dh : h2 <= h1 ? dh + TAU : dh - TAU; - const deltaH = 2 * sqrt(c1 * c2) * sin(dh / 2); - const H = 0.5 * (absH <= PI ? sumH : sumH < TAU ? sumH + TAU : sumH - TAU); - return { deltaH, H }; + const h1 = atan2Abs(b1, a1); + const h2 = atan2Abs(b2, a2); + if (c1 <= eps || c2 <= eps) return { deltaH: 0, H: h1 + h2 }; + let dh = h2 - h1; + const sumH = h1 + h2; + const absH = abs(dh); + dh = absH <= PI ? dh : h2 <= h1 ? dh + TAU : dh - TAU; + const deltaH = 2 * sqrt(c1 * c2) * sin(dh / 2); + const H = 0.5 * (absH <= PI ? sumH : sumH < TAU ? sumH + TAU : sumH - TAU); + return { deltaH, H }; }; const H35 = 35 * DEG2RAD; @@ -203,29 +203,29 @@ const H345 = 345 * DEG2RAD; * - https://en.wikipedia.org/wiki/Color_difference#CMC_l:c_(1984) */ export const distCMC = - (kl = 1, kc = 1): ColorDistance => - (a, b) => { - let { 0: l1, 1: a1, 2: b1 } = labD65(a); - let { 0: l2, 1: a2, 2: b2 } = labD65(b); - l1 *= 100; - a1 *= 100; - b1 *= 100; - l2 *= 100; - a2 *= 100; - b2 *= 100; - const c1 = hypot(a1, b1); - const c2 = hypot(a2, b2); - const dC = c1 - c2; - const dH = sqrt((a2 - a1) ** 2 + (b2 - b1) ** 2 - dC ** 2); - const h1 = atan2Abs(b1, a1); - const t = - h1 >= H164 && h1 <= H345 - ? 0.56 + abs(0.2 * cos(h1 + H168)) - : 0.36 + abs(0.4 * cos(h1 + H35)); - const c14 = c1 ** 4; - const f = sqrt(c14 / (c14 + 1900)); - const Sl = l1 >= 16 ? (0.040975 * l1) / (1 + 0.01765 * l1) : 0.511; - const Sc = (0.0638 * c1) / (1 + 0.0131 * c1) + 0.638; - const Sh = Sc * (f * t + 1 - f); - return hypot((l1 - l2) / (kl * Sl), dC / (kc * Sc), dH / Sh); - }; + (kl = 1, kc = 1): ColorDistance => + (a, b) => { + let { 0: l1, 1: a1, 2: b1 } = labD65(a); + let { 0: l2, 1: a2, 2: b2 } = labD65(b); + l1 *= 100; + a1 *= 100; + b1 *= 100; + l2 *= 100; + a2 *= 100; + b2 *= 100; + const c1 = hypot(a1, b1); + const c2 = hypot(a2, b2); + const dC = c1 - c2; + const dH = sqrt((a2 - a1) ** 2 + (b2 - b1) ** 2 - dC ** 2); + const h1 = atan2Abs(b1, a1); + const t = + h1 >= H164 && h1 <= H345 + ? 0.56 + abs(0.2 * cos(h1 + H168)) + : 0.36 + abs(0.4 * cos(h1 + H35)); + const c14 = c1 ** 4; + const f = sqrt(c14 / (c14 + 1900)); + const Sl = l1 >= 16 ? (0.040975 * l1) / (1 + 0.01765 * l1) : 0.511; + const Sc = (0.0638 * c1) / (1 + 0.0131 * c1) + 0.638; + const Sh = Sc * (f * t + 1 - f); + return hypot((l1 - l2) / (kl * Sl), dC / (kc * Sc), dH / Sh); + }; diff --git a/packages/color/src/gradients.ts b/packages/color/src/gradients.ts index 36e4d2a762..aa344be866 100644 --- a/packages/color/src/gradients.ts +++ b/packages/color/src/gradients.ts @@ -50,20 +50,20 @@ import { mix as $mix } from "./mix.js"; * @param isABGR - */ export function multiColorGradient>( - opts: GradientOpts + opts: GradientOpts ): T[]; export function multiColorGradient>( - opts: GradientOpts, - isABGR: boolean + opts: GradientOpts, + isABGR: boolean ): number[]; export function multiColorGradient>( - opts: GradientOpts, - isABGR?: boolean + opts: GradientOpts, + isABGR?: boolean ) { - const cols = [...gradient(opts)]; - if (isABGR === undefined) return cols; - const rgba = cols.map((x) => argb32(x)[0]); - return isABGR ? rgba.map(intArgb32Abgr32) : rgba; + const cols = [...gradient(opts)]; + if (isABGR === undefined) return cols; + const rgba = cols.map((x) => argb32(x)[0]); + return isABGR ? rgba.map(intArgb32Abgr32) : rgba; } /** @@ -81,34 +81,34 @@ export function multiColorGradient>( * @param estride - element stride (default: 4) */ export const multiColorGradientBuffer = >( - opts: GradientOpts, - buffer: NumericArray = [], - offset = 0, - cstride = 1, - estride = 4 + opts: GradientOpts, + buffer: NumericArray = [], + offset = 0, + cstride = 1, + estride = 4 ) => { - for (let col of gradient(opts)) { - setS4(buffer, col, offset, 0, cstride); - offset += estride; - } - return buffer; + for (let col of gradient(opts)) { + setS4(buffer, col, offset, 0, cstride); + offset += estride; + } + return buffer; }; /** @internal */ const gradient = >({ - num, - stops, - easing, - mix, + num, + stops, + easing, + mix, }: GradientOpts): Iterable => - tween({ - num: num - 1, - stops, - easing, - min: 0, - max: 1, - init: (a, b) => [a, b], - mix: mix - ? ([a, b], t) => mix(a.empty(), a, b, t) - : ([a, b], t) => $mix(a.empty(), a, b, t), - }); + tween({ + num: num - 1, + stops, + easing, + min: 0, + max: 1, + init: (a, b) => [a, b], + mix: mix + ? ([a, b], t) => mix(a.empty(), a, b, t) + : ([a, b], t) => $mix(a.empty(), a, b, t), + }); diff --git a/packages/color/src/hcy/hcy-rgb.ts b/packages/color/src/hcy/hcy-rgb.ts index 114f1e8290..8ded7e2b2f 100644 --- a/packages/color/src/hcy/hcy-rgb.ts +++ b/packages/color/src/hcy/hcy-rgb.ts @@ -6,20 +6,20 @@ import { luminanceRgb } from "../luminance-rgb.js"; import { hueRgb } from "../rgb/hue-rgb.js"; export const hcyRgb: ColorOp = (out, src) => { - const h = src[0]; - let c = src[1]; - const y = src[2]; - const rgb = hueRgb(out || src, h, __ensureAlpha(src[3])); - const lum = luminanceRgb(rgb); - if (y < lum) { - c *= y / lum; - } else if (lum < 1) { - c *= (1 - y) / (1 - lum); - } - return setC3( - rgb, - clamp01((rgb[0] - lum) * c + y), - clamp01((rgb[1] - lum) * c + y), - clamp01((rgb[2] - lum) * c + y) - ); + const h = src[0]; + let c = src[1]; + const y = src[2]; + const rgb = hueRgb(out || src, h, __ensureAlpha(src[3])); + const lum = luminanceRgb(rgb); + if (y < lum) { + c *= y / lum; + } else if (lum < 1) { + c *= (1 - y) / (1 - lum); + } + return setC3( + rgb, + clamp01((rgb[0] - lum) * c + y), + clamp01((rgb[1] - lum) * c + y), + clamp01((rgb[2] - lum) * c + y) + ); }; diff --git a/packages/color/src/hcy/hcy.ts b/packages/color/src/hcy/hcy.ts index dc092fed0c..5acfdf3436 100644 --- a/packages/color/src/hcy/hcy.ts +++ b/packages/color/src/hcy/hcy.ts @@ -9,33 +9,33 @@ import { rgbSrgb } from "../rgb/rgb-srgb.js"; import { hcyRgb } from "./hcy-rgb.js"; export declare class HCY implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - h: number; - c: number; - y: number; - alpha: number; - [id: number]: number; - readonly mode: "hcy"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): HCY; - copyView(): HCY; - deref(): Color; - empty(): HCY; - eqDelta(o: HCY, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + h: number; + c: number; + y: number; + alpha: number; + [id: number]: number; + readonly mode: "hcy"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): HCY; + copyView(): HCY; + deref(): Color; + empty(): HCY; + eqDelta(o: HCY, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const hcy = >defColor({ - mode: "hcy", - channels: { h: { hue: true } }, - order: ["h", "c", "y", "alpha"], - from: { rgb: rgbHcy, srgb: rgbHcy, lch: [lchLab, labRgb, rgbSrgb, rgbHcy] }, - toRgb: hcyRgb, + mode: "hcy", + channels: { h: { hue: true } }, + order: ["h", "c", "y", "alpha"], + from: { rgb: rgbHcy, srgb: rgbHcy, lch: [lchLab, labRgb, rgbSrgb, rgbHcy] }, + toRgb: hcyRgb, }); diff --git a/packages/color/src/hsi/hsi-rgb.ts b/packages/color/src/hsi/hsi-rgb.ts index e3f89313d2..1d29f6d9d3 100644 --- a/packages/color/src/hsi/hsi-rgb.ts +++ b/packages/color/src/hsi/hsi-rgb.ts @@ -5,32 +5,32 @@ import { clampH } from "../clamp.js"; // https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSI export const hsiRgb: ColorOp = (out, src) => { - out = clampH(out || src, src); - const s = out[1]; - const i = out[2]; - if (s < 1e-6) { - return setC3(out, i, i, i); - } - const h = (out[0] * 6) % 6; - const m = i * (1 - s); - const z = 1 - Math.abs((h % 2) - 1); - let c = (3 * i * s) / (1 + z); - const x = c * z + m; - c += m; - switch (h | 0) { - case 0: - return setC3(out, c, x, m); - case 1: - return setC3(out, x, c, m); - case 2: - return setC3(out, m, c, x); - case 3: - return setC3(out, m, x, c); - case 4: - return setC3(out, x, m, c); - case 5: - return setC3(out, c, m, x); - default: - return setC3(out, m, m, m); - } + out = clampH(out || src, src); + const s = out[1]; + const i = out[2]; + if (s < 1e-6) { + return setC3(out, i, i, i); + } + const h = (out[0] * 6) % 6; + const m = i * (1 - s); + const z = 1 - Math.abs((h % 2) - 1); + let c = (3 * i * s) / (1 + z); + const x = c * z + m; + c += m; + switch (h | 0) { + case 0: + return setC3(out, c, x, m); + case 1: + return setC3(out, x, c, m); + case 2: + return setC3(out, m, c, x); + case 3: + return setC3(out, m, x, c); + case 4: + return setC3(out, x, m, c); + case 5: + return setC3(out, c, m, x); + default: + return setC3(out, m, m, m); + } }; diff --git a/packages/color/src/hsi/hsi.ts b/packages/color/src/hsi/hsi.ts index 55d0c11b6b..35e22cd23d 100644 --- a/packages/color/src/hsi/hsi.ts +++ b/packages/color/src/hsi/hsi.ts @@ -9,33 +9,33 @@ import { rgbSrgb } from "../rgb/rgb-srgb.js"; import { hsiRgb } from "./hsi-rgb.js"; export declare class HSI implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - h: number; - s: number; - i: number; - alpha: number; - [id: number]: number; - readonly mode: "hsi"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): HSI; - copyView(): HSI; - deref(): Color; - empty(): HSI; - eqDelta(o: HSI, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + h: number; + s: number; + i: number; + alpha: number; + [id: number]: number; + readonly mode: "hsi"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): HSI; + copyView(): HSI; + deref(): Color; + empty(): HSI; + eqDelta(o: HSI, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const hsi = >defColor({ - mode: "hsi", - channels: { h: { hue: true } }, - order: ["h", "s", "i", "alpha"], - from: { rgb: rgbHsi, srgb: rgbHsi, lch: [lchLab, labRgb, rgbSrgb, rgbHsi] }, - toRgb: hsiRgb, + mode: "hsi", + channels: { h: { hue: true } }, + order: ["h", "s", "i", "alpha"], + from: { rgb: rgbHsi, srgb: rgbHsi, lch: [lchLab, labRgb, rgbSrgb, rgbHsi] }, + toRgb: hsiRgb, }); diff --git a/packages/color/src/hsl/hsl-css.ts b/packages/color/src/hsl/hsl-css.ts index c599f37d7e..59f9c263b1 100644 --- a/packages/color/src/hsl/hsl-css.ts +++ b/packages/color/src/hsl/hsl-css.ts @@ -5,13 +5,13 @@ import { FF, PC } from "../api/constants.js"; import { __ensureAlpha } from "../internal/ensure.js"; export const hslCss = (src: ReadonlyColor) => { - const h = FF(fract(src[0]) * 360); - const s = PC(clamp01(src[1])); - const l = PC(clamp01(src[2])); - const a = __ensureAlpha(src[3]); - // TODO update to new syntax once CSS Color L4 is more widely supported - // https://www.w3.org/TR/css-color-4/#serializing-lab-lch - // https://test.csswg.org/harness/results/css-color-4_dev/grouped/ (test reports) - // return `hsl(${h} ${s} ${l}` + (a < 1 ? `/${FF(a)})` : ")"); - return a < 1 ? `hsla(${h},${s},${l},${FF(a)})` : `hsl(${h},${s},${l})`; + const h = FF(fract(src[0]) * 360); + const s = PC(clamp01(src[1])); + const l = PC(clamp01(src[2])); + const a = __ensureAlpha(src[3]); + // TODO update to new syntax once CSS Color L4 is more widely supported + // https://www.w3.org/TR/css-color-4/#serializing-lab-lch + // https://test.csswg.org/harness/results/css-color-4_dev/grouped/ (test reports) + // return `hsl(${h} ${s} ${l}` + (a < 1 ? `/${FF(a)})` : ")"); + return a < 1 ? `hsla(${h},${s},${l},${FF(a)})` : `hsl(${h},${s},${l})`; }; diff --git a/packages/color/src/hsl/hsl-hsv.ts b/packages/color/src/hsl/hsl-hsv.ts index 231a96af27..bf3cd6c16b 100644 --- a/packages/color/src/hsl/hsl-hsv.ts +++ b/packages/color/src/hsl/hsl-hsv.ts @@ -2,12 +2,12 @@ import type { ColorOp } from "../api.js"; import { clampH } from "../clamp.js"; export const hslHsv: ColorOp = (out, src) => { - out = clampH(out || src, src); - const s = out[1]; - const l = out[2]; - const l2 = 2 * l; - const v = (l2 + s * (1 - Math.abs(l2 - 1))) * 0.5; - out[1] = (2 * (v - l)) / v; - out[2] = v; - return out; + out = clampH(out || src, src); + const s = out[1]; + const l = out[2]; + const l2 = 2 * l; + const v = (l2 + s * (1 - Math.abs(l2 - 1))) * 0.5; + out[1] = (2 * (v - l)) / v; + out[2] = v; + return out; }; diff --git a/packages/color/src/hsl/hsl-rgb.ts b/packages/color/src/hsl/hsl-rgb.ts index ded6db22df..c6929af523 100644 --- a/packages/color/src/hsl/hsl-rgb.ts +++ b/packages/color/src/hsl/hsl-rgb.ts @@ -5,14 +5,14 @@ import { hueRgb } from "../rgb/hue-rgb.js"; import { __ensureAlpha } from "../internal/ensure.js"; export const hslRgb: ColorOp = (out, src) => { - const s = clamp01(src[1]); - const l = clamp01(src[2]); - out = hueRgb(out || src, src[0], __ensureAlpha(src[3])); - const c = (1 - Math.abs(2 * l - 1)) * s; - return setC3( - out, - (out[0] - 0.5) * c + l, - (out[1] - 0.5) * c + l, - (out[2] - 0.5) * c + l - ); + const s = clamp01(src[1]); + const l = clamp01(src[2]); + out = hueRgb(out || src, src[0], __ensureAlpha(src[3])); + const c = (1 - Math.abs(2 * l - 1)) * s; + return setC3( + out, + (out[0] - 0.5) * c + l, + (out[1] - 0.5) * c + l, + (out[2] - 0.5) * c + l + ); }; diff --git a/packages/color/src/hsl/hsl.ts b/packages/color/src/hsl/hsl.ts index b30b76ab03..841ff09637 100644 --- a/packages/color/src/hsl/hsl.ts +++ b/packages/color/src/hsl/hsl.ts @@ -10,38 +10,38 @@ import { rgbSrgb } from "../rgb/rgb-srgb.js"; import { hslRgb } from "./hsl-rgb.js"; export declare class HSL implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - h: number; - s: number; - l: number; - alpha: number; - [id: number]: number; - readonly mode: "hsl"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): HSL; - copyView(): HSL; - deref(): Color; - empty(): HSL; - eqDelta(o: HSL, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + h: number; + s: number; + l: number; + alpha: number; + [id: number]: number; + readonly mode: "hsl"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): HSL; + copyView(): HSL; + deref(): Color; + empty(): HSL; + eqDelta(o: HSL, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const hsl = >defColor({ - mode: "hsl", - channels: { h: { hue: true } }, - order: ["h", "s", "l", "alpha"], - from: { - rgb: rgbHsl, - srgb: rgbHsl, - hsv: hsvHsl, - lch: [lchLab, labRgb, rgbSrgb, rgbHsl], - }, - toRgb: hslRgb, + mode: "hsl", + channels: { h: { hue: true } }, + order: ["h", "s", "l", "alpha"], + from: { + rgb: rgbHsl, + srgb: rgbHsl, + hsv: hsvHsl, + lch: [lchLab, labRgb, rgbSrgb, rgbHsl], + }, + toRgb: hslRgb, }); diff --git a/packages/color/src/hsv/hsv-hsl.ts b/packages/color/src/hsv/hsv-hsl.ts index d4525117d1..3539e79e7c 100644 --- a/packages/color/src/hsv/hsv-hsl.ts +++ b/packages/color/src/hsv/hsv-hsl.ts @@ -2,11 +2,11 @@ import type { ColorOp } from "../api.js"; import { clampH } from "../clamp.js"; export const hsvHsl: ColorOp = (out, src) => { - out = clampH(out || src, src); - const s = out[1]; - const v = out[2]; - const l = ((2 - s) * v) / 2; - out[2] = l; - out[1] = l && l < 1 ? (s * v) / (l < 0.5 ? l * 2 : 2 - l * 2) : s; - return out; + out = clampH(out || src, src); + const s = out[1]; + const v = out[2]; + const l = ((2 - s) * v) / 2; + out[2] = l; + out[1] = l && l < 1 ? (s * v) / (l < 0.5 ? l * 2 : 2 - l * 2) : s; + return out; }; diff --git a/packages/color/src/hsv/hsv-rgb.ts b/packages/color/src/hsv/hsv-rgb.ts index 73bac88598..64a3bd2873 100644 --- a/packages/color/src/hsv/hsv-rgb.ts +++ b/packages/color/src/hsv/hsv-rgb.ts @@ -4,14 +4,14 @@ import { clampH } from "../clamp.js"; import { hueRgb } from "../rgb/hue-rgb.js"; export const hsvRgb: ColorOp = (out, src) => { - out = clampH(out || src, src); - const s = out[1]; - const v = out[2]; - hueRgb(out, src[0], out[3]); - return setC3( - out, - ((out[0] - 1) * s + 1) * v, - ((out[1] - 1) * s + 1) * v, - ((out[2] - 1) * s + 1) * v - ); + out = clampH(out || src, src); + const s = out[1]; + const v = out[2]; + hueRgb(out, src[0], out[3]); + return setC3( + out, + ((out[0] - 1) * s + 1) * v, + ((out[1] - 1) * s + 1) * v, + ((out[2] - 1) * s + 1) * v + ); }; diff --git a/packages/color/src/hsv/hsv.ts b/packages/color/src/hsv/hsv.ts index df4ee0a253..282a3e8710 100644 --- a/packages/color/src/hsv/hsv.ts +++ b/packages/color/src/hsv/hsv.ts @@ -10,38 +10,38 @@ import { rgbSrgb } from "../rgb/rgb-srgb.js"; import { hsvRgb } from "./hsv-rgb.js"; export declare class HSV implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - h: number; - s: number; - v: number; - alpha: number; - [id: number]: number; - readonly mode: "hsv"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): HSV; - copyView(): HSV; - deref(): Color; - empty(): HSV; - eqDelta(o: HSV, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + h: number; + s: number; + v: number; + alpha: number; + [id: number]: number; + readonly mode: "hsv"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): HSV; + copyView(): HSV; + deref(): Color; + empty(): HSV; + eqDelta(o: HSV, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const hsv = >defColor({ - mode: "hsv", - channels: { h: { hue: true } }, - order: ["h", "s", "v", "alpha"], - from: { - rgb: rgbHsv, - srgb: rgbHsv, - hsl: hslHsv, - lch: [lchLab, labRgb, rgbSrgb, rgbHsv], - }, - toRgb: hsvRgb, + mode: "hsv", + channels: { h: { hue: true } }, + order: ["h", "s", "v", "alpha"], + from: { + rgb: rgbHsv, + srgb: rgbHsv, + hsl: hslHsv, + lch: [lchLab, labRgb, rgbSrgb, rgbHsv], + }, + toRgb: hsvRgb, }); diff --git a/packages/color/src/int/int-css.ts b/packages/color/src/int/int-css.ts index 33e87feb86..7ba7500691 100644 --- a/packages/color/src/int/int-css.ts +++ b/packages/color/src/int/int-css.ts @@ -2,12 +2,12 @@ import { U24 } from "@thi.ng/strings/radix"; import { FF, INV8BIT } from "../api/constants.js"; export const intArgb32Css = (src: number) => { - const a = src >>> 24; - return a < 255 - ? `rgba(${(src >> 16) & 0xff},${(src >> 8) & 0xff},${src & 0xff},${FF( - a * INV8BIT - )})` - : `#${U24(src & 0xffffff)}`; + const a = src >>> 24; + return a < 255 + ? `rgba(${(src >> 16) & 0xff},${(src >> 8) & 0xff},${src & 0xff},${FF( + a * INV8BIT + )})` + : `#${U24(src & 0xffffff)}`; }; export const intRgb24Css = (src: number) => `#${U24(src & 0xffffff)}`; diff --git a/packages/color/src/int/int-int.ts b/packages/color/src/int/int-int.ts index 941940e980..e6717e3003 100644 --- a/packages/color/src/int/int-int.ts +++ b/packages/color/src/int/int-int.ts @@ -3,7 +3,7 @@ import { swapLane13 } from "@thi.ng/binary/swizzle"; /** * Convert ARGB int to ABGR and vice versa. * - * @param x - + * @param x - */ export const intArgb32Abgr32 = swapLane13; diff --git a/packages/color/src/int/int-rgb.ts b/packages/color/src/int/int-rgb.ts index 7521b9cd70..3fe5f3fd1c 100644 --- a/packages/color/src/int/int-rgb.ts +++ b/packages/color/src/int/int-rgb.ts @@ -1,20 +1,20 @@ import type { Color } from "../api.js"; import { srgbRgb } from "../srgb/srgb-rgb.js"; import { - intAbgr32Srgb, - intArgb32Srgb, - intBgr24Srgb, - intRgb24Srgb, + intAbgr32Srgb, + intArgb32Srgb, + intBgr24Srgb, + intRgb24Srgb, } from "./int-srgb.js"; export const intArgb32Rgb = (out: Color | null, src: number) => - srgbRgb(null, intArgb32Srgb(out, src)); + srgbRgb(null, intArgb32Srgb(out, src)); export const intRgb24Rgb = (out: Color | null, src: number) => - srgbRgb(null, intRgb24Srgb(out, src)); + srgbRgb(null, intRgb24Srgb(out, src)); export const intAbgr32Rgb = (out: Color | null, src: number) => - srgbRgb(null, intAbgr32Srgb(out, src)); + srgbRgb(null, intAbgr32Srgb(out, src)); export const intBgr24Rgb = (out: Color | null, src: number) => - srgbRgb(null, intBgr24Srgb(out, src)); + srgbRgb(null, intBgr24Srgb(out, src)); diff --git a/packages/color/src/int/int-srgb.ts b/packages/color/src/int/int-srgb.ts index be5e908809..f530e5c8eb 100644 --- a/packages/color/src/int/int-srgb.ts +++ b/packages/color/src/int/int-srgb.ts @@ -3,25 +3,25 @@ import type { Color } from "../api.js"; import { INV8BIT } from "../api/constants.js"; export const intArgb32Srgb = (out: Color | null, src: number) => - setC4( - out || [], - ((src >>> 16) & 0xff) * INV8BIT, - ((src >>> 8) & 0xff) * INV8BIT, - (src & 0xff) * INV8BIT, - (src >>> 24) * INV8BIT - ); + setC4( + out || [], + ((src >>> 16) & 0xff) * INV8BIT, + ((src >>> 8) & 0xff) * INV8BIT, + (src & 0xff) * INV8BIT, + (src >>> 24) * INV8BIT + ); export const intAbgr32Srgb = (out: Color | null, src: number) => - setC4( - out || [], - (src & 0xff) * INV8BIT, - ((src >>> 8) & 0xff) * INV8BIT, - ((src >>> 16) & 0xff) * INV8BIT, - (src >>> 24) * INV8BIT - ); + setC4( + out || [], + (src & 0xff) * INV8BIT, + ((src >>> 8) & 0xff) * INV8BIT, + ((src >>> 16) & 0xff) * INV8BIT, + (src >>> 24) * INV8BIT + ); export const intRgb24Srgb = (out: Color | null, src: number) => - intArgb32Srgb(out, src | 0xff000000); + intArgb32Srgb(out, src | 0xff000000); export const intBgr24Srgb = (out: Color | null, src: number) => - intAbgr32Srgb(out, src | 0xff000000); + intAbgr32Srgb(out, src | 0xff000000); diff --git a/packages/color/src/int/int.ts b/packages/color/src/int/int.ts index f6fe8db1c8..da0569eb2e 100644 --- a/packages/color/src/int/int.ts +++ b/packages/color/src/int/int.ts @@ -10,12 +10,12 @@ import { mapStridedBuffer } from "@thi.ng/vectors/buffer"; import { declareIndex } from "@thi.ng/vectors/compile/accessors"; import { eqDelta4 } from "@thi.ng/vectors/eqdelta"; import type { - ColorFactory, - ColorMode, - IColor, - MaybeColor, - ReadonlyColor, - TypedColor, + ColorFactory, + ColorMode, + IColor, + MaybeColor, + ReadonlyColor, + TypedColor, } from "../api.js"; import { parseCss } from "../css/parse-css.js"; import { __scale8bit } from "../internal/scale.js"; @@ -24,217 +24,217 @@ import { srgbIntAbgr32, srgbIntArgb32 } from "../srgb/srgb-int.js"; import { intArgb32Srgb } from "./int-srgb.js"; export abstract class Int32> implements TypedColor { - buf: NumericArray; - value!: number; - [id: number]: number; + buf: NumericArray; + value!: number; + [id: number]: number; - constructor(buf?: NumericArray, public offset = 0, public stride = 1) { - this.buf = buf || [0]; - } + constructor(buf?: NumericArray, public offset = 0, public stride = 1) { + this.buf = buf || [0]; + } - abstract get mode(): ColorMode; + abstract get mode(): ColorMode; - abstract copy(): T; + abstract copy(): T; - abstract copyView(): T; + abstract copyView(): T; - abstract empty(): T; + abstract empty(): T; - eqDelta(o: T, eps?: number): boolean { - return eqDelta4( - // channel order irrelevant here... - intArgb32Srgb([], this[0]), - intArgb32Srgb([], o[0]), - eps - ); - } + eqDelta(o: T, eps?: number): boolean { + return eqDelta4( + // channel order irrelevant here... + intArgb32Srgb([], this[0]), + intArgb32Srgb([], o[0]), + eps + ); + } - get length() { - return 1; - } + get length() { + return 1; + } - get range(): [ReadonlyColor, ReadonlyColor] { - return [[0], [0xffffffff]]; - } + get range(): [ReadonlyColor, ReadonlyColor] { + return [[0], [0xffffffff]]; + } - get alpha() { - return (this[0] >>> 24) / 255; - } + get alpha() { + return (this[0] >>> 24) / 255; + } - set alpha(x: number) { - this[0] = (this[0] & 0xffffff) | __scale8bit(x, 24); - } + set alpha(x: number) { + this[0] = (this[0] & 0xffffff) | __scale8bit(x, 24); + } - *[Symbol.iterator]() { - yield this[0]; - } + *[Symbol.iterator]() { + yield this[0]; + } - deref() { - return [this[0]]; - } + deref() { + return [this[0]]; + } - randomize(rnd: IRandom = SYSTEM): this { - const x = this[0]; - this[0] = (x & 0xff000000) | (rnd.int() & 0xffffff); - return this; - } + randomize(rnd: IRandom = SYSTEM): this { + const x = this[0]; + this[0] = (x & 0xff000000) | (rnd.int() & 0xffffff); + return this; + } - clamp() { - return this; - } + clamp() { + return this; + } - set(src: ArrayLikeIterable) { - this[0] = src[0]; - return this; - } + set(src: ArrayLikeIterable) { + this[0] = src[0]; + return this; + } - toJSON(): number[] { - return [this[0]]; - } + toJSON(): number[] { + return [this[0]]; + } } export class ARGB extends Int32 implements TypedColor { - [id: number]: number; + [id: number]: number; - get mode(): ColorMode { - return "argb32"; - } + get mode(): ColorMode { + return "argb32"; + } - get r() { - return ((this[0] >> 16) & 0xff) / 255; - } + get r() { + return ((this[0] >> 16) & 0xff) / 255; + } - set r(x: number) { - this[0] = (this[0] & 0xff00ffff) | __scale8bit(x, 16); - } + set r(x: number) { + this[0] = (this[0] & 0xff00ffff) | __scale8bit(x, 16); + } - get g() { - return ((this[0] >> 8) & 0xff) / 255; - } + get g() { + return ((this[0] >> 8) & 0xff) / 255; + } - set g(x: number) { - this[0] = (this[0] & 0xffff00ff) | __scale8bit(x, 8); - } + set g(x: number) { + this[0] = (this[0] & 0xffff00ff) | __scale8bit(x, 8); + } - get b() { - return (this[0] & 0xff) / 255; - } + get b() { + return (this[0] & 0xff) / 255; + } - set b(x: number) { - this[0] = (this[0] & 0xffffff00) | __scale8bit(x); - } + set b(x: number) { + this[0] = (this[0] & 0xffffff00) | __scale8bit(x); + } - copy(): ARGB { - return new ARGB([this[0]]); - } + copy(): ARGB { + return new ARGB([this[0]]); + } - copyView(): ARGB { - return new ARGB(this.buf, this.offset, this.stride); - } + copyView(): ARGB { + return new ARGB(this.buf, this.offset, this.stride); + } - empty(): ARGB { - return new ARGB(); - } + empty(): ARGB { + return new ARGB(); + } } declareIndex(ARGB.prototype, "value", 0); export class ABGR extends Int32 implements TypedColor { - [id: number]: number; + [id: number]: number; - get mode(): ColorMode { - return "abgr32"; - } + get mode(): ColorMode { + return "abgr32"; + } - get r() { - return (this[0] & 0xff) / 255; - } + get r() { + return (this[0] & 0xff) / 255; + } - set r(x: number) { - this[0] = (this[0] & 0xffffff00) | __scale8bit(x); - } + set r(x: number) { + this[0] = (this[0] & 0xffffff00) | __scale8bit(x); + } - get g() { - return ((this[0] >> 8) & 0xff) / 255; - } + get g() { + return ((this[0] >> 8) & 0xff) / 255; + } - set g(x: number) { - this[0] = (this[0] & 0xffff00ff) | __scale8bit(x, 8); - } + set g(x: number) { + this[0] = (this[0] & 0xffff00ff) | __scale8bit(x, 8); + } - get b() { - return ((this[0] >> 16) & 0xff) / 255; - } + get b() { + return ((this[0] >> 16) & 0xff) / 255; + } - set b(x: number) { - this[0] = (this[0] & 0xff00ffff) | __scale8bit(x, 16); - } + set b(x: number) { + this[0] = (this[0] & 0xff00ffff) | __scale8bit(x, 16); + } - copy() { - return new ABGR([this[0]]); - } + copy() { + return new ABGR([this[0]]); + } - copyView() { - return new ABGR(this.buf, this.offset, this.stride); - } + copyView() { + return new ABGR(this.buf, this.offset, this.stride); + } - empty() { - return new ABGR(); - } + empty() { + return new ABGR(); + } } declareIndex(ABGR.prototype, "value", 0); interface Int32Constructor { - new (buf?: NumericArray, offset?: number, stride?: number): T; + new (buf?: NumericArray, offset?: number, stride?: number): T; } const defInt = >( - ctor: Int32Constructor, - fromSrgb: Fn + ctor: Int32Constructor, + fromSrgb: Fn ): ColorFactory => { - const factory = (src?: MaybeColor, ...xs: any[]): any => - src == null - ? new ctor() - : isNumber(src) - ? xs.length && xs.every(isNumber) - ? new ctor([srgbIntArgb32([src, ...xs])]) - : new ctor([src], ...xs) - : isString(src) - ? factory(parseCss(src)) - : isArrayLike(src) - ? isString((src).mode) - ? new ctor([fromSrgb(srgb(src))], ...xs) - : new ctor(src, ...xs) - : implementsFunction(src, "deref") - ? new ctor([fromSrgb(srgb(src))], ...xs) - : illegalArgs(`can't create a ARGB32 color from: ${src}`); - - factory.class = ctor; - - factory.range = <[ReadonlyColor, ReadonlyColor]>[[0], [0xffffffff]]; - - factory.random = ( - rnd: IRandom = SYSTEM, - buf?: NumericArray, - idx?: number, - stride?: number - ) => - ( - new ctor(buf, idx, stride).set([ - (rnd.int() & 0xffffff) | 0xff000000, - ]) - ); - - factory.mapBuffer = ( - buf: NumericArray, - num = buf.length, - start = 0, - cstride = 1, - estride = 1 - ) => mapStridedBuffer(ctor, buf, num, start, cstride, estride); - - return factory; + const factory = (src?: MaybeColor, ...xs: any[]): any => + src == null + ? new ctor() + : isNumber(src) + ? xs.length && xs.every(isNumber) + ? new ctor([srgbIntArgb32([src, ...xs])]) + : new ctor([src], ...xs) + : isString(src) + ? factory(parseCss(src)) + : isArrayLike(src) + ? isString((src).mode) + ? new ctor([fromSrgb(srgb(src))], ...xs) + : new ctor(src, ...xs) + : implementsFunction(src, "deref") + ? new ctor([fromSrgb(srgb(src))], ...xs) + : illegalArgs(`can't create a ARGB32 color from: ${src}`); + + factory.class = ctor; + + factory.range = <[ReadonlyColor, ReadonlyColor]>[[0], [0xffffffff]]; + + factory.random = ( + rnd: IRandom = SYSTEM, + buf?: NumericArray, + idx?: number, + stride?: number + ) => + ( + new ctor(buf, idx, stride).set([ + (rnd.int() & 0xffffff) | 0xff000000, + ]) + ); + + factory.mapBuffer = ( + buf: NumericArray, + num = buf.length, + start = 0, + cstride = 1, + estride = 1 + ) => mapStridedBuffer(ctor, buf, num, start, cstride, estride); + + return factory; }; export const argb32 = defInt(ARGB, srgbIntArgb32); diff --git a/packages/color/src/internal/ensure.ts b/packages/color/src/internal/ensure.ts index 137564872d..f0fdae130a 100644 --- a/packages/color/src/internal/ensure.ts +++ b/packages/color/src/internal/ensure.ts @@ -2,21 +2,21 @@ import { clamp01 } from "@thi.ng/math/interval"; /** @internal */ export const __ensureAlpha = (x: number, def = 1) => - x != undefined ? clamp01(x) : def; + x != undefined ? clamp01(x) : def; /** @internal */ export const __ensureArgs = (args: any[]) => { - if (typeof args[0] === "number") { - switch (args.length) { - case 1: - return args.push(0, 0, 1), [args]; - case 2: - return args.push(0, 1), [args]; - case 3: - return args.push(1), [args]; - default: - return [args]; - } - } - return args; + if (typeof args[0] === "number") { + switch (args.length) { + case 1: + return args.push(0, 0, 1), [args]; + case 2: + return args.push(0, 1), [args]; + case 3: + return args.push(1), [args]; + default: + return [args]; + } + } + return args; }; diff --git a/packages/color/src/internal/matrix-ops.ts b/packages/color/src/internal/matrix-ops.ts index 81d257ef30..cdac4e12ca 100644 --- a/packages/color/src/internal/matrix-ops.ts +++ b/packages/color/src/internal/matrix-ops.ts @@ -7,57 +7,57 @@ import { __ensureAlpha } from "./ensure.js"; /** @internal */ export const __mulV33 = ( - out: Color | null, - mat: ReadonlyVec, - src: ReadonlyColor, - clampOut = false + out: Color | null, + mat: ReadonlyVec, + src: ReadonlyColor, + clampOut = false ) => { - const x = dotS3(mat, src, 0, 0, 3); - const y = dotS3(mat, src, 1, 0, 3); - const z = dotS3(mat, src, 2, 0, 3); - const a = __ensureAlpha(src[3]); - return clampOut - ? setC4(out || src, clamp01(x), clamp01(y), clamp01(z), a) - : setC4(out || src, x, y, z, a); + const x = dotS3(mat, src, 0, 0, 3); + const y = dotS3(mat, src, 1, 0, 3); + const z = dotS3(mat, src, 2, 0, 3); + const a = __ensureAlpha(src[3]); + return clampOut + ? setC4(out || src, clamp01(x), clamp01(y), clamp01(z), a) + : setC4(out || src, x, y, z, a); }; /** @internal */ export const __mulV45 = ( - out: Color | null, - mat: ColorMatrix, - src: ReadonlyColor, - clampOut = true + out: Color | null, + mat: ColorMatrix, + src: ReadonlyColor, + clampOut = true ) => { - out = setC4(out || src, src[0], src[1], src[2], __ensureAlpha(src[3])); - const x = dotS4(out, mat, 0, 0) + mat[4]; - const y = dotS4(out, mat, 0, 5) + mat[9]; - const z = dotS4(out, mat, 0, 10) + mat[14]; - const w = dotS4(out, mat, 0, 15) + mat[19]; - return clampOut - ? setC4(out, clamp01(x), clamp01(y), clamp01(z), clamp01(w)) - : setC4(out, x, y, z, w); + out = setC4(out || src, src[0], src[1], src[2], __ensureAlpha(src[3])); + const x = dotS4(out, mat, 0, 0) + mat[4]; + const y = dotS4(out, mat, 0, 5) + mat[9]; + const z = dotS4(out, mat, 0, 10) + mat[14]; + const w = dotS4(out, mat, 0, 15) + mat[19]; + return clampOut + ? setC4(out, clamp01(x), clamp01(y), clamp01(z), clamp01(w)) + : setC4(out, x, y, z, w); }; /** @internal */ export const __mulM45 = (a: ColorMatrix, b: ColorMatrix): ColorMatrix => [ - dotS4(b, a, 0, 0, 1, 5), - dotS4(b, a, 0, 1, 1, 5), - dotS4(b, a, 0, 2, 1, 5), - dotS4(b, a, 0, 3, 1, 5), - dotS4(b, a, 0, 4, 1, 5) + b[4], - dotS4(b, a, 5, 0, 1, 5), - dotS4(b, a, 5, 1, 1, 5), - dotS4(b, a, 5, 2, 1, 5), - dotS4(b, a, 5, 3, 1, 5), - dotS4(b, a, 5, 4, 1, 5) + b[9], - dotS4(b, a, 10, 0, 1, 5), - dotS4(b, a, 10, 1, 1, 5), - dotS4(b, a, 10, 2, 1, 5), - dotS4(b, a, 10, 3, 1, 5), - dotS4(b, a, 10, 4, 1, 5) + b[14], - dotS4(b, a, 15, 0, 1, 5), - dotS4(b, a, 15, 1, 1, 5), - dotS4(b, a, 15, 2, 1, 5), - dotS4(b, a, 15, 3, 1, 5), - dotS4(b, a, 15, 4, 1, 5) + b[19], + dotS4(b, a, 0, 0, 1, 5), + dotS4(b, a, 0, 1, 1, 5), + dotS4(b, a, 0, 2, 1, 5), + dotS4(b, a, 0, 3, 1, 5), + dotS4(b, a, 0, 4, 1, 5) + b[4], + dotS4(b, a, 5, 0, 1, 5), + dotS4(b, a, 5, 1, 1, 5), + dotS4(b, a, 5, 2, 1, 5), + dotS4(b, a, 5, 3, 1, 5), + dotS4(b, a, 5, 4, 1, 5) + b[9], + dotS4(b, a, 10, 0, 1, 5), + dotS4(b, a, 10, 1, 1, 5), + dotS4(b, a, 10, 2, 1, 5), + dotS4(b, a, 10, 3, 1, 5), + dotS4(b, a, 10, 4, 1, 5) + b[14], + dotS4(b, a, 15, 0, 1, 5), + dotS4(b, a, 15, 1, 1, 5), + dotS4(b, a, 15, 2, 1, 5), + dotS4(b, a, 15, 3, 1, 5), + dotS4(b, a, 15, 4, 1, 5) + b[19], ]; diff --git a/packages/color/src/internal/scale.ts b/packages/color/src/internal/scale.ts index 7c304b4b7d..230f21f1ed 100644 --- a/packages/color/src/internal/scale.ts +++ b/packages/color/src/internal/scale.ts @@ -1,3 +1,3 @@ /** @internal */ export const __scale8bit = (x: number, shift = 0) => - ((x < 0 ? 0 : x > 1 ? 1 : x) * 0xff + 0.5) << shift; + ((x < 0 ? 0 : x > 1 ? 1 : x) * 0xff + 0.5) << shift; diff --git a/packages/color/src/invert.ts b/packages/color/src/invert.ts index e82a8c0eff..2726874512 100644 --- a/packages/color/src/invert.ts +++ b/packages/color/src/invert.ts @@ -20,8 +20,8 @@ import { __ensureAlpha } from "./internal/ensure.js"; * @param src - source color */ export const invertRgb: ColorOp = (out, src) => { - out = clamp(out || src, src); - return sub3(out, ONE3, out); + out = clamp(out || src, src); + return sub3(out, ONE3, out); }; /** @@ -35,52 +35,52 @@ export const invertRgb: ColorOp = (out, src) => { export const invertInt = (src: number) => src ^ 0xffffff; export const invert = defmulti, Color>( - __dispatch1, - { - hcy: "hsv", - hsi: "hsv", - hsl: "hsl", - labD65: "labD50", - oklab: "labD50", - srgb: "rgb", - }, - { - hsv: (out, src) => - setC4( - out || src, - fract(src[0] + 0.5), - src[1], - 1 - src[2], - __ensureAlpha(src[3]) - ), - labD50: (out, src) => { - const [min, max] = src.range; - return setC4( - out || src, - 1 - src[0], - __invert1(src[1], min[1], max[1]), - __invert1(src[2], min[2], max[2]), - __ensureAlpha(src[3]) - ); - }, - lch: (out, src) => - setC4( - out || src, - 1 - src[0], - src[1], - fract(src[2] + 0.5), - __ensureAlpha(src[3]) - ), - rgb: invertRgb, - ycc: (out, src) => - setC4( - out || src, - 1 - src[0], - __invert1(src[1], -0.5, 0.5), - __invert1(src[2], -0.5, 0.5), - __ensureAlpha(src[3]) - ), - } + __dispatch1, + { + hcy: "hsv", + hsi: "hsv", + hsl: "hsl", + labD65: "labD50", + oklab: "labD50", + srgb: "rgb", + }, + { + hsv: (out, src) => + setC4( + out || src, + fract(src[0] + 0.5), + src[1], + 1 - src[2], + __ensureAlpha(src[3]) + ), + labD50: (out, src) => { + const [min, max] = src.range; + return setC4( + out || src, + 1 - src[0], + __invert1(src[1], min[1], max[1]), + __invert1(src[2], min[2], max[2]), + __ensureAlpha(src[3]) + ); + }, + lch: (out, src) => + setC4( + out || src, + 1 - src[0], + src[1], + fract(src[2] + 0.5), + __ensureAlpha(src[3]) + ), + rgb: invertRgb, + ycc: (out, src) => + setC4( + out || src, + 1 - src[0], + __invert1(src[1], -0.5, 0.5), + __invert1(src[2], -0.5, 0.5), + __ensureAlpha(src[3]) + ), + } ); const __invert1: FnN3 = (x, a, b) => fit(x, a, b, b, a); diff --git a/packages/color/src/is-black.ts b/packages/color/src/is-black.ts index f53dcf71cb..b1f25065d9 100644 --- a/packages/color/src/is-black.ts +++ b/packages/color/src/is-black.ts @@ -7,25 +7,25 @@ import { rgb } from "./rgb/rgb.js"; const isBlackHsv = (x: ReadonlyColor, eps = EPS) => x[2] <= eps; const isBlackRgb = (x: ReadonlyColor, eps = EPS) => - x[0] <= eps && x[1] <= eps && x[2] <= eps; + x[0] <= eps && x[1] <= eps && x[2] <= eps; const isBlackLch = (x: ReadonlyColor, eps = EPS) => x[0] <= eps; export const isBlack = defmulti, number | undefined, boolean>( - __dispatch0, - { - hcy: "hsv", - hsi: "hsv", - hsl: "hsv", - labD50: "lch", - labD65: "lch", - srgb: "rgb", - ycc: "rgb", - }, - { - hsv: isBlackHsv, - lch: isBlackLch, - rgb: isBlackRgb, - [DEFAULT]: (x: any) => isBlackRgb(rgb(x)), - } + __dispatch0, + { + hcy: "hsv", + hsi: "hsv", + hsl: "hsv", + labD50: "lch", + labD65: "lch", + srgb: "rgb", + ycc: "rgb", + }, + { + hsv: isBlackHsv, + lch: isBlackLch, + rgb: isBlackRgb, + [DEFAULT]: (x: any) => isBlackRgb(rgb(x)), + } ); diff --git a/packages/color/src/is-gamut.ts b/packages/color/src/is-gamut.ts index fb266bbfe6..fbdfa254b6 100644 --- a/packages/color/src/is-gamut.ts +++ b/packages/color/src/is-gamut.ts @@ -5,16 +5,16 @@ import { rgb } from "./rgb/rgb.js"; /** * Returns true, if given color is within the RGB gamut (without clipping). * - * @param src - - * @param eps - + * @param src - + * @param eps - */ export const isRgbGamut = (src: TypedColor, eps = 1e-3) => { - const min = -eps; - const max = 1 + eps; - const col = src.mode === "rgb" || src.mode === "srgb" ? src : rgb(src); - return ( - inRange(col[0], min, max) && - inRange(col[1], min, max) && - inRange(col[2], min, max) - ); + const min = -eps; + const max = 1 + eps; + const col = src.mode === "rgb" || src.mode === "srgb" ? src : rgb(src); + return ( + inRange(col[0], min, max) && + inRange(col[1], min, max) && + inRange(col[2], min, max) + ); }; diff --git a/packages/color/src/is-gray.ts b/packages/color/src/is-gray.ts index c34316ad9a..b0f9a2e766 100644 --- a/packages/color/src/is-gray.ts +++ b/packages/color/src/is-gray.ts @@ -8,26 +8,26 @@ import { rgb } from "./rgb/rgb.js"; const isGrayHsv = (x: ReadonlyColor, eps = EPS) => x[1] <= eps; const isGrayRgb = (x: ReadonlyColor, eps = EPS) => - eqDelta(x[0], x[1], eps) && eqDelta(x[0], x[2], eps); + eqDelta(x[0], x[1], eps) && eqDelta(x[0], x[2], eps); const isGrayLab = (x: ReadonlyColor, eps = EPS) => - eqDelta(x[1], 0, eps) && eqDelta(x[2], 0, eps); + eqDelta(x[1], 0, eps) && eqDelta(x[2], 0, eps); export const isGray = defmulti, number | undefined, boolean>( - __dispatch0, - { - hcy: "hsv", - hsi: "hsv", - hsl: "hsv", - lch: "hsv", - labD65: "labD50", - srgb: "rgb", - ycc: "labD50", - }, - { - hsv: isGrayHsv, - labD50: isGrayLab, - rgb: isGrayRgb, - [DEFAULT]: (x: any) => isGrayRgb(rgb(x)), - } + __dispatch0, + { + hcy: "hsv", + hsi: "hsv", + hsl: "hsv", + lch: "hsv", + labD65: "labD50", + srgb: "rgb", + ycc: "labD50", + }, + { + hsv: isGrayHsv, + labD50: isGrayLab, + rgb: isGrayRgb, + [DEFAULT]: (x: any) => isGrayRgb(rgb(x)), + } ); diff --git a/packages/color/src/is-white.ts b/packages/color/src/is-white.ts index 315347b171..3fdd675ec9 100644 --- a/packages/color/src/is-white.ts +++ b/packages/color/src/is-white.ts @@ -5,31 +5,31 @@ import { __dispatch0 } from "./internal/dispatch.js"; import { rgb } from "./rgb/rgb.js"; const isWhiteHsv = (x: ReadonlyColor, eps = EPS) => - x[1] <= eps && x[2] >= 1 - eps; + x[1] <= eps && x[2] >= 1 - eps; const isWhiteRgb = (x: ReadonlyColor, eps = EPS) => { - eps = 1 - eps; - return x[0] >= eps && x[1] >= eps && x[2] >= eps; + eps = 1 - eps; + return x[0] >= eps && x[1] >= eps && x[2] >= eps; }; const isWhiteLch = (x: ReadonlyColor, eps = EPS) => - x[1] <= eps && x[0] >= 1 - eps; + x[1] <= eps && x[0] >= 1 - eps; export const isWhite = defmulti, number | undefined, boolean>( - __dispatch0, - { - hsl: "hsv", - hsi: "hsv", - labD50: "lch", - labD65: "lch", - srgb: "rgb", - ycc: "lch", - }, - { - hcy: (x, eps = EPS) => x[1] <= eps && x[2] >= 1 - eps, - hsv: isWhiteHsv, - lch: isWhiteLch, - rgb: isWhiteRgb, - [DEFAULT]: (x: any) => isWhiteRgb(rgb(x)), - } + __dispatch0, + { + hsl: "hsv", + hsi: "hsv", + labD50: "lch", + labD65: "lch", + srgb: "rgb", + ycc: "lch", + }, + { + hcy: (x, eps = EPS) => x[1] <= eps && x[2] >= 1 - eps, + hsv: isWhiteHsv, + lch: isWhiteLch, + rgb: isWhiteRgb, + [DEFAULT]: (x: any) => isWhiteRgb(rgb(x)), + } ); diff --git a/packages/color/src/lab/lab-css.ts b/packages/color/src/lab/lab-css.ts index b0c5f240a5..709f49f169 100644 --- a/packages/color/src/lab/lab-css.ts +++ b/packages/color/src/lab/lab-css.ts @@ -9,12 +9,12 @@ import { __ensureAlpha } from "../internal/ensure.js"; * https://www.w3.org/TR/css-color-4/#specifying-lab-lch * https://test.csswg.org/harness/results/css-color-4_dev/grouped/ (test reports) * - * @param src - + * @param src - */ export const labCss = (src: ReadonlyColor) => { - const l = PC(clamp0(src[0])); - const a = FF(src[1] * 100); - const b = FF(src[2] * 100); - const alpha = __ensureAlpha(src[3]); - return `lab(${l} ${a} ${b}` + (alpha < 1 ? `/${FF(alpha)})` : ")"); + const l = PC(clamp0(src[0])); + const a = FF(src[1] * 100); + const b = FF(src[2] * 100); + const alpha = __ensureAlpha(src[3]); + return `lab(${l} ${a} ${b}` + (alpha < 1 ? `/${FF(alpha)})` : ")"); }; diff --git a/packages/color/src/lab/lab-lab.ts b/packages/color/src/lab/lab-lab.ts index 5380a21b6f..33170d2e8f 100644 --- a/packages/color/src/lab/lab-lab.ts +++ b/packages/color/src/lab/lab-lab.ts @@ -4,7 +4,7 @@ import { xyzXyzD50_65, xyzXyzD65_50 } from "../xyz/xyz-xyz.js"; import { labXyz, labXyzD65 } from "./lab-xyz.js"; export const labLabD50_65: ColorOp = (out, src) => - xyzLabD65(out, xyzXyzD50_65(out, labXyz(out, src))); + xyzLabD65(out, xyzXyzD50_65(out, labXyz(out, src))); export const labLabD65_50: ColorOp = (out, src) => - xyzLab(out, xyzXyzD65_50(out, labXyzD65(out, src))); + xyzLab(out, xyzXyzD65_50(out, labXyzD65(out, src))); diff --git a/packages/color/src/lab/lab-lch.ts b/packages/color/src/lab/lab-lch.ts index 87e48a19fe..005de923d0 100644 --- a/packages/color/src/lab/lab-lch.ts +++ b/packages/color/src/lab/lab-lch.ts @@ -5,21 +5,21 @@ import type { ColorOp } from "../api.js"; import { __ensureAlpha } from "../internal/ensure.js"; export const labLch: ColorOp = (out, src) => { - const { 1: a, 2: b } = src; - return setC4( - out || src, - src[0], - Math.hypot(a, b), - a === 0 && b === 0 ? 0 : atan2Abs(b, a) * INV_TAU, - __ensureAlpha(src[3]) - ); + const { 1: a, 2: b } = src; + return setC4( + out || src, + src[0], + Math.hypot(a, b), + a === 0 && b === 0 ? 0 : atan2Abs(b, a) * INV_TAU, + __ensureAlpha(src[3]) + ); }; export const lchLab: ColorOp = (out, src) => { - let { 1: c, 2: h } = src; - h *= TAU; - const a = __ensureAlpha(src[3]); - return c > 0 - ? setC4(out || src, src[0], Math.cos(h) * c, Math.sin(h) * c, a) - : setC4(out || src, src[0], 0, 0, a); + let { 1: c, 2: h } = src; + h *= TAU; + const a = __ensureAlpha(src[3]); + return c > 0 + ? setC4(out || src, src[0], Math.cos(h) * c, Math.sin(h) * c, a) + : setC4(out || src, src[0], 0, 0, a); }; diff --git a/packages/color/src/lab/lab-rgb.ts b/packages/color/src/lab/lab-rgb.ts index bf2e6e52de..ddbc4d05ac 100644 --- a/packages/color/src/lab/lab-rgb.ts +++ b/packages/color/src/lab/lab-rgb.ts @@ -5,16 +5,16 @@ import { xyzRgb, xyzRgbD65 } from "../xyz/xyz-rgb.js"; /** * Converts Lab to linear RGB (via XYZ) using {@link D50} white point. * - * @param out - - * @param src - + * @param out - + * @param src - */ export const labRgb: ColorOp = (out, src) => xyzRgb(null, labXyz(out, src)); /** * Same as {@link labRgb}, but using {@link D65} white point. * - * @param out - - * @param src - + * @param out - + * @param src - */ export const labRgbD65: ColorOp = (out, src) => - xyzRgbD65(null, labXyzD65(out, src)); + xyzRgbD65(null, labXyzD65(out, src)); diff --git a/packages/color/src/lab/lab-xyz.ts b/packages/color/src/lab/lab-xyz.ts index b403d16374..2007931672 100644 --- a/packages/color/src/lab/lab-xyz.ts +++ b/packages/color/src/lab/lab-xyz.ts @@ -4,33 +4,33 @@ import { D50, D65 } from "../api/constants.js"; import { __ensureAlpha } from "../internal/ensure.js"; const transform = (x: number) => { - const y = x ** 3; - return y > 0.008856 ? y : (x - 16 / 116) / 7.787; + const y = x ** 3; + return y > 0.008856 ? y : (x - 16 / 116) / 7.787; }; /** * Converts Lab to XYZ using provided white point (default: {@link D50}). Also * see {@link labXyzD65}. * - * @param out - - * @param src - - * @param white - + * @param out - + * @param src - + * @param white - */ export const labXyz = (out: Color | null, src: ReadonlyColor, white = D50) => { - const y = (src[0] + 0.16) / 1.16; - return setC4( - out || src, - transform(src[1] / 5.0 + y) * white[0], - transform(y) * white[1], - transform(y - src[2] / 2.0) * white[2], - __ensureAlpha(src[3]) - ); + const y = (src[0] + 0.16) / 1.16; + return setC4( + out || src, + transform(src[1] / 5.0 + y) * white[0], + transform(y) * white[1], + transform(y - src[2] / 2.0) * white[2], + __ensureAlpha(src[3]) + ); }; /** * Same as {@link labXyz}, but using hardcoded {@link D65} white point. * - * @param out - - * @param src - + * @param out - + * @param src - */ export const labXyzD65: ColorOp = (out, src) => labXyz(out, src, D65); diff --git a/packages/color/src/lab/lab50.ts b/packages/color/src/lab/lab50.ts index e9a118bcee..e77e89b0a8 100644 --- a/packages/color/src/lab/lab50.ts +++ b/packages/color/src/lab/lab50.ts @@ -10,42 +10,42 @@ import { lchLab } from "./lab-lch.js"; import { labRgb } from "./lab-rgb.js"; export declare class LabD50 implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - l: number; - a: number; - b: number; - alpha: number; - [id: number]: number; - readonly mode: "lab50"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): LabD50; - copyView(): LabD50; - deref(): Color; - empty(): LabD50; - eqDelta(o: LabD50, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + l: number; + a: number; + b: number; + alpha: number; + [id: number]: number; + readonly mode: "lab50"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): LabD50; + copyView(): LabD50; + deref(): Color; + empty(): LabD50; + eqDelta(o: LabD50, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const labD50 = >defColor({ - mode: "lab50", - channels: { - a: { range: [-0.7929, 0.9355] }, - b: { range: [-1.1203, 0.9339] }, - }, - order: ["l", "a", "b", "alpha"], - from: { - rgb: rgbLab, - lch: lchLab, - lab65: labLabD65_50, - xyz50: xyzLab, - xyz65: [xyzXyzD65_50, xyzLab], - }, - toRgb: labRgb, + mode: "lab50", + channels: { + a: { range: [-0.7929, 0.9355] }, + b: { range: [-1.1203, 0.9339] }, + }, + order: ["l", "a", "b", "alpha"], + from: { + rgb: rgbLab, + lch: lchLab, + lab65: labLabD65_50, + xyz50: xyzLab, + xyz65: [xyzXyzD65_50, xyzLab], + }, + toRgb: labRgb, }); diff --git a/packages/color/src/lab/lab65.ts b/packages/color/src/lab/lab65.ts index 91a65202b7..39240f21c9 100644 --- a/packages/color/src/lab/lab65.ts +++ b/packages/color/src/lab/lab65.ts @@ -10,42 +10,42 @@ import { lchLab } from "./lab-lch.js"; import { labRgbD65 } from "./lab-rgb.js"; export declare class LabD65 implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - l: number; - a: number; - b: number; - alpha: number; - [id: number]: number; - readonly mode: "lab65"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): LabD65; - copyView(): LabD65; - deref(): Color; - empty(): LabD65; - eqDelta(o: LabD65, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + l: number; + a: number; + b: number; + alpha: number; + [id: number]: number; + readonly mode: "lab65"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): LabD65; + copyView(): LabD65; + deref(): Color; + empty(): LabD65; + eqDelta(o: LabD65, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const labD65 = >defColor({ - mode: "lab65", - channels: { - a: { range: [-0.8618, 0.9823] }, - b: { range: [-1.0786, 0.9448] }, - }, - order: ["l", "a", "b", "alpha"], - from: { - rgb: rgbLabD65, - lch: [lchLab, labLabD50_65], - lab50: labLabD50_65, - xyz50: [xyzXyzD50_65, xyzLabD65], - xyz65: xyzLabD65, - }, - toRgb: labRgbD65, + mode: "lab65", + channels: { + a: { range: [-0.8618, 0.9823] }, + b: { range: [-1.0786, 0.9448] }, + }, + order: ["l", "a", "b", "alpha"], + from: { + rgb: rgbLabD65, + lch: [lchLab, labLabD50_65], + lab50: labLabD50_65, + xyz50: [xyzXyzD50_65, xyzLabD65], + xyz65: xyzLabD65, + }, + toRgb: labRgbD65, }); diff --git a/packages/color/src/lch/lch-css.ts b/packages/color/src/lch/lch-css.ts index e4e7d3c172..301de1b7b4 100644 --- a/packages/color/src/lch/lch-css.ts +++ b/packages/color/src/lch/lch-css.ts @@ -10,12 +10,12 @@ import { __ensureAlpha } from "../internal/ensure.js"; * https://www.w3.org/TR/css-color-4/#specifying-lab-lch * https://test.csswg.org/harness/results/css-color-4_dev/grouped/ (test reports) * - * @param src - + * @param src - */ export const lchCss = (src: ReadonlyColor) => { - const l = PC(clamp0(src[0])); - const c = FF(clamp0(src[1]) * 100); - const h = FF(fract(src[2]) * 360); - const a = __ensureAlpha(src[3]); - return `lch(${l} ${c} ${h}` + (a < 1 ? `/${FF(a)})` : ")"); + const l = PC(clamp0(src[0])); + const c = FF(clamp0(src[1]) * 100); + const h = FF(fract(src[2]) * 360); + const a = __ensureAlpha(src[3]); + return `lch(${l} ${c} ${h}` + (a < 1 ? `/${FF(a)})` : ")"); }; diff --git a/packages/color/src/lch/lch.ts b/packages/color/src/lch/lch.ts index e67522f4e5..a4287f5654 100644 --- a/packages/color/src/lch/lch.ts +++ b/packages/color/src/lch/lch.ts @@ -10,27 +10,27 @@ import { xyzLab } from "../xyz/xyz-lab.js"; import { xyzXyzD65_50 } from "../xyz/xyz-xyz.js"; export declare class LCH implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - l: number; - c: number; - h: number; - alpha: number; - [id: number]: number; - readonly mode: "lch"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): LCH; - copyView(): LCH; - deref(): Color; - empty(): LCH; - eqDelta(o: LCH, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + l: number; + c: number; + h: number; + alpha: number; + [id: number]: number; + readonly mode: "lch"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): LCH; + copyView(): LCH; + deref(): Color; + empty(): LCH; + eqDelta(o: LCH, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } /** @@ -38,18 +38,18 @@ export declare class LCH implements TypedColor { * spec). */ export const lch = >defColor({ - mode: "lch", - channels: { - c: { range: [0, 1.312] }, - h: { hue: true }, - }, - order: ["l", "c", "h", "alpha"], - from: { - rgb: (out, src) => labLch(null, rgbLab(out, src)), - lab50: labLch, - lab65: [labLabD65_50, labLch], - xyz50: [xyzLab, labLch], - xyz65: [xyzXyzD65_50, xyzLab, labLch], - }, - toRgb: [lchLab, labRgb], + mode: "lch", + channels: { + c: { range: [0, 1.312] }, + h: { hue: true }, + }, + order: ["l", "c", "h", "alpha"], + from: { + rgb: (out, src) => labLch(null, rgbLab(out, src)), + lab50: labLch, + lab65: [labLabD65_50, labLch], + xyz50: [xyzLab, labLch], + xyz65: [xyzXyzD65_50, xyzLab, labLch], + }, + toRgb: [lchLab, labRgb], }); diff --git a/packages/color/src/lighten.ts b/packages/color/src/lighten.ts index e39150af31..74817c19f3 100644 --- a/packages/color/src/lighten.ts +++ b/packages/color/src/lighten.ts @@ -5,25 +5,25 @@ import type { Color, TypedColor } from "./api.js"; import { __dispatch1 } from "./internal/dispatch.js"; const $ = - (id: number) => (out: Color | null, src: TypedColor, n: number) => { - out = set4(out || src, src); - out[id] = clamp01(out[id] + n); - return out; - }; + (id: number) => (out: Color | null, src: TypedColor, n: number) => { + out = set4(out || src, src); + out[id] = clamp01(out[id] + n); + return out; + }; /** * Adjust the "lightness" (luma, brightness etc.) channel of given `src` color * and `delta` offset. Writes result into `out` (or if null, back into `src`). * - * @param out - - * @param src - - * @param delta - + * @param out - + * @param src - + * @param delta - */ export const lighten = defmulti, number, Color>( - __dispatch1, - { hsv: "hsl", hsi: "hsl", hcy: "hsl" }, - { - hsl: $(2), - lch: $(0), - } + __dispatch1, + { hsv: "hsl", hsi: "hsl", hcy: "hsl" }, + { + hsl: $(2), + lch: $(0), + } ); diff --git a/packages/color/src/linear.ts b/packages/color/src/linear.ts index 20f661ecfc..6f27eda94a 100644 --- a/packages/color/src/linear.ts +++ b/packages/color/src/linear.ts @@ -6,7 +6,7 @@ * @param x - channel value */ export const linearSrgb = (x: number) => - x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055; + x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055; /** * Maps a single linear sRGB channel value to linear RGB. @@ -16,4 +16,4 @@ export const linearSrgb = (x: number) => * @param x - channel value */ export const srgbLinear = (x: number) => - x <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); + x <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); diff --git a/packages/color/src/luminance-rgb.ts b/packages/color/src/luminance-rgb.ts index d01a8b03d2..9ee20fed1b 100644 --- a/packages/color/src/luminance-rgb.ts +++ b/packages/color/src/luminance-rgb.ts @@ -6,34 +6,34 @@ import { RGB_LUMINANCE_REC709, RGB_LUMINANCE_REC601 } from "./api/constants.js"; * Computes RGB luminance, optionally using provided weights (by default: * {@link RGB_LUMINANCE_REC709}). * - * @param rgb - - * @param weights - + * @param rgb - + * @param weights - */ export const luminanceRgb = ( - rgb: ReadonlyColor, - weights = RGB_LUMINANCE_REC709 + rgb: ReadonlyColor, + weights = RGB_LUMINANCE_REC709 ) => dot3(rgb, weights); /** * Similar to {@link luminanceRgb}, but uses {@link RGB_LUMINANCE_REC601} coeffs */ export const luminanceSrgb = (rgb: ReadonlyColor) => - dot3(rgb, RGB_LUMINANCE_REC601); + dot3(rgb, RGB_LUMINANCE_REC601); export const luminanceIntArgb32 = (rgb: number) => - (((rgb >>> 16) & 0xff) * 76 + - ((rgb >>> 8) & 0xff) * 150 + - (rgb & 0xff) * 29) / - 0xfe01; + (((rgb >>> 16) & 0xff) * 76 + + ((rgb >>> 8) & 0xff) * 150 + + (rgb & 0xff) * 29) / + 0xfe01; export const luminanceIntAbgr32 = (rgb: number) => - (((rgb >>> 16) & 0xff) * 29 + - ((rgb >>> 8) & 0xff) * 150 + - (rgb & 0xff) * 76) / - 0xfe01; + (((rgb >>> 16) & 0xff) * 29 + + ((rgb >>> 8) & 0xff) * 150 + + (rgb & 0xff) * 76) / + 0xfe01; export const luminanceArgb32 = (argb: ReadonlyColor) => - luminanceIntArgb32(argb[0]); + luminanceIntArgb32(argb[0]); export const luminanceAbgr32 = (argb: ReadonlyColor) => - luminanceIntAbgr32(argb[0]); + luminanceIntAbgr32(argb[0]); diff --git a/packages/color/src/luminance.ts b/packages/color/src/luminance.ts index 355c4d92c1..c80fec4763 100644 --- a/packages/color/src/luminance.ts +++ b/packages/color/src/luminance.ts @@ -2,10 +2,10 @@ import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti"; import type { MaybeColor } from "./api.js"; import { rgb } from "./rgb/rgb.js"; import { - luminanceAbgr32, - luminanceArgb32, - luminanceRgb, - luminanceSrgb, + luminanceAbgr32, + luminanceArgb32, + luminanceRgb, + luminanceSrgb, } from "./luminance-rgb.js"; import { __dispatch0 } from "./internal/dispatch.js"; @@ -20,16 +20,16 @@ import { __dispatch0 } from "./internal/dispatch.js"; * to linear RGB. */ export const luminance = defmulti( - __dispatch0, - { lch: "lab", oklab: "lab", ycc: "lab", xyy: "hcy" }, - { - argb32: luminanceArgb32, - abgr32: luminanceAbgr32, - hcy: (x: any) => x[2], - lab: (x: any) => x[0], - rgb: luminanceRgb, - srgb: luminanceSrgb, - xyz: (x: any) => x[1], - [DEFAULT]: (x: any) => luminanceRgb(rgb(x)), - } + __dispatch0, + { lch: "lab", oklab: "lab", ycc: "lab", xyy: "hcy" }, + { + argb32: luminanceArgb32, + abgr32: luminanceAbgr32, + hcy: (x: any) => x[2], + lab: (x: any) => x[0], + rgb: luminanceRgb, + srgb: luminanceSrgb, + xyz: (x: any) => x[1], + [DEFAULT]: (x: any) => luminanceRgb(rgb(x)), + } ); diff --git a/packages/color/src/max-chroma.ts b/packages/color/src/max-chroma.ts index 0a28cffb39..0c7e4bd371 100644 --- a/packages/color/src/max-chroma.ts +++ b/packages/color/src/max-chroma.ts @@ -6,250 +6,250 @@ import { lch } from "./lch/lch.js"; // LUT generated via tools/max-chroma.ts /** @internal */ const MAX_CHROMA = [ - [ - 4, 22, 30, 34, 38, 43, 47, 52, 56, 61, 66, 70, 75, 80, 84, 74, 65, 56, - 47, 39, 32, 25, 18, 11, 5, - ], - [ - 4, 22, 30, 34, 39, 43, 48, 52, 57, 61, 66, 71, 75, 80, 82, 72, 63, 54, - 46, 38, 31, 24, 17, 10, 5, - ], - [ - 4, 22, 31, 35, 39, 44, 49, 53, 58, 63, 67, 72, 77, 82, 83, 73, 63, 54, - 46, 38, 31, 24, 17, 10, 5, - ], - [ - 3, 20, 32, 37, 41, 46, 51, 55, 60, 65, 70, 75, 80, 85, 85, 74, 65, 55, - 47, 39, 31, 24, 17, 10, 5, - ], - [ - 2, 15, 27, 39, 44, 49, 54, 59, 64, 69, 75, 80, 85, 90, 89, 78, 67, 57, - 48, 40, 32, 25, 18, 10, 5, - ], - [ - 2, 11, 22, 32, 43, 53, 59, 64, 70, 76, 81, 87, 93, 99, 96, 84, 72, 61, - 51, 42, 34, 26, 19, 11, 5, - ], - [ - 1, 10, 19, 27, 36, 44, 52, 59, 66, 72, 77, 83, 88, 93, 98, 92, 79, 67, - 56, 45, 36, 28, 20, 12, 6, - ], - [ - 1, 9, 17, 24, 31, 38, 45, 51, 57, 61, 66, 70, 75, 79, 84, 88, 90, 75, - 62, 51, 40, 30, 22, 13, 6, - ], - [ - 1, 8, 15, 22, 28, 35, 40, 46, 50, 54, 58, 62, 66, 70, 74, 78, 82, 86, - 73, 59, 46, 35, 24, 15, 7, - ], - [ - 1, 7, 13, 20, 26, 32, 37, 42, 46, 50, 53, 57, 61, 64, 68, 72, 75, 79, - 83, 72, 55, 41, 29, 18, 8, - ], - [ - 1, 7, 12, 19, 25, 30, 35, 40, 43, 47, 50, 54, 57, 60, 64, 67, 71, 74, - 78, 81, 73, 53, 36, 22, 10, - ], - [ - 1, 7, 12, 19, 24, 29, 34, 38, 42, 45, 48, 51, 55, 58, 61, 65, 68, 71, - 75, 78, 82, 79, 50, 29, 12, - ], - [ - 1, 7, 12, 19, 24, 29, 33, 37, 41, 44, 47, 50, 54, 57, 60, 63, 67, 70, - 73, 77, 80, 83, 86, 48, 19, - ], - [ - 1, 7, 12, 19, 24, 29, 33, 37, 41, 44, 47, 50, 53, 57, 60, 63, 66, 70, - 73, 76, 80, 83, 86, 90, 45, - ], - [ - 1, 7, 12, 19, 25, 30, 34, 38, 41, 44, 48, 51, 54, 58, 61, 64, 67, 71, - 74, 78, 81, 84, 88, 91, 94, - ], - [ - 1, 7, 13, 20, 26, 31, 35, 39, 43, 46, 49, 53, 56, 60, 63, 66, 70, 73, - 77, 80, 84, 87, 91, 94, 60, - ], - [ - 1, 8, 15, 21, 27, 33, 37, 42, 45, 49, 52, 56, 59, 63, 66, 70, 74, 77, - 81, 85, 88, 92, 96, 96, 44, - ], - [ - 1, 9, 16, 23, 29, 35, 40, 45, 49, 52, 56, 60, 64, 68, 72, 76, 80, 84, - 87, 91, 95, 99, 103, 77, 35, - ], - [ - 1, 10, 18, 26, 33, 39, 44, 48, 52, 56, 60, 65, 69, 73, 77, 82, 86, 90, - 94, 99, 103, 107, 110, 65, 30, - ], - [ - 2, 10, 21, 28, 30, 34, 37, 40, 44, 47, 51, 55, 58, 62, 65, 69, 73, 76, - 80, 83, 87, 91, 94, 58, 26, - ], - [ - 1, 10, 18, 24, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 60, 63, 66, - 69, 72, 75, 78, 81, 52, 24, - ], - [ - 1, 8, 15, 21, 23, 26, 28, 31, 33, 36, 39, 42, 44, 47, 50, 53, 55, 58, - 61, 64, 66, 69, 72, 48, 22, - ], - [ - 1, 7, 13, 19, 21, 23, 25, 28, 30, 33, 35, 37, 40, 42, 45, 47, 50, 52, - 55, 57, 60, 62, 65, 46, 21, - ], - [ - 1, 7, 12, 17, 19, 21, 23, 25, 28, 30, 32, 34, 37, 39, 41, 44, 46, 48, - 50, 53, 55, 57, 60, 44, 20, - ], - [ - 1, 6, 11, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 39, 41, 43, 45, - 47, 49, 51, 54, 56, 43, 19, - ], - [ - 1, 6, 11, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, - 45, 47, 49, 51, 53, 43, 19, - ], - [ - 1, 6, 10, 15, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 35, 37, 39, 41, - 43, 45, 47, 49, 51, 43, 20, - ], - [ - 1, 6, 10, 13, 16, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, - 43, 45, 46, 48, 49, 33, 16, - ], - [ - 1, 6, 10, 13, 16, 18, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, - 42, 44, 46, 48, 37, 25, 11, - ], - [ - 1, 7, 11, 13, 16, 18, 20, 22, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, - 43, 45, 47, 40, 30, 20, 10, - ], - [ - 1, 7, 11, 15, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, - 44, 46, 43, 34, 26, 17, 8, - ], - [ - 1, 7, 12, 15, 17, 19, 21, 23, 25, 27, 29, 31, 34, 36, 38, 40, 42, 44, - 46, 46, 38, 31, 23, 15, 7, - ], - [ - 1, 8, 13, 16, 18, 20, 22, 25, 27, 29, 31, 33, 36, 38, 40, 42, 45, 47, - 49, 42, 35, 28, 21, 13, 7, - ], - [ - 1, 10, 15, 18, 20, 22, 24, 27, 29, 31, 34, 36, 39, 41, 43, 46, 48, 51, - 46, 40, 33, 26, 19, 12, 6, - ], - [ - 2, 10, 17, 19, 22, 24, 27, 29, 32, 35, 37, 40, 43, 45, 48, 51, 53, 51, - 44, 38, 31, 25, 18, 11, 6, - ], - [ - 2, 13, 19, 22, 25, 28, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 56, 49, - 43, 37, 30, 24, 18, 11, 6, - ], - [ - 3, 17, 22, 26, 29, 32, 36, 39, 42, 46, 49, 53, 56, 60, 63, 62, 55, 49, - 43, 36, 30, 24, 18, 11, 6, - ], - [ - 6, 23, 27, 31, 35, 39, 43, 47, 51, 56, 60, 64, 68, 73, 69, 62, 56, 49, - 43, 36, 30, 24, 18, 11, 6, - ], - [ - 18, 30, 34, 39, 44, 49, 54, 60, 65, 70, 76, 81, 84, 77, 70, 64, 57, 50, - 44, 37, 31, 25, 18, 11, 6, - ], - [ - 12, 41, 45, 52, 59, 66, 73, 80, 87, 94, 102, 95, 88, 81, 74, 66, 60, 53, - 46, 39, 32, 26, 19, 11, 6, - ], - [ - 9, 58, 65, 75, 85, 96, 106, 117, 124, 116, 109, 101, 93, 86, 78, 71, 63, - 56, 49, 42, 34, 27, 20, 12, 6, - ], - [ - 7, 42, 58, 66, 74, 82, 91, 99, 108, 117, 119, 110, 102, 94, 85, 77, 69, - 61, 53, 45, 37, 30, 22, 13, 7, - ], - [ - 6, 34, 47, 53, 60, 67, 74, 81, 88, 95, 102, 109, 114, 105, 95, 86, 77, - 68, 59, 51, 42, 33, 25, 16, 8, - ], - [ - 5, 29, 40, 46, 52, 57, 63, 70, 76, 82, 88, 94, 100, 107, 110, 100, 89, - 79, 69, 58, 48, 38, 29, 19, 9, - ], - [ - 4, 26, 36, 41, 46, 52, 57, 62, 68, 73, 79, 85, 90, 96, 101, 104, 91, 79, - 67, 56, 45, 35, 26, 17, 8, - ], - [ - 4, 24, 34, 38, 43, 48, 53, 58, 63, 68, 73, 78, 83, 88, 94, 91, 79, 69, - 59, 49, 40, 31, 23, 15, 7, - ], - [ - 4, 23, 32, 36, 40, 45, 50, 55, 59, 64, 69, 74, 79, 84, 89, 82, 72, 62, - 53, 44, 36, 28, 20, 12, 6, - ], - [ - 4, 22, 31, 35, 39, 44, 48, 53, 57, 62, 67, 71, 76, 81, 86, 77, 67, 58, - 50, 41, 33, 26, 19, 11, 6, - ], + [ + 4, 22, 30, 34, 38, 43, 47, 52, 56, 61, 66, 70, 75, 80, 84, 74, 65, 56, + 47, 39, 32, 25, 18, 11, 5, + ], + [ + 4, 22, 30, 34, 39, 43, 48, 52, 57, 61, 66, 71, 75, 80, 82, 72, 63, 54, + 46, 38, 31, 24, 17, 10, 5, + ], + [ + 4, 22, 31, 35, 39, 44, 49, 53, 58, 63, 67, 72, 77, 82, 83, 73, 63, 54, + 46, 38, 31, 24, 17, 10, 5, + ], + [ + 3, 20, 32, 37, 41, 46, 51, 55, 60, 65, 70, 75, 80, 85, 85, 74, 65, 55, + 47, 39, 31, 24, 17, 10, 5, + ], + [ + 2, 15, 27, 39, 44, 49, 54, 59, 64, 69, 75, 80, 85, 90, 89, 78, 67, 57, + 48, 40, 32, 25, 18, 10, 5, + ], + [ + 2, 11, 22, 32, 43, 53, 59, 64, 70, 76, 81, 87, 93, 99, 96, 84, 72, 61, + 51, 42, 34, 26, 19, 11, 5, + ], + [ + 1, 10, 19, 27, 36, 44, 52, 59, 66, 72, 77, 83, 88, 93, 98, 92, 79, 67, + 56, 45, 36, 28, 20, 12, 6, + ], + [ + 1, 9, 17, 24, 31, 38, 45, 51, 57, 61, 66, 70, 75, 79, 84, 88, 90, 75, + 62, 51, 40, 30, 22, 13, 6, + ], + [ + 1, 8, 15, 22, 28, 35, 40, 46, 50, 54, 58, 62, 66, 70, 74, 78, 82, 86, + 73, 59, 46, 35, 24, 15, 7, + ], + [ + 1, 7, 13, 20, 26, 32, 37, 42, 46, 50, 53, 57, 61, 64, 68, 72, 75, 79, + 83, 72, 55, 41, 29, 18, 8, + ], + [ + 1, 7, 12, 19, 25, 30, 35, 40, 43, 47, 50, 54, 57, 60, 64, 67, 71, 74, + 78, 81, 73, 53, 36, 22, 10, + ], + [ + 1, 7, 12, 19, 24, 29, 34, 38, 42, 45, 48, 51, 55, 58, 61, 65, 68, 71, + 75, 78, 82, 79, 50, 29, 12, + ], + [ + 1, 7, 12, 19, 24, 29, 33, 37, 41, 44, 47, 50, 54, 57, 60, 63, 67, 70, + 73, 77, 80, 83, 86, 48, 19, + ], + [ + 1, 7, 12, 19, 24, 29, 33, 37, 41, 44, 47, 50, 53, 57, 60, 63, 66, 70, + 73, 76, 80, 83, 86, 90, 45, + ], + [ + 1, 7, 12, 19, 25, 30, 34, 38, 41, 44, 48, 51, 54, 58, 61, 64, 67, 71, + 74, 78, 81, 84, 88, 91, 94, + ], + [ + 1, 7, 13, 20, 26, 31, 35, 39, 43, 46, 49, 53, 56, 60, 63, 66, 70, 73, + 77, 80, 84, 87, 91, 94, 60, + ], + [ + 1, 8, 15, 21, 27, 33, 37, 42, 45, 49, 52, 56, 59, 63, 66, 70, 74, 77, + 81, 85, 88, 92, 96, 96, 44, + ], + [ + 1, 9, 16, 23, 29, 35, 40, 45, 49, 52, 56, 60, 64, 68, 72, 76, 80, 84, + 87, 91, 95, 99, 103, 77, 35, + ], + [ + 1, 10, 18, 26, 33, 39, 44, 48, 52, 56, 60, 65, 69, 73, 77, 82, 86, 90, + 94, 99, 103, 107, 110, 65, 30, + ], + [ + 2, 10, 21, 28, 30, 34, 37, 40, 44, 47, 51, 55, 58, 62, 65, 69, 73, 76, + 80, 83, 87, 91, 94, 58, 26, + ], + [ + 1, 10, 18, 24, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 60, 63, 66, + 69, 72, 75, 78, 81, 52, 24, + ], + [ + 1, 8, 15, 21, 23, 26, 28, 31, 33, 36, 39, 42, 44, 47, 50, 53, 55, 58, + 61, 64, 66, 69, 72, 48, 22, + ], + [ + 1, 7, 13, 19, 21, 23, 25, 28, 30, 33, 35, 37, 40, 42, 45, 47, 50, 52, + 55, 57, 60, 62, 65, 46, 21, + ], + [ + 1, 7, 12, 17, 19, 21, 23, 25, 28, 30, 32, 34, 37, 39, 41, 44, 46, 48, + 50, 53, 55, 57, 60, 44, 20, + ], + [ + 1, 6, 11, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 39, 41, 43, 45, + 47, 49, 51, 54, 56, 43, 19, + ], + [ + 1, 6, 11, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, + 45, 47, 49, 51, 53, 43, 19, + ], + [ + 1, 6, 10, 15, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 35, 37, 39, 41, + 43, 45, 47, 49, 51, 43, 20, + ], + [ + 1, 6, 10, 13, 16, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, + 43, 45, 46, 48, 49, 33, 16, + ], + [ + 1, 6, 10, 13, 16, 18, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, + 42, 44, 46, 48, 37, 25, 11, + ], + [ + 1, 7, 11, 13, 16, 18, 20, 22, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, + 43, 45, 47, 40, 30, 20, 10, + ], + [ + 1, 7, 11, 15, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, + 44, 46, 43, 34, 26, 17, 8, + ], + [ + 1, 7, 12, 15, 17, 19, 21, 23, 25, 27, 29, 31, 34, 36, 38, 40, 42, 44, + 46, 46, 38, 31, 23, 15, 7, + ], + [ + 1, 8, 13, 16, 18, 20, 22, 25, 27, 29, 31, 33, 36, 38, 40, 42, 45, 47, + 49, 42, 35, 28, 21, 13, 7, + ], + [ + 1, 10, 15, 18, 20, 22, 24, 27, 29, 31, 34, 36, 39, 41, 43, 46, 48, 51, + 46, 40, 33, 26, 19, 12, 6, + ], + [ + 2, 10, 17, 19, 22, 24, 27, 29, 32, 35, 37, 40, 43, 45, 48, 51, 53, 51, + 44, 38, 31, 25, 18, 11, 6, + ], + [ + 2, 13, 19, 22, 25, 28, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 56, 49, + 43, 37, 30, 24, 18, 11, 6, + ], + [ + 3, 17, 22, 26, 29, 32, 36, 39, 42, 46, 49, 53, 56, 60, 63, 62, 55, 49, + 43, 36, 30, 24, 18, 11, 6, + ], + [ + 6, 23, 27, 31, 35, 39, 43, 47, 51, 56, 60, 64, 68, 73, 69, 62, 56, 49, + 43, 36, 30, 24, 18, 11, 6, + ], + [ + 18, 30, 34, 39, 44, 49, 54, 60, 65, 70, 76, 81, 84, 77, 70, 64, 57, 50, + 44, 37, 31, 25, 18, 11, 6, + ], + [ + 12, 41, 45, 52, 59, 66, 73, 80, 87, 94, 102, 95, 88, 81, 74, 66, 60, 53, + 46, 39, 32, 26, 19, 11, 6, + ], + [ + 9, 58, 65, 75, 85, 96, 106, 117, 124, 116, 109, 101, 93, 86, 78, 71, 63, + 56, 49, 42, 34, 27, 20, 12, 6, + ], + [ + 7, 42, 58, 66, 74, 82, 91, 99, 108, 117, 119, 110, 102, 94, 85, 77, 69, + 61, 53, 45, 37, 30, 22, 13, 7, + ], + [ + 6, 34, 47, 53, 60, 67, 74, 81, 88, 95, 102, 109, 114, 105, 95, 86, 77, + 68, 59, 51, 42, 33, 25, 16, 8, + ], + [ + 5, 29, 40, 46, 52, 57, 63, 70, 76, 82, 88, 94, 100, 107, 110, 100, 89, + 79, 69, 58, 48, 38, 29, 19, 9, + ], + [ + 4, 26, 36, 41, 46, 52, 57, 62, 68, 73, 79, 85, 90, 96, 101, 104, 91, 79, + 67, 56, 45, 35, 26, 17, 8, + ], + [ + 4, 24, 34, 38, 43, 48, 53, 58, 63, 68, 73, 78, 83, 88, 94, 91, 79, 69, + 59, 49, 40, 31, 23, 15, 7, + ], + [ + 4, 23, 32, 36, 40, 45, 50, 55, 59, 64, 69, 74, 79, 84, 89, 82, 72, 62, + 53, 44, 36, 28, 20, 12, 6, + ], + [ + 4, 22, 31, 35, 39, 44, 48, 53, 57, 62, 67, 71, 76, 81, 86, 77, 67, 58, + 50, 41, 33, 26, 19, 11, 6, + ], ]; const RES_H = MAX_CHROMA.length; const RES_L = MAX_CHROMA[0].length; export const maxChroma = (l: number, h: number) => { - h = fract(h); - l = clamp01(l); - if (l < 1) { - const h1 = (h * RES_H) | 0; - const l1 = (l * RES_L) | 0; - const lutH1 = MAX_CHROMA[h1]; - const lutH2 = MAX_CHROMA[(h1 + 1) % RES_H]; - return ( - mixBilinear( - lutH1[l1], - lutH2[l1], - l1 < RES_L - 1 ? lutH1[l1 + 1] : 0, - l1 < RES_L - 1 ? lutH2[l1 + 1] : 0, - h * RES_H - h1, - l * RES_L - l1 - ) * 0.01 - ); - } - return 0; + h = fract(h); + l = clamp01(l); + if (l < 1) { + const h1 = (h * RES_H) | 0; + const l1 = (l * RES_L) | 0; + const lutH1 = MAX_CHROMA[h1]; + const lutH2 = MAX_CHROMA[(h1 + 1) % RES_H]; + return ( + mixBilinear( + lutH1[l1], + lutH2[l1], + l1 < RES_L - 1 ? lutH1[l1 + 1] : 0, + l1 < RES_L - 1 ? lutH2[l1 + 1] : 0, + h * RES_H - h1, + l * RES_L - l1 + ) * 0.01 + ); + } + return 0; }; export const lchMaxChroma = (l: number, h: number, a = 1) => - lch(l, maxChroma(l, h), h, a); + lch(l, maxChroma(l, h), h, a); export const maxLumaChroma = (h: number) => { - const h1 = (fract(h) * RES_H) | 0; - const t = h * RES_H - h1; - const [l1, c1] = __maxLC(MAX_CHROMA[h1]); - const [l2, c2] = __maxLC(MAX_CHROMA[(h1 + 1) % RES_H]); - return { l: mix(l1, l2, t) / RES_L, c: mix(c1, c2, t) * 0.01 }; + const h1 = (fract(h) * RES_H) | 0; + const t = h * RES_H - h1; + const [l1, c1] = __maxLC(MAX_CHROMA[h1]); + const [l2, c2] = __maxLC(MAX_CHROMA[(h1 + 1) % RES_H]); + return { l: mix(l1, l2, t) / RES_L, c: mix(c1, c2, t) * 0.01 }; }; export const lchMaxLumaChroma = (h: number, a = 1) => { - const max = maxLumaChroma(h); - return lch(max.l, max.c, h, a); + const max = maxLumaChroma(h); + return lch(max.l, max.c, h, a); }; /** @internal */ const __maxLC = (chroma: number[]) => { - let maxL = 0; - let maxC = 0; - for (let i = RES_L; i-- > 0; ) { - if (chroma[i] >= maxC) { - maxC = chroma[i]; - maxL = i; - } else break; - } - return [maxL, maxC]; + let maxL = 0; + let maxC = 0; + for (let i = RES_L; i-- > 0; ) { + if (chroma[i] >= maxC) { + maxC = chroma[i]; + maxL = i; + } else break; + } + return [maxL, maxC]; }; diff --git a/packages/color/src/mix.ts b/packages/color/src/mix.ts index 7b5de21bdf..81554e199a 100644 --- a/packages/color/src/mix.ts +++ b/packages/color/src/mix.ts @@ -11,20 +11,20 @@ import { __dispatch1 } from "./internal/dispatch.js"; * HOF color mix function. Takes 4 scalar mix fns (one per color channel) and * returns new {@link ColorMixFn}. * - * @param x - - * @param y - - * @param z - - * @param alpha - + * @param x - + * @param y - + * @param z - + * @param alpha - */ export const defMix: FnU4 = - (x, y, z, alpha) => (out, a, b, t) => - setC4( - out || a, - x(a[0], b[0], t), - y(a[1], b[1], t), - z(a[2], b[2], t), - alpha(a[3], b[3], t) - ); + (x, y, z, alpha) => (out, a, b, t) => + setC4( + out || a, + x(a[0], b[0], t), + y(a[1], b[1], t), + z(a[2], b[2], t), + alpha(a[3], b[3], t) + ); /** * Single channel interpolation for (normalized) hues. Always interpolates via @@ -35,26 +35,26 @@ export const defMix: FnU4 = * @param t - interpolation factor */ export const mixH: FnN3 = (a, b, t) => { - a = fract(a); - b = fract(b); - const delta = b - a; - return fract( - a + - (Math.abs(delta) > 0.5 - ? delta < 0 - ? delta + 1 - : -(1 - delta) - : delta) * - t - ); + a = fract(a); + b = fract(b); + const delta = b - a; + return fract( + a + + (Math.abs(delta) > 0.5 + ? delta < 0 + ? delta + 1 + : -(1 - delta) + : delta) * + t + ); }; /** * Single channel linear interpolation function. * - * @param a - - * @param b - - * @param t - + * @param a - + * @param b - + * @param t - */ export const mixN = $mix; @@ -102,20 +102,20 @@ export const mixNNNN: ColorMixFn = mixN4; * @param t - */ export const mix = defmulti< - Color | null, - TypedColor, - TypedColor, - number, - Color + Color | null, + TypedColor, + TypedColor, + number, + Color >( - __dispatch1, - {}, - { - hcy: mixHNNN, - hsi: mixHNNN, - hsl: mixHNNN, - hsv: mixHNNN, - lch: mixNNHN, - [DEFAULT]: mixN4, - } + __dispatch1, + {}, + { + hcy: mixHNNN, + hsi: mixHNNN, + hsl: mixHNNN, + hsv: mixHNNN, + lch: mixNNHN, + [DEFAULT]: mixN4, + } ); diff --git a/packages/color/src/oklab/oklab-rgb.ts b/packages/color/src/oklab/oklab-rgb.ts index dcdda79243..337b581b9c 100644 --- a/packages/color/src/oklab/oklab-rgb.ts +++ b/packages/color/src/oklab/oklab-rgb.ts @@ -2,21 +2,21 @@ import type { ColorOp } from "../api.js"; import { __mulV33 } from "../internal/matrix-ops.js"; const LMS_CONE = [ - 4.0767245293, -1.2681437731, -0.0041119885, -3.3072168827, 2.6093323231, - -0.7034763098, 0.2307590544, -0.341134429, 1.7068625689, + 4.0767245293, -1.2681437731, -0.0041119885, -3.3072168827, 2.6093323231, + -0.7034763098, 0.2307590544, -0.341134429, 1.7068625689, ]; /** * @remarks * Reference: https://bottosson.github.io/posts/oklab/ * - * @param out - - * @param src - + * @param out - + * @param src - */ export const oklabRgb: ColorOp = (out, { 0: l, 1: a, 2: b, 3: alpha }) => - __mulV33(out, LMS_CONE, [ - (l + 0.3963377774 * a + 0.2158037573 * b) ** 3, - (l - 0.1055613458 * a - 0.0638541728 * b) ** 3, - (l - 0.0894841775 * a - 1.291485548 * b) ** 3, - alpha, - ]); + __mulV33(out, LMS_CONE, [ + (l + 0.3963377774 * a + 0.2158037573 * b) ** 3, + (l - 0.1055613458 * a - 0.0638541728 * b) ** 3, + (l - 0.0894841775 * a - 1.291485548 * b) ** 3, + alpha, + ]); diff --git a/packages/color/src/oklab/oklab-xyz.ts b/packages/color/src/oklab/oklab-xyz.ts index 2eaec5330c..b30ba59e11 100644 --- a/packages/color/src/oklab/oklab-xyz.ts +++ b/packages/color/src/oklab/oklab-xyz.ts @@ -4,17 +4,17 @@ import { __mulV33 } from "../internal/matrix-ops.js"; // inverted version of OKLAB_M1 const M1I = [ - 1.2270138511035211, -0.04058017842328059, -0.0763812845057069, - -0.5577999806518222, 1.1122568696168302, -0.4214819784180127, - 0.28125614896646783, -0.07167667866560119, 1.586163220440795, + 1.2270138511035211, -0.04058017842328059, -0.0763812845057069, + -0.5577999806518222, 1.1122568696168302, -0.4214819784180127, + 0.28125614896646783, -0.07167667866560119, 1.586163220440795, ]; // inverted version of OKLAB_M2 const M2I = [ - 0.9999999984505198, 1.0000000088817607, 1.0000000546724108, - 0.3963377921737678, -0.10556134232365633, -0.08948418209496575, - 0.21580375806075877, -0.06385417477170588, -1.2914855378640917, + 0.9999999984505198, 1.0000000088817607, 1.0000000546724108, + 0.3963377921737678, -0.10556134232365633, -0.08948418209496575, + 0.21580375806075877, -0.06385417477170588, -1.2914855378640917, ]; export const oklabXyzD65: ColorOp = (out, src) => - __mulV33(null, M1I, powN3(null, __mulV33(out, M2I, src), 3)); + __mulV33(null, M1I, powN3(null, __mulV33(out, M2I, src), 3)); diff --git a/packages/color/src/oklab/oklab.ts b/packages/color/src/oklab/oklab.ts index f27a2ee7d4..764bc62931 100644 --- a/packages/color/src/oklab/oklab.ts +++ b/packages/color/src/oklab/oklab.ts @@ -10,27 +10,27 @@ import { xyzXyzD50_65 } from "../xyz/xyz-xyz.js"; import { oklabRgb } from "./oklab-rgb.js"; export declare class Oklab implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - l: number; - a: number; - b: number; - alpha: number; - [id: number]: number; - readonly mode: "oklab"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): Oklab; - copyView(): Oklab; - deref(): Color; - empty(): Oklab; - eqDelta(o: Oklab, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + l: number; + a: number; + b: number; + alpha: number; + [id: number]: number; + readonly mode: "oklab"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): Oklab; + copyView(): Oklab; + deref(): Color; + empty(): Oklab; + eqDelta(o: Oklab, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } /** @@ -40,19 +40,19 @@ export declare class Oklab implements TypedColor { * Reference: https://bottosson.github.io/posts/oklab/ */ export const oklab = >defColor({ - mode: "oklab", - channels: { - a: { range: [-0.2339, 0.2763] }, - b: { range: [-0.3116, 0.1985] }, - }, - order: ["l", "a", "b", "alpha"], - from: { - lab50: [labXyz, xyzXyzD50_65, xyzOklab], - lab65: [labXyzD65, xyzOklab], - lch: [lchLab, labXyzD65, xyzOklab], - rgb: rgbOklab, - xyz50: [xyzXyzD50_65, xyzOklab], - xyz65: xyzOklab, - }, - toRgb: oklabRgb, + mode: "oklab", + channels: { + a: { range: [-0.2339, 0.2763] }, + b: { range: [-0.3116, 0.1985] }, + }, + order: ["l", "a", "b", "alpha"], + from: { + lab50: [labXyz, xyzXyzD50_65, xyzOklab], + lab65: [labXyzD65, xyzOklab], + lch: [lchLab, labXyzD65, xyzOklab], + rgb: rgbOklab, + xyz50: [xyzXyzD50_65, xyzOklab], + xyz65: xyzOklab, + }, + toRgb: oklabRgb, }); diff --git a/packages/color/src/rgb/hue-rgb.ts b/packages/color/src/rgb/hue-rgb.ts index 1b97751633..2fcfec1648 100644 --- a/packages/color/src/rgb/hue-rgb.ts +++ b/packages/color/src/rgb/hue-rgb.ts @@ -11,15 +11,15 @@ import type { Color, Hue } from "../api.js"; * @param hue - normalized hue */ export const hueRgb = (out: Color | null, hue: number, alpha = 1): Color => { - hue = fract(hue) * 6; - return setC4( - out || [], - clamp01(Math.abs(hue - 3) - 1), - clamp01(2 - Math.abs(hue - 2)), - clamp01(2 - Math.abs(hue - 4)), - alpha - ); + hue = fract(hue) * 6; + return setC4( + out || [], + clamp01(Math.abs(hue - 3) - 1), + clamp01(2 - Math.abs(hue - 2)), + clamp01(2 - Math.abs(hue - 4)), + alpha + ); }; export const namedHueRgb = (out: Color | null, hue: Hue, alpha = 1) => - hueRgb(out, hue / 12, alpha); + hueRgb(out, hue / 12, alpha); diff --git a/packages/color/src/rgb/kelvin-rgba.ts b/packages/color/src/rgb/kelvin-rgba.ts index 339c43f98e..118f7388a5 100644 --- a/packages/color/src/rgb/kelvin-rgba.ts +++ b/packages/color/src/rgb/kelvin-rgba.ts @@ -27,27 +27,29 @@ const B3 = 0.453646839257496; * @param alpha - target alpha channel */ export const kelvinRgb = (out: SRGB | null, kelvin: number, alpha = 1) => { - kelvin *= 0.01; - let t: number; - return kelvin < 66 - ? ( - setC4( - out || srgb(), - 1, - clamp01(G1 + G2 * (t = kelvin - 2) + G3 * Math.log(t)), - kelvin < 20 - ? 0 - : clamp01(B1 + B2 * (t = kelvin - 10) + B3 * Math.log(t)), - alpha - ) - ) - : ( - setC4( - out || srgb(), - clamp01(R1 + R2 * (t = kelvin - 55) + R3 * Math.log(t)), - clamp01(G4 + G5 * (t = kelvin - 50) - G6 * Math.log(t)), - 1, - alpha - ) - ); + kelvin *= 0.01; + let t: number; + return kelvin < 66 + ? ( + setC4( + out || srgb(), + 1, + clamp01(G1 + G2 * (t = kelvin - 2) + G3 * Math.log(t)), + kelvin < 20 + ? 0 + : clamp01( + B1 + B2 * (t = kelvin - 10) + B3 * Math.log(t) + ), + alpha + ) + ) + : ( + setC4( + out || srgb(), + clamp01(R1 + R2 * (t = kelvin - 55) + R3 * Math.log(t)), + clamp01(G4 + G5 * (t = kelvin - 50) - G6 * Math.log(t)), + 1, + alpha + ) + ); }; diff --git a/packages/color/src/rgb/rgb-hcv.ts b/packages/color/src/rgb/rgb-hcv.ts index aea07bf53a..41b4d80fac 100644 --- a/packages/color/src/rgb/rgb-hcv.ts +++ b/packages/color/src/rgb/rgb-hcv.ts @@ -12,18 +12,18 @@ import { clamp } from "../clamp.js"; * @param src - source color */ export const rgbHcv: ColorOp = (out, src) => { - out = clamp(out || src, src); - const p = - out[1] < out[2] - ? [out[2], out[1], -1, 2 / 3] - : [out[1], out[2], 0, -1 / 3]; - const q = - out[0] < p[0] ? [p[0], p[1], p[3], out[0]] : [out[0], p[1], p[2], p[0]]; - const c = q[0] - Math.min(q[1], q[3]); - return setC3( - out, - clamp01(Math.abs((q[3] - q[1]) / (6 * c + EPS) + q[2])), - clamp01(c), - clamp01(q[0]) - ); + out = clamp(out || src, src); + const p = + out[1] < out[2] + ? [out[2], out[1], -1, 2 / 3] + : [out[1], out[2], 0, -1 / 3]; + const q = + out[0] < p[0] ? [p[0], p[1], p[3], out[0]] : [out[0], p[1], p[2], p[0]]; + const c = q[0] - Math.min(q[1], q[3]); + return setC3( + out, + clamp01(Math.abs((q[3] - q[1]) / (6 * c + EPS) + q[2])), + clamp01(c), + clamp01(q[0]) + ); }; diff --git a/packages/color/src/rgb/rgb-hcy.ts b/packages/color/src/rgb/rgb-hcy.ts index 9aa4461772..8247603568 100644 --- a/packages/color/src/rgb/rgb-hcy.ts +++ b/packages/color/src/rgb/rgb-hcy.ts @@ -12,10 +12,10 @@ import { rgbHcv } from "./rgb-hcv.js"; * @param src - source color */ export const rgbHcy: ColorOp = (out, src) => { - const y = luminanceRgb(src); - out = rgbHcv(out, src); - const z = luminanceRgb(hueRgb([], out[0])); - out[1] *= y < z ? z / (y + EPS) : (1 - z) / (1 + EPS - y); - out[2] = y; - return out; + const y = luminanceRgb(src); + out = rgbHcv(out, src); + const z = luminanceRgb(hueRgb([], out[0])); + out[1] *= y < z ? z / (y + EPS) : (1 - z) / (1 + EPS - y); + out[2] = y; + return out; }; diff --git a/packages/color/src/rgb/rgb-hsi.ts b/packages/color/src/rgb/rgb-hsi.ts index dd1fe93f64..e26e5fefed 100644 --- a/packages/color/src/rgb/rgb-hsi.ts +++ b/packages/color/src/rgb/rgb-hsi.ts @@ -9,15 +9,15 @@ import { clamp } from "../clamp.js"; const SQRT32 = SQRT3 / 2; export const rgbHsi: ColorOp = (out, src) => { - out = clamp(out || src, src); - const { 0: r, 1: g, 2: b } = out; - const i = THIRD * (r + g + b); - return i < 1e-6 || (r === g && r === b) - ? setC3(out, 0, 0, i) - : setC3( - out, - atan2Abs(SQRT32 * (g - b), 0.5 * (2 * r - g - b)) / TAU, - 1 - Math.min(r, g, b) / i, - i - ); + out = clamp(out || src, src); + const { 0: r, 1: g, 2: b } = out; + const i = THIRD * (r + g + b); + return i < 1e-6 || (r === g && r === b) + ? setC3(out, 0, 0, i) + : setC3( + out, + atan2Abs(SQRT32 * (g - b), 0.5 * (2 * r - g - b)) / TAU, + 1 - Math.min(r, g, b) / i, + i + ); }; diff --git a/packages/color/src/rgb/rgb-hsl.ts b/packages/color/src/rgb/rgb-hsl.ts index b6ab33df86..daa1aad655 100644 --- a/packages/color/src/rgb/rgb-hsl.ts +++ b/packages/color/src/rgb/rgb-hsl.ts @@ -3,8 +3,8 @@ import type { ColorOp } from "../api.js"; import { rgbHcv } from "./rgb-hcv.js"; export const rgbHsl: ColorOp = (out, src) => { - out = rgbHcv(out, src); - out[2] -= out[1] * 0.5; - out[1] /= 1 + EPS - Math.abs(out[2] * 2 - 1); - return out; + out = rgbHcv(out, src); + out[2] -= out[1] * 0.5; + out[1] /= 1 + EPS - Math.abs(out[2] * 2 - 1); + return out; }; diff --git a/packages/color/src/rgb/rgb-hsv.ts b/packages/color/src/rgb/rgb-hsv.ts index d129438859..d10937397a 100644 --- a/packages/color/src/rgb/rgb-hsv.ts +++ b/packages/color/src/rgb/rgb-hsv.ts @@ -3,7 +3,7 @@ import type { ColorOp } from "../api.js"; import { rgbHcv } from "./rgb-hcv.js"; export const rgbHsv: ColorOp = (out, src) => { - out = rgbHcv(out, src); - out[1] /= out[2] + EPS; - return out; + out = rgbHcv(out, src); + out[1] /= out[2] + EPS; + return out; }; diff --git a/packages/color/src/rgb/rgb-lab.ts b/packages/color/src/rgb/rgb-lab.ts index 829a1741c6..b8ab4efea7 100644 --- a/packages/color/src/rgb/rgb-lab.ts +++ b/packages/color/src/rgb/rgb-lab.ts @@ -9,16 +9,16 @@ import { xyzLab, xyzLabD65 } from "../xyz/xyz-lab.js"; * Important: We're using a normalized Lab space w/ all three coordinates * divided by 100 (normalized to 100% luminance). * - * @param out - - * @param src - + * @param out - + * @param src - */ export const rgbLab: ColorOp = (out, src) => xyzLab(null, rgbXyz(out, src)); /** * Same as {@link rgbLab}, but using {@link D65} white point. * - * @param out - - * @param src - + * @param out - + * @param src - */ export const rgbLabD65: ColorOp = (out, src) => - xyzLabD65(null, rgbXyzD65(out, src)); + xyzLabD65(null, rgbXyzD65(out, src)); diff --git a/packages/color/src/rgb/rgb-oklab.ts b/packages/color/src/rgb/rgb-oklab.ts index 9954b2dd2f..6d05f7e339 100644 --- a/packages/color/src/rgb/rgb-oklab.ts +++ b/packages/color/src/rgb/rgb-oklab.ts @@ -4,16 +4,16 @@ import { OKLAB_M2 } from "../api/constants.js"; import { __mulV33 } from "../internal/matrix-ops.js"; const CONE_LMS = [ - 0.412165612, 0.211859107, 0.0883097947, 0.536275208, 0.6807189584, - 0.2818474174, 0.0514575653, 0.107406579, 0.6302613616, + 0.412165612, 0.211859107, 0.0883097947, 0.536275208, 0.6807189584, + 0.2818474174, 0.0514575653, 0.107406579, 0.6302613616, ]; /** * @remarks * Reference: https://bottosson.github.io/posts/oklab/ * - * @param out - - * @param src - + * @param out - + * @param src - */ export const rgbOklab: ColorOp = (out, src) => - __mulV33(null, OKLAB_M2, powN3(null, __mulV33(out, CONE_LMS, src), 1 / 3)); + __mulV33(null, OKLAB_M2, powN3(null, __mulV33(out, CONE_LMS, src), 1 / 3)); diff --git a/packages/color/src/rgb/rgb-srgb.ts b/packages/color/src/rgb/rgb-srgb.ts index 7b2f174e44..1fea5258aa 100644 --- a/packages/color/src/rgb/rgb-srgb.ts +++ b/packages/color/src/rgb/rgb-srgb.ts @@ -10,21 +10,21 @@ import { linearSrgb } from "../linear.js"; * @param src - source color */ export const rgbSrgb: ColorOp = (out, src) => - setC4( - out || src, - linearSrgb(src[0]), - linearSrgb(src[1]), - linearSrgb(src[2]), - __ensureAlpha(src[3]) - ); + setC4( + out || src, + linearSrgb(src[0]), + linearSrgb(src[1]), + linearSrgb(src[2]), + __ensureAlpha(src[3]) + ); const GAMMA = 1 / 2.2; export const rgbSrgbApprox: ColorOp = (out, src) => - setC4( - out || src, - src[0] ** GAMMA, - src[1] ** GAMMA, - src[2] ** GAMMA, - __ensureAlpha(src[3]) - ); + setC4( + out || src, + src[0] ** GAMMA, + src[1] ** GAMMA, + src[2] ** GAMMA, + __ensureAlpha(src[3]) + ); diff --git a/packages/color/src/rgb/rgb-xyz.ts b/packages/color/src/rgb/rgb-xyz.ts index 0ef33926fa..7515961f58 100644 --- a/packages/color/src/rgb/rgb-xyz.ts +++ b/packages/color/src/rgb/rgb-xyz.ts @@ -14,16 +14,16 @@ import { __mulV33 } from "../internal/matrix-ops.js"; * @param mat - transform matrix */ export const rgbXyz = ( - out: Color | null, - src: ReadonlyColor, - mat = RGB_XYZ_D50 + out: Color | null, + src: ReadonlyColor, + mat = RGB_XYZ_D50 ) => __mulV33(null, mat, clamp(out, src)); /** * Same as {@link rgbXyz}, but hard coded to use {@link D65} white point (via * {@link RGB_XYZ_D65} matrix). * - * @param out - - * @param src - + * @param out - + * @param src - */ export const rgbXyzD65: ColorOp = (out, src) => rgbXyz(out, src, RGB_XYZ_D65); diff --git a/packages/color/src/rgb/rgb-ycc.ts b/packages/color/src/rgb/rgb-ycc.ts index 5d6c3cf3b5..0c52caf7e9 100644 --- a/packages/color/src/rgb/rgb-ycc.ts +++ b/packages/color/src/rgb/rgb-ycc.ts @@ -14,21 +14,21 @@ import { __ensureAlpha } from "../internal/ensure.js"; * - https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.709_conversion * - https://en.wikipedia.org/wiki/Rec._709 * - * @param out - - * @param src - - * @param luma - + * @param out - + * @param src - + * @param luma - */ export const rgbYcc = ( - out: Color | null, - src: ReadonlyColor, - luma = RGB_LUMINANCE_REC709 + out: Color | null, + src: ReadonlyColor, + luma = RGB_LUMINANCE_REC709 ) => { - const y = dot3(src, luma); - return setC4( - out || src, - y, - (0.5 * (src[2] - y)) / (1 - luma[2]), - (0.5 * (src[0] - y)) / (1 - luma[0]), - __ensureAlpha(src[3]) - ); + const y = dot3(src, luma); + return setC4( + out || src, + y, + (0.5 * (src[2] - y)) / (1 - luma[2]), + (0.5 * (src[0] - y)) / (1 - luma[0]), + __ensureAlpha(src[3]) + ); }; diff --git a/packages/color/src/rgb/rgb.ts b/packages/color/src/rgb/rgb.ts index 6eef43bc45..606d0d52a5 100644 --- a/packages/color/src/rgb/rgb.ts +++ b/packages/color/src/rgb/rgb.ts @@ -17,49 +17,49 @@ import { xyzRgb, xyzRgbD65 } from "../xyz/xyz-rgb.js"; import { yccRgb } from "../ycc/ycc-rgb.js"; export declare class RGB implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - r: number; - g: number; - b: number; - alpha: number; - [id: number]: number; - readonly mode: "rgb"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): RGB; - copyView(): RGB; - deref(): Color; - empty(): RGB; - eqDelta(o: RGB, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + r: number; + g: number; + b: number; + alpha: number; + [id: number]: number; + readonly mode: "rgb"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): RGB; + copyView(): RGB; + deref(): Color; + empty(): RGB; + eqDelta(o: RGB, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const rgb = >defColor({ - mode: "rgb", - order: ["r", "g", "b", "alpha"], - from: { - abgr32: (out, src) => intAbgr32Rgb(out, src[0]), - argb32: (out, src) => intArgb32Rgb(out, src[0]), - hcy: hcyRgb, - hsi: hsiRgb, - hsl: hslRgb, - hsv: hsvRgb, - lab50: labRgb, - lab65: labRgbD65, - lch: [lchLab, labRgb], - oklab: oklabRgb, - rgb: set4, - srgb: srgbRgb, - xyy: [xyyXyz, xyzRgbD65], - xyz50: xyzRgb, - xyz65: xyzRgbD65, - ycc: yccRgb, - }, - toRgb: set4, + mode: "rgb", + order: ["r", "g", "b", "alpha"], + from: { + abgr32: (out, src) => intAbgr32Rgb(out, src[0]), + argb32: (out, src) => intArgb32Rgb(out, src[0]), + hcy: hcyRgb, + hsi: hsiRgb, + hsl: hslRgb, + hsv: hsvRgb, + lab50: labRgb, + lab65: labRgbD65, + lch: [lchLab, labRgb], + oklab: oklabRgb, + rgb: set4, + srgb: srgbRgb, + xyy: [xyyXyz, xyzRgbD65], + xyz50: xyzRgb, + xyz65: xyzRgbD65, + ycc: yccRgb, + }, + toRgb: set4, }); diff --git a/packages/color/src/rotate.ts b/packages/color/src/rotate.ts index 540e6ef51d..0d6a51c620 100644 --- a/packages/color/src/rotate.ts +++ b/packages/color/src/rotate.ts @@ -6,27 +6,27 @@ import { __dispatch1 } from "./internal/dispatch.js"; import { __ensureAlpha } from "./internal/ensure.js"; export const rotate = defmulti, number, Color>( - __dispatch1, - { hsv: "hsl", hsi: "hsl", hcy: "hsl" }, - { - hsl: (out, src, theta) => - setC4( - out || src, - fract(src[0] + theta), - src[1], - src[2], - __ensureAlpha(src[3]) - ), - lch: (out, src, theta) => - setC4( - out || src, - src[0], - src[1], - fract(src[2] + theta), - __ensureAlpha(src[3]) - ), - } + __dispatch1, + { hsv: "hsl", hsi: "hsl", hcy: "hsl" }, + { + hsl: (out, src, theta) => + setC4( + out || src, + fract(src[0] + theta), + src[1], + src[2], + __ensureAlpha(src[3]) + ), + lch: (out, src, theta) => + setC4( + out || src, + src[0], + src[1], + fract(src[2] + theta), + __ensureAlpha(src[3]) + ), + } ); export const complementary = (out: Color | null, src: TypedColor) => - rotate(out, src, 0.5); + rotate(out, src, 0.5); diff --git a/packages/color/src/sort.ts b/packages/color/src/sort.ts index 2761f227e9..5dcc2f1abf 100644 --- a/packages/color/src/sort.ts +++ b/packages/color/src/sort.ts @@ -16,13 +16,13 @@ export const selectChannel = (id: number) => (col: ReadonlyColor) => col[id]; * function for {@link sort} to compute the distance metric of a color to the * given `target`. * - * @param target - - * @param dist - + * @param target - + * @param dist - */ export const proximity = - (target: ReadonlyColor, dist: ColorDistance = distEucledian3) => - (col: ReadonlyColor) => - dist(target, col); + (target: ReadonlyColor, dist: ColorDistance = distEucledian3) => + (col: ReadonlyColor) => + dist(target, col); /** * Similar to {@link proximity}, but intended as syntax sugar for {@link ABGR} @@ -30,18 +30,18 @@ export const proximity = * function (default: {@link distEucledian3}) and will be in sRGB space. Hence * given `target` color should be provided in the same space too. * - * @param target - - * @param dist - + * @param target - + * @param dist - */ export const proximityABGR32 = - (target: ReadonlyColor, dist: ColorDistance = distEucledian3) => - (col: ReadonlyColor) => - dist(target, intAbgr32Srgb([], col[0])); + (target: ReadonlyColor, dist: ColorDistance = distEucledian3) => + (col: ReadonlyColor) => + dist(target, intAbgr32Srgb([], col[0])); export const sort = ( - colors: ReadonlyColor[], - key: Fn, - isReverse = false + colors: ReadonlyColor[], + key: Fn, + isReverse = false ) => sortByCachedKey(colors, key, isReverse ? compareNumDesc : compareNumAsc); /** @@ -78,23 +78,23 @@ export const sort = ( * // ] * ``` * - * @param colors - - * @param key - - * @param isReverse - + * @param colors - + * @param key - + * @param isReverse - */ export const sortMapped = >( - colors: T[], - key: Fn, - isReverse = false + colors: T[], + key: Fn, + isReverse = false ) => { - if (!colors.length) return colors; - const keys = colors.map(key); - const tmp = typedArray(typedArrayType(colors[0].buf), colors[0].length); - quickSort(keys, isReverse ? compareNumDesc : compareNumAsc, (_, x, y) => { - swap(keys, x, y); - tmp.set(colors[x]); - colors[x].set(colors[y]); - colors[y].set(tmp); - }); - return colors; + if (!colors.length) return colors; + const keys = colors.map(key); + const tmp = typedArray(typedArrayType(colors[0].buf), colors[0].length); + quickSort(keys, isReverse ? compareNumDesc : compareNumAsc, (_, x, y) => { + swap(keys, x, y); + tmp.set(colors[x]); + colors[x].set(colors[y]); + colors[y].set(tmp); + }); + return colors; }; diff --git a/packages/color/src/srgb/srgb-css.ts b/packages/color/src/srgb/srgb-css.ts index 76e1d49542..6ce5a23760 100644 --- a/packages/color/src/srgb/srgb-css.ts +++ b/packages/color/src/srgb/srgb-css.ts @@ -5,12 +5,12 @@ import { __ensureAlpha } from "../internal/ensure.js"; import { __scale8bit } from "../internal/scale.js"; export const srgbCss = (src: ReadonlyColor) => { - const r = __scale8bit(src[0]); - const g = __scale8bit(src[1]); - const b = __scale8bit(src[2]); - const a = __ensureAlpha(src[3]); - // TODO update to `rgb(${r} ${g} ${b}/${FF(a)})` (CSS L4 syntax) - return a < 1 - ? `rgba(${r},${g},${b},${FF(a)})` - : `#${U24((r << 16) | (g << 8) | b)}`; + const r = __scale8bit(src[0]); + const g = __scale8bit(src[1]); + const b = __scale8bit(src[2]); + const a = __ensureAlpha(src[3]); + // TODO update to `rgb(${r} ${g} ${b}/${FF(a)})` (CSS L4 syntax) + return a < 1 + ? `rgba(${r},${g},${b},${FF(a)})` + : `#${U24((r << 16) | (g << 8) | b)}`; }; diff --git a/packages/color/src/srgb/srgb-int.ts b/packages/color/src/srgb/srgb-int.ts index e24213cd2c..a5390c2859 100644 --- a/packages/color/src/srgb/srgb-int.ts +++ b/packages/color/src/srgb/srgb-int.ts @@ -3,15 +3,15 @@ import type { ReadonlyColor } from "../api.js"; import { __ensureAlpha } from "../internal/ensure.js"; export const srgbIntArgb32 = (src: ReadonlyColor) => - (((__ensureAlpha(src[3]) * 0xff + 0.5) << 24) | - ((clamp01(src[0]) * 0xff + 0.5) << 16) | - ((clamp01(src[1]) * 0xff + 0.5) << 8) | - (clamp01(src[2]) * 0xff + 0.5)) >>> - 0; + (((__ensureAlpha(src[3]) * 0xff + 0.5) << 24) | + ((clamp01(src[0]) * 0xff + 0.5) << 16) | + ((clamp01(src[1]) * 0xff + 0.5) << 8) | + (clamp01(src[2]) * 0xff + 0.5)) >>> + 0; export const srgbIntAbgr32 = (src: ReadonlyColor) => - (((__ensureAlpha(src[3]) * 0xff + 0.5) << 24) | - ((clamp01(src[2]) * 0xff + 0.5) << 16) | - ((clamp01(src[1]) * 0xff + 0.5) << 8) | - (clamp01(src[0]) * 0xff + 0.5)) >>> - 0; + (((__ensureAlpha(src[3]) * 0xff + 0.5) << 24) | + ((clamp01(src[2]) * 0xff + 0.5) << 16) | + ((clamp01(src[1]) * 0xff + 0.5) << 8) | + (clamp01(src[0]) * 0xff + 0.5)) >>> + 0; diff --git a/packages/color/src/srgb/srgb-rgb.ts b/packages/color/src/srgb/srgb-rgb.ts index d83546ce44..460ad48a0b 100644 --- a/packages/color/src/srgb/srgb-rgb.ts +++ b/packages/color/src/srgb/srgb-rgb.ts @@ -10,21 +10,21 @@ import { srgbLinear } from "../linear.js"; * @param src - source color */ export const srgbRgb: ColorOp = (out, src) => - setC4( - out || src, - srgbLinear(src[0]), - srgbLinear(src[1]), - srgbLinear(src[2]), - __ensureAlpha(src[3]) - ); + setC4( + out || src, + srgbLinear(src[0]), + srgbLinear(src[1]), + srgbLinear(src[2]), + __ensureAlpha(src[3]) + ); const GAMMA = 2.2; export const srgbRgbApprox: ColorOp = (out, src) => - setC4( - out || src, - src[0] ** GAMMA, - src[1] ** GAMMA, - src[2] ** GAMMA, - __ensureAlpha(src[3]) - ); + setC4( + out || src, + src[0] ** GAMMA, + src[1] ** GAMMA, + src[2] ** GAMMA, + __ensureAlpha(src[3]) + ); diff --git a/packages/color/src/srgb/srgb.ts b/packages/color/src/srgb/srgb.ts index c0a9d5ee1d..d9e5c97596 100644 --- a/packages/color/src/srgb/srgb.ts +++ b/packages/color/src/srgb/srgb.ts @@ -11,40 +11,40 @@ import { rgbSrgb } from "../rgb/rgb-srgb.js"; import { srgbRgb } from "./srgb-rgb.js"; export declare class SRGB implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - r: number; - g: number; - b: number; - alpha: number; - [id: number]: number; - readonly mode: "srgb"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): SRGB; - copyView(): SRGB; - deref(): Color; - empty(): SRGB; - eqDelta(o: SRGB, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + r: number; + g: number; + b: number; + alpha: number; + [id: number]: number; + readonly mode: "srgb"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): SRGB; + copyView(): SRGB; + deref(): Color; + empty(): SRGB; + eqDelta(o: SRGB, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const srgb = >defColor({ - mode: "srgb", - order: ["r", "g", "b", "alpha"], - from: { - abgr32: (out, src) => intAbgr32Srgb(out, src[0]), - argb32: (out, src) => intArgb32Srgb(out, src[0]), - hcy: hcyRgb, - hsi: hsiRgb, - hsl: hslRgb, - hsv: hsvRgb, - rgb: rgbSrgb, - }, - toRgb: srgbRgb, + mode: "srgb", + order: ["r", "g", "b", "alpha"], + from: { + abgr32: (out, src) => intAbgr32Srgb(out, src[0]), + argb32: (out, src) => intArgb32Srgb(out, src[0]), + hcy: hcyRgb, + hsi: hsiRgb, + hsl: hslRgb, + hsv: hsvRgb, + rgb: rgbSrgb, + }, + toRgb: srgbRgb, }); diff --git a/packages/color/src/strategies.ts b/packages/color/src/strategies.ts index 449ca00e51..da35decb91 100644 --- a/packages/color/src/strategies.ts +++ b/packages/color/src/strategies.ts @@ -3,23 +3,23 @@ import { lch, LCH } from "./lch/lch.js"; import { rotate } from "./rotate.js"; const $ = (src: LCH, l = 0, c = 0) => { - src.l = clamp01(src.l + l); - src.c = clamp(src.c + c, 0, 1.312); - return src; + src.l = clamp01(src.l + l); + src.c = clamp(src.c + c, 0, 1.312); + return src; }; /** * Returns array of `src` color and its complementary color, possibly adjusted * via optional `deltaL` and `deltaC` args (offsets for L & C channels). * - * @param src - - * @param deltaL - - * @param deltaC - + * @param src - + * @param deltaL - + * @param deltaC - */ export const complementaryStrategy = ( - src: LCH, - deltaL?: number, - deltaC?: number + src: LCH, + deltaL?: number, + deltaC?: number ) => [src, $(rotate(lch(), src, 0.5), deltaL, deltaC)]; /** @@ -27,20 +27,20 @@ export const complementaryStrategy = ( * normalized `theta` and possibly adjusted via optional `deltaL` and `deltaC` * args (offsets for L & C channels). * - * @param src - - * @param theta - - * @param deltaL - - * @param deltaC - + * @param src - + * @param theta - + * @param deltaL - + * @param deltaC - */ export const analogStrategy = ( - src: LCH, - theta = 1 / 12, - deltaL?: number, - deltaC?: number + src: LCH, + theta = 1 / 12, + deltaL?: number, + deltaC?: number ) => [ - src, - $(rotate(lch(), src, -theta), deltaL, deltaC), - $(rotate(lch(), src, theta), deltaL, deltaC), + src, + $(rotate(lch(), src, -theta), deltaL, deltaC), + $(rotate(lch(), src, theta), deltaL, deltaC), ]; /** @@ -49,19 +49,19 @@ export const analogStrategy = ( * normalized `theta` and possibly adjusted via optional `deltaL` and `deltaC` * args (offsets for L & C channels). * - * @param src - - * @param theta - - * @param deltaL - - * @param deltaC - + * @param src - + * @param theta - + * @param deltaL - + * @param deltaC - */ export const splitAnalogStrategy = ( - src: LCH, - theta = 1 / 12, - deltaL?: number, - deltaC?: number + src: LCH, + theta = 1 / 12, + deltaL?: number, + deltaC?: number ) => [ - ...splitComplementaryStrategy(src, theta, deltaL, deltaC), - $(rotate(lch(), src, theta), deltaL, deltaC), + ...splitComplementaryStrategy(src, theta, deltaL, deltaC), + $(rotate(lch(), src, theta), deltaL, deltaC), ]; /** @@ -70,20 +70,20 @@ export const splitAnalogStrategy = ( * (from the complementary hue) and possibly adjusted via optional `deltaL` and * `deltaC` args (offsets for L & C channels). * - * @param src - - * @param theta - - * @param deltaL - - * @param deltaC - + * @param src - + * @param theta - + * @param deltaL - + * @param deltaC - */ export const splitComplementaryStrategy = ( - src: LCH, - theta = 1 / 12, - deltaL?: number, - deltaC?: number + src: LCH, + theta = 1 / 12, + deltaL?: number, + deltaC?: number ) => [ - src, - $(rotate(lch(), src, 0.5 - theta), deltaL, deltaC), - $(rotate(lch(), src, 0.5 + theta), deltaL, deltaC), + src, + $(rotate(lch(), src, 0.5 - theta), deltaL, deltaC), + $(rotate(lch(), src, 0.5 + theta), deltaL, deltaC), ]; /** @@ -91,19 +91,19 @@ export const splitComplementaryStrategy = ( * color, but with these luminance settings: 0.0, 0.25, 0.5, 0.75, 1.0. Chroma * can be adjusted via optional `deltaC` offset. * - * @param src - - * @param deltaC - + * @param src - + * @param deltaC - */ export const monochromeStrategy = (src: LCH, deltaC = 0) => { - let [_, c, h, a] = src; - c = clamp(c + deltaC, 0, 1.312); - return [ - lch(0.0, c, h, a), - lch(0.25, c, h, a), - lch(0.5, c, h, a), - lch(0.75, c, h, a), - lch(1, c, h, a), - ]; + let [_, c, h, a] = src; + c = clamp(c + deltaC, 0, 1.312); + return [ + lch(0.0, c, h, a), + lch(0.25, c, h, a), + lch(0.5, c, h, a), + lch(0.75, c, h, a), + lch(1, c, h, a), + ]; }; /** @@ -112,12 +112,12 @@ export const monochromeStrategy = (src: LCH, deltaC = 0) => { * (possibly adjusted via optional `deltaL` and `deltaC` args, aka offsets for L * & C channels). * - * @param src - - * @param deltaL - - * @param deltaC - + * @param src - + * @param deltaL - + * @param deltaC - */ export const triadicStrategy = (src: LCH, deltaL?: number, deltaC?: number) => - splitComplementaryStrategy(src, 1 / 6, deltaL, deltaC); + splitComplementaryStrategy(src, 1 / 6, deltaL, deltaC); /** * Returns array of `src` color and 3 other colors whose hues form a rectangle, @@ -126,21 +126,21 @@ export const triadicStrategy = (src: LCH, deltaL?: number, deltaC?: number) => * possibly adjusted via optional `deltaL` and `deltaC` args (offsets for L & C * channels). * - * @param src - - * @param theta - - * @param deltaL - - * @param deltaC - + * @param src - + * @param theta - + * @param deltaL - + * @param deltaC - */ export const tetradicStrategy = ( - src: LCH, - theta = 1 / 12, - deltaL?: number, - deltaC?: number + src: LCH, + theta = 1 / 12, + deltaL?: number, + deltaC?: number ) => [ - src, - $(rotate(lch(), src, theta), deltaL, deltaC), - $(rotate(lch(), src, 0.5), deltaL, deltaC), - $(rotate(lch(), src, 0.5 + theta), deltaL, deltaC), + src, + $(rotate(lch(), src, theta), deltaL, deltaC), + $(rotate(lch(), src, 0.5), deltaL, deltaC), + $(rotate(lch(), src, 0.5 + theta), deltaL, deltaC), ]; /** @@ -149,9 +149,9 @@ export const tetradicStrategy = ( * adjusted via optional `deltaL` and `deltaC` args, aka offsets for L & C * channels). * - * @param src - - * @param deltaL - - * @param deltaC - + * @param src - + * @param deltaL - + * @param deltaC - */ export const squareStrategy = (src: LCH, deltaL?: number, deltaC?: number) => - tetradicStrategy(src, 0.25, deltaL, deltaC); + tetradicStrategy(src, 0.25, deltaL, deltaC); diff --git a/packages/color/src/swatches.ts b/packages/color/src/swatches.ts index 75a2a2b521..f9a1e3df46 100644 --- a/packages/color/src/swatches.ts +++ b/packages/color/src/swatches.ts @@ -2,57 +2,57 @@ import type { Fn2, IObjectOf } from "@thi.ng/api"; import type { ReadonlyColor } from "./api.js"; export const swatches = ( - cols: (ReadonlyColor | string)[], - shapeFn: Fn2, - attribs: IObjectOf = {} + cols: (ReadonlyColor | string)[], + shapeFn: Fn2, + attribs: IObjectOf = {} ) => ["g", attribs, ...cols.map(shapeFn)]; export const swatchesH = ( - cols: (ReadonlyColor | string)[], - w = 5, - h = 50, - gap = 0, - attribs?: IObjectOf + cols: (ReadonlyColor | string)[], + w = 5, + h = 50, + gap = 0, + attribs?: IObjectOf ) => - swatches( - cols, - (fill, i) => ["rect", { fill }, [i * (w + gap), 0], w, h], - attribs - ); + swatches( + cols, + (fill, i) => ["rect", { fill }, [i * (w + gap), 0], w, h], + attribs + ); export const dotsH = ( - cols: (ReadonlyColor | string)[], - r = 25, - gap = 0, - attribs?: IObjectOf + cols: (ReadonlyColor | string)[], + r = 25, + gap = 0, + attribs?: IObjectOf ) => - swatches( - cols, - (fill, i) => ["circle", { fill }, [i * (r * 2 + gap), 0], r], - attribs - ); + swatches( + cols, + (fill, i) => ["circle", { fill }, [i * (r * 2 + gap), 0], r], + attribs + ); export const swatchesV = ( - cols: (ReadonlyColor | string)[], - w = 50, - h = 5, - gap = 0, - attribs: IObjectOf = {} + cols: (ReadonlyColor | string)[], + w = 50, + h = 5, + gap = 0, + attribs: IObjectOf = {} ) => - swatches( - cols, - (fill, i) => ["rect", { fill }, [0, i * (h + gap)], w, h], - attribs - ); + swatches( + cols, + (fill, i) => ["rect", { fill }, [0, i * (h + gap)], w, h], + attribs + ); export const dotsV = ( - cols: (ReadonlyColor | string)[], - r = 25, - gap = 0, - attribs?: IObjectOf + cols: (ReadonlyColor | string)[], + r = 25, + gap = 0, + attribs?: IObjectOf ) => - swatches( - cols, - (fill, i) => ["circle", { fill }, [0, i * (r * 2 + gap)], r], - attribs - ); + swatches( + cols, + (fill, i) => ["circle", { fill }, [0, i * (r * 2 + gap)], r], + attribs + ); diff --git a/packages/color/src/tint.ts b/packages/color/src/tint.ts index 6f84d6d32e..3a0eb492ce 100644 --- a/packages/color/src/tint.ts +++ b/packages/color/src/tint.ts @@ -7,36 +7,36 @@ import { __dispatch1 } from "./internal/dispatch.js"; import { __ensureAlpha } from "./internal/ensure.js"; export const tint: MultiFn3O< - Color | null, - TypedColor, - number, - number, - Color + Color | null, + TypedColor, + number, + number, + Color > = defmulti, number, number | undefined, Color>( - __dispatch1, - { hcy: "hsv", hsi: "hsv", hsl: "hsv" }, - { - hsv: (out, src, n, l = 1) => - setC4( - out || src, - src[0], - src[1] * (1 - n), - mix(src[2], l, n), - __ensureAlpha(src[3]) - ), - lch: (out, src, n, l = 1) => - setC4( - out || src, - mix(src[0], l, n), - src[1] * (1 - n), - src[2], - __ensureAlpha(src[3]) - ), - } + __dispatch1, + { hcy: "hsv", hsi: "hsv", hsl: "hsv" }, + { + hsv: (out, src, n, l = 1) => + setC4( + out || src, + src[0], + src[1] * (1 - n), + mix(src[2], l, n), + __ensureAlpha(src[3]) + ), + lch: (out, src, n, l = 1) => + setC4( + out || src, + mix(src[0], l, n), + src[1] * (1 - n), + src[2], + __ensureAlpha(src[3]) + ), + } ); export const tone = (out: Color | null, src: TypedColor, n: number) => - tint(out, src, n, 0.5); + tint(out, src, n, 0.5); export const shade = (out: Color | null, src: TypedColor, n: number) => - tint(out, src, n, 0); + tint(out, src, n, 0); diff --git a/packages/color/src/transform.ts b/packages/color/src/transform.ts index 19bf423bc6..864e2af1da 100644 --- a/packages/color/src/transform.ts +++ b/packages/color/src/transform.ts @@ -45,7 +45,7 @@ export const transform = __mulV45; * @param xs - other matrices */ export const concat = (mat: ColorMatrix, ...xs: ColorMatrix[]) => - xs.reduce(__mulM45, mat); + xs.reduce(__mulM45, mat); // prettier-ignore export const IDENTITY: ColorMatrix = [ diff --git a/packages/color/src/variations.ts b/packages/color/src/variations.ts index 9ee639ec20..d4a4eb8305 100644 --- a/packages/color/src/variations.ts +++ b/packages/color/src/variations.ts @@ -6,41 +6,41 @@ import { analog } from "./analog.js"; import type { TypedColor } from "./api.js"; export interface VariationOpts { - /** - * Max. number of color variations to produce. - * - * @defaultValue Infinity - */ - num: number; - /** - * Weights (given in same order as colors) for randomly selecting colors. By - * default colors will be chosen with uniform distribution. - */ - weights: number[]; - /** - * Normalized tolerance value for randomizing channel values - * - * @defaultValue 0.05 - */ - delta: number; - /** - * PRNG instance to use. - * - * @defaultValue SYSTEM (aka `Math.random()`) - */ - rnd: IRandom; + /** + * Max. number of color variations to produce. + * + * @defaultValue Infinity + */ + num: number; + /** + * Weights (given in same order as colors) for randomly selecting colors. By + * default colors will be chosen with uniform distribution. + */ + weights: number[]; + /** + * Normalized tolerance value for randomizing channel values + * + * @defaultValue 0.05 + */ + delta: number; + /** + * PRNG instance to use. + * + * @defaultValue SYSTEM (aka `Math.random()`) + */ + rnd: IRandom; } export function* variations>( - src: T[], - opts?: Partial + src: T[], + opts?: Partial ) { - opts = { rnd: SYSTEM, delta: 0.05, num: Infinity, ...opts }; - const pick = opts.weights - ? weightedRandom(src, opts.weights, opts.rnd) - : () => pickRandom(src, opts!.rnd); - for (let i = opts.num!; i-- > 0; ) { - const col = pick(); - yield analog(col.empty(), col, opts.delta!, opts.rnd); - } + opts = { rnd: SYSTEM, delta: 0.05, num: Infinity, ...opts }; + const pick = opts.weights + ? weightedRandom(src, opts.weights, opts.rnd) + : () => pickRandom(src, opts!.rnd); + for (let i = opts.num!; i-- > 0; ) { + const col = pick(); + yield analog(col.empty(), col, opts.delta!, opts.rnd); + } } diff --git a/packages/color/src/xyy/xyy-xyz.ts b/packages/color/src/xyy/xyy-xyz.ts index e115875236..bfcf839f2b 100644 --- a/packages/color/src/xyy/xyy-xyz.ts +++ b/packages/color/src/xyy/xyy-xyz.ts @@ -8,16 +8,16 @@ import { __ensureAlpha } from "../internal/ensure.js"; * https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space * https://gamedev.stackexchange.com/a/70049 * - * @param out - - * @param src - + * @param out - + * @param src - */ export const xyyXyz: ColorOp = (out, src) => { - const { 0: x, 1: y, 2: Y } = src; - return setC4( - out || src, - safeDiv(Y * x, y), - Y, - safeDiv(Y * (1 - x - y), y), - __ensureAlpha(src[3]) - ); + const { 0: x, 1: y, 2: Y } = src; + return setC4( + out || src, + safeDiv(Y * x, y), + Y, + safeDiv(Y * (1 - x - y), y), + __ensureAlpha(src[3]) + ); }; diff --git a/packages/color/src/xyy/xyy.ts b/packages/color/src/xyy/xyy.ts index 2cb9a15711..29bf7ed3b4 100644 --- a/packages/color/src/xyy/xyy.ts +++ b/packages/color/src/xyy/xyy.ts @@ -8,40 +8,40 @@ import { xyzXyy } from "../xyz/xyz-xyy.js"; import { xyyXyz } from "./xyy-xyz.js"; export declare class XYY implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - x: number; - y: number; - Y: number; - alpha: number; - [id: number]: number; - readonly mode: "xyy"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): XYY; - copyView(): XYY; - deref(): Color; - empty(): XYY; - eqDelta(o: XYY, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + x: number; + y: number; + Y: number; + alpha: number; + [id: number]: number; + readonly mode: "xyy"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): XYY; + copyView(): XYY; + deref(): Color; + empty(): XYY; + eqDelta(o: XYY, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const xyy = >defColor({ - mode: "xyy", - channels: { - x: { range: [0, 0.6484] }, - y: { range: [0, 0.5979] }, - }, - order: ["x", "y", "Y", "alpha"], - from: { - rgb: (out, src) => xyzXyy(null, rgbXyzD65(out, src)), - xyz50: xyzXyy, - xyz65: xyzXyy, - }, - toRgb: [xyyXyz, xyzRgbD65], + mode: "xyy", + channels: { + x: { range: [0, 0.6484] }, + y: { range: [0, 0.5979] }, + }, + order: ["x", "y", "Y", "alpha"], + from: { + rgb: (out, src) => xyzXyy(null, rgbXyzD65(out, src)), + xyz50: xyzXyy, + xyz65: xyzXyy, + }, + toRgb: [xyyXyz, xyzRgbD65], }); diff --git a/packages/color/src/xyz/wavelength-xyz.ts b/packages/color/src/xyz/wavelength-xyz.ts index 6d2864d1b7..0b6ad1b43f 100644 --- a/packages/color/src/xyz/wavelength-xyz.ts +++ b/packages/color/src/xyz/wavelength-xyz.ts @@ -17,32 +17,32 @@ import { xyzD65, XYZD65 } from "./xyz65.js"; * - 570 => yellow * - 610 => red * - * @param out - - * @param lambda - - * @param alpha - + * @param out - + * @param lambda - + * @param alpha - */ export const wavelengthXyz = ( - out: XYZD65 | null, - lambda: number, - alpha = 1 + out: XYZD65 | null, + lambda: number, + alpha = 1 ) => { - lambda *= 10; - return ( - setC4( - out || xyzD65(), - gaussian(lambda, 1.056, 5998, 379, 310) + - gaussian(lambda, 0.362, 4420, 160, 267) + - gaussian(lambda, -0.065, 5011, 204, 262), - gaussian(lambda, 0.821, 5688, 469, 405) + - gaussian(lambda, 0.286, 5309, 163, 311), - gaussian(lambda, 1.217, 4370, 118, 360) + - gaussian(lambda, 0.681, 4590, 260, 138), - alpha - ) - ); + lambda *= 10; + return ( + setC4( + out || xyzD65(), + gaussian(lambda, 1.056, 5998, 379, 310) + + gaussian(lambda, 0.362, 4420, 160, 267) + + gaussian(lambda, -0.065, 5011, 204, 262), + gaussian(lambda, 0.821, 5688, 469, 405) + + gaussian(lambda, 0.286, 5309, 163, 311), + gaussian(lambda, 1.217, 4370, 118, 360) + + gaussian(lambda, 0.681, 4590, 260, 138), + alpha + ) + ); }; const gaussian: FnU5 = (x, alpha, m, s1, s2) => { - const t = (x - m) / (x < m ? s1 : s2); - return alpha * Math.exp(-(t * t) / 2); + const t = (x - m) / (x < m ? s1 : s2); + return alpha * Math.exp(-(t * t) / 2); }; diff --git a/packages/color/src/xyz/xyz-lab.ts b/packages/color/src/xyz/xyz-lab.ts index 7dd440e848..93eb537a7d 100644 --- a/packages/color/src/xyz/xyz-lab.ts +++ b/packages/color/src/xyz/xyz-lab.ts @@ -5,7 +5,7 @@ import { __ensureAlpha } from "../internal/ensure.js"; /** @internal */ const transform = (x: number) => - x > 0.00885645 ? Math.cbrt(x) : 7.787037 * x + 16 / 116; + x > 0.00885645 ? Math.cbrt(x) : 7.787037 * x + 16 / 116; /** * Converts XYZ to Lab, using provided reference white point (default: @@ -15,27 +15,27 @@ const transform = (x: number) => * Important: We're using a normalized Lab space w/ all three coordinates * divided by 100 (normalized to 100% luminance). * - * @param out - - * @param src - - * @param white - + * @param out - + * @param src - + * @param white - */ export const xyzLab = (out: Color | null, src: ReadonlyColor, white = D50) => { - const x = transform(src[0] / white[0]); - const y = transform(src[1] / white[1]); - const z = transform(src[2] / white[2]); - return setC4( - out || src, - 1.16 * y - 0.16, - 5.0 * (x - y), - 2.0 * (y - z), - __ensureAlpha(src[3]) - ); + const x = transform(src[0] / white[0]); + const y = transform(src[1] / white[1]); + const z = transform(src[2] / white[2]); + return setC4( + out || src, + 1.16 * y - 0.16, + 5.0 * (x - y), + 2.0 * (y - z), + __ensureAlpha(src[3]) + ); }; /** * Same as {@link xyzLab}, but hard coded to use {@link D65} white point. * - * @param out - - * @param src - + * @param out - + * @param src - */ export const xyzLabD65: ColorOp = (out, src) => xyzLab(out, src, D65); diff --git a/packages/color/src/xyz/xyz-oklab.ts b/packages/color/src/xyz/xyz-oklab.ts index a106e80e20..6627fe504c 100644 --- a/packages/color/src/xyz/xyz-oklab.ts +++ b/packages/color/src/xyz/xyz-oklab.ts @@ -4,4 +4,4 @@ import { OKLAB_M1, OKLAB_M2 } from "../api/constants.js"; import { __mulV33 } from "../internal/matrix-ops.js"; export const xyzOklab: ColorOp = (out, src) => - __mulV33(null, OKLAB_M2, powN3(null, __mulV33(out, OKLAB_M1, src), 1 / 3)); + __mulV33(null, OKLAB_M2, powN3(null, __mulV33(out, OKLAB_M1, src), 1 / 3)); diff --git a/packages/color/src/xyz/xyz-rgb.ts b/packages/color/src/xyz/xyz-rgb.ts index 6acff63646..2b03d4b1e6 100644 --- a/packages/color/src/xyz/xyz-rgb.ts +++ b/packages/color/src/xyz/xyz-rgb.ts @@ -12,16 +12,16 @@ import { __mulV33 } from "../internal/matrix-ops.js"; * @param src - source color */ export const xyzRgb = ( - out: Color | null, - src: ReadonlyColor, - mat = XYZ_RGB_D50 + out: Color | null, + src: ReadonlyColor, + mat = XYZ_RGB_D50 ) => __mulV33(out, mat, src); /** * Same as {@link xyzRgb}, but hard coded to use {@link D65} white point (via * {@link XYZ_RGB_D65} matrix). * - * @param out - - * @param src - + * @param out - + * @param src - */ export const xyzRgbD65: ColorOp = (out, src) => xyzRgb(out, src, XYZ_RGB_D65); diff --git a/packages/color/src/xyz/xyz-xyy.ts b/packages/color/src/xyz/xyz-xyy.ts index 98bccc72ab..f8b04cf22f 100644 --- a/packages/color/src/xyz/xyz-xyy.ts +++ b/packages/color/src/xyz/xyz-xyy.ts @@ -8,17 +8,17 @@ import { __ensureAlpha } from "../internal/ensure.js"; * https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space * https://gamedev.stackexchange.com/a/70049 * - * @param out - - * @param src - + * @param out - + * @param src - */ export const xyzXyy: ColorOp = (out, src) => { - const { 0: x, 1: Y } = src; - const sum = x + Y + src[2]; - return setC4( - out || src, - safeDiv(x, sum), - safeDiv(Y, sum), - Y, - __ensureAlpha(src[3]) - ); + const { 0: x, 1: Y } = src; + const sum = x + Y + src[2]; + return setC4( + out || src, + safeDiv(x, sum), + safeDiv(Y, sum), + Y, + __ensureAlpha(src[3]) + ); }; diff --git a/packages/color/src/xyz/xyz-xyz.ts b/packages/color/src/xyz/xyz-xyz.ts index 88700bde51..b70e3912cd 100644 --- a/packages/color/src/xyz/xyz-xyz.ts +++ b/packages/color/src/xyz/xyz-xyz.ts @@ -3,7 +3,7 @@ import { BRADFORD_D50_D65, BRADFORD_D65_D50 } from "../api/constants.js"; import { __mulV33 } from "../internal/matrix-ops.js"; export const xyzXyzD65_50: ColorOp = (out, src) => - __mulV33(out, BRADFORD_D65_D50, src); + __mulV33(out, BRADFORD_D65_D50, src); export const xyzXyzD50_65: ColorOp = (out, src) => - __mulV33(out, BRADFORD_D50_D65, src); + __mulV33(out, BRADFORD_D50_D65, src); diff --git a/packages/color/src/xyz/xyz50.ts b/packages/color/src/xyz/xyz50.ts index d20824d3b7..12ccfa61a1 100644 --- a/packages/color/src/xyz/xyz50.ts +++ b/packages/color/src/xyz/xyz50.ts @@ -12,44 +12,44 @@ import { xyzRgb } from "./xyz-rgb.js"; import { xyzXyzD65_50 } from "./xyz-xyz.js"; export declare class XYZD50 implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - x: number; - y: number; - z: number; - alpha: number; - [id: number]: number; - readonly mode: "xyz50"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): XYZD50; - copyView(): XYZD50; - deref(): Color; - empty(): XYZD50; - eqDelta(o: XYZD50, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + x: number; + y: number; + z: number; + alpha: number; + [id: number]: number; + readonly mode: "xyz50"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): XYZD50; + copyView(): XYZD50; + deref(): Color; + empty(): XYZD50; + eqDelta(o: XYZD50, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const xyzD50 = >defColor({ - mode: "xyz50", - channels: { - x: { range: [0, D50[0]] }, - y: { range: [0, D50[1]] }, - z: { range: [0, D50[2]] }, - }, - order: ["x", "y", "z", "alpha"], - from: { - rgb: rgbXyz, - lab50: labXyz, - lab65: [labXyzD65, xyzXyzD65_50], - lch: [lchLab, labXyz], - oklab: [oklabXyzD65, xyzXyzD65_50], - xyy: xyyXyz, - }, - toRgb: xyzRgb, + mode: "xyz50", + channels: { + x: { range: [0, D50[0]] }, + y: { range: [0, D50[1]] }, + z: { range: [0, D50[2]] }, + }, + order: ["x", "y", "z", "alpha"], + from: { + rgb: rgbXyz, + lab50: labXyz, + lab65: [labXyzD65, xyzXyzD65_50], + lch: [lchLab, labXyz], + oklab: [oklabXyzD65, xyzXyzD65_50], + xyy: xyyXyz, + }, + toRgb: xyzRgb, }); diff --git a/packages/color/src/xyz/xyz65.ts b/packages/color/src/xyz/xyz65.ts index 3048b6c95c..1f275de9e7 100644 --- a/packages/color/src/xyz/xyz65.ts +++ b/packages/color/src/xyz/xyz65.ts @@ -12,44 +12,44 @@ import { xyyXyz } from "../xyy/xyy-xyz.js"; import { xyzRgbD65 } from "./xyz-rgb.js"; export declare class XYZD65 implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - x: number; - y: number; - z: number; - alpha: number; - [id: number]: number; - readonly mode: "xyz65"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): XYZD65; - copyView(): XYZD65; - deref(): Color; - empty(): XYZD65; - eqDelta(o: XYZD65, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + x: number; + y: number; + z: number; + alpha: number; + [id: number]: number; + readonly mode: "xyz65"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): XYZD65; + copyView(): XYZD65; + deref(): Color; + empty(): XYZD65; + eqDelta(o: XYZD65, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const xyzD65 = >defColor({ - mode: "xyz65", - channels: { - x: { range: [0, D65[0]] }, - y: { range: [0, D65[1]] }, - z: { range: [0, D65[2]] }, - }, - order: ["x", "y", "z", "alpha"], - from: { - rgb: rgbXyzD65, - lab50: [labLabD50_65, labXyzD65], - lab65: labXyzD65, - lch: [lchLab, labLabD50_65, labXyzD65], - oklab: oklabXyzD65, - xyy: xyyXyz, - }, - toRgb: xyzRgbD65, + mode: "xyz65", + channels: { + x: { range: [0, D65[0]] }, + y: { range: [0, D65[1]] }, + z: { range: [0, D65[2]] }, + }, + order: ["x", "y", "z", "alpha"], + from: { + rgb: rgbXyzD65, + lab50: [labLabD50_65, labXyzD65], + lab65: labXyzD65, + lch: [lchLab, labLabD50_65, labXyzD65], + oklab: oklabXyzD65, + xyy: xyyXyz, + }, + toRgb: xyzRgbD65, }); diff --git a/packages/color/src/ycc/ycc-rgb.ts b/packages/color/src/ycc/ycc-rgb.ts index b8c4fff658..979b48c4f9 100644 --- a/packages/color/src/ycc/ycc-rgb.ts +++ b/packages/color/src/ycc/ycc-rgb.ts @@ -13,23 +13,23 @@ import { __ensureAlpha } from "../internal/ensure.js"; * - https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.709_conversion * - https://en.wikipedia.org/wiki/Rec._709 * - * @param out - - * @param src - - * @param luma - + * @param out - + * @param src - + * @param luma - */ export const yccRgb = ( - out: Color | null, - src: ReadonlyColor, - luma = RGB_LUMINANCE_REC709 + out: Color | null, + src: ReadonlyColor, + luma = RGB_LUMINANCE_REC709 ) => { - const y = src[0]; - const bb = (2 - 2 * luma[2]) * src[1]; - const rr = (2 - 2 * luma[0]) * src[2]; - return setC4( - out || src, - y + rr, - y - (luma[2] / luma[1]) * bb - (luma[0] / luma[1]) * rr, - y + bb, - __ensureAlpha(src[3]) - ); + const y = src[0]; + const bb = (2 - 2 * luma[2]) * src[1]; + const rr = (2 - 2 * luma[0]) * src[2]; + return setC4( + out || src, + y + rr, + y - (luma[2] / luma[1]) * bb - (luma[0] / luma[1]) * rr, + y + bb, + __ensureAlpha(src[3]) + ); }; diff --git a/packages/color/src/ycc/ycc.ts b/packages/color/src/ycc/ycc.ts index cacdd452e9..67d1de0349 100644 --- a/packages/color/src/ycc/ycc.ts +++ b/packages/color/src/ycc/ycc.ts @@ -6,36 +6,36 @@ import { rgbYcc } from "../rgb/rgb-ycc.js"; import { yccRgb } from "./ycc-rgb.js"; export declare class YCC implements TypedColor { - buf: NumericArray; - offset: number; - stride: number; - y: number; - cb: number; - cr: number; - alpha: number; - [id: number]: number; - readonly mode: "ycc"; - readonly length: 4; - readonly range: [ReadonlyColor, ReadonlyColor]; - [Symbol.iterator](): Iterator; - clamp(): this; - copy(): YCC; - copyView(): YCC; - deref(): Color; - empty(): YCC; - eqDelta(o: YCC, eps?: number): boolean; - randomize(rnd?: IRandom): this; - set(src: ReadonlyColor): this; - toJSON(): number[]; + buf: NumericArray; + offset: number; + stride: number; + y: number; + cb: number; + cr: number; + alpha: number; + [id: number]: number; + readonly mode: "ycc"; + readonly length: 4; + readonly range: [ReadonlyColor, ReadonlyColor]; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): YCC; + copyView(): YCC; + deref(): Color; + empty(): YCC; + eqDelta(o: YCC, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; } export const ycc = >defColor({ - mode: "ycc", - channels: { - cb: { range: [-0.5, 0.5] }, - cr: { range: [-0.5, 0.5] }, - }, - order: ["y", "cb", "cr", "alpha"], - from: { rgb: rgbYcc }, - toRgb: yccRgb, + mode: "ycc", + channels: { + cb: { range: [-0.5, 0.5] }, + cr: { range: [-0.5, 0.5] }, + }, + order: ["y", "cb", "cr", "alpha"], + from: { rgb: rgbYcc }, + toRgb: yccRgb, }); diff --git a/packages/color/test/index.ts b/packages/color/test/index.ts index 17bfc8da91..e618812b36 100644 --- a/packages/color/test/index.ts +++ b/packages/color/test/index.ts @@ -2,115 +2,115 @@ import { XsAdd } from "@thi.ng/random"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { - abgr32, - argb32, - Color, - ColorMode, - css, - hsl, - labD50, - parseCss, - rgb, - srgb, + abgr32, + argb32, + Color, + ColorMode, + css, + hsl, + labD50, + parseCss, + rgb, + srgb, } from "../src/index.js"; group("color", { - srgb: () => { - const res = srgb(0xaa / 0xff, 0xbb / 0xff, 0xcc / 0xff); - assert.ok(srgb("#abc").eqDelta(res), "#abc"); - assert.ok(srgb(0xffaabbcc).eqDelta(res), "0xaabbcc"); - assert.ok( - srgb([0xaa / 0xff, 0xbb / 0xff, 0xcc / 0xff, 1]).eqDelta(res), - "array" - ); - assert.ok(srgb(hsl("hsl(60,100%,50%)")).eqDelta(srgb(1, 1, 0)), "hsl"); - }, + srgb: () => { + const res = srgb(0xaa / 0xff, 0xbb / 0xff, 0xcc / 0xff); + assert.ok(srgb("#abc").eqDelta(res), "#abc"); + assert.ok(srgb(0xffaabbcc).eqDelta(res), "0xaabbcc"); + assert.ok( + srgb([0xaa / 0xff, 0xbb / 0xff, 0xcc / 0xff, 1]).eqDelta(res), + "array" + ); + assert.ok(srgb(hsl("hsl(60,100%,50%)")).eqDelta(srgb(1, 1, 0)), "hsl"); + }, - abgr32: () => { - const buf = abgr32.mapBuffer([0x11223344, -1, 0x55667788], 2, 0, 1, 2); - assert.deepStrictEqual( - [...srgb(buf[0])], - [0x44 / 0xff, 0x33 / 0xff, 0x22 / 0xff, 0x11 / 0xff] - ); - assert.deepStrictEqual( - [...srgb(buf[1])], - [0x88 / 0xff, 0x77 / 0xff, 0x66 / 0xff, 0x55 / 0xff] - ); - assert.strictEqual(abgr32(srgb(buf[0]))[0], 0x11223344); - assert.strictEqual(abgr32(srgb(buf[1]))[0], 0x55667788); - }, + abgr32: () => { + const buf = abgr32.mapBuffer([0x11223344, -1, 0x55667788], 2, 0, 1, 2); + assert.deepStrictEqual( + [...srgb(buf[0])], + [0x44 / 0xff, 0x33 / 0xff, 0x22 / 0xff, 0x11 / 0xff] + ); + assert.deepStrictEqual( + [...srgb(buf[1])], + [0x88 / 0xff, 0x77 / 0xff, 0x66 / 0xff, 0x55 / 0xff] + ); + assert.strictEqual(abgr32(srgb(buf[0]))[0], 0x11223344); + assert.strictEqual(abgr32(srgb(buf[1]))[0], 0x55667788); + }, - argb32: () => { - const buf = argb32.mapBuffer([0x11223344, -1, 0x55667788], 2, 0, 1, 2); - assert.deepStrictEqual( - [...srgb(buf[0])], - [0x22 / 0xff, 0x33 / 0xff, 0x44 / 0xff, 0x11 / 0xff] - ); - assert.deepStrictEqual( - [...srgb(buf[1])], - [0x66 / 0xff, 0x77 / 0xff, 0x88 / 0xff, 0x55 / 0xff] - ); - assert.strictEqual(argb32(srgb(buf[0]))[0], 0x11223344); - assert.strictEqual(argb32(srgb(buf[1]))[0], 0x55667788); - }, + argb32: () => { + const buf = argb32.mapBuffer([0x11223344, -1, 0x55667788], 2, 0, 1, 2); + assert.deepStrictEqual( + [...srgb(buf[0])], + [0x22 / 0xff, 0x33 / 0xff, 0x44 / 0xff, 0x11 / 0xff] + ); + assert.deepStrictEqual( + [...srgb(buf[1])], + [0x66 / 0xff, 0x77 / 0xff, 0x88 / 0xff, 0x55 / 0xff] + ); + assert.strictEqual(argb32(srgb(buf[0]))[0], 0x11223344); + assert.strictEqual(argb32(srgb(buf[1]))[0], 0x55667788); + }, - "css()": () => { - assert.strictEqual(css("#abc"), "#abc"); - assert.strictEqual(css(0xffbbccdd), "#bbccdd"); - assert.strictEqual(css(0xaabbccdd), "rgba(187,204,221,0.667)"); - // srgb - assert.strictEqual(css([0.2, 0.4, 0.6]), "#336699"); - assert.strictEqual(css([0.2, 0.4, 0.6, 0.8]), "rgba(51,102,153,0.800)"); - // linear - assert.strictEqual(css(rgb(0.2, 0.4, 0.6)), "#7caacb"); - assert.strictEqual( - css(rgb([0.2, 0.4, 0.6, 0.8])), - "rgba(124,170,203,0.800)" - ); - // css -> srgb -> rgb -> srgb -> css - assert.strictEqual(css(rgb("#abc")), "#aabbcc"); - assert.strictEqual(css(hsl("#ff0")), "hsl(60.000,100.000%,50.000%)"); - assert.strictEqual(css(hsl("#990")), "hsl(60.000,100.000%,30.000%)"); - }, + "css()": () => { + assert.strictEqual(css("#abc"), "#abc"); + assert.strictEqual(css(0xffbbccdd), "#bbccdd"); + assert.strictEqual(css(0xaabbccdd), "rgba(187,204,221,0.667)"); + // srgb + assert.strictEqual(css([0.2, 0.4, 0.6]), "#336699"); + assert.strictEqual(css([0.2, 0.4, 0.6, 0.8]), "rgba(51,102,153,0.800)"); + // linear + assert.strictEqual(css(rgb(0.2, 0.4, 0.6)), "#7caacb"); + assert.strictEqual( + css(rgb([0.2, 0.4, 0.6, 0.8])), + "rgba(124,170,203,0.800)" + ); + // css -> srgb -> rgb -> srgb -> css + assert.strictEqual(css(rgb("#abc")), "#aabbcc"); + assert.strictEqual(css(hsl("#ff0")), "hsl(60.000,100.000%,50.000%)"); + assert.strictEqual(css(hsl("#990")), "hsl(60.000,100.000%,30.000%)"); + }, - random: () => { - assert.ok( - rgb - .random(new XsAdd(0xdecafbad)) - .eqDelta(rgb(0.274, 0.0615, 0.1377), 1e-3) - ); - assert.ok( - labD50 - .random(new XsAdd(0xdecafbad)) - .eqDelta(labD50(0.274, -0.6866, -0.8375), 1e-3) - ); - }, + random: () => { + assert.ok( + rgb + .random(new XsAdd(0xdecafbad)) + .eqDelta(rgb(0.274, 0.0615, 0.1377), 1e-3) + ); + assert.ok( + labD50 + .random(new XsAdd(0xdecafbad)) + .eqDelta(labD50(0.274, -0.6866, -0.8375), 1e-3) + ); + }, - parseCss: () => { - const check = (src: string, mode: ColorMode, val: boolean | Color) => { - if (val === true) { - assert.throws(() => parseCss(src), src); - return; - } - const res = parseCss(src); - assert.strictEqual( - res.mode, - mode, - `${src}, expected mode: ${mode}` - ); - assert.deepStrictEqual(res.deref(), val, `${src} value`); - }; + parseCss: () => { + const check = (src: string, mode: ColorMode, val: boolean | Color) => { + if (val === true) { + assert.throws(() => parseCss(src), src); + return; + } + const res = parseCss(src); + assert.strictEqual( + res.mode, + mode, + `${src}, expected mode: ${mode}` + ); + assert.deepStrictEqual(res.deref(), val, `${src} value`); + }; - const cases: [string, ColorMode, boolean | Color][] = [ - ["#c96", "srgb", [0.8, 0.6, 0.4, 1]], - ["#dc96", "srgb", [0.8, 0.6, 0.4, 0xdd / 0xff]], - ["#cc9966", "srgb", [0.8, 0.6, 0.4, 1]], - ["#aacc9966", "srgb", [0.8, 0.6, 0.4, 0xaa / 0xff]], - ["rgb(255,254,253)", "srgb", [1, 254 / 255, 253 / 255, 1]], - ["rgb(255,254,253/0.5)", "srgb", [1, 254 / 255, 253 / 255, 0.5]], - ["rgba(255,254,253,0.5)", "srgb", [1, 254 / 255, 253 / 255, 0.5]], - ["rgba(257,-254,255.5,1.5)", "srgb", [1, 0, 1, 1]], - ]; - cases.forEach((spec) => check(...spec)); - }, + const cases: [string, ColorMode, boolean | Color][] = [ + ["#c96", "srgb", [0.8, 0.6, 0.4, 1]], + ["#dc96", "srgb", [0.8, 0.6, 0.4, 0xdd / 0xff]], + ["#cc9966", "srgb", [0.8, 0.6, 0.4, 1]], + ["#aacc9966", "srgb", [0.8, 0.6, 0.4, 0xaa / 0xff]], + ["rgb(255,254,253)", "srgb", [1, 254 / 255, 253 / 255, 1]], + ["rgb(255,254,253/0.5)", "srgb", [1, 254 / 255, 253 / 255, 0.5]], + ["rgba(255,254,253,0.5)", "srgb", [1, 254 / 255, 253 / 255, 0.5]], + ["rgba(257,-254,255.5,1.5)", "srgb", [1, 0, 1, 1]], + ]; + cases.forEach((spec) => check(...spec)); + }, }); diff --git a/packages/color/tools/blackbody.ts b/packages/color/tools/blackbody.ts index 2747c80629..d3b0aa1267 100644 --- a/packages/color/tools/blackbody.ts +++ b/packages/color/tools/blackbody.ts @@ -6,16 +6,40 @@ import { map, normRange } from "@thi.ng/transducers"; import { writeFileSync } from "fs"; import { kelvinRgb, wavelengthXyz } from "../src"; -writeFileSync("export/blackbody.svg", serialize( - svg( - { width: 500, height: 50, convert: true }, - swatchesH([...map((t)=> kelvinRgb(null,mix(1000,10000,t)), normRange(100,false))], 5, 50) - ) -)); +writeFileSync( + "export/blackbody.svg", + serialize( + svg( + { width: 500, height: 50, convert: true }, + swatchesH( + [ + ...map( + (t) => kelvinRgb(null, mix(1000, 10000, t)), + normRange(100, false) + ), + ], + 5, + 50 + ) + ) + ) +); -writeFileSync("export/wavelength.svg", serialize( - svg( - { width: 500, height: 50, convert: true }, - swatchesH([...map((t)=> wavelengthXyz(null,mix(400, 700,t)), normRange(100,false))], 5, 50) - ) -)); +writeFileSync( + "export/wavelength.svg", + serialize( + svg( + { width: 500, height: 50, convert: true }, + swatchesH( + [ + ...map( + (t) => wavelengthXyz(null, mix(400, 700, t)), + normRange(100, false) + ), + ], + 5, + 50 + ) + ) + ) +); diff --git a/packages/color/tools/gradients.ts b/packages/color/tools/gradients.ts index 1d0c4535e1..20b317191f 100644 --- a/packages/color/tools/gradients.ts +++ b/packages/color/tools/gradients.ts @@ -1,10 +1,10 @@ import { - Color, - lch, - lchLab, - multiColorGradient, - oklab, - swatchesH, + Color, + lch, + lchLab, + multiColorGradient, + oklab, + swatchesH, } from "@thi.ng/color"; import { serialize } from "@thi.ng/hiccup"; import { svg } from "@thi.ng/hiccup-svg"; @@ -12,43 +12,43 @@ import { map, normRange, push, transduce } from "@thi.ng/transducers"; import { writeFileSync } from "fs"; for (let l of [0.5, 0.6, 0.7, 0.8, 0.9]) { - const cols = transduce( - map((t) => oklab(lchLab(null, [l, 0.2, t]))), - push(), - normRange(100, false) - ); + const cols = transduce( + map((t) => oklab(lchLab(null, [l, 0.2, t]))), + push(), + normRange(100, false) + ); - writeFileSync( - `export/oklab-${l.toFixed(1)}.svg`, - serialize( - svg( - { width: 500, height: 50, convert: true }, - swatchesH(cols, 5, 50) - ) - ) - ); + writeFileSync( + `export/oklab-${l.toFixed(1)}.svg`, + serialize( + svg( + { width: 500, height: 50, convert: true }, + swatchesH(cols, 5, 50) + ) + ) + ); } const L = 0.8; const C = 0.8; const gradient = multiColorGradient({ - num: 100, - stops: [ - [0, lch(L, C, 0)], - [1 / 3, lch(L, C, 1 / 3)], - [2 / 3, lch(L, C, 2 / 3)], - [1, lch(L, 0, 1)], - ], - // easing: (t) => schlick(2, 0.5, t), + num: 100, + stops: [ + [0, lch(L, C, 0)], + [1 / 3, lch(L, C, 1 / 3)], + [2 / 3, lch(L, C, 2 / 3)], + [1, lch(L, 0, 1)], + ], + // easing: (t) => schlick(2, 0.5, t), }); writeFileSync( - `export/lch-multigradient3.svg`, - serialize( - svg( - { width: 500, height: 50, convert: true }, - swatchesH(gradient, 5, 50) - ) - ) + `export/lch-multigradient3.svg`, + serialize( + svg( + { width: 500, height: 50, convert: true }, + swatchesH(gradient, 5, 50) + ) + ) ); diff --git a/packages/color/tools/index.ts b/packages/color/tools/index.ts index f144c47500..88479c1088 100644 --- a/packages/color/tools/index.ts +++ b/packages/color/tools/index.ts @@ -2,43 +2,43 @@ import { serialize } from "@thi.ng/hiccup"; import { svg } from "@thi.ng/hiccup-svg"; import { writeFileSync } from "fs"; import { - ColorRangePreset, - colorsFromRange, - colorsFromTheme, - ColorThemePartTuple, - COLOR_RANGES, - cosineGradient, - CosineGradientPreset, - COSINE_GRADIENTS, - CSSColorName, - distCIEDE2000, - lch, - proximity, - ReadonlyColor, - selectChannel, - sort, - swatchesH, + ColorRangePreset, + colorsFromRange, + colorsFromTheme, + ColorThemePartTuple, + COLOR_RANGES, + cosineGradient, + CosineGradientPreset, + COSINE_GRADIENTS, + CSSColorName, + distCIEDE2000, + lch, + proximity, + ReadonlyColor, + selectChannel, + sort, + swatchesH, } from "../src"; Object.keys(COSINE_GRADIENTS).forEach((id) => { - const fname = `export/gradient-${id}-srgb.svg`; - console.log(fname); - writeFileSync( - fname, - serialize( - svg( - { width: 500, height: 50, convert: true }, - swatchesH( - cosineGradient( - 100, - COSINE_GRADIENTS[id] - ), - 5, - 50 - ) - ) - ) - ); + const fname = `export/gradient-${id}-srgb.svg`; + console.log(fname); + writeFileSync( + fname, + serialize( + svg( + { width: 500, height: 50, convert: true }, + swatchesH( + cosineGradient( + 100, + COSINE_GRADIENTS[id] + ), + 5, + 50 + ) + ) + ) + ); }); //////////////////////////////////////////////////////////// @@ -46,114 +46,114 @@ Object.keys(COSINE_GRADIENTS).forEach((id) => { const V = 0.05; const sorted = (cols: ReadonlyColor[]) => - sort(cols, proximity(lch("#fff"), distCIEDE2000())); + sort(cols, proximity(lch("#fff"), distCIEDE2000())); const sortedRange = (id: string, base: CSSColorName, num: number) => - sorted([ - ...colorsFromRange(id, { - variance: V, - base, - num, - }), - ]); + sorted([ + ...colorsFromRange(id, { + variance: V, + base, + num, + }), + ]); for (let id in COLOR_RANGES) { - writeFileSync( - `export/swatches-range-${id}-hue.svg`, - serialize( - svg( - { width: 500, height: 50, convert: true }, - swatchesH( - sort( - [ - ...colorsFromRange(id, { - num: 100, - variance: V, - }), - ], - selectChannel(2) - ), - 5, - 50 - ) - ) - ) - ); - writeFileSync( - `export/swatches-range-${id}-chunks.svg`, - serialize( - svg( - { width: 500, height: 50, convert: true }, - swatchesH( - [ - ...sortedRange(id, "goldenrod", 22), - ...sortedRange(id, "turquoise", 22), - ...sortedRange(id, "pink", 22), - ...sortedRange(id, "black", 11), - ...sortedRange(id, "gray", 11), - ...sortedRange(id, "white", 11), - ], - 5, - 50 - ) - ) - ) - ); - writeFileSync( - `export/swatches-range-${id}-mixed.svg`, - serialize( - svg( - { width: 500, height: 50, convert: true }, - swatchesH( - [ - ...colorsFromTheme( - [ - [id, "goldenrod", 22], - [id, "turquoise", 22], - [id, "pink", 22], - [id, "black", 11], - [id, "gray", 11], - [id, "white", 11], - ], - { num: 100, variance: V } - ), - ], - 5, - 50 - ) - ) - ) - ); + writeFileSync( + `export/swatches-range-${id}-hue.svg`, + serialize( + svg( + { width: 500, height: 50, convert: true }, + swatchesH( + sort( + [ + ...colorsFromRange(id, { + num: 100, + variance: V, + }), + ], + selectChannel(2) + ), + 5, + 50 + ) + ) + ) + ); + writeFileSync( + `export/swatches-range-${id}-chunks.svg`, + serialize( + svg( + { width: 500, height: 50, convert: true }, + swatchesH( + [ + ...sortedRange(id, "goldenrod", 22), + ...sortedRange(id, "turquoise", 22), + ...sortedRange(id, "pink", 22), + ...sortedRange(id, "black", 11), + ...sortedRange(id, "gray", 11), + ...sortedRange(id, "white", 11), + ], + 5, + 50 + ) + ) + ) + ); + writeFileSync( + `export/swatches-range-${id}-mixed.svg`, + serialize( + svg( + { width: 500, height: 50, convert: true }, + swatchesH( + [ + ...colorsFromTheme( + [ + [id, "goldenrod", 22], + [id, "turquoise", 22], + [id, "pink", 22], + [id, "black", 11], + [id, "gray", 11], + [id, "white", 11], + ], + { num: 100, variance: V } + ), + ], + 5, + 50 + ) + ) + ) + ); } //////////////////////////////////////////////////////////// const theme: ColorThemePartTuple[] = [ - ["cool", "goldenrod"], - ["hard", "hotpink", 0.1], - ["fresh", "springgreen", 0.1], + ["cool", "goldenrod"], + ["hard", "hotpink", 0.1], + ["fresh", "springgreen", 0.1], ]; const colors = [...colorsFromTheme(theme, { num: 200, variance: 0.05 })]; writeFileSync( - "export/swatches-ex01.svg", - serialize( - svg( - { width: 1000, height: 50, convert: true }, - swatchesH(colors, 5, 50) - ) - ) + "export/swatches-ex01.svg", + serialize( + svg( + { width: 1000, height: 50, convert: true }, + swatchesH(colors, 5, 50) + ) + ) ); sorted(colors); writeFileSync( - "export/swatches-ex02.svg", - serialize( - svg( - { width: 1000, height: 50, convert: true }, - swatchesH(colors, 5, 50) - ) - ) + "export/swatches-ex02.svg", + serialize( + svg( + { width: 1000, height: 50, convert: true }, + swatchesH(colors, 5, 50) + ) + ) ); diff --git a/packages/color/tools/lch-slices.ts b/packages/color/tools/lch-slices.ts index 5cb1651567..4a806b2bf9 100644 --- a/packages/color/tools/lch-slices.ts +++ b/packages/color/tools/lch-slices.ts @@ -6,34 +6,34 @@ import { comp, filter, iterator, map, normRange2d } from "@thi.ng/transducers"; import { writeFileSync } from "fs"; const isValidRgb = (lch: LCH) => { - const [r, g, b] = rgb(lch); - return r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1; + const [r, g, b] = rgb(lch); + return r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1; }; const luminanceSlice = (lum: number, n = 100) => [ - ...iterator( - comp( - map(([c, h]) => lch(lum, c * 1.31, h)), - filter(isValidRgb), - map((lch) => - rect([lch[2] * 100, (lch[1] * 100) / 1.31], 100 / n, 100 / n, { - fill: lch, - }) - ) - ), - normRange2d(n, n) - ), + ...iterator( + comp( + map(([c, h]) => lch(lum, c * 1.31, h)), + filter(isValidRgb), + map((lch) => + rect([lch[2] * 100, (lch[1] * 100) / 1.31], 100 / n, 100 / n, { + fill: lch, + }) + ) + ), + normRange2d(n, n) + ), ]; const svgSlice = (i: number, lum: number, n?: number) => - writeFileSync( - `export/lslice-${Z3(i)}.svg`, - serialize( - svg( - { width: 600, height: 600, viewBox: "0 0 100 100" }, - ...luminanceSlice(lum, n) - ) - ) - ); + writeFileSync( + `export/lslice-${Z3(i)}.svg`, + serialize( + svg( + { width: 600, height: 600, viewBox: "0 0 100 100" }, + ...luminanceSlice(lum, n) + ) + ) + ); for (let i = 0; i < 100; i++) svgSlice(i, i / 100, 200); diff --git a/packages/color/tools/limits.ts b/packages/color/tools/limits.ts index f9293be833..839df9a079 100644 --- a/packages/color/tools/limits.ts +++ b/packages/color/tools/limits.ts @@ -6,33 +6,33 @@ import { ColorMode, convert } from "../src"; const FMT = vector(4, 4); const modes: ColorMode[] = [ - "hcy", - "hsi", - "hsl", - "hsv", - "lab50", - "lab65", - "lch", - "oklab", - "srgb", - "xyy", - "xyz50", - "xyz65", - "ycc", + "hcy", + "hsi", + "hsl", + "hsv", + "lab50", + "lab65", + "lch", + "oklab", + "srgb", + "xyy", + "xyz50", + "xyz65", + "ycc", ]; for (let mode of modes) { - const bounds = transduce( - map((x) => convert(null, x, mode, "rgb")), - reducer( - () => [[...MAX4], [...MIN4]], - (acc, x) => { - min4(null, acc[0], x); - max4(null, acc[1], x); - return acc; - } - ), - normRange3d(100, 100, 100) - ); - console.log(mode, FMT(bounds[0]), FMT(bounds[1])); + const bounds = transduce( + map((x) => convert(null, x, mode, "rgb")), + reducer( + () => [[...MAX4], [...MIN4]], + (acc, x) => { + min4(null, acc[0], x); + max4(null, acc[1], x); + return acc; + } + ), + normRange3d(100, 100, 100) + ); + console.log(mode, FMT(bounds[0]), FMT(bounds[1])); } diff --git a/packages/color/tools/max-chroma.ts b/packages/color/tools/max-chroma.ts index 3963a71b15..4ef5ca090d 100644 --- a/packages/color/tools/max-chroma.ts +++ b/packages/color/tools/max-chroma.ts @@ -1,32 +1,32 @@ import { peek } from "@thi.ng/arrays"; import { - comp, - filter, - map, - normRange, - partitionBy, - push, - range2d, - transduce, + comp, + filter, + map, + normRange, + partitionBy, + push, + range2d, + transduce, } from "@thi.ng/transducers"; import { isRgbGamut, lch } from "../src/index.js"; const chroma = (h: number) => - transduce( - comp( - map(([c, l]) => ({ - c, - l, - valid: isRgbGamut(lch(l, c, h)), - })), - filter((x) => x.valid), - partitionBy((x) => x.l), - map((x) => peek(x).c), - map((x) => (x * 100) | 0) - ), - push(), - range2d(0, 1.5, 0, 1.0, 0.01, 0.04) - ); + transduce( + comp( + map(([c, l]) => ({ + c, + l, + valid: isRgbGamut(lch(l, c, h)), + })), + filter((x) => x.valid), + partitionBy((x) => x.l), + map((x) => peek(x).c), + map((x) => (x * 100) | 0) + ), + push(), + range2d(0, 1.5, 0, 1.0, 0.01, 0.04) + ); // hue resolution: 48 console.log([...map(chroma, normRange(48, false))]); diff --git a/packages/color/tsconfig.json b/packages/color/tsconfig.json index 89a3682c74..42cb23eebb 100644 --- a/packages/color/tsconfig.json +++ b/packages/color/tsconfig.json @@ -1,8 +1,8 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": ".", - "preserveConstEnums": true - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "preserveConstEnums": true + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/colored-noise/api-extractor.json b/packages/colored-noise/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/colored-noise/api-extractor.json +++ b/packages/colored-noise/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/colored-noise/package.json b/packages/colored-noise/package.json index 35fd82c17d..a5a76ea110 100644 --- a/packages/colored-noise/package.json +++ b/packages/colored-noise/package.json @@ -1,111 +1,111 @@ { - "name": "@thi.ng/colored-noise", - "version": "0.3.10", - "description": "Customizable O(1) ES6 generators for colored noise", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/colored-noise#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/binary": "^3.3.0", - "@thi.ng/random": "^3.3.3" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/api": "^8.3.8", - "@thi.ng/dsp": "^4.2.6", - "@thi.ng/dsp-io-wav": "^2.1.12", - "@thi.ng/testament": "^0.2.9", - "@thi.ng/text-canvas": "^2.4.1", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/vectors": "^7.5.8", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "1d", - "blue", - "brown", - "fft", - "filter", - "generator", - "green", - "noise", - "random", - "red", - "sample", - "spectrum", - "typescript", - "white" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./blue": { - "default": "./blue.js" - }, - "./green": { - "default": "./green.js" - }, - "./pink": { - "default": "./pink.js" - }, - "./red": { - "default": "./red.js" - }, - "./violet": { - "default": "./violet.js" - }, - "./white": { - "default": "./white.js" - } - }, - "thi.ng": { - "related": [ - "dsp", - "lowdisc", - "random" - ], - "year": 2015 - } + "name": "@thi.ng/colored-noise", + "version": "0.3.10", + "description": "Customizable O(1) ES6 generators for colored noise", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/colored-noise#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/binary": "^3.3.0", + "@thi.ng/random": "^3.3.3" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/api": "^8.3.8", + "@thi.ng/dsp": "^4.2.6", + "@thi.ng/dsp-io-wav": "^2.1.12", + "@thi.ng/testament": "^0.2.9", + "@thi.ng/text-canvas": "^2.4.1", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/vectors": "^7.5.8", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "1d", + "blue", + "brown", + "fft", + "filter", + "generator", + "green", + "noise", + "random", + "red", + "sample", + "spectrum", + "typescript", + "white" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./blue": { + "default": "./blue.js" + }, + "./green": { + "default": "./green.js" + }, + "./pink": { + "default": "./pink.js" + }, + "./red": { + "default": "./red.js" + }, + "./violet": { + "default": "./violet.js" + }, + "./white": { + "default": "./white.js" + } + }, + "thi.ng": { + "related": [ + "dsp", + "lowdisc", + "random" + ], + "year": 2015 + } } diff --git a/packages/colored-noise/src/blue.ts b/packages/colored-noise/src/blue.ts index dc6fa518e7..0b886ce85a 100644 --- a/packages/colored-noise/src/blue.ts +++ b/packages/colored-noise/src/blue.ts @@ -5,19 +5,19 @@ import { preseed, sum } from "./utils.js"; /** * High-pass filtered noise. Opposite of {@link red}. * - * @param n - - * @param scale - - * @param rnd - + * @param n - + * @param scale - + * @param rnd - */ export function* blue(n = 2, scale = 1, rnd: INorm = SYSTEM) { - const state = preseed(n, scale, rnd); - state.forEach((x, i) => (state[i] = i & 1 ? x : -x)); - const invN = 1 / n; - let acc = sum(state); - for (let i = 0, sign = -1; true; ++i >= n && (i = 0)) { - acc -= state[i]; - acc += state[i] = sign * rnd.norm(scale); - sign ^= 0xfffffffe; - yield sign * acc * invN; - } + const state = preseed(n, scale, rnd); + state.forEach((x, i) => (state[i] = i & 1 ? x : -x)); + const invN = 1 / n; + let acc = sum(state); + for (let i = 0, sign = -1; true; ++i >= n && (i = 0)) { + acc -= state[i]; + acc += state[i] = sign * rnd.norm(scale); + sign ^= 0xfffffffe; + yield sign * acc * invN; + } } diff --git a/packages/colored-noise/src/green.ts b/packages/colored-noise/src/green.ts index d4af9bdb40..9b1e7bfd91 100644 --- a/packages/colored-noise/src/green.ts +++ b/packages/colored-noise/src/green.ts @@ -7,9 +7,9 @@ import { interleave } from "./utils.js"; * Band-pass filtered noise (interleaved blue noise). Opposite of * {@link violet}. * - * @param n - - * @param scale - - * @param rnd - + * @param n - + * @param scale - + * @param rnd - */ export const green = (n = 2, scale = 1, rnd: INorm = SYSTEM) => - interleave(blue(n, scale, rnd), blue(n, scale, rnd)); + interleave(blue(n, scale, rnd), blue(n, scale, rnd)); diff --git a/packages/colored-noise/src/pink.ts b/packages/colored-noise/src/pink.ts index 8ac1d34c44..0a00279b2c 100644 --- a/packages/colored-noise/src/pink.ts +++ b/packages/colored-noise/src/pink.ts @@ -15,18 +15,18 @@ import { preseed, sum } from "./utils.js"; * - https://www.dsprelated.com/showarticle/908.php * - https://www.firstpr.com.au/dsp/pink-noise/#Voss-McCartney * - * @param n - - * @param scale - - * @param rnd - + * @param n - + * @param scale - + * @param rnd - */ export function* pink(n = 8, scale = 1, rnd: INorm = SYSTEM) { - const state = preseed(n, scale, rnd); - const invN = 1 / n; - let acc = sum(state); - for (let i = 0; true; i = (i + 1) >>> 0) { - const id = ctz32(i) % n; - acc -= state[id]; - acc += state[id] = rnd.norm(scale); - yield acc * invN; - } + const state = preseed(n, scale, rnd); + const invN = 1 / n; + let acc = sum(state); + for (let i = 0; true; i = (i + 1) >>> 0) { + const id = ctz32(i) % n; + acc -= state[id]; + acc += state[id] = rnd.norm(scale); + yield acc * invN; + } } diff --git a/packages/colored-noise/src/red.ts b/packages/colored-noise/src/red.ts index 3d344075af..a3c649d136 100644 --- a/packages/colored-noise/src/red.ts +++ b/packages/colored-noise/src/red.ts @@ -5,17 +5,17 @@ import { preseed, sum } from "./utils.js"; /** * Low-pass filtered noise (same as brown noise). Opposite of {@link blue}. * - * @param n - - * @param scale - - * @param rnd - + * @param n - + * @param scale - + * @param rnd - */ export function* red(n = 2, scale = 1, rnd: INorm = SYSTEM) { - const state = preseed(n, scale, rnd); - const invN = 1 / n; - let acc = sum(state); - for (let i = 0; true; ++i >= n && (i = 0)) { - acc -= state[i]; - acc += state[i] = rnd.norm(scale); - yield acc * invN; - } + const state = preseed(n, scale, rnd); + const invN = 1 / n; + let acc = sum(state); + for (let i = 0; true; ++i >= n && (i = 0)) { + acc -= state[i]; + acc += state[i] = rnd.norm(scale); + yield acc * invN; + } } diff --git a/packages/colored-noise/src/utils.ts b/packages/colored-noise/src/utils.ts index 7d606675eb..d46637ad60 100644 --- a/packages/colored-noise/src/utils.ts +++ b/packages/colored-noise/src/utils.ts @@ -2,20 +2,20 @@ import type { INorm } from "@thi.ng/random"; export const preseed = (n: number, scale: number, rnd: INorm) => { - const state = new Array(n); - for (let i = 0; i < n; i++) { - state[i] = rnd.norm(scale); - } - return state; + const state = new Array(n); + for (let i = 0; i < n; i++) { + state[i] = rnd.norm(scale); + } + return state; }; export const sum = (src: number[]) => src.reduce((sum, x) => sum + x, 0); export function* interleave(a: Iterable, b: Iterable) { - const src = [a[Symbol.iterator](), b[Symbol.iterator]()]; - for (let i = 0; true; i ^= 1) { - const next = src[i].next(); - if (next.done) return; - yield next.value!; - } + const src = [a[Symbol.iterator](), b[Symbol.iterator]()]; + for (let i = 0; true; i ^= 1) { + const next = src[i].next(); + if (next.done) return; + yield next.value!; + } } diff --git a/packages/colored-noise/src/violet.ts b/packages/colored-noise/src/violet.ts index 7e5956855b..03b4d770a8 100644 --- a/packages/colored-noise/src/violet.ts +++ b/packages/colored-noise/src/violet.ts @@ -6,9 +6,9 @@ import { interleave } from "./utils.js"; /** * Band-stop filtered noise (interleaved red noise). Opposite of {@link green}. * - * @param n - - * @param scale - - * @param rnd - + * @param n - + * @param scale - + * @param rnd - */ export const violet = (n = 2, scale = 1, rnd: INorm = SYSTEM) => - interleave(red(n, scale, rnd), red(n, scale, rnd)); + interleave(red(n, scale, rnd), red(n, scale, rnd)); diff --git a/packages/colored-noise/src/white.ts b/packages/colored-noise/src/white.ts index 206e487261..3dd57721f2 100644 --- a/packages/colored-noise/src/white.ts +++ b/packages/colored-noise/src/white.ts @@ -5,11 +5,11 @@ import { SYSTEM } from "@thi.ng/random/system"; * Unfiltered noise w/ uniform distribution. Merely yields samples from * given PRNG. * - * @param scale - - * @param rnd - + * @param scale - + * @param rnd - */ export function* white(scale = 1, rnd: INorm = SYSTEM) { - while (true) { - yield rnd.norm(scale); - } + while (true) { + yield rnd.norm(scale); + } } diff --git a/packages/colored-noise/tools/hihat.ts b/packages/colored-noise/tools/hihat.ts index bb01ce39c7..1d1ebd9c64 100644 --- a/packages/colored-noise/tools/hihat.ts +++ b/packages/colored-noise/tools/hihat.ts @@ -6,17 +6,17 @@ import { writeFileSync } from "fs"; const FS = 44100; const signal = product( - // wrap green noise as IGen - iterable(green(16), 0), - // apply gain envelope - adsr({ a: 0.005 * FS, d: 0.2 * FS, s: 0 }) + // wrap green noise as IGen + iterable(green(16), 0), + // apply gain envelope + adsr({ a: 0.005 * FS, d: 0.2 * FS, s: 0 }) ); // output as WAV file writeFileSync( - "export/hihat.wav", - wavByteArray( - { bits: 16, channels: 1, length: 0.2 * FS, sampleRate: FS }, - signal - ) + "export/hihat.wav", + wavByteArray( + { bits: 16, channels: 1, length: 0.2 * FS, sampleRate: FS }, + signal + ) ); diff --git a/packages/colored-noise/tools/spectrum.ts b/packages/colored-noise/tools/spectrum.ts index 6c3d3f10e3..cc58e14734 100644 --- a/packages/colored-noise/tools/spectrum.ts +++ b/packages/colored-noise/tools/spectrum.ts @@ -6,34 +6,34 @@ import { add, divN, Vec, zeroes } from "@thi.ng/vectors"; import { blue, green, pink, red, violet } from "../src"; const computeSpectrum = (src: Fn0>, size = 128, num = 1000) => - divN( - null, - transduce( - map(() => spectrumPow(fft([...take(size, src())]), true)), - reducer( - () => zeroes(size / 2), - (acc, x) => add(null, acc, x) - ), - range(num) - ), - num - ); + divN( + null, + transduce( + map(() => spectrumPow(fft([...take(size, src())]), true)), + reducer( + () => zeroes(size / 2), + (acc, x) => add(null, acc, x) + ), + range(num) + ), + num + ); const spectrumString = (spec: Vec) => barChartHStr(12, spec, -72, -24); const printSpectrum = (id: string, fn: Fn0>) => { - console.log(id); - console.log(spectrumString(computeSpectrum(fn))); + console.log(id); + console.log(spectrumString(computeSpectrum(fn))); }; const printBatch = ( - id: string, - fn: Fn>, - steps = [2, 4, 8, 16, 32] + id: string, + fn: Fn>, + steps = [2, 4, 8, 16, 32] ) => { - for (let n of steps) { - printSpectrum(`${id}${n}`, () => fn(n)); - } + for (let n of steps) { + printSpectrum(`${id}${n}`, () => fn(n)); + } }; printBatch("blue", blue); diff --git a/packages/colored-noise/tools/write-samples.ts b/packages/colored-noise/tools/write-samples.ts index e7e64120b5..1a5163c327 100644 --- a/packages/colored-noise/tools/write-samples.ts +++ b/packages/colored-noise/tools/write-samples.ts @@ -7,25 +7,25 @@ const FS = 44100; const LENGTH = 5 * FS; const write = (id: string, src: Iterable) => { - const path = `export/${id}.wav`; - console.log(`writing: ${path}...`); - writeFileSync( - path, - wavByteArray( - { bits: 16, channels: 1, length: LENGTH, sampleRate: FS }, - src - ) - ); + const path = `export/${id}.wav`; + console.log(`writing: ${path}...`); + writeFileSync( + path, + wavByteArray( + { bits: 16, channels: 1, length: LENGTH, sampleRate: FS }, + src + ) + ); }; const writeBatch = ( - id: string, - fn: Fn>, - steps = [2, 4, 8, 16, 32] + id: string, + fn: Fn>, + steps = [2, 4, 8, 16, 32] ) => { - for (let n of steps) { - write(`${id}-${n}`, fn(n)); - } + for (let n of steps) { + write(`${id}-${n}`, fn(n)); + } }; writeBatch("blue", blue); diff --git a/packages/colored-noise/tsconfig.json b/packages/colored-noise/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/colored-noise/tsconfig.json +++ b/packages/colored-noise/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/compare/api-extractor.json b/packages/compare/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/compare/api-extractor.json +++ b/packages/compare/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/compare/package.json b/packages/compare/package.json index 7de4fb8191..07f0c2bb9b 100644 --- a/packages/compare/package.json +++ b/packages/compare/package.json @@ -1,79 +1,79 @@ { - "name": "@thi.ng/compare", - "version": "2.1.8", - "description": "Comparators with support for types implementing the @thi.ng/api/ICompare interface", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/compare#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "comparator", - "sort", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./compare": { - "default": "./compare.js" - }, - "./keys": { - "default": "./keys.js" - }, - "./numeric": { - "default": "./numeric.js" - }, - "./reverse": { - "default": "./reverse.js" - } - } + "name": "@thi.ng/compare", + "version": "2.1.8", + "description": "Comparators with support for types implementing the @thi.ng/api/ICompare interface", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/compare#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "comparator", + "sort", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./compare": { + "default": "./compare.js" + }, + "./keys": { + "default": "./keys.js" + }, + "./numeric": { + "default": "./numeric.js" + }, + "./reverse": { + "default": "./reverse.js" + } + } } diff --git a/packages/compare/src/compare.ts b/packages/compare/src/compare.ts index 8bbdb6b9fc..96aedfbfd7 100644 --- a/packages/compare/src/compare.ts +++ b/packages/compare/src/compare.ts @@ -1,18 +1,18 @@ export const compare = (a: any, b: any): number => { - if (a === b) { - return 0; - } - if (a == null) { - return b == null ? 0 : -1; - } - if (b == null) { - return a == null ? 0 : 1; - } - if (typeof a.compare === "function") { - return a.compare(b); - } - if (typeof b.compare === "function") { - return -b.compare(a); - } - return a < b ? -1 : a > b ? 1 : 0; + if (a === b) { + return 0; + } + if (a == null) { + return b == null ? 0 : -1; + } + if (b == null) { + return a == null ? 0 : 1; + } + if (typeof a.compare === "function") { + return a.compare(b); + } + if (typeof b.compare === "function") { + return -b.compare(a); + } + return a < b ? -1 : a > b ? 1 : 0; }; diff --git a/packages/compare/src/keys.ts b/packages/compare/src/keys.ts index 71f25c35d5..410c622afe 100644 --- a/packages/compare/src/keys.ts +++ b/packages/compare/src/keys.ts @@ -2,7 +2,7 @@ import type { Comparator, Fn, Keys, Val1 } from "@thi.ng/api"; import { compare } from "./compare.js"; const getKey = (k: string | Fn) => - typeof k === "function" ? k : (x: any) => x[k]; + typeof k === "function" ? k : (x: any) => x[k]; /** * HOF comparator. Returns new comparator to sort objects by given `key` @@ -12,19 +12,19 @@ const getKey = (k: string | Fn) => * @param cmp - */ export function compareByKey>( - a: A, - cmp?: Comparator> + a: A, + cmp?: Comparator> ): Comparator; export function compareByKey( - a: Fn, - cmp?: Comparator + a: Fn, + cmp?: Comparator ): Comparator; export function compareByKey( - a: string | Fn, - cmp: Comparator = compare + a: string | Fn, + cmp: Comparator = compare ): Comparator { - const k = getKey(a); - return (x, y) => cmp(k(x), k(y)); + const k = getKey(a); + return (x, y) => cmp(k(x), k(y)); } /** @@ -38,29 +38,29 @@ export function compareByKey( * @param cmpB - */ export function compareByKeys2, B extends Keys>( - major: A, - minor: B, - cmpA?: Comparator>, - cmpB?: Comparator> + major: A, + minor: B, + cmpA?: Comparator>, + cmpB?: Comparator> ): Comparator; export function compareByKeys2( - major: Fn, - minor: Fn, - cmpA?: Comparator, - cmpB?: Comparator + major: Fn, + minor: Fn, + cmpA?: Comparator, + cmpB?: Comparator ): Comparator; export function compareByKeys2( - a: string | Fn, - b: string | Fn, - cmpA: Comparator = compare, - cmpB: Comparator = compare + a: string | Fn, + b: string | Fn, + cmpA: Comparator = compare, + cmpB: Comparator = compare ): Comparator { - const ka = getKey(a); - const kb = getKey(b); - return (x, y) => { - let res = cmpA(ka(x), ka(y)); - return res === 0 ? cmpB(kb(x), kb(y)) : res; - }; + const ka = getKey(a); + const kb = getKey(b); + return (x, y) => { + let res = cmpA(ka(x), ka(y)); + return res === 0 ? cmpB(kb(x), kb(y)) : res; + }; } /** @@ -74,45 +74,45 @@ export function compareByKeys2( * @param cmpC - */ export function compareByKeys3< - T, - A extends Keys, - B extends Keys, - C extends Keys + T, + A extends Keys, + B extends Keys, + C extends Keys >( - major: A, - minor: B, - patch: B, - cmpA?: Comparator>, - cmpB?: Comparator>, - cmpC?: Comparator> + major: A, + minor: B, + patch: B, + cmpA?: Comparator>, + cmpB?: Comparator>, + cmpC?: Comparator> ): Comparator; export function compareByKeys3( - major: Fn, - minor: Fn, - patch: Fn, - cmpA?: Comparator, - cmpB?: Comparator, - cmpC?: Comparator + major: Fn, + minor: Fn, + patch: Fn, + cmpA?: Comparator, + cmpB?: Comparator, + cmpC?: Comparator ): Comparator; export function compareByKeys3( - a: string | Fn, - b: string | Fn, - c: string | Fn, - cmpA: Comparator = compare, - cmpB: Comparator = compare, - cmpC: Comparator = compare + a: string | Fn, + b: string | Fn, + c: string | Fn, + cmpA: Comparator = compare, + cmpB: Comparator = compare, + cmpC: Comparator = compare ): Comparator { - const ka = getKey(a); - const kb = getKey(b); - const kc = getKey(c); - return (x, y) => { - let res = cmpA(ka(x), ka(y)); - return res === 0 - ? (res = cmpB(kb(x), kb(y))) === 0 - ? cmpC(kc(x), kc(y)) - : res - : res; - }; + const ka = getKey(a); + const kb = getKey(b); + const kc = getKey(c); + return (x, y) => { + let res = cmpA(ka(x), ka(y)); + return res === 0 + ? (res = cmpB(kb(x), kb(y))) === 0 + ? cmpC(kc(x), kc(y)) + : res + : res; + }; } /** @@ -128,53 +128,53 @@ export function compareByKeys3( * @param cmpD - */ export function compareByKeys4< - T, - A extends Keys, - B extends Keys, - C extends Keys, - D extends Keys + T, + A extends Keys, + B extends Keys, + C extends Keys, + D extends Keys >( - a: A, - b: B, - c: C, - d: D, - cmpA?: Comparator>, - cmpB?: Comparator>, - cmpC?: Comparator>, - cmpD?: Comparator> + a: A, + b: B, + c: C, + d: D, + cmpA?: Comparator>, + cmpB?: Comparator>, + cmpC?: Comparator>, + cmpD?: Comparator> ): Comparator; export function compareByKeys4( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - cmpA?: Comparator, - cmpB?: Comparator, - cmpC?: Comparator, - cmpD?: Comparator + a: Fn, + b: Fn, + c: Fn, + d: Fn, + cmpA?: Comparator, + cmpB?: Comparator, + cmpC?: Comparator, + cmpD?: Comparator ): Comparator; export function compareByKeys4( - a: string | Fn, - b: string | Fn, - c: string | Fn, - d: string | Fn, - cmpA: Comparator = compare, - cmpB: Comparator = compare, - cmpC: Comparator = compare, - cmpD: Comparator = compare + a: string | Fn, + b: string | Fn, + c: string | Fn, + d: string | Fn, + cmpA: Comparator = compare, + cmpB: Comparator = compare, + cmpC: Comparator = compare, + cmpD: Comparator = compare ): Comparator { - const ka = getKey(a); - const kb = getKey(b); - const kc = getKey(c); - const kd = getKey(d); - return (x, y) => { - let res = cmpA(ka(x), ka(y)); - return res === 0 - ? (res = cmpB(kb(x), kb(y))) === 0 - ? (res = cmpC(kc(x), kc(y))) === 0 - ? cmpD(kd(x), kd(y)) - : res - : res - : res; - }; + const ka = getKey(a); + const kb = getKey(b); + const kc = getKey(c); + const kd = getKey(d); + return (x, y) => { + let res = cmpA(ka(x), ka(y)); + return res === 0 + ? (res = cmpB(kb(x), kb(y))) === 0 + ? (res = cmpC(kc(x), kc(y))) === 0 + ? cmpD(kd(x), kd(y)) + : res + : res + : res; + }; } diff --git a/packages/compare/src/reverse.ts b/packages/compare/src/reverse.ts index e933dc8ba5..c8ceeb0124 100644 --- a/packages/compare/src/reverse.ts +++ b/packages/compare/src/reverse.ts @@ -4,7 +4,9 @@ import type { Comparator } from "@thi.ng/api"; * HOF comparator. Returns new comparator with reversed order of given * comparator. * - * @param cmp - + * @param cmp - */ -export const reverse = (cmp: Comparator): Comparator => (a, b) => - -cmp(a, b); +export const reverse = + (cmp: Comparator): Comparator => + (a, b) => + -cmp(a, b); diff --git a/packages/compare/test/index.ts b/packages/compare/test/index.ts index 8ea31410bd..e561fd1fe1 100644 --- a/packages/compare/test/index.ts +++ b/packages/compare/test/index.ts @@ -1,111 +1,111 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; import { - compareByKey, - compareByKeys2, - compareByKeys3, - compareByKeys4, -} from "../src/index.js" + compareByKey, + compareByKeys2, + compareByKeys3, + compareByKeys4, +} from "../src/index.js"; group("compare", { - compareByKey: () => { - const src = [ - { a: 2, b: 2 }, - { a: 1, b: 1 }, - { a: 2, b: 1 }, - { a: 1, b: 2, c: 3 }, - ]; - const res = [ - { a: 1, b: 1 }, - { a: 1, b: 2, c: 3 }, - { a: 2, b: 2 }, - { a: 2, b: 1 }, - ]; - assert.deepStrictEqual([...src].sort(compareByKey("a")), res); - assert.deepStrictEqual([...src].sort(compareByKey((x) => x.a)), res); - }, + compareByKey: () => { + const src = [ + { a: 2, b: 2 }, + { a: 1, b: 1 }, + { a: 2, b: 1 }, + { a: 1, b: 2, c: 3 }, + ]; + const res = [ + { a: 1, b: 1 }, + { a: 1, b: 2, c: 3 }, + { a: 2, b: 2 }, + { a: 2, b: 1 }, + ]; + assert.deepStrictEqual([...src].sort(compareByKey("a")), res); + assert.deepStrictEqual([...src].sort(compareByKey((x) => x.a)), res); + }, - compareByKeys2: () => { - const src = [ - { a: 2, b: 2 }, - { a: 1, b: 1 }, - { a: 2, b: 1 }, - { a: 1, b: 2, c: 3 }, - ]; - const res = [ - { a: 1, b: 1 }, - { a: 1, b: 2, c: 3 }, - { a: 2, b: 1 }, - { a: 2, b: 2 }, - ]; - assert.deepStrictEqual([...src].sort(compareByKeys2("a", "b")), res); - assert.deepStrictEqual( - [...src].sort( - compareByKeys2( - (x) => x.a, - (x) => x.b - ) - ), - res - ); - }, + compareByKeys2: () => { + const src = [ + { a: 2, b: 2 }, + { a: 1, b: 1 }, + { a: 2, b: 1 }, + { a: 1, b: 2, c: 3 }, + ]; + const res = [ + { a: 1, b: 1 }, + { a: 1, b: 2, c: 3 }, + { a: 2, b: 1 }, + { a: 2, b: 2 }, + ]; + assert.deepStrictEqual([...src].sort(compareByKeys2("a", "b")), res); + assert.deepStrictEqual( + [...src].sort( + compareByKeys2( + (x) => x.a, + (x) => x.b + ) + ), + res + ); + }, - compareByKeys3: () => { - const src = [ - { a: 1, b: 2, c: 3 }, - { a: 1, b: 2, c: 1 }, - { a: 1, b: 1, c: 3 }, - { a: 0, b: 1, c: 4 }, - ]; - const res = [ - { a: 0, b: 1, c: 4 }, - { a: 1, b: 1, c: 3 }, - { a: 1, b: 2, c: 1 }, - { a: 1, b: 2, c: 3 }, - ]; - assert.deepStrictEqual( - [...src].sort(compareByKeys3("a", "b", "c")), - res - ); - assert.deepStrictEqual( - [...src].sort( - compareByKeys3( - (x) => x.a, - (x) => x.b, - (x) => x.c - ) - ), - res - ); - }, + compareByKeys3: () => { + const src = [ + { a: 1, b: 2, c: 3 }, + { a: 1, b: 2, c: 1 }, + { a: 1, b: 1, c: 3 }, + { a: 0, b: 1, c: 4 }, + ]; + const res = [ + { a: 0, b: 1, c: 4 }, + { a: 1, b: 1, c: 3 }, + { a: 1, b: 2, c: 1 }, + { a: 1, b: 2, c: 3 }, + ]; + assert.deepStrictEqual( + [...src].sort(compareByKeys3("a", "b", "c")), + res + ); + assert.deepStrictEqual( + [...src].sort( + compareByKeys3( + (x) => x.a, + (x) => x.b, + (x) => x.c + ) + ), + res + ); + }, - compareByKeys4: () => { - const src = [ - { a: 1, b: 2, c: 3, d: 3 }, - { a: 1, b: 2, c: 3, d: 2 }, - { a: 1, b: 2, c: 3, d: 0 }, - { a: 1, b: 2, c: 3, d: 1 }, - ]; - const res = [ - { a: 1, b: 2, c: 3, d: 0 }, - { a: 1, b: 2, c: 3, d: 1 }, - { a: 1, b: 2, c: 3, d: 2 }, - { a: 1, b: 2, c: 3, d: 3 }, - ]; - assert.deepStrictEqual( - [...src].sort(compareByKeys4("a", "b", "c", "d")), - res - ); - assert.deepStrictEqual( - [...src].sort( - compareByKeys4( - (x) => x.a, - (x) => x.b, - (x) => x.c, - (x) => x.d - ) - ), - res - ); - }, + compareByKeys4: () => { + const src = [ + { a: 1, b: 2, c: 3, d: 3 }, + { a: 1, b: 2, c: 3, d: 2 }, + { a: 1, b: 2, c: 3, d: 0 }, + { a: 1, b: 2, c: 3, d: 1 }, + ]; + const res = [ + { a: 1, b: 2, c: 3, d: 0 }, + { a: 1, b: 2, c: 3, d: 1 }, + { a: 1, b: 2, c: 3, d: 2 }, + { a: 1, b: 2, c: 3, d: 3 }, + ]; + assert.deepStrictEqual( + [...src].sort(compareByKeys4("a", "b", "c", "d")), + res + ); + assert.deepStrictEqual( + [...src].sort( + compareByKeys4( + (x) => x.a, + (x) => x.b, + (x) => x.c, + (x) => x.d + ) + ), + res + ); + }, }); diff --git a/packages/compare/tsconfig.json b/packages/compare/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/compare/tsconfig.json +++ b/packages/compare/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/complex/api-extractor.json b/packages/complex/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/complex/api-extractor.json +++ b/packages/complex/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/complex/package.json b/packages/complex/package.json index ac3d7fa956..0840353e1a 100644 --- a/packages/complex/package.json +++ b/packages/complex/package.json @@ -1,81 +1,81 @@ { - "name": "@thi.ng/complex", - "version": "0.1.7", - "description": "Array-based complex number algebra", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/complex#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/math": "^5.3.4", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "algebra", - "complex", - "math", - "number", - "trigonometry", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=14" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "status": "alpha", - "year": 2022 - } + "name": "@thi.ng/complex", + "version": "0.1.7", + "description": "Array-based complex number algebra", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/complex#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/math": "^5.3.4", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "algebra", + "complex", + "math", + "number", + "trigonometry", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=14" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "status": "alpha", + "year": 2022 + } } diff --git a/packages/complex/src/index.ts b/packages/complex/src/index.ts index b5c70bb785..a0db1c1bdd 100644 --- a/packages/complex/src/index.ts +++ b/packages/complex/src/index.ts @@ -59,7 +59,7 @@ export const abs: ComplexOpN = (a) => Math.sqrt(norm(a)); * @param a - */ export const acos: ComplexOp1 = (a) => - mul(J, log(add(a, mul(I, sqrt(sub(ONE, sq(a))))))); + mul(J, log(add(a, mul(I, sqrt(sub(ONE, sq(a))))))); /** * Complex hyperbolic arccosine. @@ -70,8 +70,8 @@ export const acos: ComplexOp1 = (a) => * @param a - */ export const acosh: ComplexOp1 = (a) => { - const [r, i] = acos(a); - return i > 0 ? [i, -r] : [-i, r]; + const [r, i] = acos(a); + return i > 0 ? [i, -r] : [-i, r]; }; /** @@ -83,7 +83,7 @@ export const acosh: ComplexOp1 = (a) => { * @param a - */ export const acot: ComplexOp1 = ([r, i]) => - mul([0, -0.5], log(div([r, i + 1], [r, i - 1]))); + mul([0, -0.5], log(div([r, i + 1], [r, i - 1]))); /** * Complex hyperbolic arccotangent. @@ -94,7 +94,7 @@ export const acot: ComplexOp1 = ([r, i]) => * @param a - */ export const acoth: ComplexOp1 = ([r, i]) => - mul(log(div([r + 1, i], [r - 1, i])), 0.5); + mul(log(div([r + 1, i], [r - 1, i])), 0.5); /** * Complex addition. Operand `b` can be real or complex. @@ -103,7 +103,7 @@ export const acoth: ComplexOp1 = ([r, i]) => * @param b - */ export const add: ComplexOp2 = (a, b) => - isNumber(b) ? [a[0] + b, a[1]] : add2([], a, b); + isNumber(b) ? [a[0] + b, a[1]] : add2([], a, b); /** * Returns the argument/angle of a complex number, i.e. atan2(im,re) @@ -124,7 +124,7 @@ export const arg: ComplexOpN = (a) => Math.atan2(a[1], a[0]); * @param a - */ export const asin: ComplexOp1 = (a) => - mul(J, log(add([-a[1], a[0]], sqrt(sub(ONE, sq(a)))))); + mul(J, log(add([-a[1], a[0]], sqrt(sub(ONE, sq(a)))))); /** * Complex hyperbolic arcsine. @@ -145,7 +145,7 @@ export const asinh: ComplexOp1 = ([r, i]) => mul(I, asin([i, -r])); * @param a - */ export const atan: ComplexOp1 = ([r, i]) => - mul([0, -0.5], log(div([-r, 1 - i], [r, i + 1]))); + mul([0, -0.5], log(div([-r, 1 - i], [r, i + 1]))); /** * Complex hyperbolic arctangent. @@ -156,7 +156,7 @@ export const atan: ComplexOp1 = ([r, i]) => * @param a - */ export const atanh: ComplexOp1 = ([r, i]) => - mul(log(div([1 + r, i], [1 - r, -i])), 0.5); + mul(log(div([1 + r, i], [1 - r, -i])), 0.5); /** * Complex number conjugation. @@ -174,7 +174,7 @@ export const conjugate: ComplexOp1 = (a) => [a[0], -a[1]]; * @param a - */ export const cos: ComplexOp1 = ([r, i]) => - divN2(null, add(exp([-i, r]), exp([i, -r])), 2); + divN2(null, add(exp([-i, r]), exp([i, -r])), 2); /** * Complex hyperbolic cosine. @@ -195,10 +195,10 @@ export const cosh: ComplexOp1 = (a) => mul(add(exp(a), exp(neg(a))), 0.5); * @param a - */ export const cot: ComplexOp1 = ([r, i]) => { - const e1 = exp([-i, r]); - const e2 = exp([i, -r]); - const b = submN2([], e1, e2, 0.5); - return div(addmN2([], e1, e2, 0.5), [b[1], -b[0]]); + const e1 = exp([-i, r]); + const e2 = exp([i, -r]); + const b = submN2([], e1, e2, 0.5); + return div(addmN2([], e1, e2, 0.5), [b[1], -b[0]]); }; /** @@ -210,8 +210,8 @@ export const cot: ComplexOp1 = ([r, i]) => { * @param a - */ export const coth: ComplexOp1 = (a) => { - const [er, ei] = exp(mulN2([], a, 2)); - return div([er + 1, ei], [er - 1, ei]); + const [er, ei] = exp(mulN2([], a, 2)); + return div([er + 1, ei], [er - 1, ei]); }; /** @@ -221,17 +221,17 @@ export const coth: ComplexOp1 = (a) => { * @param b - */ export const div: ComplexOp2 = (a, b) => { - if (isNumber(b)) return divN2([], a, b); - const d = norm(b); - return d !== 0 - ? [(a[0] * b[0] + a[1] * b[1]) / d, (a[1] * b[0] - a[0] * b[1]) / d] - : INF; + if (isNumber(b)) return divN2([], a, b); + const d = norm(b); + return d !== 0 + ? [(a[0] * b[0] + a[1] * b[1]) / d, (a[1] * b[0] - a[0] * b[1]) / d] + : INF; }; export const eq: Predicate2 = equals2; export const eqDelta: (a: Complex, b: Complex, eps?: number) => boolean = - eqDelta2; + eqDelta2; /** * Returns e (the base of natural logarithms) raised to a complex power. @@ -249,7 +249,7 @@ export const exp: ComplexOp1 = (a) => cossin(a[1], Math.exp(a[0])); * @param a */ export const inv: ComplexOp1 = (a) => - isZero(a) ? INF : isInfinite(a) ? ZERO : div(ONE, a); + isZero(a) ? INF : isInfinite(a) ? ZERO : div(ONE, a); /** * Complex number (natural) logarithm. @@ -265,9 +265,9 @@ export const log: ComplexOp1 = (a) => [Math.log(abs(a)), arg(a)]; * @param b - */ export const mul: ComplexOp2 = (a, b) => - isNumber(b) - ? mulN2([], a, b) - : [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]]; + isNumber(b) + ? mulN2([], a, b) + : [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]]; export const neg: ComplexOp1 = (a) => [-a[0], -a[1]]; @@ -315,7 +315,7 @@ export const pow: ComplexOp2 = (a, b) => exp(mul(log(a), b)); * @param a - */ export const sin: ComplexOp1 = ([r, i]) => - div(sub(exp([-i, r]), exp([i, -r])), [0, 2]); + div(sub(exp([-i, r]), exp([i, -r])), [0, 2]); /** * Complex hyperbolic sine. @@ -344,11 +344,11 @@ export const sq: ComplexOp1 = (a) => mul(a, a); * @param a - */ export const sqrt: ComplexOp1 = (a) => { - const mag = abs(a); - return [ - Math.sqrt((mag + a[0]) / 2), - Math.sign(a[1]) * Math.sqrt((mag - a[0]) / 2), - ]; + const mag = abs(a); + return [ + Math.sqrt((mag + a[0]) / 2), + Math.sign(a[1]) * Math.sqrt((mag - a[0]) / 2), + ]; }; /** @@ -358,7 +358,7 @@ export const sqrt: ComplexOp1 = (a) => { * @param b - */ export const sub: ComplexOp2 = (a, b) => - isNumber(b) ? [a[0] - b, a[1]] : sub2([], a, b); + isNumber(b) ? [a[0] - b, a[1]] : sub2([], a, b); /** * Complex tangent. @@ -369,12 +369,12 @@ export const sub: ComplexOp2 = (a, b) => * @param a - */ export const tan: ComplexOp1 = ([r, i]) => { - const e1 = exp([-i, r]); - const e2 = exp([i, -r]); - const a = submN2([], e1, e2, 0.5); - const b = addmN2([], e1, e2, 0.5); - // div(sin(a),cos(a)) - return div([a[1], -a[0]], [b[0], b[1]]); + const e1 = exp([-i, r]); + const e2 = exp([i, -r]); + const a = submN2([], e1, e2, 0.5); + const b = addmN2([], e1, e2, 0.5); + // div(sin(a),cos(a)) + return div([a[1], -a[0]], [b[0], b[1]]); }; /** @@ -386,6 +386,6 @@ export const tan: ComplexOp1 = ([r, i]) => { * @param a - */ export const tanh: ComplexOp1 = (a) => { - const [er, ei] = exp(mulN2([], a, 2)); - return div([er - 1, ei], [er + 1, ei]); + const [er, ei] = exp(mulN2([], a, 2)); + return div([er - 1, ei], [er + 1, ei]); }; diff --git a/packages/complex/test/index.ts b/packages/complex/test/index.ts index 269446cd82..2b61526898 100644 --- a/packages/complex/test/index.ts +++ b/packages/complex/test/index.ts @@ -2,41 +2,41 @@ import { eqDelta as $eq } from "@thi.ng/math"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { - abs, - acos, - acosh, - acot, - acoth, - add, - arg, - asin, - asinh, - atan, - atanh, - Complex, - ComplexOp1, - ComplexOp2, - ComplexOpN, - conjugate, - cos, - cosh, - cot, - coth, - div, - eqDelta, - exp, - inv, - log, - mul, - neg, - norm, - pow, - sin, - sinh, - sqrt, - sub, - tan, - tanh, + abs, + acos, + acosh, + acot, + acoth, + add, + arg, + asin, + asinh, + atan, + atanh, + Complex, + ComplexOp1, + ComplexOp2, + ComplexOpN, + conjugate, + cos, + cosh, + cot, + coth, + div, + eqDelta, + exp, + inv, + log, + mul, + neg, + norm, + pow, + sin, + sinh, + sqrt, + sub, + tan, + tanh, } from "../src/index.js"; const A = [3, 2]; @@ -45,195 +45,195 @@ const CASES: Complex[] = [A, B]; const EPS = 1e-3; const checkN = (fn: ComplexOpN, res: number[]) => { - CASES.forEach((x, i) => - assert.ok($eq(fn(x), res[i], EPS), `expected: ${res[i]}, got ${fn(x)}`) - ); + CASES.forEach((x, i) => + assert.ok($eq(fn(x), res[i], EPS), `expected: ${res[i]}, got ${fn(x)}`) + ); }; export const check1 = (fn: ComplexOp1, res: Complex[]) => { - CASES.forEach((x, i) => - assert.ok( - eqDelta(fn(x), res[i], EPS), - `expected: ${res[i]}, got ${fn(x)}` - ) - ); + CASES.forEach((x, i) => + assert.ok( + eqDelta(fn(x), res[i], EPS), + `expected: ${res[i]}, got ${fn(x)}` + ) + ); }; export const check2 = ( - fn: ComplexOp2, - arg: Complex | number, - res: Complex[] + fn: ComplexOp2, + arg: Complex | number, + res: Complex[] ) => { - CASES.forEach((x, i) => - assert.ok( - eqDelta(fn(x, arg), res[i], EPS), - `expected: ${res[i]}, got ${fn(x, arg)}` - ) - ); + CASES.forEach((x, i) => + assert.ok( + eqDelta(fn(x, arg), res[i], EPS), + `expected: ${res[i]}, got ${fn(x, arg)}` + ) + ); }; group( - "complex", - { - abs: () => checkN(abs, [3.606, 0.901]), - acos: () => - check1(acos, [ - [0.606, -1.969], - [1.173, 0.743], - ]), - acosh: () => - check1(acosh, [ - [1.968, 0.606], - [0.743, -1.173], - ]), - acot: () => - check1(acot, [ - [0.232, -0.147], - [0.878, 0.59], - ]), - acoth: () => - check1(acoth, [ - [0.229, -0.161], - [0.31, 0.8475], - ]), - add: () => - check2( - add, - [10, -20], - [ - [13, -18], - [10.5, -20.75], - ] - ), - arg: () => checkN(arg, [0.588, -0.983]), - asin: () => - check1(asin, [ - [0.965, 1.969], - [0.398, -0.743], - ]), - asinh: () => - check1(asinh, [ - [1.983, 0.571], - [0.606, -0.682], - ]), - atan: () => - check1(atan, [ - [1.339, 0.147], - [0.693, -0.59], - ]), - atanh: () => - check1(atanh, [ - [0.229, 1.41], - [0.31, -0.723], - ]), - conjugate: () => - check1(conjugate, [ - [3, -2], - [0.5, 0.75], - ]), - cos: () => - check1(cos, [ - [-3.725, -0.512], - [1.136, 0.394], - ]), - cosh: () => - check1(cosh, [ - [-4.1896, 9.109], - [0.825, -0.355], - ]), - cot: () => - check1(cot, [ - [-0.011, -1.036], - [0.464, 1.175], - ]), - coth: () => - check1(coth, [ - [0.997, 0.004], - [0.798, 0.677], - ]), - div: () => - check2( - div, - [10, -20], - [ - [-0.02, 0.16], - [0.04, 0.005], - ] - ), - exp: () => - check1(exp, [ - [-8.359, 18.264], - [1.206, -1.124], - ]), - inv: () => - check1(inv, [ - [0.231, -0.154], - [0.615, 0.923], - ]), - log: () => - check1(log, [ - [1.282, 0.588], - [-0.104, -0.983], - ]), - mul: () => - check2( - mul, - [10, -20], - [ - [70, -40], - [-10, -17.5], - ] - ), - neg: () => - check1(neg, [ - [-3, -2], - [-0.5, 0.75], - ]), - norm: () => checkN(norm, [3 * 3 + 2 * 2, 0.5 * 0.5 + 0.75 * 0.75]), - pow: () => - check2( - pow, - [4, -5], - [ - [-1939.824, 2541.011], - [-0.0047, 0.0013], - ] - ), - sin: () => - check1(sin, [ - [0.531, -3.591], - [0.621, -0.722], - ]), - sinh: () => - check1(sinh, [ - [-4.169, 9.154], - [0.381, -0.769], - ]), - sqrt: () => - check1(sqrt, [ - [1.817, 0.55], - [0.837, -0.448], - ]), - sub: () => - check2( - sub, - [10, -20], - [ - [-7, 22], - [-9.5, 19.25], - ] - ), - tan: () => - check1(tan, [ - [-0.0098, 0.965], - [0.291, -0.736], - ]), - tanh: () => { - check1(tanh, [ - [1.003, -0.004], - [0.728, -0.618], - ]); - }, - }, - {} + "complex", + { + abs: () => checkN(abs, [3.606, 0.901]), + acos: () => + check1(acos, [ + [0.606, -1.969], + [1.173, 0.743], + ]), + acosh: () => + check1(acosh, [ + [1.968, 0.606], + [0.743, -1.173], + ]), + acot: () => + check1(acot, [ + [0.232, -0.147], + [0.878, 0.59], + ]), + acoth: () => + check1(acoth, [ + [0.229, -0.161], + [0.31, 0.8475], + ]), + add: () => + check2( + add, + [10, -20], + [ + [13, -18], + [10.5, -20.75], + ] + ), + arg: () => checkN(arg, [0.588, -0.983]), + asin: () => + check1(asin, [ + [0.965, 1.969], + [0.398, -0.743], + ]), + asinh: () => + check1(asinh, [ + [1.983, 0.571], + [0.606, -0.682], + ]), + atan: () => + check1(atan, [ + [1.339, 0.147], + [0.693, -0.59], + ]), + atanh: () => + check1(atanh, [ + [0.229, 1.41], + [0.31, -0.723], + ]), + conjugate: () => + check1(conjugate, [ + [3, -2], + [0.5, 0.75], + ]), + cos: () => + check1(cos, [ + [-3.725, -0.512], + [1.136, 0.394], + ]), + cosh: () => + check1(cosh, [ + [-4.1896, 9.109], + [0.825, -0.355], + ]), + cot: () => + check1(cot, [ + [-0.011, -1.036], + [0.464, 1.175], + ]), + coth: () => + check1(coth, [ + [0.997, 0.004], + [0.798, 0.677], + ]), + div: () => + check2( + div, + [10, -20], + [ + [-0.02, 0.16], + [0.04, 0.005], + ] + ), + exp: () => + check1(exp, [ + [-8.359, 18.264], + [1.206, -1.124], + ]), + inv: () => + check1(inv, [ + [0.231, -0.154], + [0.615, 0.923], + ]), + log: () => + check1(log, [ + [1.282, 0.588], + [-0.104, -0.983], + ]), + mul: () => + check2( + mul, + [10, -20], + [ + [70, -40], + [-10, -17.5], + ] + ), + neg: () => + check1(neg, [ + [-3, -2], + [-0.5, 0.75], + ]), + norm: () => checkN(norm, [3 * 3 + 2 * 2, 0.5 * 0.5 + 0.75 * 0.75]), + pow: () => + check2( + pow, + [4, -5], + [ + [-1939.824, 2541.011], + [-0.0047, 0.0013], + ] + ), + sin: () => + check1(sin, [ + [0.531, -3.591], + [0.621, -0.722], + ]), + sinh: () => + check1(sinh, [ + [-4.169, 9.154], + [0.381, -0.769], + ]), + sqrt: () => + check1(sqrt, [ + [1.817, 0.55], + [0.837, -0.448], + ]), + sub: () => + check2( + sub, + [10, -20], + [ + [-7, 22], + [-9.5, 19.25], + ] + ), + tan: () => + check1(tan, [ + [-0.0098, 0.965], + [0.291, -0.736], + ]), + tanh: () => { + check1(tanh, [ + [1.003, -0.004], + [0.728, -0.618], + ]); + }, + }, + {} ); diff --git a/packages/complex/tools/mandelbrot.ts b/packages/complex/tools/mandelbrot.ts index 5b74807d54..c63c0b9d7b 100644 --- a/packages/complex/tools/mandelbrot.ts +++ b/packages/complex/tools/mandelbrot.ts @@ -5,12 +5,12 @@ import { fit2 } from "@thi.ng/vectors"; // mandelbrot evaluation const mandelbrot = (pos: Complex, escapeRadius: number, maxIter: number) => { - let i = 0; - let z: Complex = pos; - while (++i < maxIter && abs(z) < escapeRadius) { - z = add(mul(z, z), pos); - } - return maxIter - i; + let i = 0; + let z: Complex = pos; + while (++i < maxIter && abs(z) < escapeRadius) { + z = add(mul(z, z), pos); + } + return maxIter - i; }; // text canvas setup @@ -19,21 +19,21 @@ const maxSize: [number, number] = [canv.width, canv.height]; // evaluate for all pixels and visualize as ASCII art run( - map((p) => { - canv.setAt( - p[0], - p[1], - SHADES_ASCII_16[ - mandelbrot( - // map pixel pos to mandelbrot region - fit2([], p, [0, 0], maxSize, [-2, -1.25], [0.65, 1.25]), - 2000, - 15 - ) - ] - ); - }), - range2d(...maxSize) + map((p) => { + canv.setAt( + p[0], + p[1], + SHADES_ASCII_16[ + mandelbrot( + // map pixel pos to mandelbrot region + fit2([], p, [0, 0], maxSize, [-2, -1.25], [0.65, 1.25]), + 2000, + 15 + ) + ] + ); + }), + range2d(...maxSize) ); // output canvas as string diff --git a/packages/complex/tsconfig.json b/packages/complex/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/complex/tsconfig.json +++ b/packages/complex/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/compose/api-extractor.json b/packages/compose/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/compose/api-extractor.json +++ b/packages/compose/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/compose/package.json b/packages/compose/package.json index 7b51f23967..dd61a1e13d 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,108 +1,108 @@ { - "name": "@thi.ng/compose", - "version": "2.1.8", - "description": "Optimized functional composition helpers", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/compose#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/errors": "^2.1.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "async", - "composition", - "functional", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./comp": { - "default": "./comp.js" - }, - "./complement": { - "default": "./complement.js" - }, - "./constantly": { - "default": "./constantly.js" - }, - "./delay": { - "default": "./delay.js" - }, - "./delayed": { - "default": "./delayed.js" - }, - "./identity": { - "default": "./identity.js" - }, - "./ifdef": { - "default": "./ifdef.js" - }, - "./juxt": { - "default": "./juxt.js" - }, - "./partial": { - "default": "./partial.js" - }, - "./promisify": { - "default": "./promisify.js" - }, - "./thread-first": { - "default": "./thread-first.js" - }, - "./thread-last": { - "default": "./thread-last.js" - }, - "./trampoline": { - "default": "./trampoline.js" - } - } + "name": "@thi.ng/compose", + "version": "2.1.8", + "description": "Optimized functional composition helpers", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/compose#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/errors": "^2.1.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "async", + "composition", + "functional", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./comp": { + "default": "./comp.js" + }, + "./complement": { + "default": "./complement.js" + }, + "./constantly": { + "default": "./constantly.js" + }, + "./delay": { + "default": "./delay.js" + }, + "./delayed": { + "default": "./delayed.js" + }, + "./identity": { + "default": "./identity.js" + }, + "./ifdef": { + "default": "./ifdef.js" + }, + "./juxt": { + "default": "./juxt.js" + }, + "./partial": { + "default": "./partial.js" + }, + "./promisify": { + "default": "./promisify.js" + }, + "./thread-first": { + "default": "./thread-first.js" + }, + "./thread-last": { + "default": "./thread-last.js" + }, + "./trampoline": { + "default": "./trampoline.js" + } + } } diff --git a/packages/compose/src/comp.ts b/packages/compose/src/comp.ts index 3a1a640ffc..015eaf1e9d 100644 --- a/packages/compose/src/comp.ts +++ b/packages/compose/src/comp.ts @@ -12,109 +12,109 @@ export function comp(a: FnAny): FnAny; export function comp(a: Fn, b: FnAny): FnAny; export function comp(a: Fn, b: Fn, c: FnAny): FnAny; export function comp( - a: Fn, - b: Fn, - c: Fn, - d: FnAny + a: Fn, + b: Fn, + c: Fn, + d: FnAny ): FnAny; export function comp( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: FnAny + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: FnAny ): FnAny; export function comp( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: FnAny + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: FnAny ): FnAny; export function comp( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: FnAny + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: FnAny ): FnAny; export function comp( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn, - h: FnAny + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn, + h: FnAny ): FnAny; export function comp( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn, - h: Fn, - i: FnAny + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn, + h: Fn, + i: FnAny ): FnAny; export function comp( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn, - h: Fn, - i: Fn, - j: FnAny + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn, + h: Fn, + i: Fn, + j: FnAny ): FnAny; export function comp( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn, - h: Fn, - i: Fn, - j: Fn, - ...fns: FnAny[] + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn, + h: Fn, + i: Fn, + j: Fn, + ...fns: FnAny[] ): FnAny; export function comp(...fns: any[]): any { - let [a, b, c, d, e, f, g, h, i, j] = fns; - switch (fns.length) { - case 0: - illegalArity(0); - case 1: - return a; - case 2: - return (...xs: any[]) => a(b(...xs)); - case 3: - return (...xs: any[]) => a(b(c(...xs))); - case 4: - return (...xs: any[]) => a(b(c(d(...xs)))); - case 5: - return (...xs: any[]) => a(b(c(d(e(...xs))))); - case 6: - return (...xs: any[]) => a(b(c(d(e(f(...xs)))))); - case 7: - return (...xs: any[]) => a(b(c(d(e(f(g(...xs))))))); - case 8: - return (...xs: any[]) => a(b(c(d(e(f(g(h(...xs)))))))); - case 9: - return (...xs: any[]) => a(b(c(d(e(f(g(h(i(...xs))))))))); - case 10: - default: - const fn = (...xs: any[]) => a(b(c(d(e(f(g(h(i(j(...xs)))))))))); - return fns.length === 10 ? fn : (comp)(fn, ...fns.slice(10)); - } + let [a, b, c, d, e, f, g, h, i, j] = fns; + switch (fns.length) { + case 0: + illegalArity(0); + case 1: + return a; + case 2: + return (...xs: any[]) => a(b(...xs)); + case 3: + return (...xs: any[]) => a(b(c(...xs))); + case 4: + return (...xs: any[]) => a(b(c(d(...xs)))); + case 5: + return (...xs: any[]) => a(b(c(d(e(...xs))))); + case 6: + return (...xs: any[]) => a(b(c(d(e(f(...xs)))))); + case 7: + return (...xs: any[]) => a(b(c(d(e(f(g(...xs))))))); + case 8: + return (...xs: any[]) => a(b(c(d(e(f(g(h(...xs)))))))); + case 9: + return (...xs: any[]) => a(b(c(d(e(f(g(h(i(...xs))))))))); + case 10: + default: + const fn = (...xs: any[]) => a(b(c(d(e(f(g(h(i(j(...xs)))))))))); + return fns.length === 10 ? fn : (comp)(fn, ...fns.slice(10)); + } } /** @@ -124,83 +124,83 @@ export function compL(a: FnAny): FnAny; export function compL(a: FnAny, b: Fn): FnAny; export function compL(a: FnAny, b: Fn, c: Fn): FnAny; export function compL( - a: FnAny, - b: Fn, - c: Fn, - d: Fn + a: FnAny, + b: Fn, + c: Fn, + d: Fn ): FnAny; export function compL( - a: FnAny, - b: Fn, - c: Fn, - d: Fn, - e: Fn + a: FnAny, + b: Fn, + c: Fn, + d: Fn, + e: Fn ): FnAny; export function compL( - a: FnAny, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn + a: FnAny, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn ): FnAny; export function compL( - a: FnAny, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn + a: FnAny, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn ): FnAny; export function compL( - a: FnAny, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn, - h: Fn + a: FnAny, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn, + h: Fn ): FnAny; export function compL( - a: FnAny, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn, - h: Fn, - i: Fn + a: FnAny, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn, + h: Fn, + i: Fn ): FnAny; export function compL( - a: FnAny, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn, - h: Fn, - i: Fn, - j: Fn + a: FnAny, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn, + h: Fn, + i: Fn, + j: Fn ): FnAny; export function compL( - a: FnAny, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn, - h: Fn, - i: Fn, - j: Fn, - ...xs: Fn[] + a: FnAny, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn, + h: Fn, + i: Fn, + j: Fn, + ...xs: Fn[] ): FnAny; export function compL(...fns: any[]): any { - return comp.apply(null, fns.reverse()); + return comp.apply(null, fns.reverse()); } /** diff --git a/packages/compose/src/complement.ts b/packages/compose/src/complement.ts index c37ff03617..e808df5f76 100644 --- a/packages/compose/src/complement.ts +++ b/packages/compose/src/complement.ts @@ -1,37 +1,37 @@ import type { - Fn, - Fn0, - Fn2, - Fn3, - Fn4, - Fn5, - Fn6, - Fn7, - Fn8, - FnAny, + Fn, + Fn0, + Fn2, + Fn3, + Fn4, + Fn5, + Fn6, + Fn7, + Fn8, + FnAny, } from "@thi.ng/api"; export function complement(f: Fn0): Fn0; export function complement(f: Fn): Fn; export function complement(f: Fn2): Fn2; export function complement( - f: Fn3 + f: Fn3 ): Fn3; export function complement( - f: Fn4 + f: Fn4 ): Fn4; export function complement( - f: Fn5 + f: Fn5 ): Fn5; export function complement( - f: Fn6 + f: Fn6 ): Fn6; export function complement( - f: Fn7 + f: Fn7 ): Fn7; export function complement( - f: Fn8 + f: Fn8 ): Fn8; export function complement(f: FnAny) { - return (...xs: any[]) => !f(...xs); + return (...xs: any[]) => !f(...xs); } diff --git a/packages/compose/src/constantly.ts b/packages/compose/src/constantly.ts index 07d0e9f1a1..8f0c19ad7a 100644 --- a/packages/compose/src/constantly.ts +++ b/packages/compose/src/constantly.ts @@ -1,3 +1,6 @@ import type { FnAny } from "@thi.ng/api"; -export const constantly = (x: T): FnAny => () => x; +export const constantly = + (x: T): FnAny => + () => + x; diff --git a/packages/compose/src/delay.ts b/packages/compose/src/delay.ts index f494969013..a51b4cfb53 100644 --- a/packages/compose/src/delay.ts +++ b/packages/compose/src/delay.ts @@ -3,24 +3,24 @@ import type { Fn0, IDeref } from "@thi.ng/api"; export const delay = (body: Fn0) => new Delay(body); export class Delay implements IDeref { - protected value!: T; - protected body: Fn0; - protected realized: boolean; + protected value!: T; + protected body: Fn0; + protected realized: boolean; - constructor(body: Fn0) { - this.body = body; - this.realized = false; - } + constructor(body: Fn0) { + this.body = body; + this.realized = false; + } - deref() { - if (!this.realized) { - this.value = this.body(); - this.realized = true; - } - return this.value; - } + deref() { + if (!this.realized) { + this.value = this.body(); + this.realized = true; + } + return this.value; + } - isRealized() { - return this.realized; - } + isRealized() { + return this.realized; + } } diff --git a/packages/compose/src/delayed.ts b/packages/compose/src/delayed.ts index 14388c052a..a0cd4bff71 100644 --- a/packages/compose/src/delayed.ts +++ b/packages/compose/src/delayed.ts @@ -1,2 +1,2 @@ export const delayed = (x: T, t: number) => - new Promise((resolve) => setTimeout(() => resolve(x), t)); + new Promise((resolve) => setTimeout(() => resolve(x), t)); diff --git a/packages/compose/src/ifdef.ts b/packages/compose/src/ifdef.ts index e41a6dd4c3..9ee076ea85 100644 --- a/packages/compose/src/ifdef.ts +++ b/packages/compose/src/ifdef.ts @@ -7,4 +7,4 @@ import type { Fn } from "@thi.ng/api"; * @param x - value */ export const ifDef = (f: Fn, x: A | null | undefined) => - x != null ? f(x) : undefined; + x != null ? f(x) : undefined; diff --git a/packages/compose/src/juxt.ts b/packages/compose/src/juxt.ts index 635dcd5191..0ec1478a75 100644 --- a/packages/compose/src/juxt.ts +++ b/packages/compose/src/juxt.ts @@ -3,87 +3,87 @@ import type { Fn } from "@thi.ng/api"; export function juxt(a: Fn): Fn; export function juxt(a: Fn, b: Fn): Fn; export function juxt( - a: Fn, - b: Fn, - c: Fn + a: Fn, + b: Fn, + c: Fn ): Fn; export function juxt( - a: Fn, - b: Fn, - c: Fn, - d: Fn + a: Fn, + b: Fn, + c: Fn, + d: Fn ): Fn; export function juxt( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: Fn + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: Fn ): Fn; export function juxt( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn ): Fn; export function juxt( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn ): Fn; export function juxt( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn, - h: Fn + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn, + h: Fn ): Fn; export function juxt( - a: Fn, - b: Fn, - c: Fn, - d: Fn, - e: Fn, - f: Fn, - g: Fn, - h: Fn, - ...xs: Fn[] + a: Fn, + b: Fn, + c: Fn, + d: Fn, + e: Fn, + f: Fn, + g: Fn, + h: Fn, + ...xs: Fn[] ): Fn; export function juxt(...fns: Fn[]) { - const [a, b, c, d, e, f, g, h] = fns; - switch (fns.length) { - case 1: - return (x: T) => [a(x)]; - case 2: - return (x: T) => [a(x), b(x)]; - case 3: - return (x: T) => [a(x), b(x), c(x)]; - case 4: - return (x: T) => [a(x), b(x), c(x), d(x)]; - case 5: - return (x: T) => [a(x), b(x), c(x), d(x), e(x)]; - case 6: - return (x: T) => [a(x), b(x), c(x), d(x), e(x), f(x)]; - case 7: - return (x: T) => [a(x), b(x), c(x), d(x), e(x), f(x), g(x)]; - case 8: - return (x: T) => [a(x), b(x), c(x), d(x), e(x), f(x), g(x), h(x)]; - default: - return (x: T) => { - let res = new Array(fns.length); - for (let i = fns.length; i-- > 0; ) { - res[i] = fns[i](x); - } - return res; - }; - } + const [a, b, c, d, e, f, g, h] = fns; + switch (fns.length) { + case 1: + return (x: T) => [a(x)]; + case 2: + return (x: T) => [a(x), b(x)]; + case 3: + return (x: T) => [a(x), b(x), c(x)]; + case 4: + return (x: T) => [a(x), b(x), c(x), d(x)]; + case 5: + return (x: T) => [a(x), b(x), c(x), d(x), e(x)]; + case 6: + return (x: T) => [a(x), b(x), c(x), d(x), e(x), f(x)]; + case 7: + return (x: T) => [a(x), b(x), c(x), d(x), e(x), f(x), g(x)]; + case 8: + return (x: T) => [a(x), b(x), c(x), d(x), e(x), f(x), g(x), h(x)]; + default: + return (x: T) => { + let res = new Array(fns.length); + for (let i = fns.length; i-- > 0; ) { + res[i] = fns[i](x); + } + return res; + }; + } } diff --git a/packages/compose/src/partial.ts b/packages/compose/src/partial.ts index 61b8d62945..cf2e922b2b 100644 --- a/packages/compose/src/partial.ts +++ b/packages/compose/src/partial.ts @@ -1,89 +1,89 @@ import type { - FnAny, - FnO, - FnO2, - FnO3, - FnO4, - FnO5, - FnO6, - FnO7, - FnO8, + FnAny, + FnO, + FnO2, + FnO3, + FnO4, + FnO5, + FnO6, + FnO7, + FnO8, } from "@thi.ng/api"; import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; export function partial(fn: FnO, a: A): FnAny; export function partial(fn: FnO2, a: A, b: B): FnAny; export function partial( - fn: FnO3, - a: A, - b: B, - c: C + fn: FnO3, + a: A, + b: B, + c: C ): FnAny; export function partial( - fn: FnO4, - a: A, - b: B, - c: C, - d: D + fn: FnO4, + a: A, + b: B, + c: C, + d: D ): FnAny; export function partial( - fn: FnO5, - a: A, - b: B, - c: C, - d: D, - e: E + fn: FnO5, + a: A, + b: B, + c: C, + d: D, + e: E ): FnAny; export function partial( - fn: FnO6, - a: A, - b: B, - c: C, - d: D, - e: E, - f: F + fn: FnO6, + a: A, + b: B, + c: C, + d: D, + e: E, + f: F ): FnAny; export function partial( - fn: FnO7, - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G + fn: FnO7, + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G ): FnAny; export function partial( - fn: FnO8, - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H + fn: FnO8, + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H ): FnAny; export function partial(fn: any, ...args: any[]) { - let [a, b, c, d, e, f, g, h] = args; - switch (args.length) { - case 1: - return (...xs: any[]) => fn(a, ...xs); - case 2: - return (...xs: any[]) => fn(a, b, ...xs); - case 3: - return (...xs: any[]) => fn(a, b, c, ...xs); - case 4: - return (...xs: any[]) => fn(a, b, c, d, ...xs); - case 5: - return (...xs: any[]) => fn(a, b, c, d, e, ...xs); - case 6: - return (...xs: any[]) => fn(a, b, c, d, e, f, ...xs); - case 7: - return (...xs: any[]) => fn(a, b, c, d, e, f, g, ...xs); - case 8: - return (...xs: any[]) => fn(a, b, c, d, e, f, g, h, ...xs); - default: - illegalArgs(); - } + let [a, b, c, d, e, f, g, h] = args; + switch (args.length) { + case 1: + return (...xs: any[]) => fn(a, ...xs); + case 2: + return (...xs: any[]) => fn(a, b, ...xs); + case 3: + return (...xs: any[]) => fn(a, b, c, ...xs); + case 4: + return (...xs: any[]) => fn(a, b, c, d, ...xs); + case 5: + return (...xs: any[]) => fn(a, b, c, d, e, ...xs); + case 6: + return (...xs: any[]) => fn(a, b, c, d, e, f, ...xs); + case 7: + return (...xs: any[]) => fn(a, b, c, d, e, f, g, ...xs); + case 8: + return (...xs: any[]) => fn(a, b, c, d, e, f, g, h, ...xs); + default: + illegalArgs(); + } } diff --git a/packages/compose/src/promisify.ts b/packages/compose/src/promisify.ts index 0435285ef3..173cd24da7 100644 --- a/packages/compose/src/promisify.ts +++ b/packages/compose/src/promisify.ts @@ -19,6 +19,6 @@ import type { Fn, Fn2 } from "@thi.ng/api"; * @param fn - function accepting a callback */ export const promisify = (fn: Fn, void>) => - new Promise((resolve, reject) => - fn((err, result) => (err != null ? reject(err) : resolve(result))) - ); + new Promise((resolve, reject) => + fn((err, result) => (err != null ? reject(err) : resolve(result))) + ); diff --git a/packages/compose/src/thread-first.ts b/packages/compose/src/thread-first.ts index 1c3d75d991..006702f0f9 100644 --- a/packages/compose/src/thread-first.ts +++ b/packages/compose/src/thread-first.ts @@ -28,13 +28,13 @@ import type { FnAny } from "@thi.ng/api"; * @param fns - functions / S-expressions */ export const threadFirst = ( - init: any, - ...fns: (FnAny | [FnAny, ...any[]])[] + init: any, + ...fns: (FnAny | [FnAny, ...any[]])[] ) => - fns.reduce( - (acc, expr) => - typeof expr === "function" - ? expr(acc) - : expr[0](acc, ...expr.slice(1)), - init - ); + fns.reduce( + (acc, expr) => + typeof expr === "function" + ? expr(acc) + : expr[0](acc, ...expr.slice(1)), + init + ); diff --git a/packages/compose/src/thread-last.ts b/packages/compose/src/thread-last.ts index 7cc498d586..7c7a997f3b 100644 --- a/packages/compose/src/thread-last.ts +++ b/packages/compose/src/thread-last.ts @@ -28,13 +28,13 @@ import type { FnAny } from "@thi.ng/api"; * @param fns - functions / S-expressions */ export const threadLast = ( - init: any, - ...fns: (FnAny | [FnAny, ...any[]])[] + init: any, + ...fns: (FnAny | [FnAny, ...any[]])[] ) => - fns.reduce( - (acc, expr) => - typeof expr === "function" - ? expr(acc) - : expr[0](...expr.slice(1), acc), - init - ); + fns.reduce( + (acc, expr) => + typeof expr === "function" + ? expr(acc) + : expr[0](...expr.slice(1), acc), + init + ); diff --git a/packages/compose/src/trampoline.ts b/packages/compose/src/trampoline.ts index 1d8570217e..711a6fe1bc 100644 --- a/packages/compose/src/trampoline.ts +++ b/packages/compose/src/trampoline.ts @@ -31,8 +31,8 @@ import type { Fn0 } from "@thi.ng/api"; * @param f - function */ export const trampoline = (f: T | Fn0>) => { - while (typeof f === "function") { - f = (f)(); - } - return f; + while (typeof f === "function") { + f = (f)(); + } + return f; }; diff --git a/packages/compose/test/delay.ts b/packages/compose/test/delay.ts index ef5236d8fb..ba7ea1ac1f 100644 --- a/packages/compose/test/delay.ts +++ b/packages/compose/test/delay.ts @@ -1,14 +1,14 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { delay } from "../src/index.js" +import { delay } from "../src/index.js"; group("delay", { - "only executes once": () => { - let num = 0; - const a = delay(() => ++num); - assert.ok(!a.isRealized()); - assert.strictEqual(a.deref(), 1); - assert.strictEqual(a.deref(), 1); - assert.ok(a.isRealized()); - }, + "only executes once": () => { + let num = 0; + const a = delay(() => ++num); + assert.ok(!a.isRealized()); + assert.strictEqual(a.deref(), 1); + assert.strictEqual(a.deref(), 1); + assert.ok(a.isRealized()); + }, }); diff --git a/packages/compose/test/juxt.ts b/packages/compose/test/juxt.ts index 770440259b..3910c07c57 100644 --- a/packages/compose/test/juxt.ts +++ b/packages/compose/test/juxt.ts @@ -1,116 +1,116 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { juxt } from "../src/index.js" +import { juxt } from "../src/index.js"; group("juxt", { - "2-args": () => { - const a = juxt( - (x: number) => x + 1, - (x: number) => x * 10 - ); - assert.deepStrictEqual(a(1), [2, 10]); - }, + "2-args": () => { + const a = juxt( + (x: number) => x + 1, + (x: number) => x * 10 + ); + assert.deepStrictEqual(a(1), [2, 10]); + }, - "3-args": () => { - const a = juxt( - (x: number) => x + 1, - (x: number) => x * 10, - (x: number) => "id-" + x - ); - assert.deepStrictEqual(a(1), [2, 10, "id-1"]); - }, + "3-args": () => { + const a = juxt( + (x: number) => x + 1, + (x: number) => x * 10, + (x: number) => "id-" + x + ); + assert.deepStrictEqual(a(1), [2, 10, "id-1"]); + }, - "4-args": () => { - const a = juxt( - (x: number) => x + 1, - (x: number) => x * 10, - (x: number) => "id-" + x, - (x: number) => [x, x] - ); - assert.deepStrictEqual(a(1), [2, 10, "id-1", [1, 1]]); - }, + "4-args": () => { + const a = juxt( + (x: number) => x + 1, + (x: number) => x * 10, + (x: number) => "id-" + x, + (x: number) => [x, x] + ); + assert.deepStrictEqual(a(1), [2, 10, "id-1", [1, 1]]); + }, - "5-args": () => { - const a = juxt( - (x: number) => x + 1, - (x: number) => x * 10, - (x: number) => "id-" + x, - (x: number) => [x, x], - (x: number) => ({ a: x }) - ); - assert.deepStrictEqual(a(1), [2, 10, "id-1", [1, 1], { a: 1 }]); - }, + "5-args": () => { + const a = juxt( + (x: number) => x + 1, + (x: number) => x * 10, + (x: number) => "id-" + x, + (x: number) => [x, x], + (x: number) => ({ a: x }) + ); + assert.deepStrictEqual(a(1), [2, 10, "id-1", [1, 1], { a: 1 }]); + }, - "6-args": () => { - const a = juxt( - (x: number) => x + 1, - (x: number) => x - 1, - (x: number) => x * 10, - (x: number) => "id-" + x, - (x: number) => [x, x], - (x: number) => ({ a: x }) - ); - assert.deepStrictEqual(a(1), [2, 0, 10, "id-1", [1, 1], { a: 1 }]); - }, + "6-args": () => { + const a = juxt( + (x: number) => x + 1, + (x: number) => x - 1, + (x: number) => x * 10, + (x: number) => "id-" + x, + (x: number) => [x, x], + (x: number) => ({ a: x }) + ); + assert.deepStrictEqual(a(1), [2, 0, 10, "id-1", [1, 1], { a: 1 }]); + }, - "7-args": () => { - const a = juxt( - (x: number) => x + 1, - (x: number) => x - 1, - (x: number) => x * 10, - (x: number) => x * 100, - (x: number) => "id-" + x, - (x: number) => [x, x], - (x: number) => ({ a: x }) - ); - assert.deepStrictEqual(a(1), [2, 0, 10, 100, "id-1", [1, 1], { a: 1 }]); - }, + "7-args": () => { + const a = juxt( + (x: number) => x + 1, + (x: number) => x - 1, + (x: number) => x * 10, + (x: number) => x * 100, + (x: number) => "id-" + x, + (x: number) => [x, x], + (x: number) => ({ a: x }) + ); + assert.deepStrictEqual(a(1), [2, 0, 10, 100, "id-1", [1, 1], { a: 1 }]); + }, - "8-args": () => { - const a = juxt( - (x: number) => x + 1, - (x: number) => x - 1, - (x: number) => x * 10, - (x: number) => x * 100, - (x: number) => x * 1000, - (x: number) => "id-" + x, - (x: number) => [x, x], - (x: number) => ({ a: x }) - ); - assert.deepStrictEqual(a(1), [ - 2, - 0, - 10, - 100, - 1000, - "id-1", - [1, 1], - { a: 1 }, - ]); - }, + "8-args": () => { + const a = juxt( + (x: number) => x + 1, + (x: number) => x - 1, + (x: number) => x * 10, + (x: number) => x * 100, + (x: number) => x * 1000, + (x: number) => "id-" + x, + (x: number) => [x, x], + (x: number) => ({ a: x }) + ); + assert.deepStrictEqual(a(1), [ + 2, + 0, + 10, + 100, + 1000, + "id-1", + [1, 1], + { a: 1 }, + ]); + }, - "9-args": () => { - const a = juxt( - (x: number) => x + 1, - (x: number) => x - 1, - (x: number) => x * 10, - (x: number) => x * 100, - (x: number) => x * 1000, - (x: number) => x * 10000, - (x: number) => "id-" + x, - (x: number) => [x, x], - (x: number) => ({ a: x }) - ); - assert.deepStrictEqual(a(1), [ - 2, - 0, - 10, - 100, - 1000, - 10000, - "id-1", - [1, 1], - { a: 1 }, - ]); - }, + "9-args": () => { + const a = juxt( + (x: number) => x + 1, + (x: number) => x - 1, + (x: number) => x * 10, + (x: number) => x * 100, + (x: number) => x * 1000, + (x: number) => x * 10000, + (x: number) => "id-" + x, + (x: number) => [x, x], + (x: number) => ({ a: x }) + ); + assert.deepStrictEqual(a(1), [ + 2, + 0, + 10, + 100, + 1000, + 10000, + "id-1", + [1, 1], + { a: 1 }, + ]); + }, }); diff --git a/packages/compose/test/partial.ts b/packages/compose/test/partial.ts index 4824b6e125..daebf4ed05 100644 --- a/packages/compose/test/partial.ts +++ b/packages/compose/test/partial.ts @@ -1,50 +1,50 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { partial } from "../src/index.js" +import { partial } from "../src/index.js"; const fn = ( - a: any, - b: any, - c: any, - d: any, - e: any, - f: any, - g: any, - h: any, - i: any + a: any, + b: any, + c: any, + d: any, + e: any, + f: any, + g: any, + h: any, + i: any ) => [a, b, c, d, e, f, g, h, i]; const res = [0, 1, 2, 3, 4, 5, 6, 7, 8]; group("partial", { - "1-arg": () => { - assert.deepStrictEqual(partial(fn, 0)(1, 2, 3, 4, 5, 6, 7, 8), res); - }, + "1-arg": () => { + assert.deepStrictEqual(partial(fn, 0)(1, 2, 3, 4, 5, 6, 7, 8), res); + }, - "2-arg": () => { - assert.deepStrictEqual(partial(fn, 0, 1)(2, 3, 4, 5, 6, 7, 8), res); - }, + "2-arg": () => { + assert.deepStrictEqual(partial(fn, 0, 1)(2, 3, 4, 5, 6, 7, 8), res); + }, - "3-arg": () => { - assert.deepStrictEqual(partial(fn, 0, 1, 2)(3, 4, 5, 6, 7, 8), res); - }, + "3-arg": () => { + assert.deepStrictEqual(partial(fn, 0, 1, 2)(3, 4, 5, 6, 7, 8), res); + }, - "4-arg": () => { - assert.deepStrictEqual(partial(fn, 0, 1, 2, 3)(4, 5, 6, 7, 8), res); - }, + "4-arg": () => { + assert.deepStrictEqual(partial(fn, 0, 1, 2, 3)(4, 5, 6, 7, 8), res); + }, - "5-arg": () => { - assert.deepStrictEqual(partial(fn, 0, 1, 2, 3, 4)(5, 6, 7, 8), res); - }, + "5-arg": () => { + assert.deepStrictEqual(partial(fn, 0, 1, 2, 3, 4)(5, 6, 7, 8), res); + }, - "6-arg": () => { - assert.deepStrictEqual(partial(fn, 0, 1, 2, 3, 4, 5)(6, 7, 8), res); - }, + "6-arg": () => { + assert.deepStrictEqual(partial(fn, 0, 1, 2, 3, 4, 5)(6, 7, 8), res); + }, - "7-arg": () => { - assert.deepStrictEqual(partial(fn, 0, 1, 2, 3, 4, 5, 6)(7, 8), res); - }, + "7-arg": () => { + assert.deepStrictEqual(partial(fn, 0, 1, 2, 3, 4, 5, 6)(7, 8), res); + }, - "8-arg": () => { - assert.deepStrictEqual(partial(fn, 0, 1, 2, 3, 4, 5, 6, 7)(8), res); - }, + "8-arg": () => { + assert.deepStrictEqual(partial(fn, 0, 1, 2, 3, 4, 5, 6, 7)(8), res); + }, }); diff --git a/packages/compose/tsconfig.json b/packages/compose/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/compose/tsconfig.json +++ b/packages/compose/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/csp/api-extractor.json b/packages/csp/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/csp/api-extractor.json +++ b/packages/csp/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/csp/package.json b/packages/csp/package.json index 04558f67f0..f0e56cc89a 100644 --- a/packages/csp/package.json +++ b/packages/csp/package.json @@ -1,105 +1,105 @@ { - "name": "@thi.ng/csp", - "version": "2.1.12", - "description": "ES6 promise based CSP primitives & operations", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/csp#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test", - "testasync": "tsc -p test && node build/test/async.js", - "testfile": "tsc -p test && node build/test/file.js", - "testgraph": "tsc -p test && node build/test/graph.js", - "testnode": "tsc -p test && node build/test/node.js" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/arrays": "^2.3.1", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/dcons": "^3.2.7", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/transducers": "^8.3.7" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "async", - "channel", - "csp", - "datastructure", - "multiplex", - "pipeline", - "promise", - "pubsub", - "transducer", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./buffer": { - "default": "./buffer.js" - }, - "./channel": { - "default": "./channel.js" - }, - "./mult": { - "default": "./mult.js" - }, - "./pubsub": { - "default": "./pubsub.js" - } - }, - "thi.ng": { - "related": [ - "rstream" - ], - "status": "deprecated", - "year": 2016 - } + "name": "@thi.ng/csp", + "version": "2.1.12", + "description": "ES6 promise based CSP primitives & operations", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/csp#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test", + "testasync": "tsc -p test && node build/test/async.js", + "testfile": "tsc -p test && node build/test/file.js", + "testgraph": "tsc -p test && node build/test/graph.js", + "testnode": "tsc -p test && node build/test/node.js" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/arrays": "^2.3.1", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/dcons": "^3.2.7", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/transducers": "^8.3.7" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "async", + "channel", + "csp", + "datastructure", + "multiplex", + "pipeline", + "promise", + "pubsub", + "transducer", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./buffer": { + "default": "./buffer.js" + }, + "./channel": { + "default": "./channel.js" + }, + "./mult": { + "default": "./mult.js" + }, + "./pubsub": { + "default": "./pubsub.js" + } + }, + "thi.ng": { + "related": [ + "rstream" + ], + "status": "deprecated", + "year": 2016 + } } diff --git a/packages/csp/src/api.ts b/packages/csp/src/api.ts index 1e62c5dc35..9457437356 100644 --- a/packages/csp/src/api.ts +++ b/packages/csp/src/api.ts @@ -2,33 +2,33 @@ import type { Fn, IID, ILength, IRelease } from "@thi.ng/api"; import type { Channel } from "./channel.js"; export interface ChannelItem { - value(): Promise; - resolve(success: boolean): void; + value(): Promise; + resolve(success: boolean): void; } export interface IBuffer extends ILength, IRelease { - isEmpty(): boolean; - isFull(): boolean; - drop(): ChannelItem | undefined; - push(x: ChannelItem): boolean; + isEmpty(): boolean; + isFull(): boolean; + drop(): ChannelItem | undefined; + push(x: ChannelItem): boolean; } export interface IChannel extends IID { - channel(): Channel; - close(flush?: boolean): Promise | undefined; + channel(): Channel; + close(flush?: boolean): Promise | undefined; } export interface IReadableChannel extends IChannel { - read(): Promise; + read(): Promise; } export interface IWriteableChannel extends IChannel { - write(val: any): Promise; + write(val: any): Promise; } export interface IReadWriteableChannel - extends IReadableChannel, - IWriteableChannel {} + extends IReadableChannel, + IWriteableChannel {} export type TopicFn = Fn; diff --git a/packages/csp/src/buffer.ts b/packages/csp/src/buffer.ts index 3ce3230b13..59abdfaca8 100644 --- a/packages/csp/src/buffer.ts +++ b/packages/csp/src/buffer.ts @@ -2,76 +2,76 @@ import { DCons } from "@thi.ng/dcons/dcons"; import type { ChannelItem, IBuffer } from "./api.js"; export class FixedBuffer implements IBuffer { - buf: DCons>; - limit: number; + buf: DCons>; + limit: number; - constructor(limit = 1) { - this.buf = new DCons(); - this.limit = limit; - } + constructor(limit = 1) { + this.buf = new DCons(); + this.limit = limit; + } - get length() { - return this.buf.length; - } + get length() { + return this.buf.length; + } - isEmpty() { - return this.buf.length === 0; - } + isEmpty() { + return this.buf.length === 0; + } - isFull() { - return this.buf.length >= this.limit; - } + isFull() { + return this.buf.length >= this.limit; + } - release() { - return this.buf.release(); - } + release() { + return this.buf.release(); + } - push(x: ChannelItem) { - if (!this.isFull()) { - this.buf.push(x); - return true; - } - return false; - } + push(x: ChannelItem) { + if (!this.isFull()) { + this.buf.push(x); + return true; + } + return false; + } - drop() { - if (!this.isEmpty()) { - return this.buf.drop(); - } - } + drop() { + if (!this.isEmpty()) { + return this.buf.drop(); + } + } } export class DroppingBuffer extends FixedBuffer { - constructor(limit = 1) { - super(limit); - } + constructor(limit = 1) { + super(limit); + } - isFull() { - return false; - } + isFull() { + return false; + } - push(x: ChannelItem) { - if (this.buf.length < this.limit) { - this.buf.push(x); - } - return true; - } + push(x: ChannelItem) { + if (this.buf.length < this.limit) { + this.buf.push(x); + } + return true; + } } export class SlidingBuffer extends FixedBuffer { - constructor(limit = 1) { - super(limit); - } + constructor(limit = 1) { + super(limit); + } - isFull() { - return false; - } + isFull() { + return false; + } - push(x: ChannelItem) { - if (this.buf.length >= this.limit) { - this.buf.drop(); - } - this.buf.push(x); - return true; - } + push(x: ChannelItem) { + if (this.buf.length >= this.limit) { + this.buf.drop(); + } + this.buf.push(x); + return true; + } } diff --git a/packages/csp/src/channel.ts b/packages/csp/src/channel.ts index 48d80c48a4..fe21922de1 100644 --- a/packages/csp/src/channel.ts +++ b/packages/csp/src/channel.ts @@ -9,621 +9,621 @@ import { delayed } from "@thi.ng/transducers/delayed"; import { range } from "@thi.ng/transducers/range"; import { isReduced, unreduced } from "@thi.ng/transducers/reduced"; import type { - ChannelItem, - ErrorHandler, - IBuffer, - IReadWriteableChannel, + ChannelItem, + ErrorHandler, + IBuffer, + IReadWriteableChannel, } from "./api.js"; import { FixedBuffer } from "./buffer.js"; const enum State { - OPEN, - CLOSED, - DONE, + OPEN, + CLOSED, + DONE, } export class Channel implements IReadWriteableChannel { - static constantly(x: T, delay?: number) { - const chan = new Channel(delay ? delayed(delay) : null); - chan.produce(() => x); - return chan; - } - - static repeatedly(fn: Fn0, delay?: number) { - const chan = new Channel(delay ? delayed(delay) : null); - chan.produce(fn); - return chan; - } - - static cycle(src: Iterable, delay?: number) { - return Channel.from(cycle(src), delay ? delayed(delay) : null); - } - - static range(): Channel; - static range(to: number): Channel; - static range(from: number, to: number): Channel; - static range(from: number, to: number, step: number): Channel; - static range( - from: number, - to: number, - step: number, - delay: number - ): Channel; - static range(...args: any[]): Channel { - const [from, to, step, delay] = args; - return Channel.from( - range(from, to, step), - delay !== undefined ? delayed(delay) : null - ); - } - - /** - * Constructs new channel which closes automatically after given period. - * - * @param delay - time in ms - */ - static timeout(delay: number): Channel { - const chan = new Channel(`timeout-${Channel.NEXT_ID++}`); - setTimeout(() => chan.close(), delay); - return chan; - } - - /** - * Shorthand for: `Channel.timeout(delay).take()` - * - * @param delay - time in ms - */ - static sleep(delay: number) { - return Channel.timeout(delay).read(); - } - - /** - * Creates new channel with single value from given promise, then closes - * automatically iff promise has been resolved. - * - * @param p - promise - */ - static fromPromise(p: Promise) { - const chan = new Channel(); - p.then((x) => - (async () => { - await chan.write(x); - await chan.close(); - return x; - })() - ); - return chan; - } - - static from(src: Iterable): Channel; - static from(src: Iterable, close: boolean): Channel; - static from(src: Iterable, tx: Transducer): Channel; - static from( - src: Iterable, - tx: Transducer, - close: boolean - ): Channel; - static from(...args: any[]) { - let close, tx; - switch (args.length) { - case 1: - break; - case 2: - if (typeof args[1] === "boolean") { - close = args[1]; - } else { - tx = args[1]; - } - break; - case 3: - tx = args[1]; - close = args[2]; - break; - default: - illegalArity(args.length); - } - const chan = new Channel(tx); - chan.into(args[0], close); - return chan; - } - - /** - * Takes an array of channels and blocks until any of them becomes - * readable (or has been closed). The returned promised resolves into - * an array of `[value, channel]`. Channel order is repeatedly - * shuffled for each read attempt. - * - * @param chans - source channels - */ - static select(chans: Channel[]) { - return new Promise((resolve) => { - const _select = () => { - for (let c of shuffle(chans)) { - if (c.isReadable() || c.isClosed()) { - c.read().then((x: any) => resolve([x, c])); - return; - } - } - Channel.SCHEDULE.call(null, _select, 0); - }; - Channel.SCHEDULE.call(null, _select, 0); - }); - } - - /** - * Takes an array of channels to merge into new channel. Any closed - * channels will be automatically removed from the input selection. - * Once all inputs are closed, the target channel will close too (by - * default). - * - * @remarks - * If `named` is true, the merged channel will have tuples of: - * `[src-id, val]` If false (default), only received values will be - * forwarded. - * - * @param chans - source channels - * @param out - result channel - * @param close - true, if result closes - * @param named - true, to emit labeled tuples - */ - static merge( - chans: Channel[], - out?: Channel, - close = true, - named = false - ) { - out = out || new Channel(); - (async () => { - while (true) { - let [x, ch] = await Channel.select(chans); - if (x === undefined) { - chans.splice(chans.indexOf(ch), 1); - if (!chans.length) { - close && (await out.close()); - break; - } - } else { - await out.write(named ? [ch.id, x] : x); - } - } - })(); - return out; - } - - /** - * Takes an array of channels to merge into new channel of tuples. - * Whereas `Channel.merge()` realizes a sequential merging with no - * guarantees about ordering of the output. - * - * @remarks - * The output channel of this function will collect values from all - * channels and a new tuple is emitted only once a new value has - * been read from ALL channels. Therefore the overall throughput is - * dictated by the slowest of the inputs. - * - * Once any of the inputs closes, the process is terminated and the - * output channel is closed too (by default). - * - * @example - * ```ts - * Channel.mergeTuples([ - * Channel.from([1, 2, 3]), - * Channel.from([10, 20, 30]), - * Channel.from([100, 200, 300]) - * ]).consume(); - * - * // chan-0 : [ 1, 10, 100 ] - * // chan-0 : [ 2, 20, 200 ] - * // chan-0 : [ 3, 30, 300 ] - * // chan-0 done - * - * Channel.mergeTuples([ - * Channel.from([1, 2, 3]), - * Channel.from([10, 20, 30]), - * Channel.from([100, 200, 300]) - * ], null, false).consume(); - * ``` - * - * @param chans - source channels - * @param out - result channel - * @param closeOnFirst - true, if result closes when first input is done - * @param closeOutput - true, if result closes when all inputs are done - */ - static mergeTuples( - chans: Channel[], - out?: Channel, - closeOnFirst = true, - closeOutput = true - ) { - out = out || new Channel(); - (async () => { - let buf = []; - let orig = [...chans]; - let sel = new Set(chans); - let n = chans.length; - while (true) { - let [x, ch] = await Channel.select([...sel]); - let idx = orig.indexOf(ch); - if (x === undefined) { - if (closeOnFirst || chans.length === 1) { - break; - } - chans.splice(idx, 1); - } - buf[idx] = x; - sel.delete(ch); - if (--n === 0) { - await out.write(buf); - buf = []; - n = chans.length; - sel = new Set(chans); - } - } - closeOutput && (await out.close()); - })(); - return out; - } - - static MAX_WRITES = 1024; - static NEXT_ID = 0; - - static SCHEDULE: Fn2, number, void> = - typeof setImmediate === "function" ? setImmediate : setTimeout; - - private static RFN: Reducer, any> = [ - (() => null), - (acc) => acc, - (acc: DCons, x) => acc.push(x), - ]; - - id: string; - onerror: ErrorHandler; - - protected state: State; - protected buf: IBuffer; - protected tx: Reducer, T>; - protected writes: DCons>; - protected reads: DCons<(x: T) => void>; - protected txbuf: DCons; - - protected isBusy: boolean; - - constructor(); - constructor(id: string); - constructor(buf: number | IBuffer); - constructor(tx: Transducer); - constructor(tx: Transducer, err: ErrorHandler); - constructor(id: string, buf: number | IBuffer); - constructor(id: string, tx: Transducer); - constructor(id: string, tx: Transducer, err: ErrorHandler); - constructor(id: string, buf: number | IBuffer, tx: Transducer); - constructor( - id: string, - buf: number | IBuffer, - tx: Transducer, - err: ErrorHandler - ); - constructor(...args: any[]) { - let id, buf, tx, err; - let [a, b] = args; - switch (args.length) { - case 0: - break; - case 1: - if (typeof a === "string") { - id = a; - } else if (maybeBuffer(a)) { - buf = a; - } else { - tx = a; - } - break; - case 2: - if (typeof a === "string") { - id = a; - if (maybeBuffer(b)) { - buf = b; - } else { - tx = b; - } - } else { - [tx, err] = args; - } - break; - case 3: - if (isFunction(args[1]) && isFunction(args[2])) { - [id, tx, err] = args; - } else { - [id, buf, tx] = args; - } - break; - case 4: - [id, buf, tx, err] = args; - break; - default: - illegalArity(args.length); - } - this.id = id || `chan-${Channel.NEXT_ID++}`; - buf = buf || 1; - this.buf = typeof buf === "number" ? new FixedBuffer(buf) : buf; - this.writes = new DCons(); - this.reads = new DCons(); - this.txbuf = new DCons(); - this.tx = tx ? tx(Channel.RFN) : null; - this.onerror = tx && (err || defaultErrorHandler); - this.state = State.OPEN; - this.isBusy = false; - } - - channel() { - return this; - } - - write(value: any): Promise { - return new Promise((resolve) => { - if (this.state !== State.OPEN) { - resolve(false); - } - if (this.writes.length < Channel.MAX_WRITES) { - this.writes.push({ - value: this.tx - ? async () => { - try { - if ( - isReduced(this.tx[2](this.txbuf, value)) - ) { - this.state = State.CLOSED; - } - } catch (e) { - this.onerror(e, this, value); - } - } - : () => value, - resolve, - }); - this.process(); - } else { - throw new Error( - `channel stalled (${Channel.MAX_WRITES} unprocessed writes)` - ); - } - }); - } - - read(): Promise { - return new Promise((resolve) => { - if (this.state === State.DONE) { - resolve(undefined); - } - this.reads.push(resolve); - this.process(); - }); - } - - tryRead(timeout = 1000) { - return new Promise((resolve) => { - (async () => - resolve( - (await Channel.select([this, Channel.timeout(timeout)]))[0] - ))(); - }); - } - - close(flush = false) { - if (this.state === State.OPEN) { - this.state = State.CLOSED; - flush && this.flush(); - return this.process(); - } - } - - isClosed() { - return this.state !== State.OPEN; - } - - isReadable() { - return ( - (this.state !== State.DONE && this.buf && this.buf.length > 0) || - (this.writes && this.writes.length > 0) || - (this.txbuf && this.txbuf.length > 0) - ); - } - - consume(fn: Fn = (x) => console.log(this.id, ":", x)) { - return (async () => { - let x: Nullable; - while (((x = null), (x = await this.read())) !== undefined) { - await fn(x); - } - })(); - } - - produce(fn: Fn0, close = true) { - return (async () => { - while (!this.isClosed()) { - const val = await fn(); - if (val === undefined) { - close && (await this.close()); - break; - } - await this.write(val); - } - })(); - } - - consumeWhileReadable(fn: Fn = (x) => console.log(this.id, ":", x)) { - return (async () => { - let x; - while (this.isReadable()) { - x = await this.read(); - if (x === undefined) { - break; - } - await fn(x); - x = null; - } - })(); - } - - reduce(rfn: Reducer, acc?: A): Promise { - return (async () => { - const [init, complete, reduce] = rfn; - acc = acc != null ? acc : init(); - let x: Nullable; - while (((x = null), (x = await this.read())) !== undefined) { - acc = reduce(acc!, x); - if (isReduced(acc)) { - acc = (acc).deref(); - break; - } - } - return unreduced(complete(acc!)); - })(); - } - - transduce( - tx: Transducer, - rfn: Reducer, - acc?: A - ): Promise { - return (async () => { - const _rfn = tx(rfn); - return unreduced(_rfn[1](await this.reduce(_rfn, acc))); - })(); - } - - into(src: Iterable, close = true) { - return (async () => { - for (let x of src) { - if (this.isClosed()) { - break; - } - await this.write(x); - } - close && (await this.close()); - })(); - } - - pipe(dest: Channel | Transducer, close = true) { - if (!(dest instanceof Channel)) { - dest = new Channel(dest); - } - this.consume((x: T) => (>dest).write(x)) // return undefined here? - .then(() => { - close && (>dest).close(); - }); - return dest; - } - - split( - pred: Predicate, - truthy?: Channel, - falsey?: Channel, - close = true - ) { - if (!(truthy instanceof Channel)) { - truthy = new Channel(); - } - if (!(falsey instanceof Channel)) { - falsey = new Channel(); - } - this.consume((x: T) => (pred(x) ? truthy! : falsey!).write(x)).then( - () => { - close && (truthy!.close(), falsey!.close()); - } - ); - return [truthy, falsey]; - } - - concat(chans: Iterable>, close = true) { - return (async () => { - for (let c of chans) { - await c.consume((x: T) => this.write(x)); - } - close && (await this.close()); - })(); - } - - release() { - if (this.state === State.CLOSED) { - this.state = State.DONE; - this.flush(); - this.buf.release(); - delete (this).reads; - delete (this).writes; - delete (this).buf; - delete (this).txbuf; - delete (this).tx; - delete (this).isBusy; - delete (this).onerror; - } - } - - protected async process() { - if (!this.isBusy) { - this.isBusy = true; - const { buf, txbuf, reads, writes } = this; - let doProcess: any = true; - while (doProcess) { - while (reads.length && (txbuf.length || buf.length)) { - if (txbuf.length) { - const val = txbuf.drop(); - if (val !== undefined) { - reads.drop()!(val); - } - } else { - const val = await buf.drop()!.value(); - if (val !== undefined) { - reads.drop()!(val); - } - } - } - while (writes.length && !buf.isFull()) { - const put = writes.drop()!; - buf.push(put); - put.resolve(true); - } - if (this.state === State.CLOSED) { - if (this.tx && !writes.length) { - try { - // finalize/complete transducer - this.tx[1](this.txbuf); - } catch (e) { - this.onerror(e, this); - } - } - if (!this.isReadable()) { - this.release(); - return; - } - } - doProcess = - (reads.length && (txbuf.length || buf.length)) || - (writes.length && !buf.isFull()); - } - this.isBusy = false; - } - } - - protected flush() { - let op: any; - while ((op = this.reads.drop())) { - op(); - } - while ((op = this.writes.drop())) { - op.resolve(false); - } - this.buf.release(); - } + static constantly(x: T, delay?: number) { + const chan = new Channel(delay ? delayed(delay) : null); + chan.produce(() => x); + return chan; + } + + static repeatedly(fn: Fn0, delay?: number) { + const chan = new Channel(delay ? delayed(delay) : null); + chan.produce(fn); + return chan; + } + + static cycle(src: Iterable, delay?: number) { + return Channel.from(cycle(src), delay ? delayed(delay) : null); + } + + static range(): Channel; + static range(to: number): Channel; + static range(from: number, to: number): Channel; + static range(from: number, to: number, step: number): Channel; + static range( + from: number, + to: number, + step: number, + delay: number + ): Channel; + static range(...args: any[]): Channel { + const [from, to, step, delay] = args; + return Channel.from( + range(from, to, step), + delay !== undefined ? delayed(delay) : null + ); + } + + /** + * Constructs new channel which closes automatically after given period. + * + * @param delay - time in ms + */ + static timeout(delay: number): Channel { + const chan = new Channel(`timeout-${Channel.NEXT_ID++}`); + setTimeout(() => chan.close(), delay); + return chan; + } + + /** + * Shorthand for: `Channel.timeout(delay).take()` + * + * @param delay - time in ms + */ + static sleep(delay: number) { + return Channel.timeout(delay).read(); + } + + /** + * Creates new channel with single value from given promise, then closes + * automatically iff promise has been resolved. + * + * @param p - promise + */ + static fromPromise(p: Promise) { + const chan = new Channel(); + p.then((x) => + (async () => { + await chan.write(x); + await chan.close(); + return x; + })() + ); + return chan; + } + + static from(src: Iterable): Channel; + static from(src: Iterable, close: boolean): Channel; + static from(src: Iterable, tx: Transducer): Channel; + static from( + src: Iterable, + tx: Transducer, + close: boolean + ): Channel; + static from(...args: any[]) { + let close, tx; + switch (args.length) { + case 1: + break; + case 2: + if (typeof args[1] === "boolean") { + close = args[1]; + } else { + tx = args[1]; + } + break; + case 3: + tx = args[1]; + close = args[2]; + break; + default: + illegalArity(args.length); + } + const chan = new Channel(tx); + chan.into(args[0], close); + return chan; + } + + /** + * Takes an array of channels and blocks until any of them becomes + * readable (or has been closed). The returned promised resolves into + * an array of `[value, channel]`. Channel order is repeatedly + * shuffled for each read attempt. + * + * @param chans - source channels + */ + static select(chans: Channel[]) { + return new Promise((resolve) => { + const _select = () => { + for (let c of shuffle(chans)) { + if (c.isReadable() || c.isClosed()) { + c.read().then((x: any) => resolve([x, c])); + return; + } + } + Channel.SCHEDULE.call(null, _select, 0); + }; + Channel.SCHEDULE.call(null, _select, 0); + }); + } + + /** + * Takes an array of channels to merge into new channel. Any closed + * channels will be automatically removed from the input selection. + * Once all inputs are closed, the target channel will close too (by + * default). + * + * @remarks + * If `named` is true, the merged channel will have tuples of: + * `[src-id, val]` If false (default), only received values will be + * forwarded. + * + * @param chans - source channels + * @param out - result channel + * @param close - true, if result closes + * @param named - true, to emit labeled tuples + */ + static merge( + chans: Channel[], + out?: Channel, + close = true, + named = false + ) { + out = out || new Channel(); + (async () => { + while (true) { + let [x, ch] = await Channel.select(chans); + if (x === undefined) { + chans.splice(chans.indexOf(ch), 1); + if (!chans.length) { + close && (await out.close()); + break; + } + } else { + await out.write(named ? [ch.id, x] : x); + } + } + })(); + return out; + } + + /** + * Takes an array of channels to merge into new channel of tuples. + * Whereas `Channel.merge()` realizes a sequential merging with no + * guarantees about ordering of the output. + * + * @remarks + * The output channel of this function will collect values from all + * channels and a new tuple is emitted only once a new value has + * been read from ALL channels. Therefore the overall throughput is + * dictated by the slowest of the inputs. + * + * Once any of the inputs closes, the process is terminated and the + * output channel is closed too (by default). + * + * @example + * ```ts + * Channel.mergeTuples([ + * Channel.from([1, 2, 3]), + * Channel.from([10, 20, 30]), + * Channel.from([100, 200, 300]) + * ]).consume(); + * + * // chan-0 : [ 1, 10, 100 ] + * // chan-0 : [ 2, 20, 200 ] + * // chan-0 : [ 3, 30, 300 ] + * // chan-0 done + * + * Channel.mergeTuples([ + * Channel.from([1, 2, 3]), + * Channel.from([10, 20, 30]), + * Channel.from([100, 200, 300]) + * ], null, false).consume(); + * ``` + * + * @param chans - source channels + * @param out - result channel + * @param closeOnFirst - true, if result closes when first input is done + * @param closeOutput - true, if result closes when all inputs are done + */ + static mergeTuples( + chans: Channel[], + out?: Channel, + closeOnFirst = true, + closeOutput = true + ) { + out = out || new Channel(); + (async () => { + let buf = []; + let orig = [...chans]; + let sel = new Set(chans); + let n = chans.length; + while (true) { + let [x, ch] = await Channel.select([...sel]); + let idx = orig.indexOf(ch); + if (x === undefined) { + if (closeOnFirst || chans.length === 1) { + break; + } + chans.splice(idx, 1); + } + buf[idx] = x; + sel.delete(ch); + if (--n === 0) { + await out.write(buf); + buf = []; + n = chans.length; + sel = new Set(chans); + } + } + closeOutput && (await out.close()); + })(); + return out; + } + + static MAX_WRITES = 1024; + static NEXT_ID = 0; + + static SCHEDULE: Fn2, number, void> = + typeof setImmediate === "function" ? setImmediate : setTimeout; + + private static RFN: Reducer, any> = [ + (() => null), + (acc) => acc, + (acc: DCons, x) => acc.push(x), + ]; + + id: string; + onerror: ErrorHandler; + + protected state: State; + protected buf: IBuffer; + protected tx: Reducer, T>; + protected writes: DCons>; + protected reads: DCons<(x: T) => void>; + protected txbuf: DCons; + + protected isBusy: boolean; + + constructor(); + constructor(id: string); + constructor(buf: number | IBuffer); + constructor(tx: Transducer); + constructor(tx: Transducer, err: ErrorHandler); + constructor(id: string, buf: number | IBuffer); + constructor(id: string, tx: Transducer); + constructor(id: string, tx: Transducer, err: ErrorHandler); + constructor(id: string, buf: number | IBuffer, tx: Transducer); + constructor( + id: string, + buf: number | IBuffer, + tx: Transducer, + err: ErrorHandler + ); + constructor(...args: any[]) { + let id, buf, tx, err; + let [a, b] = args; + switch (args.length) { + case 0: + break; + case 1: + if (typeof a === "string") { + id = a; + } else if (maybeBuffer(a)) { + buf = a; + } else { + tx = a; + } + break; + case 2: + if (typeof a === "string") { + id = a; + if (maybeBuffer(b)) { + buf = b; + } else { + tx = b; + } + } else { + [tx, err] = args; + } + break; + case 3: + if (isFunction(args[1]) && isFunction(args[2])) { + [id, tx, err] = args; + } else { + [id, buf, tx] = args; + } + break; + case 4: + [id, buf, tx, err] = args; + break; + default: + illegalArity(args.length); + } + this.id = id || `chan-${Channel.NEXT_ID++}`; + buf = buf || 1; + this.buf = typeof buf === "number" ? new FixedBuffer(buf) : buf; + this.writes = new DCons(); + this.reads = new DCons(); + this.txbuf = new DCons(); + this.tx = tx ? tx(Channel.RFN) : null; + this.onerror = tx && (err || defaultErrorHandler); + this.state = State.OPEN; + this.isBusy = false; + } + + channel() { + return this; + } + + write(value: any): Promise { + return new Promise((resolve) => { + if (this.state !== State.OPEN) { + resolve(false); + } + if (this.writes.length < Channel.MAX_WRITES) { + this.writes.push({ + value: this.tx + ? async () => { + try { + if ( + isReduced(this.tx[2](this.txbuf, value)) + ) { + this.state = State.CLOSED; + } + } catch (e) { + this.onerror(e, this, value); + } + } + : () => value, + resolve, + }); + this.process(); + } else { + throw new Error( + `channel stalled (${Channel.MAX_WRITES} unprocessed writes)` + ); + } + }); + } + + read(): Promise { + return new Promise((resolve) => { + if (this.state === State.DONE) { + resolve(undefined); + } + this.reads.push(resolve); + this.process(); + }); + } + + tryRead(timeout = 1000) { + return new Promise((resolve) => { + (async () => + resolve( + (await Channel.select([this, Channel.timeout(timeout)]))[0] + ))(); + }); + } + + close(flush = false) { + if (this.state === State.OPEN) { + this.state = State.CLOSED; + flush && this.flush(); + return this.process(); + } + } + + isClosed() { + return this.state !== State.OPEN; + } + + isReadable() { + return ( + (this.state !== State.DONE && this.buf && this.buf.length > 0) || + (this.writes && this.writes.length > 0) || + (this.txbuf && this.txbuf.length > 0) + ); + } + + consume(fn: Fn = (x) => console.log(this.id, ":", x)) { + return (async () => { + let x: Nullable; + while (((x = null), (x = await this.read())) !== undefined) { + await fn(x); + } + })(); + } + + produce(fn: Fn0, close = true) { + return (async () => { + while (!this.isClosed()) { + const val = await fn(); + if (val === undefined) { + close && (await this.close()); + break; + } + await this.write(val); + } + })(); + } + + consumeWhileReadable(fn: Fn = (x) => console.log(this.id, ":", x)) { + return (async () => { + let x; + while (this.isReadable()) { + x = await this.read(); + if (x === undefined) { + break; + } + await fn(x); + x = null; + } + })(); + } + + reduce(rfn: Reducer, acc?: A): Promise { + return (async () => { + const [init, complete, reduce] = rfn; + acc = acc != null ? acc : init(); + let x: Nullable; + while (((x = null), (x = await this.read())) !== undefined) { + acc = reduce(acc!, x); + if (isReduced(acc)) { + acc = (acc).deref(); + break; + } + } + return unreduced(complete(acc!)); + })(); + } + + transduce( + tx: Transducer, + rfn: Reducer, + acc?: A + ): Promise { + return (async () => { + const _rfn = tx(rfn); + return unreduced(_rfn[1](await this.reduce(_rfn, acc))); + })(); + } + + into(src: Iterable, close = true) { + return (async () => { + for (let x of src) { + if (this.isClosed()) { + break; + } + await this.write(x); + } + close && (await this.close()); + })(); + } + + pipe(dest: Channel | Transducer, close = true) { + if (!(dest instanceof Channel)) { + dest = new Channel(dest); + } + this.consume((x: T) => (>dest).write(x)) // return undefined here? + .then(() => { + close && (>dest).close(); + }); + return dest; + } + + split( + pred: Predicate, + truthy?: Channel, + falsey?: Channel, + close = true + ) { + if (!(truthy instanceof Channel)) { + truthy = new Channel(); + } + if (!(falsey instanceof Channel)) { + falsey = new Channel(); + } + this.consume((x: T) => (pred(x) ? truthy! : falsey!).write(x)).then( + () => { + close && (truthy!.close(), falsey!.close()); + } + ); + return [truthy, falsey]; + } + + concat(chans: Iterable>, close = true) { + return (async () => { + for (let c of chans) { + await c.consume((x: T) => this.write(x)); + } + close && (await this.close()); + })(); + } + + release() { + if (this.state === State.CLOSED) { + this.state = State.DONE; + this.flush(); + this.buf.release(); + delete (this).reads; + delete (this).writes; + delete (this).buf; + delete (this).txbuf; + delete (this).tx; + delete (this).isBusy; + delete (this).onerror; + } + } + + protected async process() { + if (!this.isBusy) { + this.isBusy = true; + const { buf, txbuf, reads, writes } = this; + let doProcess: any = true; + while (doProcess) { + while (reads.length && (txbuf.length || buf.length)) { + if (txbuf.length) { + const val = txbuf.drop(); + if (val !== undefined) { + reads.drop()!(val); + } + } else { + const val = await buf.drop()!.value(); + if (val !== undefined) { + reads.drop()!(val); + } + } + } + while (writes.length && !buf.isFull()) { + const put = writes.drop()!; + buf.push(put); + put.resolve(true); + } + if (this.state === State.CLOSED) { + if (this.tx && !writes.length) { + try { + // finalize/complete transducer + this.tx[1](this.txbuf); + } catch (e) { + this.onerror(e, this); + } + } + if (!this.isReadable()) { + this.release(); + return; + } + } + doProcess = + (reads.length && (txbuf.length || buf.length)) || + (writes.length && !buf.isFull()); + } + this.isBusy = false; + } + } + + protected flush() { + let op: any; + while ((op = this.reads.drop())) { + op(); + } + while ((op = this.writes.drop())) { + op.resolve(false); + } + this.buf.release(); + } } const defaultErrorHandler = (e: Error, chan: Channel, val?: any) => - console.log( - chan.id, - "error occurred", - e.message, - val !== undefined ? val : "" - ); + console.log( + chan.id, + "error occurred", + e.message, + val !== undefined ? val : "" + ); const maybeBuffer = (x: any) => - x instanceof FixedBuffer || typeof x === "number"; + x instanceof FixedBuffer || typeof x === "number"; diff --git a/packages/csp/src/mult.ts b/packages/csp/src/mult.ts index dd1da65e5d..2c08c0ab89 100644 --- a/packages/csp/src/mult.ts +++ b/packages/csp/src/mult.ts @@ -5,121 +5,121 @@ import type { IWriteableChannel } from "./api.js"; import { Channel } from "./channel.js"; export class Mult implements IWriteableChannel { - protected static nextID = 0; + protected static nextID = 0; - protected src: Channel; - protected taps: DCons>; - protected tapID = 0; + protected src: Channel; + protected taps: DCons>; + protected tapID = 0; - constructor(); - constructor(id: string); - constructor(src: Channel); - constructor(tx: Transducer); - constructor(id: string, tx: Transducer); - constructor(...args: any[]) { - let id, src; - switch (args.length) { - case 2: - id = args[0]; - src = args[1]; - break; - case 1: - if (typeof args[0] === "string") { - id = args[0]; - } else { - src = args[0]; - } - break; - case 0: - id = "mult" + Mult.nextID++; - break; - default: - illegalArity(args.length); - } - if (src instanceof Channel) { - this.src = src; - } else { - this.src = new Channel(id, src); - } - this.taps = new DCons(); - this.process(); - } + constructor(); + constructor(id: string); + constructor(src: Channel); + constructor(tx: Transducer); + constructor(id: string, tx: Transducer); + constructor(...args: any[]) { + let id, src; + switch (args.length) { + case 2: + id = args[0]; + src = args[1]; + break; + case 1: + if (typeof args[0] === "string") { + id = args[0]; + } else { + src = args[0]; + } + break; + case 0: + id = "mult" + Mult.nextID++; + break; + default: + illegalArity(args.length); + } + if (src instanceof Channel) { + this.src = src; + } else { + this.src = new Channel(id, src); + } + this.taps = new DCons(); + this.process(); + } - get id() { - return this.src && this.src.id; - } + get id() { + return this.src && this.src.id; + } - set id(id: string) { - this.src && (this.src.id = id); - } + set id(id: string) { + this.src && (this.src.id = id); + } - channel() { - return this.src; - } + channel() { + return this.src; + } - write(val: any) { - if (this.src) { - return this.src.write(val); - } - return Promise.resolve(false); - } + write(val: any) { + if (this.src) { + return this.src.write(val); + } + return Promise.resolve(false); + } - close(flush = false) { - return this.src ? this.src.close(flush) : undefined; - } + close(flush = false) { + return this.src ? this.src.close(flush) : undefined; + } - tap(ch?: Channel | Transducer) { - if (this.taps) { - if (!(ch instanceof Channel)) { - ch = new Channel(this.src.id + "-tap" + this.tapID++, ch!); - } else if (this.taps.find(ch)) { - return ch; - } - this.taps.push(ch); - return ch; - } - } + tap(ch?: Channel | Transducer) { + if (this.taps) { + if (!(ch instanceof Channel)) { + ch = new Channel(this.src.id + "-tap" + this.tapID++, ch!); + } else if (this.taps.find(ch)) { + return ch; + } + this.taps.push(ch); + return ch; + } + } - untap(ch: Channel) { - if (this.taps) { - const t = this.taps.find(ch); - if (t) { - this.taps.remove(t); - return true; - } - } - return false; - } + untap(ch: Channel) { + if (this.taps) { + const t = this.taps.find(ch); + if (t) { + this.taps.remove(t); + return true; + } + } + return false; + } - untapAll(close = true) { - if (this.taps) { - let tap = this.taps.head; - while (tap) { - close && tap.value.close(); - this.taps.remove(tap); - tap = tap.next; - } - return true; - } - return false; - } + untapAll(close = true) { + if (this.taps) { + let tap = this.taps.head; + while (tap) { + close && tap.value.close(); + this.taps.remove(tap); + tap = tap.next; + } + return true; + } + return false; + } - protected async process() { - let x; - while (((x = null), (x = await this.src.read())) !== undefined) { - let t = this.taps.head; - while (t) { - if (!(await t.value.write(x))) { - this.taps.remove(t); - } - t = t.next; - } - } - for (let t of this.taps) { - await t.close(); - } - delete (this).src; - delete (this).taps; - delete (this).tapID; - } + protected async process() { + let x; + while (((x = null), (x = await this.src.read())) !== undefined) { + let t = this.taps.head; + while (t) { + if (!(await t.value.write(x))) { + this.taps.remove(t); + } + t = t.next; + } + } + for (let t of this.taps) { + await t.close(); + } + delete (this).src; + delete (this).taps; + delete (this).tapID; + } } diff --git a/packages/csp/src/pubsub.ts b/packages/csp/src/pubsub.ts index c753545d13..cf08686fba 100644 --- a/packages/csp/src/pubsub.ts +++ b/packages/csp/src/pubsub.ts @@ -6,104 +6,104 @@ import { Channel } from "./channel.js"; import { Mult } from "./mult.js"; export class PubSub implements IWriteableChannel { - protected static NEXT_ID = 0; + protected static NEXT_ID = 0; - protected src!: Channel; - protected fn!: TopicFn; - protected topics: IObjectOf>; + protected src!: Channel; + protected fn!: TopicFn; + protected topics: IObjectOf>; - constructor(fn: TopicFn); - constructor(src: Channel, fn: TopicFn); - constructor(...args: any[]) { - switch (args.length) { - case 2: - this.src = args[0]; - this.fn = args[1]; - break; - case 1: - this.src = new Channel("pubsub" + PubSub.NEXT_ID++); - this.fn = args[0]; - break; - default: - illegalArity(args.length); - } - this.topics = {}; - this.process(); - } + constructor(fn: TopicFn); + constructor(src: Channel, fn: TopicFn); + constructor(...args: any[]) { + switch (args.length) { + case 2: + this.src = args[0]; + this.fn = args[1]; + break; + case 1: + this.src = new Channel("pubsub" + PubSub.NEXT_ID++); + this.fn = args[0]; + break; + default: + illegalArity(args.length); + } + this.topics = {}; + this.process(); + } - get id() { - return this.src && this.src.id; - } + get id() { + return this.src && this.src.id; + } - set id(id: string) { - this.src && (this.src.id = id); - } + set id(id: string) { + this.src && (this.src.id = id); + } - channel() { - return this.src; - } + channel() { + return this.src; + } - write(val: any) { - if (this.src) { - return this.src.write(val); - } - return Promise.resolve(false); - } + write(val: any) { + if (this.src) { + return this.src.write(val); + } + return Promise.resolve(false); + } - close(flush = false) { - return this.src ? this.src.close(flush) : undefined; - } + close(flush = false) { + return this.src ? this.src.close(flush) : undefined; + } - /** - * Creates a new topic subscription channel and returns it. - * Each topic is managed by its own {@link Mult} and can have arbitrary - * number of subscribers. If the optional transducer is given, it will - * only be applied to the new subscription channel. - * - * The special "*" topic can be used to subscribe to all messages and - * acts as multiplexed pass-through of the source channel. - * - * @param id - topic id - * @param tx - transducer for new subscription - */ - sub(id: string, tx?: Transducer) { - let topic = this.topics[id]; - if (!topic) { - this.topics[id] = topic = new Mult(this.src.id + "-" + id); - } - return topic.tap(tx); - } + /** + * Creates a new topic subscription channel and returns it. + * Each topic is managed by its own {@link Mult} and can have arbitrary + * number of subscribers. If the optional transducer is given, it will + * only be applied to the new subscription channel. + * + * The special "*" topic can be used to subscribe to all messages and + * acts as multiplexed pass-through of the source channel. + * + * @param id - topic id + * @param tx - transducer for new subscription + */ + sub(id: string, tx?: Transducer) { + let topic = this.topics[id]; + if (!topic) { + this.topics[id] = topic = new Mult(this.src.id + "-" + id); + } + return topic.tap(tx); + } - unsub(id: string, ch: Channel) { - let topic = this.topics[id]; - if (topic) { - return topic.untap(ch); - } - return false; - } + unsub(id: string, ch: Channel) { + let topic = this.topics[id]; + if (topic) { + return topic.untap(ch); + } + return false; + } - unsubAll(id: string, close = true) { - let topic = this.topics[id]; - if (topic) { - return topic.untapAll(close); - } - return false; - } + unsubAll(id: string, close = true) { + let topic = this.topics[id]; + if (topic) { + return topic.untapAll(close); + } + return false; + } - protected async process() { - let x; - while (((x = null), (x = await this.src.read())) !== undefined) { - const id = await this.fn(x); - let topic = this.topics[id]; - topic && (await topic.write(x)); - topic = this.topics["*"]; - topic && (await topic.write(x)); - } - for (let id of Object.keys(this.topics)) { - this.topics[id].close(); - } - delete (this).src; - delete (this).topics; - delete (this).fn; - } + protected async process() { + let x; + while (((x = null), (x = await this.src.read())) !== undefined) { + const id = await this.fn(x); + let topic = this.topics[id]; + topic && (await topic.write(x)); + topic = this.topics["*"]; + topic && (await topic.write(x)); + } + for (let id of Object.keys(this.topics)) { + this.topics[id].close(); + } + delete (this).src; + delete (this).topics; + delete (this).fn; + } } diff --git a/packages/csp/test/async.ts b/packages/csp/test/async.ts index 103b7601ff..853c950717 100644 --- a/packages/csp/test/async.ts +++ b/packages/csp/test/async.ts @@ -1,104 +1,104 @@ import * as tx from "@thi.ng/transducers"; -import { Channel, PubSub } from "../src/index.js" +import { Channel, PubSub } from "../src/index.js"; async function pingpong() { - async function ping(ch: Channel) { - for (let i = 0; i < 10; i++) { - await ch.write(i); - } - ch.close(); - console.log("ping done"); - } + async function ping(ch: Channel) { + for (let i = 0; i < 10; i++) { + await ch.write(i); + } + ch.close(); + console.log("ping done"); + } - async function pong(id: number, ch: Channel) { - let x; - while ((x = await ch.read()) !== undefined) { - console.log("take", id, x); - } - console.log("pong", id, "done"); - done.write(true); - } + async function pong(id: number, ch: Channel) { + let x; + while ((x = await ch.read()) !== undefined) { + console.log("take", id, x); + } + console.log("pong", id, "done"); + done.write(true); + } - const a = new Channel(); - const b = a.pipe(tx.map((x) => x * 10)); - const done = new Channel(); - ping(a); - pong(1, b); - pong(2, b); - pong(3, b); - await done.read(); - await done.read(); - await done.read(); - done.close(); + const a = new Channel(); + const b = a.pipe(tx.map((x) => x * 10)); + const done = new Channel(); + ping(a); + pong(1, b); + pong(2, b); + pong(3, b); + await done.read(); + await done.read(); + await done.read(); + done.close(); } async function alts() { - const a = new Channel("a"); - const b = new Channel("b"); - const c = Channel.timeout(50); - setTimeout(() => a.write(23), 50); - setTimeout(() => b.write(42), 50); - let [x, ch] = await Channel.select([a, b, c]); - console.log("selected", ch.id, x); + const a = new Channel("a"); + const b = new Channel("b"); + const c = Channel.timeout(50); + setTimeout(() => a.write(23), 50); + setTimeout(() => b.write(42), 50); + let [x, ch] = await Channel.select([a, b, c]); + console.log("selected", ch.id, x); } async function throttle(d1: number, d2: number) { - const a = Channel.range(0, 1000, 1, d1); - const done = new Channel(); - const t0 = Date.now(); - a.pipe(tx.throttleTime(d2)) - .consume((x) => console.log("throttled", x, Date.now() - t0)) - .then(() => { - console.log("throttle done"); - done.close(); - }); - await done.read(); + const a = Channel.range(0, 1000, 1, d1); + const done = new Channel(); + const t0 = Date.now(); + a.pipe(tx.throttleTime(d2)) + .consume((x) => console.log("throttled", x, Date.now() - t0)) + .then(() => { + console.log("throttle done"); + done.close(); + }); + await done.read(); } async function pubsub() { - const pub = new PubSub( - new Channel( - "users", - tx.map((x: string) => ({ type: x.charAt(0), val: x })) - ), - (x) => x.type - ), - done = new Channel(), - topics = "abc"; - for (let i of topics) { - pub.sub(i)! - .consume() - .then(() => done.write(i)); - } - // pub.sub("*").consume(); - await pub.channel().into(["alice", "bert", "bella", "charlie", "arthur"]); - for (let i of topics) { - console.log("waiting for", i); - await done.read(); - } - console.log("---"); + const pub = new PubSub( + new Channel( + "users", + tx.map((x: string) => ({ type: x.charAt(0), val: x })) + ), + (x) => x.type + ), + done = new Channel(), + topics = "abc"; + for (let i of topics) { + pub.sub(i)! + .consume() + .then(() => done.write(i)); + } + // pub.sub("*").consume(); + await pub.channel().into(["alice", "bert", "bella", "charlie", "arthur"]); + for (let i of topics) { + console.log("waiting for", i); + await done.read(); + } + console.log("---"); } async function transducers() { - let ch = new Channel(tx.comp(tx.partition(2, true), tx.take(3))); - ch.into([1, 2, 2, 2, 1, 5, 3, 3]); - await ch.consume(); - console.log("---"); - ch = new Channel(tx.comp(tx.take(3), tx.partition(2, true))); - ch.into([1, 2, 2, 2, 1, 5, 3, 3]); - await ch.consume(); - const src = [5, 2, 8, 10, 20, 15, 12, 18, 27, 78, 35, 16, 2, 99, 123, 42]; - ch = Channel.from(src, tx.delayed(100)).pipe(tx.streamSort(8)); - await ch.consume(); + let ch = new Channel(tx.comp(tx.partition(2, true), tx.take(3))); + ch.into([1, 2, 2, 2, 1, 5, 3, 3]); + await ch.consume(); + console.log("---"); + ch = new Channel(tx.comp(tx.take(3), tx.partition(2, true))); + ch.into([1, 2, 2, 2, 1, 5, 3, 3]); + await ch.consume(); + const src = [5, 2, 8, 10, 20, 15, 12, 18, 27, 78, 35, 16, 2, 99, 123, 42]; + ch = Channel.from(src, tx.delayed(100)).pipe(tx.streamSort(8)); + await ch.consume(); } async function main() { - await pingpong(); - await alts(); - await throttle(0, 100); - await pubsub(); - await transducers(); - console.log("main done"); + await pingpong(); + await alts(); + await throttle(0, 100); + await pubsub(); + await transducers(); + console.log("main done"); } main(); diff --git a/packages/csp/test/file.ts b/packages/csp/test/file.ts index deffd6f343..692af2616d 100644 --- a/packages/csp/test/file.ts +++ b/packages/csp/test/file.ts @@ -1,23 +1,23 @@ import * as tx from "@thi.ng/transducers"; import * as fs from "fs"; -import { Channel, Mult } from "../src/index.js" +import { Channel, Mult } from "../src/index.js"; // compose transducer to split source file into words // and filter out short strings const proc: tx.Transducer = tx.comp( - tx.mapcat((src: string) => src.toLowerCase().split(/[^\w]+/g)), - tx.filter((w: string) => w.length > 1) + tx.mapcat((src: string) => src.toLowerCase().split(/[^\w]+/g)), + tx.filter((w: string) => w.length > 1) ); // define a channel which receives file paths // and resolves them with their contents const paths = new Channel( - tx.map( - (path: string) => - new Promise((resolve) => - fs.readFile(path, (_, data) => resolve(data.toString())) - ) - ) + tx.map( + (path: string) => + new Promise((resolve) => + fs.readFile(path, (_, data) => resolve(data.toString())) + ) + ) ); // define multiplexed output channel @@ -31,10 +31,10 @@ const counter = results.tap(tx.map((x) => x[1]))!.reduce(tx.add()); // (using a sliding window size of 500 items) and dropping // words with < 20 occurrences const sorted = results.tap( - tx.comp( - tx.streamSort(500, { key: (x) => x[1] }), - tx.dropWhile((x) => x[1] < 20) - ) + tx.comp( + tx.streamSort(500, { key: (x) => x[1] }), + tx.dropWhile((x) => x[1] < 20) + ) )!; // define workflow: @@ -44,9 +44,9 @@ const sorted = results.tap( // into the `sorted` channel // (`freqs` is a JS Map and is iterable) paths - .pipe(proc) - .reduce(tx.frequencies()) - .then((freqs) => results.channel().into(freqs)); + .pipe(proc) + .reduce(tx.frequencies()) + .then((freqs) => results.channel().into(freqs)); // kick off process by writing file paths into the 1st channel paths.into(["src/channel.ts", "src/mult.ts", "src/pubsub.ts"]); @@ -54,5 +54,5 @@ paths.into(["src/channel.ts", "src/mult.ts", "src/pubsub.ts"]); // start tracing sorted outputs and // wait for all to finish Promise.all([sorted.consume(), counter]).then(([_, num]) => - console.log("total words:", num) + console.log("total words:", num) ); diff --git a/packages/csp/test/graph.ts b/packages/csp/test/graph.ts index ee10b86043..1444366ccd 100644 --- a/packages/csp/test/graph.ts +++ b/packages/csp/test/graph.ts @@ -1,97 +1,97 @@ import type { IObjectOf } from "@thi.ng/api"; -import { Channel, Mult } from "../src/index.js" +import { Channel, Mult } from "../src/index.js"; export interface Node { - ins: IObjectOf>; - outs: IObjectOf>; - state: any; + ins: IObjectOf>; + outs: IObjectOf>; + state: any; } export type NodeFn = (n: Node) => void; export function node( - id: string, - ins: IObjectOf | Mult>, - outs: string[], - init: any, - fn: NodeFn + id: string, + ins: IObjectOf | Mult>, + outs: string[], + init: any, + fn: NodeFn ) { - const $: Node = { - ins: >>{}, - outs: >>{}, - state: init || {}, - }; - for (let k of Object.keys(ins)) { - const val = ins[k]; - $.ins[k] = - val instanceof Channel - ? ((val.id = k), val) - : val instanceof Mult - ? val.tap(new Channel(k))! - : new Channel(k); - } - for (let o of outs) { - $.outs[o] = new Mult(id + "-" + o); - } - (async () => { - while (true) { - let ports = Object.keys($.ins) - .map((k) => $.ins[k]) - .filter((x) => !!x); - let [x, c] = await Channel.select(ports); - if (x === undefined) { - break; - } - if ($.state[c.id] !== x) { - $.state[c.id] = x; - fn($); - } - } - for (let i of Object.keys($.ins)) { - $.ins[i].close(); - } - for (let o of Object.keys($.outs)) { - $.outs[o].close(); - } - console.log(id, "done"); - })(); - return $; + const $: Node = { + ins: >>{}, + outs: >>{}, + state: init || {}, + }; + for (let k of Object.keys(ins)) { + const val = ins[k]; + $.ins[k] = + val instanceof Channel + ? ((val.id = k), val) + : val instanceof Mult + ? val.tap(new Channel(k))! + : new Channel(k); + } + for (let o of outs) { + $.outs[o] = new Mult(id + "-" + o); + } + (async () => { + while (true) { + let ports = Object.keys($.ins) + .map((k) => $.ins[k]) + .filter((x) => !!x); + let [x, c] = await Channel.select(ports); + if (x === undefined) { + break; + } + if ($.state[c.id] !== x) { + $.state[c.id] = x; + fn($); + } + } + for (let i of Object.keys($.ins)) { + $.ins[i].close(); + } + for (let o of Object.keys($.outs)) { + $.outs[o].close(); + } + console.log(id, "done"); + })(); + return $; } export function add( - id: string, - ins?: IObjectOf | Mult>, - init?: any + id: string, + ins?: IObjectOf | Mult>, + init?: any ) { - return node( - id, - Object.assign({ a: null, b: null }, ins), - ["out"], - init, - (n) => { - if (n.state.a && n.state.b) { - n.outs.out.channel().write(n.state.a + n.state.b); - } - } - ); + return node( + id, + Object.assign({ a: null, b: null }, ins), + ["out"], + init, + (n) => { + if (n.state.a && n.state.b) { + n.outs.out.channel().write(n.state.a + n.state.b); + } + } + ); } export function mul( - id: string, - ins?: IObjectOf | Mult>, - init?: any + id: string, + ins?: IObjectOf | Mult>, + init?: any ) { - return node( - id, - Object.assign({ a: null, b: null }, ins), - ["out"], - init, - (n) => { - if (n.state.a && n.state.b) { - n.outs.out.channel().write(n.state.a * n.state.b); - } - } - ); + return node( + id, + Object.assign({ a: null, b: null }, ins), + ["out"], + init, + (n) => { + if (n.state.a && n.state.b) { + n.outs.out.channel().write(n.state.a * n.state.b); + } + } + ); } const add1 = add("add1", {}, { b: 100 }); @@ -99,14 +99,14 @@ const add2 = add("add2", { b: add1.outs.out }, { a: 1000 }); const mul1 = mul("mul1", { a: add2.outs.out, b: Channel.range(0, 10, 1, 500) }); mul1.outs.out - .tap()! - .consume(console.log) - .then(() => (add1.ins.a.close(), add2.ins.a.close())); + .tap()! + .consume(console.log) + .then(() => (add1.ins.a.close(), add2.ins.a.close())); (async () => { - let i = 0; - while (mul1.outs.out.channel()) { - await add1.ins.a.write(i++); - await Channel.sleep(100); - } + let i = 0; + while (mul1.outs.out.channel()) { + await add1.ins.a.write(i++); + await Channel.sleep(100); + } })(); diff --git a/packages/csp/test/node.ts b/packages/csp/test/node.ts index 4be5554550..011e2ac8f1 100644 --- a/packages/csp/test/node.ts +++ b/packages/csp/test/node.ts @@ -1,265 +1,265 @@ import type { IEnable, IID, IObjectOf } from "@thi.ng/api"; import { implementsFunction } from "@thi.ng/checks"; import { map, Transducer } from "@thi.ng/transducers"; -import { Channel, IBuffer, IWriteableChannel, Mult } from "../src/index.js" +import { Channel, IBuffer, IWriteableChannel, Mult } from "../src/index.js"; export type NodeInput = - | NodeInputSpec - | Channel - | Mult - | Transducer; + | NodeInputSpec + | Channel + | Mult + | Transducer; export type NodeOutput = NodeOutputSpec | IWriteableChannel; export interface NodeInputSpec { - src: Channel | Mult; - buf?: number | IBuffer; - tx?: Transducer; + src: Channel | Mult; + buf?: number | IBuffer; + tx?: Transducer; } export interface NodeOutputSpec { - dest?: IWriteableChannel; - buf?: number | IBuffer; - tx?: Transducer; + dest?: IWriteableChannel; + buf?: number | IBuffer; + tx?: Transducer; } export interface NodeHandlers { - shouldUpdate?: (node: Node) => boolean; - update: (node: Node) => any; + shouldUpdate?: (node: Node) => boolean; + update: (node: Node) => any; } export interface NodeSpec extends IID { - ins: IObjectOf; - outs: IObjectOf; - impl: NodeHandlers; - required?: string[]; - close?: boolean; - reset?: boolean; - state?: any; + ins: IObjectOf; + outs: IObjectOf; + impl: NodeHandlers; + required?: string[]; + close?: boolean; + reset?: boolean; + state?: any; } export class Node implements IEnable, IID { - static hasRequiredInputs(n: Node) { - for (let i of n.required) { - if (n.enabled[i] && n.state[i] === undefined) { - return false; - } - } - return true; - } + static hasRequiredInputs(n: Node) { + for (let i of n.required) { + if (n.enabled[i] && n.state[i] === undefined) { + return false; + } + } + return true; + } - /** - * Creates new input channel based on given port spec. - * If `buf` or `tx` are given, creates new channel (piped from src). - * If only `src` is given, returns it unmodified (if channel) or - * creates a new tap (if Mult). - * - * @param id - node id - * @param spec - node spec - */ - static inputFromSpec(id: string, spec: NodeInputSpec) { - if (spec.buf || spec.tx) { - const ch = new Channel(id, spec.buf!, spec.tx!); - return spec.src instanceof Channel - ? spec.src.pipe(ch) - : spec.src.tap(ch); - } else { - return spec.src instanceof Channel ? spec.src : spec.src.tap()!; - } - } + /** + * Creates new input channel based on given port spec. + * If `buf` or `tx` are given, creates new channel (piped from src). + * If only `src` is given, returns it unmodified (if channel) or + * creates a new tap (if Mult). + * + * @param id - node id + * @param spec - node spec + */ + static inputFromSpec(id: string, spec: NodeInputSpec) { + if (spec.buf || spec.tx) { + const ch = new Channel(id, spec.buf!, spec.tx!); + return spec.src instanceof Channel + ? spec.src.pipe(ch) + : spec.src.tap(ch); + } else { + return spec.src instanceof Channel ? spec.src : spec.src.tap()!; + } + } - static outputFromSpec(id: string, spec: NodeOutputSpec) { - const mult = new Mult( - ( - (spec.buf || spec.tx - ? new Channel(id, spec.buf!, spec.tx!) - : id) - ) - ); - if (spec.dest) { - mult.tap(spec.dest.channel()); - } - return mult; - } + static outputFromSpec(id: string, spec: NodeOutputSpec) { + const mult = new Mult( + ( + (spec.buf || spec.tx + ? new Channel(id, spec.buf!, spec.tx!) + : id) + ) + ); + if (spec.dest) { + mult.tap(spec.dest.channel()); + } + return mult; + } - static NEXT_ID = 0; + static NEXT_ID = 0; - id: string; - state: any; + id: string; + state: any; - ins: IObjectOf> = {}; - outs: IObjectOf> = {}; - enabled: IObjectOf = {}; - required: string[]; - autoClose: boolean; - autoReset: boolean; - impl: NodeHandlers; + ins: IObjectOf> = {}; + outs: IObjectOf> = {}; + enabled: IObjectOf = {}; + required: string[]; + autoClose: boolean; + autoReset: boolean; + impl: NodeHandlers; - constructor(spec: NodeSpec) { - this.id = spec.id; - this.state = {}; - this.required = spec.required || Object.keys(spec.ins); - this.autoClose = spec.close !== false; - this.autoReset = spec.reset !== false; - this.impl = spec.impl; - this.impl.shouldUpdate = - this.impl.shouldUpdate || Node.hasRequiredInputs; - for (let i of Object.keys(spec.ins)) { - this.addInput(i, spec.ins[i], spec.state[i]); - } - for (let o of Object.keys(spec.outs)) { - this.addOutput(o, spec.outs[o]); - } - this.process(); - } + constructor(spec: NodeSpec) { + this.id = spec.id; + this.state = {}; + this.required = spec.required || Object.keys(spec.ins); + this.autoClose = spec.close !== false; + this.autoReset = spec.reset !== false; + this.impl = spec.impl; + this.impl.shouldUpdate = + this.impl.shouldUpdate || Node.hasRequiredInputs; + for (let i of Object.keys(spec.ins)) { + this.addInput(i, spec.ins[i], spec.state[i]); + } + for (let o of Object.keys(spec.outs)) { + this.addOutput(o, spec.outs[o]); + } + this.process(); + } - addInput(id: string, val: NodeInput, init?: any) { - this.ins[id] = - val instanceof Channel - ? ((val.id = id), val) - : val instanceof Mult - ? val.tap(new Channel(id))! - : val == null || typeof val === "function" - ? new Channel(id, val) - : Node.inputFromSpec(id, val)!; - if (init !== undefined) { - this.state[id] = init; - this.enabled[id] = val != null; - } else { - this.enabled[id] = true; - } - } + addInput(id: string, val: NodeInput, init?: any) { + this.ins[id] = + val instanceof Channel + ? ((val.id = id), val) + : val instanceof Mult + ? val.tap(new Channel(id))! + : val == null || typeof val === "function" + ? new Channel(id, val) + : Node.inputFromSpec(id, val)!; + if (init !== undefined) { + this.state[id] = init; + this.enabled[id] = val != null; + } else { + this.enabled[id] = true; + } + } - addOutput(id: string, val: NodeOutput) { - const spec = implementsFunction(val, "channel") - ? { dest: >val } - : val; - const pid = this.id + "-" + id; - this.outs[id] = - spec != null ? Node.outputFromSpec(pid, spec) : new Mult(pid); - } + addOutput(id: string, val: NodeOutput) { + const spec = implementsFunction(val, "channel") + ? { dest: >val } + : val; + const pid = this.id + "-" + id; + this.outs[id] = + spec != null ? Node.outputFromSpec(pid, spec) : new Mult(pid); + } - disable(id?: string) { - if (id) { - if (this.enabled[id] != null) { - this.enabled[id] = false; - } - } else { - for (let id of Object.keys(this.enabled)) { - this.enabled[id] = false; - } - } - } + disable(id?: string) { + if (id) { + if (this.enabled[id] != null) { + this.enabled[id] = false; + } + } else { + for (let id of Object.keys(this.enabled)) { + this.enabled[id] = false; + } + } + } - enable(id?: string) { - if (id) { - if (this.enabled[id] != null) { - this.enabled[id] = true; - } - } else { - for (let id of Object.keys(this.enabled)) { - this.enabled[id] = true; - } - } - } + enable(id?: string) { + if (id) { + if (this.enabled[id] != null) { + this.enabled[id] = true; + } + } else { + for (let id of Object.keys(this.enabled)) { + this.enabled[id] = true; + } + } + } - isEnabled(id?: string): boolean { - if (id) { - return !!this.enabled[id]; - } else { - let ok = true; - for (let id of Object.keys(this.enabled)) { - ok = ok && this.enabled[id]; - if (!ok) { - break; - } - } - return ok; - } - } + isEnabled(id?: string): boolean { + if (id) { + return !!this.enabled[id]; + } else { + let ok = true; + for (let id of Object.keys(this.enabled)) { + ok = ok && this.enabled[id]; + if (!ok) { + break; + } + } + return ok; + } + } - solo(...ids: string[]) { - const sel = new Set(ids); - for (let id of Object.keys(this.enabled)) { - this.enabled[id] = sel.has(id); - } - } + solo(...ids: string[]) { + const sel = new Set(ids); + for (let id of Object.keys(this.enabled)) { + this.enabled[id] = sel.has(id); + } + } - clearState() { - for (let id of Object.keys(this.enabled)) { - if (this.enabled[id]) { - delete this.state[id]; - } - } - } + clearState() { + for (let id of Object.keys(this.enabled)) { + if (this.enabled[id]) { + delete this.state[id]; + } + } + } - protected async process() { - while (true) { - let ports = Object.keys(this.ins).map((k) => this.ins[k]); - let [x, c] = await Channel.select(ports); - if (x === undefined) { - if (this.autoClose) { - break; - } else { - continue; - } - } - if (this.enabled[c.id]) { - this.state[c.id] = x; - if (this.impl.shouldUpdate!(this)) { - await this.impl.update(this); - this.autoReset && this.clearState(); - } - } - } - for (let i of Object.keys(this.ins)) { - this.ins[i].close(); - } - for (let o of Object.keys(this.outs)) { - this.outs[o].close(); - } - console.log(this.id, "done"); - } + protected async process() { + while (true) { + let ports = Object.keys(this.ins).map((k) => this.ins[k]); + let [x, c] = await Channel.select(ports); + if (x === undefined) { + if (this.autoClose) { + break; + } else { + continue; + } + } + if (this.enabled[c.id]) { + this.state[c.id] = x; + if (this.impl.shouldUpdate!(this)) { + await this.impl.update(this); + this.autoReset && this.clearState(); + } + } + } + for (let i of Object.keys(this.ins)) { + this.ins[i].close(); + } + for (let o of Object.keys(this.outs)) { + this.outs[o].close(); + } + console.log(this.id, "done"); + } } export interface AddSpec { - id: string; - ins: Partial<{ a: NodeInput; b: NodeInput }>; - outs: Partial<{ out: NodeOutput }>; - state: any; - close: boolean; - clear: boolean; + id: string; + ins: Partial<{ a: NodeInput; b: NodeInput }>; + outs: Partial<{ out: NodeOutput }>; + state: any; + close: boolean; + clear: boolean; } export function add(spec: Partial) { - return new Node({ - id: spec.id || `node-${Node.NEXT_ID++}`, - state: Object.assign({}, spec.state), - ins: >( - Object.assign({ a: null, b: null }, spec.ins) - ), - outs: >Object.assign({ out: null }, spec.outs), - close: spec.close, - reset: spec.close, - impl: { - update: (n) => { - const res = n.state.a + n.state.b; - return n.outs.out.channel().write(res); - }, - }, - }); + return new Node({ + id: spec.id || `node-${Node.NEXT_ID++}`, + state: Object.assign({}, spec.state), + ins: >( + Object.assign({ a: null, b: null }, spec.ins) + ), + outs: >Object.assign({ out: null }, spec.outs), + close: spec.close, + reset: spec.close, + impl: { + update: (n) => { + const res = n.state.a + n.state.b; + return n.outs.out.channel().write(res); + }, + }, + }); } export const res = new Channel("res"); export const a = add({ id: "a", state: { b: 42 } }); export const b = add({ - id: "b", - ins: { a: { src: a.outs.out, tx: map((x) => x * 1) } }, - state: { b: 100 }, + id: "b", + ins: { a: { src: a.outs.out, tx: map((x) => x * 1) } }, + state: { b: 100 }, }); export const c = add({ - id: "c", - ins: { a: b.outs.out, b: b.outs.out }, - outs: { out: res }, + id: "c", + ins: { a: b.outs.out, b: b.outs.out }, + outs: { out: res }, }); // b.disable("b"); diff --git a/packages/csp/tsconfig.json b/packages/csp/tsconfig.json index adce2e5052..3e3eb91698 100644 --- a/packages/csp/tsconfig.json +++ b/packages/csp/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": ".", - "preserveConstEnums": false, - "isolatedModules": false - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "preserveConstEnums": false, + "isolatedModules": false + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/csv/api-extractor.json b/packages/csv/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/csv/api-extractor.json +++ b/packages/csv/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/csv/package.json b/packages/csv/package.json index f2259c4938..dff69a4df0 100644 --- a/packages/csv/package.json +++ b/packages/csv/package.json @@ -1,88 +1,88 @@ { - "name": "@thi.ng/csv", - "version": "2.2.10", - "description": "Customizable, transducer-based CSV parser/object mapper and transformer", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/csv#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/strings": "^3.3.6", - "@thi.ng/transducers": "^8.3.7" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "csv", - "fileformat", - "parser", - "string", - "transducer", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./format": { - "default": "./format.js" - }, - "./parse": { - "default": "./parse.js" - }, - "./transforms": { - "default": "./transforms.js" - } - }, - "thi.ng": { - "year": 2014 - } + "name": "@thi.ng/csv", + "version": "2.2.10", + "description": "Customizable, transducer-based CSV parser/object mapper and transformer", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/csv#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/strings": "^3.3.6", + "@thi.ng/transducers": "^8.3.7" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "csv", + "fileformat", + "parser", + "string", + "transducer", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./format": { + "default": "./format.js" + }, + "./parse": { + "default": "./parse.js" + }, + "./transforms": { + "default": "./transforms.js" + } + }, + "thi.ng": { + "year": 2014 + } } diff --git a/packages/csv/src/api.ts b/packages/csv/src/api.ts index 0d01f86601..d7d1ea9992 100644 --- a/packages/csv/src/api.ts +++ b/packages/csv/src/api.ts @@ -19,117 +19,117 @@ export type CSVRecord = Record; export type CellTransform = Fn2; export interface ColumnSpec { - /** - * Rename column to given name in result objects. - */ - alias?: string; - /** - * Cell value transformer. This is a 2-arg function receiving string value - * and (incomplete) result object of current row. Return value is used as - * actual value for the cell. - */ - tx?: CellTransform; + /** + * Rename column to given name in result objects. + */ + alias?: string; + /** + * Cell value transformer. This is a 2-arg function receiving string value + * and (incomplete) result object of current row. Return value is used as + * actual value for the cell. + */ + tx?: CellTransform; } export interface CommonCSVOpts { - /** - * Field delimiter character. - * - * @defaultValue `,` - */ - delim: string; - /** - * Field value quote character. - * - * @defaultValue `"` - */ - quote: string; - /** - * Line comment prefix. - * - * @defaultValue `#` - */ - comment: string; - /** - * If true, all leading and trailing whitespace for each field value will be - * trimmed. - * - * @defaultValue false - */ - trim: boolean; + /** + * Field delimiter character. + * + * @defaultValue `,` + */ + delim: string; + /** + * Field value quote character. + * + * @defaultValue `"` + */ + quote: string; + /** + * Line comment prefix. + * + * @defaultValue `#` + */ + comment: string; + /** + * If true, all leading and trailing whitespace for each field value will be + * trimmed. + * + * @defaultValue false + */ + trim: boolean; } export interface SimpleCSVOpts extends CommonCSVOpts { - /** - * If true (default), the first row (usually containing CSV column names) - * will be skipped. - * - * @defaultValue true - */ - header: boolean; - /** - * Array of booleans or {@link CellTransform}s (in column order), indicating - * which columns to retain in the result {@link CSVRow} tuples. If omitted - * ALL columns will be kept. - */ - cols: Nullable[]; + /** + * If true (default), the first row (usually containing CSV column names) + * will be skipped. + * + * @defaultValue true + */ + header: boolean; + /** + * Array of booleans or {@link CellTransform}s (in column order), indicating + * which columns to retain in the result {@link CSVRow} tuples. If omitted + * ALL columns will be kept. + */ + cols: Nullable[]; } export interface CSVOpts extends CommonCSVOpts { - /** - * If given, this array will be used as column names (and order) and - * overrides the default CSV behavior in which the first line defines the - * column names. Useful for parsing CSV data which doesn't contain a header - * row. - */ - header: string[]; - /** - * If true (default), all columns will be included in the result objects. - * - * @defaultValue true - */ - all: boolean; - /** - * Array or object of column specific options/transformations. - * - * If given as array: - * - * - each item will be related to its respective column (array order) - * - any nullish {@link ColumnSpec} values will be skipped - * - if a spec provides no `alias` and no column name is made available - * otherwise (i.e. either via 1st data row or the `header` option), then - * that column will be named numerically - * - * If given as object, each key must match an existing/original column name - * (either as per 1st data row or the `header` option). - */ - cols: Nullable[] | Record; + /** + * If given, this array will be used as column names (and order) and + * overrides the default CSV behavior in which the first line defines the + * column names. Useful for parsing CSV data which doesn't contain a header + * row. + */ + header: string[]; + /** + * If true (default), all columns will be included in the result objects. + * + * @defaultValue true + */ + all: boolean; + /** + * Array or object of column specific options/transformations. + * + * If given as array: + * + * - each item will be related to its respective column (array order) + * - any nullish {@link ColumnSpec} values will be skipped + * - if a spec provides no `alias` and no column name is made available + * otherwise (i.e. either via 1st data row or the `header` option), then + * that column will be named numerically + * + * If given as object, each key must match an existing/original column name + * (either as per 1st data row or the `header` option). + */ + cols: Nullable[] | Record; } export interface CSVFormatOpts { - /** - * Column names, in order of appearance. If omitted and rows are supplied as - * {@link CSVRecord}, the keys of the first item will be used as column - * names. If `header` is omitted and rows are given as array, NO header row - * will be created. - */ - header: string[]; - /** - * Column value formatters. If given as object, and the `header` option MUST be - * given and the column names given in `header` need to correspond with keys - * in the object. - */ - cols: Nullable>[] | Record>; - /** - * Column delimiter - * - * @defaultValue `,` - */ - delim: string; - /** - * Quote char - * - * @defaultValue `"` - */ - quote: string; + /** + * Column names, in order of appearance. If omitted and rows are supplied as + * {@link CSVRecord}, the keys of the first item will be used as column + * names. If `header` is omitted and rows are given as array, NO header row + * will be created. + */ + header: string[]; + /** + * Column value formatters. If given as object, and the `header` option MUST be + * given and the column names given in `header` need to correspond with keys + * in the object. + */ + cols: Nullable>[] | Record>; + /** + * Column delimiter + * + * @defaultValue `,` + */ + delim: string; + /** + * Quote char + * + * @defaultValue `"` + */ + quote: string; } diff --git a/packages/csv/src/format.ts b/packages/csv/src/format.ts index 3791115d00..dd4a9cdc2b 100644 --- a/packages/csv/src/format.ts +++ b/packages/csv/src/format.ts @@ -12,79 +12,84 @@ import { transduce } from "@thi.ng/transducers/transduce"; import type { CSVFormatOpts, CSVRecord, CSVRow } from "./api.js"; export function formatCSV( - opts?: Partial + opts?: Partial ): Transducer; export function formatCSV( - opts: Partial, - src: Iterable + opts: Partial, + src: Iterable ): IterableIterator; export function formatCSV( - opts?: Partial, - src?: Iterable + opts?: Partial, + src?: Iterable ): any { - return isIterable(src) - ? iterator(formatCSV(opts), src) - : (rfn: Reducer) => { - let { header, cols, delim, quote } = { - delim: ",", - quote: `"`, - cols: [], - ...opts, - }; - let colTx: Nullable>[]; - const reQuote = new RegExp(quote, "g"); - const reduce = rfn[2]; - let headerDone = false; - return compR(rfn, (acc, row: CSVRow | CSVRecord) => { - if (!headerDone) { - if (!header && !isArray(row)) { - header = Object.keys(row); - } - colTx = isArray(cols) - ? cols - : header - ? header.map( - (id) => - (>>cols)[id] - ) - : []; - } - const $row = isArray(row) - ? row - : header!.map((k) => (row)[k]); - const line = (header || $row) - .map((_, i) => { - const val = $row[i]; - const cell = - val != null - ? colTx[i] - ? colTx[i]!(val) - : String(val) - : ""; - return cell.indexOf(quote) !== -1 - ? wrap(quote)( - cell.replace(reQuote, `${quote}${quote}`) - ) - : cell; - }) - .join(delim); - if (!headerDone) { - if (header) { - acc = reduce(acc, header.join(delim)); - } else { - header = $row; - } - headerDone = true; - !isReduced(acc) && (acc = reduce(acc, line)); - return acc; - } else { - return reduce(acc, line); - } - }); - }; + return isIterable(src) + ? iterator(formatCSV(opts), src) + : (rfn: Reducer) => { + let { header, cols, delim, quote } = { + delim: ",", + quote: `"`, + cols: [], + ...opts, + }; + let colTx: Nullable>[]; + const reQuote = new RegExp(quote, "g"); + const reduce = rfn[2]; + let headerDone = false; + return compR(rfn, (acc, row: CSVRow | CSVRecord) => { + if (!headerDone) { + if (!header && !isArray(row)) { + header = Object.keys(row); + } + colTx = isArray(cols) + ? cols + : header + ? header.map( + (id) => + (>>cols)[ + id + ] + ) + : []; + } + const $row = isArray(row) + ? row + : header!.map((k) => (row)[k]); + const line = (header || $row) + .map((_, i) => { + const val = $row[i]; + const cell = + val != null + ? colTx[i] + ? colTx[i]!(val) + : String(val) + : ""; + return cell.indexOf(quote) !== -1 + ? wrap(quote)( + cell.replace( + reQuote, + `${quote}${quote}` + ) + ) + : cell; + }) + .join(delim); + if (!headerDone) { + if (header) { + acc = reduce(acc, header.join(delim)); + } else { + header = $row; + } + headerDone = true; + !isReduced(acc) && (acc = reduce(acc, line)); + return acc; + } else { + return reduce(acc, line); + } + }); + }; } export const formatCSVString = ( - opts: Partial = {}, - src: Iterable + opts: Partial = {}, + src: Iterable ) => transduce(formatCSV(opts), str(opts.rowDelim || "\n"), src); diff --git a/packages/csv/src/parse.ts b/packages/csv/src/parse.ts index e06fe0033f..8cd4492fe6 100644 --- a/packages/csv/src/parse.ts +++ b/packages/csv/src/parse.ts @@ -8,12 +8,12 @@ import type { Reducer, Transducer } from "@thi.ng/transducers"; import { compR } from "@thi.ng/transducers/compr"; import { iterator1 } from "@thi.ng/transducers/iterator"; import type { - ColumnSpec, - CommonCSVOpts, - CSVOpts, - CSVRecord, - CSVRow, - SimpleCSVOpts, + ColumnSpec, + CommonCSVOpts, + CSVOpts, + CSVRecord, + CSVRow, + SimpleCSVOpts, } from "./api.js"; /** @internal */ @@ -25,10 +25,10 @@ type IndexEntry = { i: number; spec: ColumnSpec }; * @internal */ const DEFAULT_OPTS: Partial = { - delim: ",", - quote: '"', - comment: "#", - trim: false, + delim: ",", + quote: '"', + comment: "#", + trim: false, }; /** @@ -83,94 +83,97 @@ const DEFAULT_OPTS: Partial = { * // ] * ``` * - * @param opts - + * @param opts - */ export function parseCSV( - opts?: Partial + opts?: Partial ): Transducer; export function parseCSV( - opts: Partial, - src: Iterable + opts: Partial, + src: Iterable ): IterableIterator; export function parseCSV(opts?: Partial, src?: Iterable): any { - return isIterable(src) - ? iterator1(parseCSV(opts), src) - : (rfn: Reducer) => { - const { all, cols, delim, quote, comment, trim, header } = { - all: true, - ...DEFAULT_OPTS, - ...opts, - }; - const reduce = rfn[2]; - let index: Record; - let revIndex: Record; - let first = true; - let isQuoted = false; - let record: string[] = []; + return isIterable(src) + ? iterator1(parseCSV(opts), src) + : (rfn: Reducer) => { + const { all, cols, delim, quote, comment, trim, header } = { + all: true, + ...DEFAULT_OPTS, + ...opts, + }; + const reduce = rfn[2]; + let index: Record; + let revIndex: Record; + let first = true; + let isQuoted = false; + let record: string[] = []; - const init = (header: string[]) => { - cols && (index = initIndex(header, cols)); - all && (revIndex = initRevIndex(header)); - first = false; - }; + const init = (header: string[]) => { + cols && (index = initIndex(header, cols)); + all && (revIndex = initRevIndex(header)); + first = false; + }; - const collectAll = (row: CSVRecord) => - record.reduce( - (acc, x, i) => ( - (acc[revIndex[i]] = trim ? x.trim() : x), acc - ), - row - ); + const collectAll = (row: CSVRecord) => + record.reduce( + (acc, x, i) => ( + (acc[revIndex[i]] = trim ? x.trim() : x), acc + ), + row + ); - const collectIndexed = (row: CSVRecord) => - Object.entries(index).reduce((acc, [id, { i, spec }]) => { - let val = record[i]; - if (val !== undefined) { - trim && (val = val.trim()); - all && spec.alias && delete acc[id]; - acc[spec.alias || id] = spec.tx - ? spec.tx(val, acc) - : val; - } - return acc; - }, row); + const collectIndexed = (row: CSVRecord) => + Object.entries(index).reduce((acc, [id, { i, spec }]) => { + let val = record[i]; + if (val !== undefined) { + trim && (val = val.trim()); + all && spec.alias && delete acc[id]; + acc[spec.alias || id] = spec.tx + ? spec.tx(val, acc) + : val; + } + return acc; + }, row); - header && init(header); + header && init(header); - return compR(rfn, (acc, line: string) => { - if ((!line.length || line.startsWith(comment!)) && !isQuoted) - return acc; - if (!first) { - isQuoted = parseLine( - line, - record, - isQuoted, - delim!, - quote! - ); - if (isQuoted) return acc; + return compR(rfn, (acc, line: string) => { + if ( + (!line.length || line.startsWith(comment!)) && + !isQuoted + ) + return acc; + if (!first) { + isQuoted = parseLine( + line, + record, + isQuoted, + delim!, + quote! + ); + if (isQuoted) return acc; - const row: CSVRecord = {}; - all && collectAll(row); - index && collectIndexed(row); - record = []; - return reduce(acc, row); - } else { - isQuoted = parseLine( - line, - record, - isQuoted, - delim!, - quote! - ); - if (!isQuoted) { - init(record); - record = []; - } - return acc; - } - }); - }; + const row: CSVRecord = {}; + all && collectAll(row); + index && collectIndexed(row); + record = []; + return reduce(acc, row); + } else { + isQuoted = parseLine( + line, + record, + isQuoted, + delim!, + quote! + ); + if (!isQuoted) { + init(record); + record = []; + } + return acc; + } + }); + }; } /** @@ -188,73 +191,76 @@ export function parseCSV(opts?: Partial, src?: Iterable): any { * // [ [ 1, 3 ], [ 4, 6 ] ] * ``` * - * @param opts - + * @param opts - */ export function parseCSVSimple( - opts?: Partial + opts?: Partial ): Transducer; export function parseCSVSimple( - opts: Partial, - src: Iterable + opts: Partial, + src: Iterable ): IterableIterator; export function parseCSVSimple( - opts?: Partial, - src?: Iterable + opts?: Partial, + src?: Iterable ): any { - return isIterable(src) - ? iterator1(parseCSVSimple(opts), src) - : (rfn: Reducer) => { - const { cols, delim, quote, comment, trim, header } = { - header: true, - ...DEFAULT_OPTS, - ...opts, - }; - const reduce = rfn[2]; - let first = header; - let isQuoted = false; - let record: string[] = []; + return isIterable(src) + ? iterator1(parseCSVSimple(opts), src) + : (rfn: Reducer) => { + const { cols, delim, quote, comment, trim, header } = { + header: true, + ...DEFAULT_OPTS, + ...opts, + }; + const reduce = rfn[2]; + let first = header; + let isQuoted = false; + let record: string[] = []; - const collect = () => - cols!.reduce((acc, col, i) => { - if (col) { - let val = record[i]; - if (val !== undefined) { - trim && (val = val.trim()); - acc.push(isFunction(col) ? col(val, acc) : val); - } - } - return acc; - }, []); + const collect = () => + cols!.reduce((acc, col, i) => { + if (col) { + let val = record[i]; + if (val !== undefined) { + trim && (val = val.trim()); + acc.push(isFunction(col) ? col(val, acc) : val); + } + } + return acc; + }, []); - return compR(rfn, (acc, line: string) => { - if ((!line.length || line.startsWith(comment!)) && !isQuoted) - return acc; - if (!first) { - isQuoted = parseLine( - line, - record, - isQuoted, - delim!, - quote! - ); - if (isQuoted) return acc; - const row: CSVRow = cols ? collect() : record; - record = []; - return reduce(acc, row); - } else { - isQuoted = parseLine( - line, - record, - isQuoted, - delim!, - quote! - ); - first = false; - record = []; - return acc; - } - }); - }; + return compR(rfn, (acc, line: string) => { + if ( + (!line.length || line.startsWith(comment!)) && + !isQuoted + ) + return acc; + if (!first) { + isQuoted = parseLine( + line, + record, + isQuoted, + delim!, + quote! + ); + if (isQuoted) return acc; + const row: CSVRow = cols ? collect() : record; + record = []; + return reduce(acc, row); + } else { + isQuoted = parseLine( + line, + record, + isQuoted, + delim!, + quote! + ); + first = false; + record = []; + return acc; + } + }); + }; } /** @@ -262,23 +268,23 @@ export function parseCSVSimple( * given source string into a line based input using * {@link @thi.ng/strings#split}. * - * @param opts - - * @param src - + * @param opts - + * @param src - */ export const parseCSVFromString = (opts: Partial, src: string) => - parseCSV(opts, split(src)); + parseCSV(opts, split(src)); /** * Syntax sugar for iterator version of {@link parseCSVSimple}, efficiently * splitting given source string into a line based input using * {@link @thi.ng/strings#split}. * - * @param opts - - * @param src - + * @param opts - + * @param src - */ export const parseCSVSimpleFromString = ( - opts: Partial, - src: string + opts: Partial, + src: string ) => parseCSVSimple(opts, split(src)); /** @@ -294,76 +300,76 @@ export const parseCSVSimpleFromString = ( * Function returns current state of `isQuoted` (i.e. if line terminated in a * quoted cell) and should be (re)called with new lines until it returns false. * - * @param line - - * @param acc - - * @param isQuoted - - * @param delim - - * @param quote - + * @param line - + * @param acc - + * @param isQuoted - + * @param delim - + * @param quote - */ const parseLine = ( - line: string, - acc: string[], - isQuoted: boolean, - delim: string, - quote: string + line: string, + acc: string[], + isQuoted: boolean, + delim: string, + quote: string ) => { - let curr = ""; - let p = ""; - let openQuote = isQuoted; - for (let i = 0, n = line.length; i < n; i++) { - const c = line[i]; - // escaped char - if (p === "\\") { - curr += ESCAPES[c] || c; - } - // quote open/close & CSV escape pair (aka `""`) - else if (c === quote) { - if (!isQuoted) { - p = ""; - isQuoted = true; - continue; - } else if (p === quote) { - curr += quote; - p = ""; - continue; - } else if (line[i + 1] !== quote) isQuoted = false; - } - // field delimiter - else if (!isQuoted && c === delim) { - collectCell(acc, curr, openQuote); - openQuote = false; - curr = ""; - } - // record unless escape seq start - else if (c !== "\\") { - curr += c; - } - p = c; - } - curr !== "" && collectCell(acc, curr, openQuote); - return isQuoted; + let curr = ""; + let p = ""; + let openQuote = isQuoted; + for (let i = 0, n = line.length; i < n; i++) { + const c = line[i]; + // escaped char + if (p === "\\") { + curr += ESCAPES[c] || c; + } + // quote open/close & CSV escape pair (aka `""`) + else if (c === quote) { + if (!isQuoted) { + p = ""; + isQuoted = true; + continue; + } else if (p === quote) { + curr += quote; + p = ""; + continue; + } else if (line[i + 1] !== quote) isQuoted = false; + } + // field delimiter + else if (!isQuoted && c === delim) { + collectCell(acc, curr, openQuote); + openQuote = false; + curr = ""; + } + // record unless escape seq start + else if (c !== "\\") { + curr += c; + } + p = c; + } + curr !== "" && collectCell(acc, curr, openQuote); + return isQuoted; }; const collectCell = (acc: string[], curr: string, openQuote: boolean) => - openQuote ? (acc[acc.length - 1] += "\n" + curr) : acc.push(curr); + openQuote ? (acc[acc.length - 1] += "\n" + curr) : acc.push(curr); const initIndex = ( - line: string[], - cols: Nullable[] | Record + line: string[], + cols: Nullable[] | Record ) => - isArray(cols) - ? cols.reduce((acc, spec, i) => { - if (spec) { - const alias = spec.alias || line[i] || String(i); - acc[alias] = { i, spec: { alias, ...spec } }; - } - return acc; - }, >{}) - : line.reduce( - (acc, id, i) => - cols![id] ? ((acc[id] = { i, spec: cols![id] }), acc) : acc, - >{} - ); + isArray(cols) + ? cols.reduce((acc, spec, i) => { + if (spec) { + const alias = spec.alias || line[i] || String(i); + acc[alias] = { i, spec: { alias, ...spec } }; + } + return acc; + }, >{}) + : line.reduce( + (acc, id, i) => + cols![id] ? ((acc[id] = { i, spec: cols![id] }), acc) : acc, + >{} + ); const initRevIndex = (line: string[]) => - line.reduce((acc, x, i) => ((acc[i] = x), acc), >{}); + line.reduce((acc, x, i) => ((acc[i] = x), acc), >{}); diff --git a/packages/csv/src/transforms.ts b/packages/csv/src/transforms.ts index c0e47d57fa..2369298654 100644 --- a/packages/csv/src/transforms.ts +++ b/packages/csv/src/transforms.ts @@ -6,14 +6,14 @@ import type { CellTransform } from "./api.js"; /** * Cell parse value transform. Returns uppercased version of given input. * - * @param x - + * @param x - */ export const upper: CellTransform = (x) => x.toUpperCase(); /** * Cell parse value transform. Returns lowercased version of given input. * - * @param x - + * @param x - */ export const lower: CellTransform = (x) => x.toLowerCase(); @@ -21,34 +21,34 @@ export const lower: CellTransform = (x) => x.toLowerCase(); * Higher-order cell parse value transform. Attempts to parse cell values as * floating point number or returns `defaultVal` if not possible. * - * @param defaultVal - + * @param defaultVal - */ export const float = - (defaultVal = 0): CellTransform => - (x) => - maybeParseFloat(x, defaultVal); + (defaultVal = 0): CellTransform => + (x) => + maybeParseFloat(x, defaultVal); /** * Higher-order cell parse value transform. Attempts to parse cell values as * integer or returns `defaultVal` if not possible. * - * @param defaultVal - + * @param defaultVal - */ export const int = - (defaultVal = 0): CellTransform => - (x) => - maybeParseInt(x, defaultVal, 10); + (defaultVal = 0): CellTransform => + (x) => + maybeParseInt(x, defaultVal, 10); /** * Higher-order cell parse value transform. Attempts to parse cell values as * hexadecimal integer or returns `defaultVal` if not possible. * - * @param defaultVal - + * @param defaultVal - */ export const hex = - (defaultVal = 0): CellTransform => - (x) => - maybeParseInt(x, defaultVal, 16); + (defaultVal = 0): CellTransform => + (x) => + maybeParseInt(x, defaultVal, 16); export const percent: CellTransform = (x) => maybeParseFloat(x) * 0.01; @@ -56,36 +56,36 @@ export const percent: CellTransform = (x) => maybeParseFloat(x) * 0.01; * Higher-order cell parse value transform. Attempts to parse cell values as * Unix epoch/timestamp or returns `defaultVal` if not possible. * - * @param defaultVal - + * @param defaultVal - */ export const epoch = - (defaultVal = 0): CellTransform => - (x) => { - const res = Date.parse(x); - return isNaN(res) ? defaultVal : res; - }; + (defaultVal = 0): CellTransform => + (x) => { + const res = Date.parse(x); + return isNaN(res) ? defaultVal : res; + }; /** * Higher-order cell parse value transform. Attempts to parse cell values as JS * `Date` instance or returns `defaultVal` if not possible. * - * @param defaultVal - + * @param defaultVal - */ export const date = - (defaultVal?: Date): CellTransform => - (x) => { - const epoch = Date.parse(x); - if (isNaN(epoch)) return defaultVal; - const res = new Date(); - res.setTime(epoch); - return res; - }; + (defaultVal?: Date): CellTransform => + (x) => { + const epoch = Date.parse(x); + if (isNaN(epoch)) return defaultVal; + const res = new Date(); + res.setTime(epoch); + return res; + }; /** * Cell parse value transform. Attempts to parse cell values as JS `URL` * instances. * - * @param x - + * @param x - */ export const url: CellTransform = (x) => new URL(x); @@ -94,9 +94,9 @@ export const url: CellTransform = (x) => new URL(x); export const zeroPad = (digits: number) => padLeft(digits, "0"); export const formatFloat = - (prec = 2) => - (x: number) => - x.toFixed(prec); + (prec = 2) => + (x: number) => + x.toFixed(prec); /** * Higher order cell value formatter. Takes normalized values and formats them diff --git a/packages/csv/test/format.ts b/packages/csv/test/format.ts index 83a2293eff..ff6d697c92 100644 --- a/packages/csv/test/format.ts +++ b/packages/csv/test/format.ts @@ -1,44 +1,44 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { formatCSV, formatFloat, zeroPad } from "../src/index.js" +import { formatCSV, formatFloat, zeroPad } from "../src/index.js"; group("format array", { - header: () => - assert.deepStrictEqual( - [...formatCSV({ header: ["a", "b"] }, [[1, 2]])], - ["a,b", "1,2"] - ), + header: () => + assert.deepStrictEqual( + [...formatCSV({ header: ["a", "b"] }, [[1, 2]])], + ["a,b", "1,2"] + ), - "no header": () => - assert.deepStrictEqual([...formatCSV({}, [[1, 2]])], ["1,2"]), + "no header": () => + assert.deepStrictEqual([...formatCSV({}, [[1, 2]])], ["1,2"]), - tx: () => - assert.deepStrictEqual( - [...formatCSV({ cols: [null, formatFloat(2)] }, [[1, 2]])], - ["1,2.00"] - ), + tx: () => + assert.deepStrictEqual( + [...formatCSV({ cols: [null, formatFloat(2)] }, [[1, 2]])], + ["1,2.00"] + ), }); group("format obj", { - header: () => - assert.deepStrictEqual( - [...formatCSV({ header: ["a", "b"] }, [{ a: 1, b: 2 }])], - ["a,b", "1,2"] - ), + header: () => + assert.deepStrictEqual( + [...formatCSV({ header: ["a", "b"] }, [{ a: 1, b: 2 }])], + ["a,b", "1,2"] + ), - "no header": () => - assert.deepStrictEqual( - [...formatCSV({}, [{ a: 1, b: 2 }])], - ["a,b", "1,2"] - ), + "no header": () => + assert.deepStrictEqual( + [...formatCSV({}, [{ a: 1, b: 2 }])], + ["a,b", "1,2"] + ), - tx: () => - assert.deepStrictEqual( - [ - ...formatCSV({ cols: { a: zeroPad(4), b: formatFloat(2) } }, [ - { a: 1, b: 2 }, - ]), - ], - ["a,b", "0001,2.00"] - ), + tx: () => + assert.deepStrictEqual( + [ + ...formatCSV({ cols: { a: zeroPad(4), b: formatFloat(2) } }, [ + { a: 1, b: 2 }, + ]), + ], + ["a,b", "0001,2.00"] + ), }); diff --git a/packages/csv/test/parse.ts b/packages/csv/test/parse.ts index 8fd130d14d..66ed3ba17b 100644 --- a/packages/csv/test/parse.ts +++ b/packages/csv/test/parse.ts @@ -1,100 +1,100 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { parseCSV, parseCSVFromString } from "../src/index.js" +import { parseCSV, parseCSVFromString } from "../src/index.js"; group("parse", { - header: () => { - assert.deepStrictEqual( - [...parseCSV({ header: ["a", "b", "c"] }, ["1,2,3"])], - [{ a: "1", b: "2", c: "3" }] - ); - }, + header: () => { + assert.deepStrictEqual( + [...parseCSV({ header: ["a", "b", "c"] }, ["1,2,3"])], + [{ a: "1", b: "2", c: "3" }] + ); + }, - "column mapping (obj)": () => { - assert.deepStrictEqual( - [ - ...parseCSV( - { - cols: { - a: { alias: "aa", tx: (x) => x.toUpperCase() }, - b: { alias: "bb", tx: (x, row) => row.aa + x }, - }, - }, - ["a,b,c", "foo,23,42", "bar,66,88"] - ), - ], - [ - { aa: "FOO", bb: "FOO23", c: "42" }, - { aa: "BAR", bb: "BAR66", c: "88" }, - ] - ); - }, + "column mapping (obj)": () => { + assert.deepStrictEqual( + [ + ...parseCSV( + { + cols: { + a: { alias: "aa", tx: (x) => x.toUpperCase() }, + b: { alias: "bb", tx: (x, row) => row.aa + x }, + }, + }, + ["a,b,c", "foo,23,42", "bar,66,88"] + ), + ], + [ + { aa: "FOO", bb: "FOO23", c: "42" }, + { aa: "BAR", bb: "BAR66", c: "88" }, + ] + ); + }, - "column mapping (array, no header)": () => { - assert.deepStrictEqual( - [ - ...parseCSV( - { - all: false, - header: [], - cols: [ - { tx: (x) => x.toUpperCase() }, - null, - { alias: "cc", tx: (x, row) => row[0] + x }, - ], - }, - ["foo,23,42", "bar,66,88"] - ), - ], - [ - { 0: "FOO", cc: "FOO42" }, - { 0: "BAR", cc: "BAR88" }, - ] - ); - }, + "column mapping (array, no header)": () => { + assert.deepStrictEqual( + [ + ...parseCSV( + { + all: false, + header: [], + cols: [ + { tx: (x) => x.toUpperCase() }, + null, + { alias: "cc", tx: (x, row) => row[0] + x }, + ], + }, + ["foo,23,42", "bar,66,88"] + ), + ], + [ + { 0: "FOO", cc: "FOO42" }, + { 0: "BAR", cc: "BAR88" }, + ] + ); + }, - "column mapping (array, w/ header)": () => { - assert.deepStrictEqual( - [ - ...parseCSV( - { - all: false, - header: ["a", "b", "c"], - cols: [ - { tx: (x) => x.toUpperCase() }, - null, - { alias: "cc", tx: (x, row) => row.a + x }, - ], - }, - ["foo,23,42", "bar,66,88"] - ), - ], - [ - { a: "FOO", cc: "FOO42" }, - { a: "BAR", cc: "BAR88" }, - ] - ); - }, + "column mapping (array, w/ header)": () => { + assert.deepStrictEqual( + [ + ...parseCSV( + { + all: false, + header: ["a", "b", "c"], + cols: [ + { tx: (x) => x.toUpperCase() }, + null, + { alias: "cc", tx: (x, row) => row.a + x }, + ], + }, + ["foo,23,42", "bar,66,88"] + ), + ], + [ + { a: "FOO", cc: "FOO42" }, + { a: "BAR", cc: "BAR88" }, + ] + ); + }, - quotes: () => { - assert.deepStrictEqual( - [...parseCSVFromString({}, `a,b,c\n"ha ""he""\nho","2,",3\n4,,6`)], - [ - { a: `ha "he"\nho`, b: "2,", c: "3" }, - { a: "4", b: "", c: "6" }, - ] - ); - }, + quotes: () => { + assert.deepStrictEqual( + [...parseCSVFromString({}, `a,b,c\n"ha ""he""\nho","2,",3\n4,,6`)], + [ + { a: `ha "he"\nho`, b: "2,", c: "3" }, + { a: "4", b: "", c: "6" }, + ] + ); + }, - "quotes in header": () => { - assert.deepStrictEqual( - [ - ...parseCSVFromString( - {}, - `"foo","bar\nbaz","fin,\n#ignore"\n#ignore2\n1,2,3\n` - ), - ], - [{ foo: "1", "bar\nbaz": "2", "fin,\n#ignore": "3" }] - ); - }, + "quotes in header": () => { + assert.deepStrictEqual( + [ + ...parseCSVFromString( + {}, + `"foo","bar\nbaz","fin,\n#ignore"\n#ignore2\n1,2,3\n` + ), + ], + [{ foo: "1", "bar\nbaz": "2", "fin,\n#ignore": "3" }] + ); + }, }); diff --git a/packages/csv/tsconfig.json b/packages/csv/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/csv/tsconfig.json +++ b/packages/csv/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/date/api-extractor.json b/packages/date/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/date/api-extractor.json +++ b/packages/date/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/date/package.json b/packages/date/package.json index e12c376084..682d54f859 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -1,132 +1,132 @@ { - "name": "@thi.ng/date", - "version": "2.3.2", - "description": "Datetime types, relative dates, math, iterators, composable formatters, locales", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/date#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/strings": "^3.3.6" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "datastructure", - "date", - "format", - "i18n", - "interval", - "iterator", - "math", - "offset", - "parser", - "precision", - "relative", - "smpte", - "string", - "time", - "typescript", - "utc" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "i18n", - "internal" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./checks": { - "default": "./checks.js" - }, - "./datetime": { - "default": "./datetime.js" - }, - "./format": { - "default": "./format.js" - }, - "./i18n/de": { - "default": "./i18n/de.js" - }, - "./i18n/en": { - "default": "./i18n/en.js" - }, - "./i18n/es": { - "default": "./i18n/es.js" - }, - "./i18n/fr": { - "default": "./i18n/fr.js" - }, - "./i18n/it": { - "default": "./i18n/it.js" - }, - "./i18n": { - "default": "./i18n.js" - }, - "./iterators": { - "default": "./iterators.js" - }, - "./relative": { - "default": "./relative.js" - }, - "./round": { - "default": "./round.js" - }, - "./timecode": { - "default": "./timecode.js" - }, - "./units": { - "default": "./units.js" - } - }, - "thi.ng": { - "year": 2020 - } + "name": "@thi.ng/date", + "version": "2.3.2", + "description": "Datetime types, relative dates, math, iterators, composable formatters, locales", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/date#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/strings": "^3.3.6" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "datastructure", + "date", + "format", + "i18n", + "interval", + "iterator", + "math", + "offset", + "parser", + "precision", + "relative", + "smpte", + "string", + "time", + "typescript", + "utc" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "i18n", + "internal" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./checks": { + "default": "./checks.js" + }, + "./datetime": { + "default": "./datetime.js" + }, + "./format": { + "default": "./format.js" + }, + "./i18n/de": { + "default": "./i18n/de.js" + }, + "./i18n/en": { + "default": "./i18n/en.js" + }, + "./i18n/es": { + "default": "./i18n/es.js" + }, + "./i18n/fr": { + "default": "./i18n/fr.js" + }, + "./i18n/it": { + "default": "./i18n/it.js" + }, + "./i18n": { + "default": "./i18n.js" + }, + "./iterators": { + "default": "./iterators.js" + }, + "./relative": { + "default": "./relative.js" + }, + "./round": { + "default": "./round.js" + }, + "./timecode": { + "default": "./timecode.js" + }, + "./units": { + "default": "./units.js" + } + }, + "thi.ng": { + "year": 2020 + } } diff --git a/packages/date/src/api.ts b/packages/date/src/api.ts index cd320c3d00..f92b6d612a 100644 --- a/packages/date/src/api.ts +++ b/packages/date/src/api.ts @@ -10,7 +10,7 @@ export const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; * LUT of day-in-year values for 1st of each month (non-leap year) */ export const DAYS_IN_MONTH_OFFSET = [ - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, ]; /** @@ -48,97 +48,97 @@ export const YEAR = (DAYS_IN_400YEARS / 400) * DAY; export const MONTH = YEAR / 12; export interface Locale { - /** - * Names of months - */ - months: string[]; - /** - * Names of weekdays (starting w/ Sunday) - */ - days: string[]; - /** - * Default date format spec for use with {@link defFormat} - * - * @defaultValue ["E", "/ED", "d", "/DM", "MMM", "/MY", "yyyy"] - */ - date: string[]; - /** - * Default time format spec for use with {@link defFormat} - * - * @defaultValue ["H", "/HM", "mm"] - */ - time: string[]; - /** - * Default combined date & time format spec for use with {@link defFormat} - * and {@link DateTime.toLocaleString}. - * - * @defaultValue concatenation of `date` and `time` options - */ - dateTime: string[]; - /** - * Separator between day & month. - * - * @defaultValue "/" - */ - sepDM: string; - /** - * Separator between month & year - * - * @defaultValue " " - */ - sepMY: string; - /** - * Separator between weekday & day - * - * @defaultValue " " - */ - sepED: string; - /** - * Separator between hour & minute - * - * @defaultValue ":" - */ - sepHM: string; - /** - * Singular & plural versions of various date/time units. - */ - units: Record; - /** - * Translated version of "less than" for use with {@link unitsLessThan}. - * (`%s` will be replaced with the provided numeric value). - */ - less: string; - /** - * Template for past tense (`%s` will be replaced with result). - */ - past: string; - /** - * Translated version of "now". - */ - now: string; - /** - * Template for future tense (`%s` will be replaced with result). - */ - future: string; + /** + * Names of months + */ + months: string[]; + /** + * Names of weekdays (starting w/ Sunday) + */ + days: string[]; + /** + * Default date format spec for use with {@link defFormat} + * + * @defaultValue ["E", "/ED", "d", "/DM", "MMM", "/MY", "yyyy"] + */ + date: string[]; + /** + * Default time format spec for use with {@link defFormat} + * + * @defaultValue ["H", "/HM", "mm"] + */ + time: string[]; + /** + * Default combined date & time format spec for use with {@link defFormat} + * and {@link DateTime.toLocaleString}. + * + * @defaultValue concatenation of `date` and `time` options + */ + dateTime: string[]; + /** + * Separator between day & month. + * + * @defaultValue "/" + */ + sepDM: string; + /** + * Separator between month & year + * + * @defaultValue " " + */ + sepMY: string; + /** + * Separator between weekday & day + * + * @defaultValue " " + */ + sepED: string; + /** + * Separator between hour & minute + * + * @defaultValue ":" + */ + sepHM: string; + /** + * Singular & plural versions of various date/time units. + */ + units: Record; + /** + * Translated version of "less than" for use with {@link unitsLessThan}. + * (`%s` will be replaced with the provided numeric value). + */ + less: string; + /** + * Template for past tense (`%s` will be replaced with result). + */ + past: string; + /** + * Translated version of "now". + */ + now: string; + /** + * Template for future tense (`%s` will be replaced with result). + */ + future: string; } export interface LocaleUnit { - /** - * Singular - */ - s: string; - /** - * Plural - */ - p: string; - /** - * Singular dativ. If omitted, the same as singular. - */ - sd?: string; - /** - * Plural dativ. If omitted, the same as plural. - */ - pd?: string; + /** + * Singular + */ + s: string; + /** + * Plural + */ + p: string; + /** + * Singular dativ. If omitted, the same as singular. + */ + sd?: string; + /** + * Plural dativ. If omitted, the same as plural. + */ + pd?: string; } /** @@ -146,15 +146,15 @@ export interface LocaleUnit { * {@link setLocale}. */ export type LocaleSpec = Pick & - Partial>; + Partial>; export interface IEpoch { - getTime(): number; + getTime(): number; } export interface EpochIteratorConstructor { - ([from, to]: MaybeDate[]): EpochIterator; - (from: MaybeDate, to: MaybeDate): EpochIterator; + ([from, to]: MaybeDate[]): EpochIterator; + (from: MaybeDate, to: MaybeDate): EpochIterator; } export type EpochIterator = IterableIterator; diff --git a/packages/date/src/checks.ts b/packages/date/src/checks.ts index 7d4809b9bd..86dbb9bc4d 100644 --- a/packages/date/src/checks.ts +++ b/packages/date/src/checks.ts @@ -6,22 +6,22 @@ import type { MaybeDate } from "./api.js"; /** * Coerces `x` to a native JS `Date` instance. * - * @param x - + * @param x - */ export const ensureDate = (x: MaybeDate): Date => - isString(x) || isNumber(x) - ? new Date(x) - : implementsFunction(x, "toDate") - ? x.toDate() - : x; + isString(x) || isNumber(x) + ? new Date(x) + : implementsFunction(x, "toDate") + ? x.toDate() + : x; /** * Coerces `x` to a timestamp. * - * @param x - + * @param x - */ export const ensureEpoch = (x: MaybeDate) => - (implementsFunction(x, "getTime") ? x : ensureDate(x)).getTime(); + (implementsFunction(x, "getTime") ? x : ensureDate(x)).getTime(); export const isLeapYear = (year: number) => - !(year % 4) && (!!(year % 100) || !(year % 400)); + !(year % 4) && (!!(year % 100) || !(year % 400)); diff --git a/packages/date/src/datetime.ts b/packages/date/src/datetime.ts index a3672bf3ed..1ca904c864 100644 --- a/packages/date/src/datetime.ts +++ b/packages/date/src/datetime.ts @@ -3,13 +3,13 @@ import { isNumber } from "@thi.ng/checks/is-number"; import { isString } from "@thi.ng/checks/is-string"; import { Z2, Z3, Z4 } from "@thi.ng/strings/pad-left"; import { - DAY, - HOUR, - MaybeDate, - MINUTE, - Period, - Precision, - SECOND, + DAY, + HOUR, + MaybeDate, + MINUTE, + Period, + Precision, + SECOND, } from "./api.js"; import { ensureDate, ensureEpoch, isLeapYear } from "./checks.js"; import { defFormat } from "./format.js"; @@ -18,376 +18,376 @@ import { __precisionToID } from "./internal/precision.js"; import { dayInYear, daysInMonth, weekInYear } from "./units.js"; export const dateTime = (epoch?: MaybeDate, prec?: Precision) => - new DateTime(epoch, prec); + new DateTime(epoch, prec); /** * Epoch abstraction with adjustable coarseness/precision. All date fields in * UTC only. */ export class DateTime - implements - ICopy, - ICompare, - IEquiv, - IEqualsDelta + implements + ICopy, + ICompare, + IEquiv, + IEqualsDelta { - t: number; - s: number; - m: number; - h: number; - d: number; - M: number; - y: number; - - constructor(epoch: MaybeDate = Date.now(), prec: Precision = "t") { - const x = ensureDate(epoch); - const id = __precisionToID(prec); - this.y = x.getUTCFullYear(); - this.M = id >= 1 ? x.getUTCMonth() : 0; - this.d = id >= 2 ? x.getUTCDate() : 1; - this.h = id >= 3 ? x.getUTCHours() : 0; - this.m = id >= 4 ? x.getUTCMinutes() : 0; - this.s = id >= 5 ? x.getUTCSeconds() : 0; - this.t = id >= 6 ? x.getUTCMilliseconds() : 0; - } - - /** - * Readonly property, returning 1-based quarter - * - * @remarks - * - 1 = Jan - Mar - * - 2 = Apr - Jun - * - 3 = Jul - Sep - * - 4 = Oct - Dec - */ - get q() { - return ((this.M / 3) | 0) + 1; - } - - /** - * Alias readonly property, same as {@link DateTime.weekInYear}. - */ - get w() { - return this.weekInYear(); - } - - set(d: MaybeDate) { - const $d = ensureDateTime(d); - this.y = $d.y; - this.M = $d.M; - this.d = $d.d; - this.h = $d.h; - this.m = $d.m; - this.s = $d.s; - this.t = $d.t; - return this; - } - - copy() { - return new DateTime(this.toISOString()); - } - - getTime() { - return Date.UTC(this.y, this.M, this.d, this.h, this.m, this.s, this.t); - } - - withPrecision(prec: Precision) { - return new DateTime(this, prec); - } - - setPrecision(prec: Precision) { - const precID = __precisionToID(prec); - precID < 6 && (this.t = 0); - precID < 5 && (this.s = 0); - precID < 4 && (this.m = 0); - precID < 3 && (this.h = 0); - precID < 2 && (this.d = 1); - precID < 1 && (this.M = 0); - return this; - } - - compare(d: MaybeDate) { - return this.getTime() - ensureEpoch(d); - } - - /** - * Returns true if this instance is before the given date, i.e. if - * `this.compare(d) < 0`. - * - * @param d - - */ - isBefore(d: MaybeDate) { - return this.compare(d) < 0; - } - - /** - * Returns true if this instance is before the given date, i.e. if - * `this.compare(d) > 0`. - * - * @param d - - */ - isAfter(d: MaybeDate) { - return this.compare(d) > 0; - } - - equiv(o: any) { - return maybeIsDate(o) ? this.compare(o) === 0 : false; - } - - eqDelta(d: MaybeDate, eps = 0) { - return Math.abs(this.getTime() - ensureDate(d).getTime()) <= eps; - } - - daysInMonth() { - return daysInMonth(this.y, this.M); - } - - dayInYear() { - return dayInYear(this.y, this.M, this.d); - } - - /** - * Returns week number according to ISO8601. - * - * @remarks - * Reference: - * https://en.wikipedia.org/wiki/Week#The_ISO_week_date_system - * - */ - weekInYear() { - return weekInYear(this.y, this.M, this.d); - } - - /** - * Leap years are multiple of 4, excludingcentennial years that aren’t - * multiples of 400. - */ - isLeapYear() { - return isLeapYear(this.y); - } - - incMillisecond() { - if (++this.t > 999) { - this.t = 0; - this.incSecond(); - } - return this.t; - } - - decMillisecond() { - if (--this.t < 0) { - this.t = 999; - this.decSecond(); - } - return this.t; - } - - incSecond() { - if (++this.s > 59) { - this.s = 0; - this.incMinute(); - } - return this.s; - } - - decSecond() { - if (--this.s < 0) { - this.s = 59; - this.decMinute(); - } - return this.s; - } - - incMinute() { - if (++this.m > 59) { - this.m = 0; - this.incHour(); - } - return this.m; - } - - decMinute() { - if (--this.m < 0) { - this.m = 59; - this.decHour(); - } - return this.m; - } - - incHour() { - if (++this.h > 23) { - this.h = 0; - this.incDay(); - } - return this.h; - } - - decHour() { - if (--this.h < 0) { - this.h = 23; - this.decDay(); - } - return this.h; - } - - incDay() { - if (++this.d > this.daysInMonth()) { - this.d = 1; - this.incMonth(); - } - return this.d; - } - - decDay() { - if (--this.d < 1) { - this.decMonth(); - this.d = this.daysInMonth(); - } - return this.d; - } - - incWeek() { - this.d += 7; - const max = this.daysInMonth(); - if (this.d > max) { - this.d -= max; - this.incMonth(); - } - return this.weekInYear(); - } - - decWeek() { - this.d -= 7; - if (this.d < 1) { - this.decMonth(); - this.d += this.daysInMonth(); - } - return this.weekInYear(); - } - - incMonth() { - if (++this.M > 11) { - this.M = 0; - ++this.y; - } - return this.M; - } - - decMonth() { - if (--this.M < 0) { - this.M = 11; - --this.y; - } - return this.M; - } - - incQuarter() { - this.M += 3; - if (this.M > 11) { - this.M %= 12; - this.y++; - } - return this.q; - } - - decQuarter() { - this.M -= 3; - if (this.M < 0) { - this.M += 12; - this.y--; - } - return this.q; - } - - incYear() { - // TODO epoch overflow handling, throw error? - return ++this.y; - } - - decYear() { - // TODO epoch underflow handling, throw error? - return --this.y; - } - - /** - * Returns a new `DateTime` instance relative to this date, but with given - * period added/subtracted. - * - * @param x - - * @param prec - - */ - add(x: number, prec: Period): DateTime { - if (prec === "w") return this.add(x * 7, "d"); - if (prec === "q") return this.add(x * 3, "M"); - const res = this.copy(); - const precID = __precisionToID(prec); - if (precID >= 2) { - res.set( - res.getTime() + x * [DAY, HOUR, MINUTE, SECOND, 1][precID - 2] - ); - } else if (prec === "M") { - const y = (x / 12) | 0; - res.y += y; - x -= y * 12; - const m = res.M + x; - m > 11 && res.y++; - m < 0 && res.y--; - res.M = m % 12; - if (res.M < 0) res.M += 12; - res.d = Math.min(res.d, res.daysInMonth()); - } else if (prec === "y") { - res.y += x; - } - return res; - } - - toDate() { - return new Date(this.toISOString()); - } - - toJSON() { - return this.toISOString(); - } - - toString() { - return this.toDate().toUTCString(); - } - - /** - * Returns formatted version using current {@link LOCALE.dateTime} - * formatter. - * - * @remarks - * The host environment's locale is NOT used. Only the currently active - * `LOCALE` is relevant. - */ - toLocaleString() { - return defFormat(LOCALE.dateTime)(this, true); - } - - toISOString() { - return `${Z4(this.y)}-${Z2(this.M + 1)}-${Z2(this.d)}T${Z2( - this.h - )}:${Z2(this.m)}:${Z2(this.s)}.${Z3(this.t)}Z`; - } - - valueOf() { - return this.getTime(); - } + t: number; + s: number; + m: number; + h: number; + d: number; + M: number; + y: number; + + constructor(epoch: MaybeDate = Date.now(), prec: Precision = "t") { + const x = ensureDate(epoch); + const id = __precisionToID(prec); + this.y = x.getUTCFullYear(); + this.M = id >= 1 ? x.getUTCMonth() : 0; + this.d = id >= 2 ? x.getUTCDate() : 1; + this.h = id >= 3 ? x.getUTCHours() : 0; + this.m = id >= 4 ? x.getUTCMinutes() : 0; + this.s = id >= 5 ? x.getUTCSeconds() : 0; + this.t = id >= 6 ? x.getUTCMilliseconds() : 0; + } + + /** + * Readonly property, returning 1-based quarter + * + * @remarks + * - 1 = Jan - Mar + * - 2 = Apr - Jun + * - 3 = Jul - Sep + * - 4 = Oct - Dec + */ + get q() { + return ((this.M / 3) | 0) + 1; + } + + /** + * Alias readonly property, same as {@link DateTime.weekInYear}. + */ + get w() { + return this.weekInYear(); + } + + set(d: MaybeDate) { + const $d = ensureDateTime(d); + this.y = $d.y; + this.M = $d.M; + this.d = $d.d; + this.h = $d.h; + this.m = $d.m; + this.s = $d.s; + this.t = $d.t; + return this; + } + + copy() { + return new DateTime(this.toISOString()); + } + + getTime() { + return Date.UTC(this.y, this.M, this.d, this.h, this.m, this.s, this.t); + } + + withPrecision(prec: Precision) { + return new DateTime(this, prec); + } + + setPrecision(prec: Precision) { + const precID = __precisionToID(prec); + precID < 6 && (this.t = 0); + precID < 5 && (this.s = 0); + precID < 4 && (this.m = 0); + precID < 3 && (this.h = 0); + precID < 2 && (this.d = 1); + precID < 1 && (this.M = 0); + return this; + } + + compare(d: MaybeDate) { + return this.getTime() - ensureEpoch(d); + } + + /** + * Returns true if this instance is before the given date, i.e. if + * `this.compare(d) < 0`. + * + * @param d - + */ + isBefore(d: MaybeDate) { + return this.compare(d) < 0; + } + + /** + * Returns true if this instance is before the given date, i.e. if + * `this.compare(d) > 0`. + * + * @param d - + */ + isAfter(d: MaybeDate) { + return this.compare(d) > 0; + } + + equiv(o: any) { + return maybeIsDate(o) ? this.compare(o) === 0 : false; + } + + eqDelta(d: MaybeDate, eps = 0) { + return Math.abs(this.getTime() - ensureDate(d).getTime()) <= eps; + } + + daysInMonth() { + return daysInMonth(this.y, this.M); + } + + dayInYear() { + return dayInYear(this.y, this.M, this.d); + } + + /** + * Returns week number according to ISO8601. + * + * @remarks + * Reference: + * https://en.wikipedia.org/wiki/Week#The_ISO_week_date_system + * + */ + weekInYear() { + return weekInYear(this.y, this.M, this.d); + } + + /** + * Leap years are multiple of 4, excludingcentennial years that aren’t + * multiples of 400. + */ + isLeapYear() { + return isLeapYear(this.y); + } + + incMillisecond() { + if (++this.t > 999) { + this.t = 0; + this.incSecond(); + } + return this.t; + } + + decMillisecond() { + if (--this.t < 0) { + this.t = 999; + this.decSecond(); + } + return this.t; + } + + incSecond() { + if (++this.s > 59) { + this.s = 0; + this.incMinute(); + } + return this.s; + } + + decSecond() { + if (--this.s < 0) { + this.s = 59; + this.decMinute(); + } + return this.s; + } + + incMinute() { + if (++this.m > 59) { + this.m = 0; + this.incHour(); + } + return this.m; + } + + decMinute() { + if (--this.m < 0) { + this.m = 59; + this.decHour(); + } + return this.m; + } + + incHour() { + if (++this.h > 23) { + this.h = 0; + this.incDay(); + } + return this.h; + } + + decHour() { + if (--this.h < 0) { + this.h = 23; + this.decDay(); + } + return this.h; + } + + incDay() { + if (++this.d > this.daysInMonth()) { + this.d = 1; + this.incMonth(); + } + return this.d; + } + + decDay() { + if (--this.d < 1) { + this.decMonth(); + this.d = this.daysInMonth(); + } + return this.d; + } + + incWeek() { + this.d += 7; + const max = this.daysInMonth(); + if (this.d > max) { + this.d -= max; + this.incMonth(); + } + return this.weekInYear(); + } + + decWeek() { + this.d -= 7; + if (this.d < 1) { + this.decMonth(); + this.d += this.daysInMonth(); + } + return this.weekInYear(); + } + + incMonth() { + if (++this.M > 11) { + this.M = 0; + ++this.y; + } + return this.M; + } + + decMonth() { + if (--this.M < 0) { + this.M = 11; + --this.y; + } + return this.M; + } + + incQuarter() { + this.M += 3; + if (this.M > 11) { + this.M %= 12; + this.y++; + } + return this.q; + } + + decQuarter() { + this.M -= 3; + if (this.M < 0) { + this.M += 12; + this.y--; + } + return this.q; + } + + incYear() { + // TODO epoch overflow handling, throw error? + return ++this.y; + } + + decYear() { + // TODO epoch underflow handling, throw error? + return --this.y; + } + + /** + * Returns a new `DateTime` instance relative to this date, but with given + * period added/subtracted. + * + * @param x - + * @param prec - + */ + add(x: number, prec: Period): DateTime { + if (prec === "w") return this.add(x * 7, "d"); + if (prec === "q") return this.add(x * 3, "M"); + const res = this.copy(); + const precID = __precisionToID(prec); + if (precID >= 2) { + res.set( + res.getTime() + x * [DAY, HOUR, MINUTE, SECOND, 1][precID - 2] + ); + } else if (prec === "M") { + const y = (x / 12) | 0; + res.y += y; + x -= y * 12; + const m = res.M + x; + m > 11 && res.y++; + m < 0 && res.y--; + res.M = m % 12; + if (res.M < 0) res.M += 12; + res.d = Math.min(res.d, res.daysInMonth()); + } else if (prec === "y") { + res.y += x; + } + return res; + } + + toDate() { + return new Date(this.toISOString()); + } + + toJSON() { + return this.toISOString(); + } + + toString() { + return this.toDate().toUTCString(); + } + + /** + * Returns formatted version using current {@link LOCALE.dateTime} + * formatter. + * + * @remarks + * The host environment's locale is NOT used. Only the currently active + * `LOCALE` is relevant. + */ + toLocaleString() { + return defFormat(LOCALE.dateTime)(this, true); + } + + toISOString() { + return `${Z4(this.y)}-${Z2(this.M + 1)}-${Z2(this.d)}T${Z2( + this.h + )}:${Z2(this.m)}:${Z2(this.s)}.${Z3(this.t)}Z`; + } + + valueOf() { + return this.getTime(); + } } /** * Coerces `x` to a {@link DateTime} instance. * - * @param x - + * @param x - */ export const ensureDateTime = (x: MaybeDate, prec: Precision = "t") => - x instanceof DateTime ? x : new DateTime(x, prec); + x instanceof DateTime ? x : new DateTime(x, prec); /** * Returns true if `x` is a {@link MaybeDate}. * - * @param x - + * @param x - */ export const maybeIsDate = (x: any) => - x instanceof DateTime || x instanceof Date || isNumber(x) || isString(x); + x instanceof DateTime || x instanceof Date || isNumber(x) || isString(x); diff --git a/packages/date/src/format.ts b/packages/date/src/format.ts index 9bc438dcb7..2820dbefe0 100644 --- a/packages/date/src/format.ts +++ b/packages/date/src/format.ts @@ -7,140 +7,140 @@ import { LOCALE } from "./i18n.js"; import { weekInYear } from "./units.js"; export const FORMATTERS: Record = { - /** - * Full year (4 digits) - */ - yyyy: (d) => Z4(d.getFullYear()), - /** - * Short year (2 digits, e.g. `2020 % 100` => 20) - */ - yy: (d) => Z2(d.getFullYear() % 100), - /** - * Month name, using current {@link LOCALE} (e.g. `Feb`) - */ - MMM: (d) => LOCALE.months[d.getMonth()], - /** - * Zero-padded 2-digit month - */ - MM: (d) => Z2(d.getMonth() + 1), - /** - * Unpadded month - */ - M: (d) => String(d.getMonth() + 1), - /** - * Zero-padded 2-digit day of month - */ - dd: (d) => Z2(d.getDate()), - /** - * Unpadded day of month - */ - d: (d) => String(d.getDate()), - /** - * Weekday name, using current {@link LOCALE} (e.g. `Mon`) - */ - E: (d) => LOCALE.days[d.getDay()], - /** - * Zero-padded 2-digit ISO week number. - */ - ww: (d) => Z2(FORMATTERS.w(d, false)), - /** - * Unpadded ISO week number. - */ - w: (d) => String(weekInYear(d.getFullYear(), d.getMonth(), d.getDate())), - /** - * Unpadded quarter: - * - * - 1 = Jan - Mar - * - 2 = Apr - Jun - * - 3 = Jul - Sep - * - 4 = Oct - Dec - */ - q: (d) => String(((d.getMonth() / 3) | 0) + 1), - /** - * Zero-padded 2-digit hour of day (0-23) - */ - HH: (d) => Z2(d.getHours()), - /** - * Unpadded our of day (0-23) - */ - H: (d) => String(d.getHours()), - /** - * Zero-padded hour of day (1-12) - */ - hh: (d) => { - const h = d.getHours() % 12; - return Z2(h > 0 ? h : 12); - }, - /** - * Unpadded hour of day (1-12) - */ - h: (d) => { - const h = d.getHours() % 12; - return String(h > 0 ? h : 12); - }, - /** - * Zero-padded 2-digit minute of hour - */ - mm: (d) => Z2(d.getMinutes()), - /** - * Unpadded minute of hour - */ - m: (d) => String(d.getMinutes()), - /** - * Zero-padded 2-digit second of minute - */ - ss: (d) => Z2(d.getSeconds()), - /** - * Unpadded second of minute - */ - s: (d) => String(d.getSeconds()), - /** - * Zero-padded 3-digit millisecond of second - */ - SS: (d) => Z3(d.getMilliseconds()), - /** - * Unpadded millisecond of second - */ - S: (d) => String(d.getMilliseconds()), - /** - * 12-hour AM/PM marker (uppercase) - */ - A: (d) => String(d.getHours() < 12 ? "AM" : "PM"), - /** - * 12-hour am/pm marker (lowercase) - */ - a: (d) => String(d.getHours() < 12 ? "am" : "pm"), - /** - * Timezone offset in signed `HH:mm` format - */ - Z: (d, utc = false) => { - const z = utc ? 0 : d.getTimezoneOffset(); - const za = Math.abs(z); - return `${z < 0 ? "+" : "-"}${Z2((za / 60) | 0)}:${Z2(za % 60)}`; - }, - /** - * Returns literal `"Z"` iff timezone offset is zero (UTC), else the same as - * `Z` formatter. - * - * @param d - - */ - ZZ: (d, utc = false) => (utc ? "Z" : FORMATTERS.Z(d, utc)), - /** - * Current {@link LOCALE}'s day-month separator. - */ - "/DM": () => LOCALE.sepDM, - /** - * Current {@link LOCALE}'s weekday-day separator. - */ - "/ED": () => LOCALE.sepED, - /** - * Current {@link LOCALE}'s hour-minute separator. - */ - "/HM": () => LOCALE.sepHM, - /** - * Current {@link LOCALE}'s month-year separator. - */ - "/MY": () => LOCALE.sepMY, + /** + * Full year (4 digits) + */ + yyyy: (d) => Z4(d.getFullYear()), + /** + * Short year (2 digits, e.g. `2020 % 100` => 20) + */ + yy: (d) => Z2(d.getFullYear() % 100), + /** + * Month name, using current {@link LOCALE} (e.g. `Feb`) + */ + MMM: (d) => LOCALE.months[d.getMonth()], + /** + * Zero-padded 2-digit month + */ + MM: (d) => Z2(d.getMonth() + 1), + /** + * Unpadded month + */ + M: (d) => String(d.getMonth() + 1), + /** + * Zero-padded 2-digit day of month + */ + dd: (d) => Z2(d.getDate()), + /** + * Unpadded day of month + */ + d: (d) => String(d.getDate()), + /** + * Weekday name, using current {@link LOCALE} (e.g. `Mon`) + */ + E: (d) => LOCALE.days[d.getDay()], + /** + * Zero-padded 2-digit ISO week number. + */ + ww: (d) => Z2(FORMATTERS.w(d, false)), + /** + * Unpadded ISO week number. + */ + w: (d) => String(weekInYear(d.getFullYear(), d.getMonth(), d.getDate())), + /** + * Unpadded quarter: + * + * - 1 = Jan - Mar + * - 2 = Apr - Jun + * - 3 = Jul - Sep + * - 4 = Oct - Dec + */ + q: (d) => String(((d.getMonth() / 3) | 0) + 1), + /** + * Zero-padded 2-digit hour of day (0-23) + */ + HH: (d) => Z2(d.getHours()), + /** + * Unpadded our of day (0-23) + */ + H: (d) => String(d.getHours()), + /** + * Zero-padded hour of day (1-12) + */ + hh: (d) => { + const h = d.getHours() % 12; + return Z2(h > 0 ? h : 12); + }, + /** + * Unpadded hour of day (1-12) + */ + h: (d) => { + const h = d.getHours() % 12; + return String(h > 0 ? h : 12); + }, + /** + * Zero-padded 2-digit minute of hour + */ + mm: (d) => Z2(d.getMinutes()), + /** + * Unpadded minute of hour + */ + m: (d) => String(d.getMinutes()), + /** + * Zero-padded 2-digit second of minute + */ + ss: (d) => Z2(d.getSeconds()), + /** + * Unpadded second of minute + */ + s: (d) => String(d.getSeconds()), + /** + * Zero-padded 3-digit millisecond of second + */ + SS: (d) => Z3(d.getMilliseconds()), + /** + * Unpadded millisecond of second + */ + S: (d) => String(d.getMilliseconds()), + /** + * 12-hour AM/PM marker (uppercase) + */ + A: (d) => String(d.getHours() < 12 ? "AM" : "PM"), + /** + * 12-hour am/pm marker (lowercase) + */ + a: (d) => String(d.getHours() < 12 ? "am" : "pm"), + /** + * Timezone offset in signed `HH:mm` format + */ + Z: (d, utc = false) => { + const z = utc ? 0 : d.getTimezoneOffset(); + const za = Math.abs(z); + return `${z < 0 ? "+" : "-"}${Z2((za / 60) | 0)}:${Z2(za % 60)}`; + }, + /** + * Returns literal `"Z"` iff timezone offset is zero (UTC), else the same as + * `Z` formatter. + * + * @param d - + */ + ZZ: (d, utc = false) => (utc ? "Z" : FORMATTERS.Z(d, utc)), + /** + * Current {@link LOCALE}'s day-month separator. + */ + "/DM": () => LOCALE.sepDM, + /** + * Current {@link LOCALE}'s weekday-day separator. + */ + "/ED": () => LOCALE.sepED, + /** + * Current {@link LOCALE}'s hour-minute separator. + */ + "/HM": () => LOCALE.sepHM, + /** + * Current {@link LOCALE}'s month-year separator. + */ + "/MY": () => LOCALE.sepMY, }; /** @@ -170,25 +170,25 @@ export const FORMATTERS: Record = { * @param fmt - */ export const defFormat = - (fmt: (string | FormatFn)[]) => - (x: MaybeDate = Date.now(), utc = false) => { - let d = ensureDate(x); - utc && (d = new Date(d.getTime() + d.getTimezoneOffset() * MINUTE)); - return fmt - .map((x) => { - let fn: FormatFn; - return isString(x) - ? x.startsWith("\\") - ? x.substring(1) - : (fn = FORMATTERS[x]) - ? fn(d, utc) - : x - : isFunction(x) - ? x(d, utc) - : x; - }) - .join(""); - }; + (fmt: (string | FormatFn)[]) => + (x: MaybeDate = Date.now(), utc = false) => { + let d = ensureDate(x); + utc && (d = new Date(d.getTime() + d.getTimezoneOffset() * MINUTE)); + return fmt + .map((x) => { + let fn: FormatFn; + return isString(x) + ? x.startsWith("\\") + ? x.substring(1) + : (fn = FORMATTERS[x]) + ? fn(d, utc) + : x + : isFunction(x) + ? x(d, utc) + : x; + }) + .join(""); + }; /** * Format preset, e.g. `2020-09-19` diff --git a/packages/date/src/i18n.ts b/packages/date/src/i18n.ts index b6ba08dff0..75164559ce 100644 --- a/packages/date/src/i18n.ts +++ b/packages/date/src/i18n.ts @@ -4,18 +4,18 @@ import type { Locale, LocaleSpec, LocaleUnit, Precision } from "./api.js"; import { EN_SHORT } from "./i18n/en.js"; const prepLocale = (spec: LocaleSpec): Locale => { - const locale = { - sepED: " ", - sepDM: "/", - sepMY: "/", - sepHM: ":", - date: ["E", "/ED", "d", "/DM", "MMM", "/MY", "yyyy"], - time: ["H", "/HM", "mm"], - ...spec, - }; - !locale.dateTime && - (locale.dateTime = [...locale.date, ", ", ...locale.time]); - return locale; + const locale = { + sepED: " ", + sepDM: "/", + sepMY: "/", + sepHM: ":", + date: ["E", "/ED", "d", "/DM", "MMM", "/MY", "yyyy"], + time: ["H", "/HM", "mm"], + ...spec, + }; + !locale.dateTime && + (locale.dateTime = [...locale.date, ", ", ...locale.time]); + return locale; }; /** @@ -25,7 +25,7 @@ const prepLocale = (spec: LocaleSpec): Locale => { * @param locale - */ export const setLocale = (locale: LocaleSpec): Locale => - (LOCALE = prepLocale(locale)); + (LOCALE = prepLocale(locale)); /** * Executes given `fn` with temporarily active `locale`. Returns result of `fn`. @@ -38,16 +38,16 @@ export const setLocale = (locale: LocaleSpec): Locale => * @param fn - */ export const withLocale = (locale: LocaleSpec, fn: Fn0) => { - const old = LOCALE; - setLocale(locale); - try { - const res = fn(); - setLocale(old); - return res; - } catch (e) { - setLocale(old); - throw e; - } + const old = LOCALE; + setLocale(locale); + try { + const res = fn(); + setLocale(old); + return res; + } catch (e) { + setLocale(old); + throw e; + } }; export let LOCALE = prepLocale(EN_SHORT); @@ -98,21 +98,21 @@ export const monthNames = () => LOCALE.months.slice(); * @param unitsOnly - */ export const units = ( - x: number, - unit: Precision | LocaleUnit, - isDativ = false, - unitsOnly = false + x: number, + unit: Precision | LocaleUnit, + isDativ = false, + unitsOnly = false ) => { - unit = isString(unit) ? LOCALE.units[unit] : unit; - const res = - x > 1 || x === 0 - ? isDativ - ? unit.pd || unit.p - : unit.p - : isDativ - ? unit.sd || unit.s - : unit.s; - return unitsOnly ? res : `${x} ${res}`; + unit = isString(unit) ? LOCALE.units[unit] : unit; + const res = + x > 1 || x === 0 + ? isDativ + ? unit.pd || unit.p + : unit.p + : isDativ + ? unit.sd || unit.s + : unit.s; + return unitsOnly ? res : `${x} ${res}`; }; /** @@ -130,16 +130,16 @@ export const units = ( * @param isDativ - */ export const unitsLessThan = ( - x: number, - unit: Precision | LocaleUnit, - isDativ = false + x: number, + unit: Precision | LocaleUnit, + isDativ = false ) => - `${LOCALE.less.replace("%s", String(x))} ${units( - Math.max(x, 1), - unit, - isDativ, - true - )}`; + `${LOCALE.less.replace("%s", String(x))} ${units( + Math.max(x, 1), + unit, + isDativ, + true + )}`; /** * Wraps given (presumably localized) string in current {@link LOCALE}'s `past` @@ -149,4 +149,4 @@ export const unitsLessThan = ( * @param res - */ export const tense = (sign: number, res: string) => - (sign < 0 ? LOCALE.past : LOCALE.future).replace("%s", res); + (sign < 0 ? LOCALE.past : LOCALE.future).replace("%s", res); diff --git a/packages/date/src/i18n/de.ts b/packages/date/src/i18n/de.ts index 17f5468131..376cfb3202 100644 --- a/packages/date/src/i18n/de.ts +++ b/packages/date/src/i18n/de.ts @@ -5,38 +5,38 @@ import type { LocaleSpec } from "../api.js"; * Reference: https://en.wikipedia.org/wiki/Date_and_time_notation_in_Europe#Germany */ export const DE_SHORT: LocaleSpec = { - months: [ - "Jan", - "Feb", - "Mär", - "Apr", - "Mai", - "Jun", - "Jul", - "Aug", - "Sep", - "Okt", - "Nov", - "Dez", - ], - days: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], - sepED: ", ", - sepDM: ".", - sepMY: ".", - date: ["d", "/DM", "M", "/MY", "yyyy"], - units: { - y: { s: "J.", p: "J." }, - M: { s: "Mo.", p: "Mo." }, - d: { s: "T.", p: "T." }, - h: { s: "Std.", p: "Std." }, - m: { s: "Min.", p: "Min." }, - s: { s: "Sek.", p: "Sek." }, - t: { s: "ms", p: "ms" }, - }, - less: "< %s", - past: "vor %s", - now: "jetzt", - future: "in %s", + months: [ + "Jan", + "Feb", + "Mär", + "Apr", + "Mai", + "Jun", + "Jul", + "Aug", + "Sep", + "Okt", + "Nov", + "Dez", + ], + days: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], + sepED: ", ", + sepDM: ".", + sepMY: ".", + date: ["d", "/DM", "M", "/MY", "yyyy"], + units: { + y: { s: "J.", p: "J." }, + M: { s: "Mo.", p: "Mo." }, + d: { s: "T.", p: "T." }, + h: { s: "Std.", p: "Std." }, + m: { s: "Min.", p: "Min." }, + s: { s: "Sek.", p: "Sek." }, + t: { s: "ms", p: "ms" }, + }, + less: "< %s", + past: "vor %s", + now: "jetzt", + future: "in %s", }; /** @@ -44,43 +44,43 @@ export const DE_SHORT: LocaleSpec = { * Reference: https://en.wikipedia.org/wiki/Date_and_time_notation_in_Europe#Germany */ export const DE_LONG: LocaleSpec = { - months: [ - "Januar", - "Februar", - "März", - "April", - "Mai", - "Juni", - "Juli", - "August", - "September", - "Oktober", - "November", - "Dezember", - ], - days: [ - "Sonntag", - "Montag", - "Dienstag", - "Mittwoch", - "Donnerstag", - "Freitag", - "Samstag", - ], - sepED: ", ", - sepDM: ". ", - sepMY: " ", - units: { - y: { s: "Jahr", p: "Jahre", pd: "Jahren" }, - M: { s: "Monat", p: "Monate", pd: "Monaten" }, - d: { s: "Tag", p: "Tage", pd: "Tagen" }, - h: { s: "Stunde", p: "Stunden" }, - m: { s: "Minute", p: "Minuten" }, - s: { s: "Sekunde", p: "Sekunden" }, - t: { s: "Millisekunde", p: "Millisekunden" }, - }, - less: "weniger als %s", - past: "vor %s", - now: "jetzt", - future: "in %s", + months: [ + "Januar", + "Februar", + "März", + "April", + "Mai", + "Juni", + "Juli", + "August", + "September", + "Oktober", + "November", + "Dezember", + ], + days: [ + "Sonntag", + "Montag", + "Dienstag", + "Mittwoch", + "Donnerstag", + "Freitag", + "Samstag", + ], + sepED: ", ", + sepDM: ". ", + sepMY: " ", + units: { + y: { s: "Jahr", p: "Jahre", pd: "Jahren" }, + M: { s: "Monat", p: "Monate", pd: "Monaten" }, + d: { s: "Tag", p: "Tage", pd: "Tagen" }, + h: { s: "Stunde", p: "Stunden" }, + m: { s: "Minute", p: "Minuten" }, + s: { s: "Sekunde", p: "Sekunden" }, + t: { s: "Millisekunde", p: "Millisekunden" }, + }, + less: "weniger als %s", + past: "vor %s", + now: "jetzt", + future: "in %s", }; diff --git a/packages/date/src/i18n/en.ts b/packages/date/src/i18n/en.ts index 38cada9498..1605ec22f0 100644 --- a/packages/date/src/i18n/en.ts +++ b/packages/date/src/i18n/en.ts @@ -5,37 +5,37 @@ import type { LocaleSpec } from "../api.js"; * Reference: https://en.wikipedia.org/wiki/Date_and_time_notation_in_the_United_Kingdom */ export const EN_SHORT: LocaleSpec = { - months: [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ], - days: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - sepHM: ".", - date: ["dd", "/DM", "MM", "/MY", "yyyy"], - time: ["h", "/HM", "mm", " ", "a"], - units: { - y: { s: "y", p: "y" }, - M: { s: "m", p: "m" }, - d: { s: "d", p: "d" }, - h: { s: "h", p: "h" }, - m: { s: "min", p: "min" }, - s: { s: "s", p: "s" }, - t: { s: "ms", p: "ms" }, - }, - less: "< %s", - past: "%s ago", - now: "just now", - future: "in %s", + months: [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ], + days: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + sepHM: ".", + date: ["dd", "/DM", "MM", "/MY", "yyyy"], + time: ["h", "/HM", "mm", " ", "a"], + units: { + y: { s: "y", p: "y" }, + M: { s: "m", p: "m" }, + d: { s: "d", p: "d" }, + h: { s: "h", p: "h" }, + m: { s: "min", p: "min" }, + s: { s: "s", p: "s" }, + t: { s: "ms", p: "ms" }, + }, + less: "< %s", + past: "%s ago", + now: "just now", + future: "in %s", }; /** @@ -43,44 +43,44 @@ export const EN_SHORT: LocaleSpec = { * Reference: https://en.wikipedia.org/wiki/Date_and_time_notation_in_the_United_Kingdom */ export const EN_LONG: LocaleSpec = { - months: [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - days: [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ], - sepDM: " ", - sepMY: " ", - sepHM: ".", - time: ["h", "/HM", "mm", " ", "a"], - units: { - y: { s: "year", p: "years" }, - M: { s: "month", p: "months" }, - d: { s: "day", p: "days" }, - h: { s: "hour", p: "hours" }, - m: { s: "minute", p: "minutes" }, - s: { s: "second", p: "seconds" }, - t: { s: "millisecond", p: "milliseconds" }, - }, - less: "less than %s", - past: "%s ago", - now: "just now", - future: "in %s", + months: [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], + days: [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ], + sepDM: " ", + sepMY: " ", + sepHM: ".", + time: ["h", "/HM", "mm", " ", "a"], + units: { + y: { s: "year", p: "years" }, + M: { s: "month", p: "months" }, + d: { s: "day", p: "days" }, + h: { s: "hour", p: "hours" }, + m: { s: "minute", p: "minutes" }, + s: { s: "second", p: "seconds" }, + t: { s: "millisecond", p: "milliseconds" }, + }, + less: "less than %s", + past: "%s ago", + now: "just now", + future: "in %s", }; diff --git a/packages/date/src/i18n/es.ts b/packages/date/src/i18n/es.ts index fee6c2c1d1..1da1aa9921 100644 --- a/packages/date/src/i18n/es.ts +++ b/packages/date/src/i18n/es.ts @@ -5,42 +5,42 @@ import type { LocaleSpec } from "../api.js"; * Reference: https://en.wikipedia.org/wiki/Date_and_time_notation_in_Spain */ export const ES_LONG: LocaleSpec = { - months: [ - "enero", - "febrero", - "marzo", - "abril", - "mayo", - "junio", - "julio", - "agosto", - "septiembre", - "octubre", - "noviembre", - "diciembre", - ], - days: [ - "domingo", - "lunes", - "martes", - "miércoles", - "jueves", - "viernes", - "sábado", - ], - sepDM: " ", - sepMY: " ", - units: { - y: { s: "año", p: "años" }, - M: { s: "mes", p: "meses" }, - d: { s: "día", p: "días" }, - h: { s: "hora", p: "horas" }, - m: { s: "minuto", p: "minutos" }, - s: { s: "segundo", p: "segundos" }, - t: { s: "millisegundo", p: "millisegundos" }, - }, - less: "menos de %s", - past: "hace %s", - now: "ahora", - future: "en %s", + months: [ + "enero", + "febrero", + "marzo", + "abril", + "mayo", + "junio", + "julio", + "agosto", + "septiembre", + "octubre", + "noviembre", + "diciembre", + ], + days: [ + "domingo", + "lunes", + "martes", + "miércoles", + "jueves", + "viernes", + "sábado", + ], + sepDM: " ", + sepMY: " ", + units: { + y: { s: "año", p: "años" }, + M: { s: "mes", p: "meses" }, + d: { s: "día", p: "días" }, + h: { s: "hora", p: "horas" }, + m: { s: "minuto", p: "minutos" }, + s: { s: "segundo", p: "segundos" }, + t: { s: "millisegundo", p: "millisegundos" }, + }, + less: "menos de %s", + past: "hace %s", + now: "ahora", + future: "en %s", }; diff --git a/packages/date/src/i18n/fr.ts b/packages/date/src/i18n/fr.ts index 843126e85f..d113182869 100644 --- a/packages/date/src/i18n/fr.ts +++ b/packages/date/src/i18n/fr.ts @@ -5,44 +5,44 @@ import type { LocaleSpec } from "../api.js"; * Reference: https://en.wikipedia.org/wiki/Date_and_time_notation_in_France */ export const FR_LONG: LocaleSpec = { - months: [ - "janvier", - "février", - "mars", - "avril", - "mai", - "juin", - "juillet", - "aout", - "septembre", - "octobre", - "novembre", - "décembre", - ], - days: [ - "dimanche", - "lundi", - "mardi", - "mercredi", - "jeudi", - "vendredi", - "samedi", - ], - sepDM: " ", - sepMY: " ", - sepHM: "h ", - units: { - y: { s: "année", sd: "an", p: "ans" }, - M: { s: "mois", p: "mois" }, - d: { s: "jour", sd: "journée", p: "jours" }, - h: { s: "heure", p: "heures" }, - m: { s: "minute", p: "minutes" }, - s: { s: "seconde", p: "secondes" }, - t: { s: "milliseconde", p: "millisecondes" }, - }, - less: "moins de %s", - past: "il y a %s", - now: "à présent", - // https://www.thoughtco.com/learn-essential-french-prepositions-4078684 - future: "dans %s", + months: [ + "janvier", + "février", + "mars", + "avril", + "mai", + "juin", + "juillet", + "aout", + "septembre", + "octobre", + "novembre", + "décembre", + ], + days: [ + "dimanche", + "lundi", + "mardi", + "mercredi", + "jeudi", + "vendredi", + "samedi", + ], + sepDM: " ", + sepMY: " ", + sepHM: "h ", + units: { + y: { s: "année", sd: "an", p: "ans" }, + M: { s: "mois", p: "mois" }, + d: { s: "jour", sd: "journée", p: "jours" }, + h: { s: "heure", p: "heures" }, + m: { s: "minute", p: "minutes" }, + s: { s: "seconde", p: "secondes" }, + t: { s: "milliseconde", p: "millisecondes" }, + }, + less: "moins de %s", + past: "il y a %s", + now: "à présent", + // https://www.thoughtco.com/learn-essential-french-prepositions-4078684 + future: "dans %s", }; diff --git a/packages/date/src/i18n/it.ts b/packages/date/src/i18n/it.ts index 9c0758e631..5c3fe5fb3c 100644 --- a/packages/date/src/i18n/it.ts +++ b/packages/date/src/i18n/it.ts @@ -5,44 +5,44 @@ import type { LocaleSpec } from "../api.js"; * Reference: https://en.wikipedia.org/wiki/Date_and_time_notation_in_Italy */ export const IT_LONG: LocaleSpec = { - months: [ - "gennaio", - "febbraio", - "marzo", - "aprile", - "maggio", - "giugno", - "luglio", - "agosto", - "settembre", - "ottobre", - "novembre", - "dicembre", - ], - days: [ - "domenica", - "lunedì", - "martedì", - "mercoledì", - "giovedì", - "venerdì", - "sabato", - ], - sepDM: " ", - sepMY: " ", - sepHM: ".", - units: { - y: { s: "anno", p: "anni" }, - M: { s: "mese", p: "mesi" }, - d: { s: "giorno", p: "giorni" }, - h: { s: "ora", p: "ore" }, - m: { s: "minuto", p: "minuti" }, - s: { s: "secondo", p: "secondi" }, - t: { s: "millisecondo", p: "millisecondi" }, - }, - less: "meno di %s", - past: "%s fa", - // https://dailyitalianwords.com/ora-vs-adesso/ - now: "ora", - future: "in %s", + months: [ + "gennaio", + "febbraio", + "marzo", + "aprile", + "maggio", + "giugno", + "luglio", + "agosto", + "settembre", + "ottobre", + "novembre", + "dicembre", + ], + days: [ + "domenica", + "lunedì", + "martedì", + "mercoledì", + "giovedì", + "venerdì", + "sabato", + ], + sepDM: " ", + sepMY: " ", + sepHM: ".", + units: { + y: { s: "anno", p: "anni" }, + M: { s: "mese", p: "mesi" }, + d: { s: "giorno", p: "giorni" }, + h: { s: "ora", p: "ore" }, + m: { s: "minuto", p: "minuti" }, + s: { s: "secondo", p: "secondi" }, + t: { s: "millisecondo", p: "millisecondi" }, + }, + less: "meno di %s", + past: "%s fa", + // https://dailyitalianwords.com/ora-vs-adesso/ + now: "ora", + future: "in %s", }; diff --git a/packages/date/src/internal/precision.ts b/packages/date/src/internal/precision.ts index 36b39d9ad8..986438a03a 100644 --- a/packages/date/src/internal/precision.ts +++ b/packages/date/src/internal/precision.ts @@ -3,7 +3,7 @@ import type { Precision } from "../api.js"; /** * Converts a {@link Precision} into a numeric ID. * - * @param prec - + * @param prec - * * @internal */ @@ -12,7 +12,7 @@ export const __precisionToID = (prec: Precision) => "yMdhmst".indexOf(prec); /** * Inverse op of {@link __precisionToID}. * - * @param id - + * @param id - * * @internal */ diff --git a/packages/date/src/iterators.ts b/packages/date/src/iterators.ts index eff206bc95..0105d1ae7b 100644 --- a/packages/date/src/iterators.ts +++ b/packages/date/src/iterators.ts @@ -1,9 +1,9 @@ import type { Fn } from "@thi.ng/api"; import { isString } from "@thi.ng/checks/is-string"; import type { - EpochIterator, - EpochIteratorConstructor, - Precision, + EpochIterator, + EpochIteratorConstructor, + Precision, } from "./api.js"; import { DateTime } from "./datetime.js"; import { floorQuarter, floorWeek } from "./round.js"; @@ -12,33 +12,33 @@ import { floorQuarter, floorWeek } from "./round.js"; * Higher-order epoch iterator factory. Returns iterator with configured * precision and `tick` fn. * - * @param prec - - * @param tick - + * @param prec - + * @param tick - */ export const defIterator = ( - prec: Precision | Fn, - tick: Fn + prec: Precision | Fn, + tick: Fn ): EpochIteratorConstructor => { - return function* (...xs: any[]): EpochIterator { - let [from, to] = ((xs.length > 1 ? xs : xs[0])).map((x) => - new DateTime(x).getTime() - ); - let state = isString(prec) ? new DateTime(from, prec) : prec(from); - let epoch = from; - while (epoch < to) { - epoch = state.getTime(); - if (epoch >= from && epoch < to) yield epoch; - tick(state); - } - }; + return function* (...xs: any[]): EpochIterator { + let [from, to] = ((xs.length > 1 ? xs : xs[0])).map((x) => + new DateTime(x).getTime() + ); + let state = isString(prec) ? new DateTime(from, prec) : prec(from); + let epoch = from; + while (epoch < to) { + epoch = state.getTime(); + if (epoch >= from && epoch < to) yield epoch; + tick(state); + } + }; }; /** * Yields iterator of UTC timestamps in given semi-open interval in yearly * precision (each timestamp is at beginning of each year). * - * @param from - - * @param to - + * @param from - + * @param to - */ export const years = defIterator("y", (d) => d.incYear()); @@ -47,20 +47,20 @@ export const years = defIterator("y", (d) => d.incYear()); * precision (each timestamp is at beginning of a month), but spaced at 3 month * intervals. * - * @param from - - * @param to - + * @param from - + * @param to - */ export const quarters = defIterator( - (from) => new DateTime(floorQuarter(from)), - (d) => d.incQuarter() + (from) => new DateTime(floorQuarter(from)), + (d) => d.incQuarter() ); /** * Yields iterator of UTC timestamps in given semi-open interval in monthly * precision (each timestamp is at beginning of each month). * - * @param from - - * @param to - + * @param from - + * @param to - */ export const months = defIterator("M", (d) => d.incMonth()); @@ -69,20 +69,20 @@ export const months = defIterator("M", (d) => d.incMonth()); * precision (each timestamp is 7 days apart). As per ISO8601, weeks start on * Mondays. * - * @param from - - * @param to - + * @param from - + * @param to - */ export const weeks = defIterator( - (from) => new DateTime(floorWeek(from)), - (d) => d.incWeek() + (from) => new DateTime(floorWeek(from)), + (d) => d.incWeek() ); /** * Yields iterator of UTC timestamps in given semi-open interval in daily * precision (each timestamp is at midnight/beginning of each day). * - * @param from - - * @param to - + * @param from - + * @param to - */ export const days = defIterator("d", (d) => d.incDay()); @@ -90,8 +90,8 @@ export const days = defIterator("d", (d) => d.incDay()); * Yields iterator of UTC timestamps in given semi-open interval in hourly * precision. * - * @param from - - * @param to - + * @param from - + * @param to - */ export const hours = defIterator("h", (d) => d.incHour()); @@ -99,8 +99,8 @@ export const hours = defIterator("h", (d) => d.incHour()); * Yields iterator of UTC timestamps in given semi-open interval in minute * precision. * - * @param from - - * @param to - + * @param from - + * @param to - */ export const minutes = defIterator("m", (d) => d.incMinute()); @@ -108,8 +108,8 @@ export const minutes = defIterator("m", (d) => d.incMinute()); * Yields iterator of UTC timestamps in given semi-open interval in second * precision. * - * @param from - - * @param to - + * @param from - + * @param to - */ export const seconds = defIterator("s", (d) => d.incSecond()); @@ -117,7 +117,7 @@ export const seconds = defIterator("s", (d) => d.incSecond()); * Yields iterator of UTC timestamps in given semi-open interval in millisecond * precision. * - * @param from - - * @param to - + * @param from - + * @param to - */ export const milliseconds = defIterator("t", (d) => d.incMillisecond()); diff --git a/packages/date/src/relative.ts b/packages/date/src/relative.ts index 88f9121659..fba5cea908 100644 --- a/packages/date/src/relative.ts +++ b/packages/date/src/relative.ts @@ -1,13 +1,13 @@ import { - DAY, - HOUR, - MaybeDate, - MINUTE, - MONTH, - Period, - Precision, - SECOND, - YEAR, + DAY, + HOUR, + MaybeDate, + MINUTE, + MONTH, + Period, + Precision, + SECOND, + YEAR, } from "./api.js"; import { ensureEpoch } from "./checks.js"; import { DateTime, dateTime, ensureDateTime } from "./datetime.js"; @@ -53,71 +53,71 @@ import { __idToPrecision, __precisionToID } from "./internal/precision.js"; * @param base - */ export const parseRelative = (offset: string, base?: MaybeDate) => { - offset = offset.toLowerCase(); - const epoch = dateTime(base); - switch (offset) { - case "today": - return epoch; - case "tomorrow": - epoch.incDay(); - return epoch; - case "yesterday": - epoch.decDay(); - return epoch; - default: { - let idx = findIndex(EN_SHORT.days, offset); - if (idx < 0) { - idx = findIndex(EN_LONG.days, offset); - } - if (idx >= 0) { - do { - epoch.incDay(); - } while (epoch.toDate().getDay() != idx); - return epoch; - } - const match = - /^(an? |next |[-+]?\d+\s?)((ms|milli(?:(s?|seconds?)))|s(?:(ecs?|econds?))?|min(?:(s|utes?))?|h(?:ours?)?|d(?:ays?)?|w(?:eeks?)?|mo(?:nths?)?|q(?:uarters?)?|y(?:ears?)?)(\s+ago)?$/.exec( - offset - ); - return match - ? relative( - parseNum(match![1], !!match[7]), - parsePeriod(match![2]), - base - ) - : undefined; - } - } + offset = offset.toLowerCase(); + const epoch = dateTime(base); + switch (offset) { + case "today": + return epoch; + case "tomorrow": + epoch.incDay(); + return epoch; + case "yesterday": + epoch.decDay(); + return epoch; + default: { + let idx = findIndex(EN_SHORT.days, offset); + if (idx < 0) { + idx = findIndex(EN_LONG.days, offset); + } + if (idx >= 0) { + do { + epoch.incDay(); + } while (epoch.toDate().getDay() != idx); + return epoch; + } + const match = + /^(an? |next |[-+]?\d+\s?)((ms|milli(?:(s?|seconds?)))|s(?:(ecs?|econds?))?|min(?:(s|utes?))?|h(?:ours?)?|d(?:ays?)?|w(?:eeks?)?|mo(?:nths?)?|q(?:uarters?)?|y(?:ears?)?)(\s+ago)?$/.exec( + offset + ); + return match + ? relative( + parseNum(match![1], !!match[7]), + parsePeriod(match![2]), + base + ) + : undefined; + } + } }; const findIndex = (items: string[], x: string) => - items.findIndex((y) => y.toLowerCase() === x); + items.findIndex((y) => y.toLowerCase() === x); const parseNum = (x: string, past: boolean) => - (x === "next " || x === "a " || x === "an " ? 1 : Number(x)) * - (past ? -1 : 1); + (x === "next " || x === "a " || x === "an " ? 1 : Number(x)) * + (past ? -1 : 1); const parsePeriod = (x: string) => { - x = - x !== "s" && x !== "ms" && x.endsWith("s") - ? x.substring(0, x.length - 1) - : x; - return { - ms: "t", - milli: "t", - millisecond: "t", - sec: "s", - second: "s", - min: "m", - minute: "m", - hour: "h", - day: "d", - week: "w", - mo: "M", - month: "M", - quarter: "q", - year: "y", - }[x] || x; + x = + x !== "s" && x !== "ms" && x.endsWith("s") + ? x.substring(0, x.length - 1) + : x; + return { + ms: "t", + milli: "t", + millisecond: "t", + sec: "s", + second: "s", + min: "m", + minute: "m", + hour: "h", + day: "d", + week: "w", + mo: "M", + month: "M", + quarter: "q", + year: "y", + }[x] || x; }; /** @@ -130,9 +130,9 @@ const parsePeriod = (x: string) => { * @param base - */ export const relative = ( - num: number, - period: Period, - base: MaybeDate = dateTime() + num: number, + period: Period, + base: MaybeDate = dateTime() ) => dateTime(base).add(num, period); /** @@ -143,7 +143,7 @@ export const relative = ( * @param b - */ export const difference = (a: MaybeDate, b: MaybeDate) => - ensureEpoch(a) - ensureEpoch(b); + ensureEpoch(a) - ensureEpoch(b); /** * Computes and decomposes difference between given dates. Returns tuple of: @@ -155,58 +155,58 @@ export const difference = (a: MaybeDate, b: MaybeDate) => * @param b - */ export const decomposeDifference = ( - a: MaybeDate, - b: MaybeDate = new Date() + a: MaybeDate, + b: MaybeDate = new Date() ) => { - const dur = ensureEpoch(a) - ensureEpoch(b); - let abs = Math.abs(dur); - const milli = abs % SECOND; - abs -= milli; - const sec = abs % MINUTE; - abs -= sec; - const min = abs % HOUR; - abs -= min; - const hour = abs % DAY; - abs -= hour; + const dur = ensureEpoch(a) - ensureEpoch(b); + let abs = Math.abs(dur); + const milli = abs % SECOND; + abs -= milli; + const sec = abs % MINUTE; + abs -= sec; + const min = abs % HOUR; + abs -= min; + const hour = abs % DAY; + abs -= hour; - const parts = [ - Math.sign(dur), - 0, // year - 0, // month - 0, // day - hour / HOUR, - min / MINUTE, - sec / SECOND, - milli, - ]; + const parts = [ + Math.sign(dur), + 0, // year + 0, // month + 0, // day + hour / HOUR, + min / MINUTE, + sec / SECOND, + milli, + ]; - if (!abs) return parts; + if (!abs) return parts; - const diff = (a: DateTime, b: DateTime): number => { - const months = (b.y - a.y) * 12 + (b.M - a.M); - const bstart = +a.add(months, "M"); - let frac = +b - bstart; - frac /= - frac < 0 - ? bstart - +a.add(months - 1, "M") - : +a.add(months + 1, "M") - bstart; - return -(months + frac) || 0; - }; + const diff = (a: DateTime, b: DateTime): number => { + const months = (b.y - a.y) * 12 + (b.M - a.M); + const bstart = +a.add(months, "M"); + let frac = +b - bstart; + frac /= + frac < 0 + ? bstart - +a.add(months - 1, "M") + : +a.add(months + 1, "M") - bstart; + return -(months + frac) || 0; + }; - const aa = ensureDateTime(a, "d"); - const bb = ensureDateTime(b, "d"); - const months = Math.abs(aa.d < bb.d ? -diff(bb, aa) : diff(aa, bb)) | 0; + const aa = ensureDateTime(a, "d"); + const bb = ensureDateTime(b, "d"); + const months = Math.abs(aa.d < bb.d ? -diff(bb, aa) : diff(aa, bb)) | 0; - const days = (start: DateTime, end: DateTime) => - Math.abs( - +start.withPrecision("d").add(months, "M") - +end.withPrecision("d") - ) / DAY; + const days = (start: DateTime, end: DateTime) => + Math.abs( + +start.withPrecision("d").add(months, "M") - +end.withPrecision("d") + ) / DAY; - parts[1] = (months / 12) | 0; - parts[2] = months % 12; - parts[3] = dur < 0 ? days(aa, bb) : days(bb, aa); + parts[1] = (months / 12) | 0; + parts[2] = months % 12; + parts[3] = dur < 0 ? days(aa, bb) : days(bb, aa); - return parts; + return parts; }; /** @@ -241,42 +241,42 @@ export const decomposeDifference = ( * @param eps - */ export const formatRelative = ( - date: MaybeDate, - base: MaybeDate = new Date(), - prec = 0, - eps = 100 + date: MaybeDate, + base: MaybeDate = new Date(), + prec = 0, + eps = 100 ) => { - const delta = difference(date, base); - if (Math.abs(delta) < eps) return LOCALE.now; + const delta = difference(date, base); + if (Math.abs(delta) < eps) return LOCALE.now; - let abs = Math.abs(delta); - let unit: Precision; - if (abs < SECOND) { - unit = "t"; - } else if (abs < MINUTE) { - abs /= SECOND; - unit = "s"; - } else if (abs < HOUR) { - abs /= MINUTE; - unit = "m"; - } else if (abs < DAY) { - abs /= HOUR; - unit = "h"; - } else if (abs < MONTH) { - abs /= DAY; - unit = "d"; - } else if (abs < YEAR) { - abs /= MONTH; - unit = "M"; - } else { - abs /= YEAR; - unit = "y"; - } + let abs = Math.abs(delta); + let unit: Precision; + if (abs < SECOND) { + unit = "t"; + } else if (abs < MINUTE) { + abs /= SECOND; + unit = "s"; + } else if (abs < HOUR) { + abs /= MINUTE; + unit = "m"; + } else if (abs < DAY) { + abs /= HOUR; + unit = "h"; + } else if (abs < MONTH) { + abs /= DAY; + unit = "d"; + } else if (abs < YEAR) { + abs /= MONTH; + unit = "M"; + } else { + abs /= YEAR; + unit = "y"; + } - const exp = 10 ** -prec; - abs = Math.round(abs / exp) * exp; + const exp = 10 ** -prec; + abs = Math.round(abs / exp) * exp; - return tense(delta, `${abs.toFixed(prec)} ${units(abs, unit, true, true)}`); + return tense(delta, `${abs.toFixed(prec)} ${units(abs, unit, true, true)}`); }; /** @@ -310,37 +310,37 @@ export const formatRelative = ( * @param eps - */ export const formatRelativeParts = ( - date: MaybeDate, - base: MaybeDate = Date.now(), - prec: Precision = "s", - eps = 1000 + date: MaybeDate, + base: MaybeDate = Date.now(), + prec: Precision = "s", + eps = 1000 ) => { - date = ensureEpoch(date); - base = ensureEpoch(base); - if (Math.abs(date - base) < eps) return LOCALE.now; - const [sign, ...parts] = decomposeDifference(date, base); - const precID = __precisionToID(prec); - let maxID = precID; - while (!parts[maxID] && maxID > 0) maxID--; - let minID = parts.findIndex((x) => x > 0); - minID < 0 && (minID = maxID); - maxID = Math.min(Math.max(maxID, minID), precID); - if (minID <= precID && precID < 6) { - parts[maxID] = Math.round( - parts[maxID] + parts[maxID + 1] / [12, 31, 24, 60, 60, 1000][maxID] - ); - } - const res = parts - .slice(0, maxID + 1) - .map((x, i) => { - let unit = LOCALE.units[__idToPrecision(i)]; - return x > 0 - ? units(x, unit, true) - : i === maxID && maxID < 6 - ? unitsLessThan(1, unit, true) - : ""; - }) - .filter((x) => !!x) - .join(", "); - return tense(sign, res); + date = ensureEpoch(date); + base = ensureEpoch(base); + if (Math.abs(date - base) < eps) return LOCALE.now; + const [sign, ...parts] = decomposeDifference(date, base); + const precID = __precisionToID(prec); + let maxID = precID; + while (!parts[maxID] && maxID > 0) maxID--; + let minID = parts.findIndex((x) => x > 0); + minID < 0 && (minID = maxID); + maxID = Math.min(Math.max(maxID, minID), precID); + if (minID <= precID && precID < 6) { + parts[maxID] = Math.round( + parts[maxID] + parts[maxID + 1] / [12, 31, 24, 60, 60, 1000][maxID] + ); + } + const res = parts + .slice(0, maxID + 1) + .map((x, i) => { + let unit = LOCALE.units[__idToPrecision(i)]; + return x > 0 + ? units(x, unit, true) + : i === maxID && maxID < 6 + ? unitsLessThan(1, unit, true) + : ""; + }) + .filter((x) => !!x) + .join(", "); + return tense(sign, res); }; diff --git a/packages/date/src/round.ts b/packages/date/src/round.ts index a305930b8a..28ddc16ce4 100644 --- a/packages/date/src/round.ts +++ b/packages/date/src/round.ts @@ -4,175 +4,175 @@ import { ensureDate } from "./checks.js"; /** * Rounds down `epoch` to minute precision. * - * @param epoch - + * @param epoch - */ export const floorSecond: RoundingFn = (epoch) => { - const d = ensureDate(epoch); - return Date.UTC( - d.getUTCFullYear(), - d.getUTCMonth(), - d.getUTCDate(), - d.getUTCHours(), - d.getUTCMinutes(), - d.getUTCSeconds() - ); + const d = ensureDate(epoch); + return Date.UTC( + d.getUTCFullYear(), + d.getUTCMonth(), + d.getUTCDate(), + d.getUTCHours(), + d.getUTCMinutes(), + d.getUTCSeconds() + ); }; /** * Rounds down `epoch` to minute precision. * - * @param epoch - + * @param epoch - */ export const floorMinute: RoundingFn = (epoch) => { - const d = ensureDate(epoch); - return Date.UTC( - d.getUTCFullYear(), - d.getUTCMonth(), - d.getUTCDate(), - d.getUTCHours(), - d.getUTCMinutes() - ); + const d = ensureDate(epoch); + return Date.UTC( + d.getUTCFullYear(), + d.getUTCMonth(), + d.getUTCDate(), + d.getUTCHours(), + d.getUTCMinutes() + ); }; /** * Rounds down `epoch` to hour precision. * - * @param epoch - + * @param epoch - */ export const floorHour: RoundingFn = (epoch) => { - const d = ensureDate(epoch); - return Date.UTC( - d.getUTCFullYear(), - d.getUTCMonth(), - d.getUTCDate(), - d.getUTCHours() - ); + const d = ensureDate(epoch); + return Date.UTC( + d.getUTCFullYear(), + d.getUTCMonth(), + d.getUTCDate(), + d.getUTCHours() + ); }; /** * Rounds down `epoch` to day precision * - * @param epoch - + * @param epoch - */ export const floorDay: RoundingFn = (epoch) => { - const d = ensureDate(epoch); - return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()); + const d = ensureDate(epoch); + return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()); }; /** * Rounds down `epoch` to week precision. Assumes ISO8601 week logic, i.e. weeks * start on Monday. * - * @param epoch - + * @param epoch - */ export const floorWeek: RoundingFn = (epoch) => { - const d = ensureDate(epoch); - const w = d.getUTCDay(); - return ( - Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()) - - ((w || 7) - 1) * DAY - ); + const d = ensureDate(epoch); + const w = d.getUTCDay(); + return ( + Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()) - + ((w || 7) - 1) * DAY + ); }; /** * Rounds down `epoch` to month precision. * - * @param epoch - + * @param epoch - */ export const floorMonth: RoundingFn = (epoch) => { - const d = ensureDate(epoch); - return Date.UTC(d.getUTCFullYear(), d.getUTCMonth()); + const d = ensureDate(epoch); + return Date.UTC(d.getUTCFullYear(), d.getUTCMonth()); }; /** * Rounds down `epoch` to month precision, but at beginning of a quarter. * - * @param epoch - + * @param epoch - */ export const floorQuarter: RoundingFn = (epoch) => { - const d = ensureDate(epoch); - return Date.UTC(d.getUTCFullYear(), ((d.getUTCMonth() / 3) | 0) * 3); + const d = ensureDate(epoch); + return Date.UTC(d.getUTCFullYear(), ((d.getUTCMonth() / 3) | 0) * 3); }; /** * Rounds down `epoch` to year precision. * - * @param epoch - + * @param epoch - */ export const floorYear: RoundingFn = (epoch) => - Date.UTC(ensureDate(epoch).getUTCFullYear(), 0); + Date.UTC(ensureDate(epoch).getUTCFullYear(), 0); /** * Rounds up `epoch` to minute precision. * - * @param epoch - + * @param epoch - */ export const ceilSecond: RoundingFn = (epoch) => - floorSecond(ensureDate(epoch).getTime() + SECOND); + floorSecond(ensureDate(epoch).getTime() + SECOND); /** * Rounds up `epoch` to minute precision. * - * @param epoch - + * @param epoch - */ export const ceilMinute: RoundingFn = (epoch) => - floorMinute(ensureDate(epoch).getTime() + MINUTE); + floorMinute(ensureDate(epoch).getTime() + MINUTE); /** * Rounds up `epoch` to hour precision. * - * @param epoch - + * @param epoch - */ export const ceilHour: RoundingFn = (epoch) => - floorHour(ensureDate(epoch).getTime() + HOUR); + floorHour(ensureDate(epoch).getTime() + HOUR); /** * Rounds up `epoch` to day precision * - * @param epoch - + * @param epoch - */ export const ceilDay: RoundingFn = (epoch) => - floorDay(ensureDate(epoch).getTime() + DAY); + floorDay(ensureDate(epoch).getTime() + DAY); /** * Rounds up `epoch` to week precision. Assumes ISO8601 week logic, i.e. weeks * start on Monday. * - * @param epoch - + * @param epoch - */ export const ceilWeek: RoundingFn = (epoch) => - floorWeek(ensureDate(epoch).getTime() + WEEK); + floorWeek(ensureDate(epoch).getTime() + WEEK); /** * Rounds up `epoch` to month precision * - * @param epoch - + * @param epoch - */ export const ceilMonth: RoundingFn = (epoch) => { - const d = ensureDate(epoch); - let y = d.getUTCFullYear(); - let m = d.getUTCMonth() + 1; - m > 11 && y++; - return Date.UTC(y, m % 12); + const d = ensureDate(epoch); + let y = d.getUTCFullYear(); + let m = d.getUTCMonth() + 1; + m > 11 && y++; + return Date.UTC(y, m % 12); }; /** * Rounds up `epoch` to month precision (beginning of next quarter) * - * @param epoch - + * @param epoch - */ export const ceilQuarter: RoundingFn = (epoch) => { - const d = ensureDate(epoch); - let y = d.getUTCFullYear(); - let m = (((d.getUTCMonth() + 3) / 3) | 0) * 3; - m > 11 && y++; - return Date.UTC(y, m % 12); + const d = ensureDate(epoch); + let y = d.getUTCFullYear(); + let m = (((d.getUTCMonth() + 3) / 3) | 0) * 3; + m > 11 && y++; + return Date.UTC(y, m % 12); }; /** * Rounds up `epoch` to year precision * - * @param epoch - + * @param epoch - */ export const ceilYear: RoundingFn = (epoch) => - Date.UTC(ensureDate(epoch).getUTCFullYear() + 1, 0); + Date.UTC(ensureDate(epoch).getUTCFullYear() + 1, 0); diff --git a/packages/date/src/timecode.ts b/packages/date/src/timecode.ts index db6edeb0fc..a62afc2ecc 100644 --- a/packages/date/src/timecode.ts +++ b/packages/date/src/timecode.ts @@ -25,45 +25,45 @@ import { DAY, HOUR, MINUTE, MONTH, SECOND, YEAR } from "./api.js"; * // "01d 01h 02' 03" 29" * ``` * - * @param fps - - * @param sep - + * @param fps - + * @param sep - */ export const defTimecode = (fps: number, sep: ArrayLike = "::::") => { - const frame = 1000 / fps; - return (t: number) => { - const [_, __, d, h, m, s, ms] = decomposeDuration(t); - const parts = [ - Z2(h), - sep[1], - Z2(m), - sep[2], - Z2(s), - sep[3], - Z2((ms / frame) | 0), - ]; - d > 0 && parts.unshift(Z2(d), sep[0]); - return parts.join(""); - }; + const frame = 1000 / fps; + return (t: number) => { + const [_, __, d, h, m, s, ms] = decomposeDuration(t); + const parts = [ + Z2(h), + sep[1], + Z2(m), + sep[2], + Z2(s), + sep[3], + Z2((ms / frame) | 0), + ]; + d > 0 && parts.unshift(Z2(d), sep[0]); + return parts.join(""); + }; }; /** * Decomposes given duration (in milliseconds) into a tuple of: `[year, month, * day, hour, minute, second, millis]`. * - * @param dur - + * @param dur - */ export const decomposeDuration = (dur: number) => { - const year = (dur / YEAR) | 0; - dur -= year * YEAR; - const month = (dur / MONTH) | 0; - dur -= month * MONTH; - const day = (dur / DAY) | 0; - dur -= day * DAY; - const hour = (dur / HOUR) | 0; - dur -= hour * HOUR; - const min = (dur / MINUTE) | 0; - dur -= min * MINUTE; - const sec = (dur / SECOND) | 0; - dur -= sec * SECOND; - return [year, month, day, hour, min, sec, dur]; + const year = (dur / YEAR) | 0; + dur -= year * YEAR; + const month = (dur / MONTH) | 0; + dur -= month * MONTH; + const day = (dur / DAY) | 0; + dur -= day * DAY; + const hour = (dur / HOUR) | 0; + dur -= hour * HOUR; + const min = (dur / MINUTE) | 0; + dur -= min * MINUTE; + const sec = (dur / SECOND) | 0; + dur -= sec * SECOND; + return [year, month, day, hour, min, sec, dur]; }; diff --git a/packages/date/src/units.ts b/packages/date/src/units.ts index 00255f9707..c8baae3fb6 100644 --- a/packages/date/src/units.ts +++ b/packages/date/src/units.ts @@ -3,20 +3,20 @@ import { DAYS_IN_MONTH, DAYS_IN_MONTH_OFFSET } from "./api.js"; import { isLeapYear } from "./checks.js"; export const daysInMonth: FnN2 = (year, month) => { - const days = DAYS_IN_MONTH[month]; - return days + ~~(month === 1 && isLeapYear(year)); + const days = DAYS_IN_MONTH[month]; + return days + ~~(month === 1 && isLeapYear(year)); }; export const dayInYear: FnN3 = (y, m, d) => - DAYS_IN_MONTH_OFFSET[m] + d + ~~(m > 1 && isLeapYear(y)); + DAYS_IN_MONTH_OFFSET[m] + d + ~~(m > 1 && isLeapYear(y)); export const weekInYear: FnN3 = (y, m, d) => { - const start = new Date(Date.UTC(y, 0, 1)).getDay() || 7; - if (!m) { - if (start === 5 && d < 4) return 53; - if (start === 6 && d < 3) return 52 + ~~isLeapYear(y - 1); - if (start === 7 && d < 2) return 52; - } - const offset = (start < 5 ? 8 : 15) - start; - return Math.ceil((dayInYear(y, m, d) - offset) / 7 + 1); + const start = new Date(Date.UTC(y, 0, 1)).getDay() || 7; + if (!m) { + if (start === 5 && d < 4) return 53; + if (start === 6 && d < 3) return 52 + ~~isLeapYear(y - 1); + if (start === 7 && d < 2) return 52; + } + const offset = (start < 5 ? 8 : 15) - start; + return Math.ceil((dayInYear(y, m, d) - offset) / 7 + 1); }; diff --git a/packages/date/test/datetime.ts b/packages/date/test/datetime.ts index 237736d602..2788adf09e 100644 --- a/packages/date/test/datetime.ts +++ b/packages/date/test/datetime.ts @@ -1,72 +1,72 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { dateTime } from "../src/index.js" +import { dateTime } from "../src/index.js"; group("datetime", { - "leap years": () => { - assert.deepStrictEqual( - [1896, 1900, 1904, 1970, 1996, 1999, 2000, 2001, 2020, 2100].map( - (y) => dateTime(new Date(y, 0, 2)).isLeapYear() - ), - [true, false, true, false, true, false, true, false, true, false] - ); - }, + "leap years": () => { + assert.deepStrictEqual( + [1896, 1900, 1904, 1970, 1996, 1999, 2000, 2001, 2020, 2100].map( + (y) => dateTime(new Date(y, 0, 2)).isLeapYear() + ), + [true, false, true, false, true, false, true, false, true, false] + ); + }, - "day in year": () => { - (<[string, number][]>[ - ["2020-01-01", 1], - ["2021-01-01", 1], - ["2020-02-28", 31 + 28], - ["2020-02-29", 31 + 29], - ["2020-03-01", 31 + 29 + 1], - ["2021-02-28", 31 + 28], - ["2021-03-01", 31 + 28 + 1], - ["2020-12-31", 366], - ["2021-12-31", 365], - ]).forEach(([date, day]) => - assert.strictEqual( - dateTime(Date.parse(date)).dayInYear(), - day, - date - ) - ); - }, + "day in year": () => { + (<[string, number][]>[ + ["2020-01-01", 1], + ["2021-01-01", 1], + ["2020-02-28", 31 + 28], + ["2020-02-29", 31 + 29], + ["2020-03-01", 31 + 29 + 1], + ["2021-02-28", 31 + 28], + ["2021-03-01", 31 + 28 + 1], + ["2020-12-31", 366], + ["2021-12-31", 365], + ]).forEach(([date, day]) => + assert.strictEqual( + dateTime(Date.parse(date)).dayInYear(), + day, + date + ) + ); + }, - "week number": () => { - (<[string, number][]>[ - // start on mon & leap - ["2024-01-01", 1], - ["2024-03-03", 9], - ["2024-03-04", 10], - // start on tue - ["2019-01-06", 1], - ["2019-01-07", 2], - // start on wed & leap year - ["2020-01-01", 1], - ["2020-03-01", 9], - ["2020-03-02", 10], - ["2020-12-31", 53], - // start on thur - ["2015-01-01", 1], - ["2015-01-04", 1], - ["2015-01-05", 2], - // start on fri & prev. leap - ["2021-01-01", 53], - ["2021-01-03", 53], - ["2021-01-04", 1], - // start on sat - ["2011-01-01", 52], - ["2011-01-02", 52], - ["2011-01-03", 1], - // start on sun & leap - ["2012-01-01", 52], - ["2012-01-02", 1], - ]).forEach(([date, week]) => - assert.strictEqual( - dateTime(Date.parse(date)).weekInYear(), - week, - date - ) - ); - }, + "week number": () => { + (<[string, number][]>[ + // start on mon & leap + ["2024-01-01", 1], + ["2024-03-03", 9], + ["2024-03-04", 10], + // start on tue + ["2019-01-06", 1], + ["2019-01-07", 2], + // start on wed & leap year + ["2020-01-01", 1], + ["2020-03-01", 9], + ["2020-03-02", 10], + ["2020-12-31", 53], + // start on thur + ["2015-01-01", 1], + ["2015-01-04", 1], + ["2015-01-05", 2], + // start on fri & prev. leap + ["2021-01-01", 53], + ["2021-01-03", 53], + ["2021-01-04", 1], + // start on sat + ["2011-01-01", 52], + ["2011-01-02", 52], + ["2011-01-03", 1], + // start on sun & leap + ["2012-01-01", 52], + ["2012-01-02", 1], + ]).forEach(([date, week]) => + assert.strictEqual( + dateTime(Date.parse(date)).weekInYear(), + week, + date + ) + ); + }, }); diff --git a/packages/date/test/format.ts b/packages/date/test/format.ts index b38c7bd784..7b05f236b4 100644 --- a/packages/date/test/format.ts +++ b/packages/date/test/format.ts @@ -1,69 +1,69 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { - dateTime, - DAY, - defFormat, - defTimecode, - HOUR, - MINUTE, - SECOND, -} from "../src/index.js" + dateTime, + DAY, + defFormat, + defTimecode, + HOUR, + MINUTE, + SECOND, +} from "../src/index.js"; group("date", { - formatters: () => { - const d = dateTime(Date.UTC(2020, 8, 1, 2, 3, 4, 5)); - const d2 = dateTime(Date.UTC(1996, 11, 13, 14, 15, 16, 17)); - const check = (fmt: string[], res1: string, res2: string) => { - assert.strictEqual( - defFormat(fmt)(d, true), - res1, - `${fmt}: ${res1} (a)` - ); - assert.strictEqual( - defFormat(fmt)(d2, true), - res2, - `${fmt}: ${res2} (b)` - ); - }; - check(["yy"], "20", "96"); - check(["yyyy"], "2020", "1996"); - check(["M"], "9", "12"); - check(["MM"], "09", "12"); - check(["MMM"], "Sep", "Dec"); - check(["d"], "1", "13"); - check(["dd"], "01", "13"); - check(["E"], "Tue", "Fri"); - check(["HH"], "02", "14"); - check(["h"], "2", "2"); - check(["A"], "AM", "PM"); - check(["m"], "3", "15"); - check(["mm"], "03", "15"); - check(["s"], "4", "16"); - check(["ss"], "04", "16"); - check(["S"], "5", "17"); - check(["yyyy", "-", "MM", "-", "dd"], "2020-09-01", "1996-12-13"); - assert.strictEqual( - defFormat(["Z", "/", "ZZ"])(Date.UTC(2021, 0), true), - "-00:00/Z" - ); - assert.strictEqual(defFormat(["\\yyyy"])(0), "yyyy"); - }, + formatters: () => { + const d = dateTime(Date.UTC(2020, 8, 1, 2, 3, 4, 5)); + const d2 = dateTime(Date.UTC(1996, 11, 13, 14, 15, 16, 17)); + const check = (fmt: string[], res1: string, res2: string) => { + assert.strictEqual( + defFormat(fmt)(d, true), + res1, + `${fmt}: ${res1} (a)` + ); + assert.strictEqual( + defFormat(fmt)(d2, true), + res2, + `${fmt}: ${res2} (b)` + ); + }; + check(["yy"], "20", "96"); + check(["yyyy"], "2020", "1996"); + check(["M"], "9", "12"); + check(["MM"], "09", "12"); + check(["MMM"], "Sep", "Dec"); + check(["d"], "1", "13"); + check(["dd"], "01", "13"); + check(["E"], "Tue", "Fri"); + check(["HH"], "02", "14"); + check(["h"], "2", "2"); + check(["A"], "AM", "PM"); + check(["m"], "3", "15"); + check(["mm"], "03", "15"); + check(["s"], "4", "16"); + check(["ss"], "04", "16"); + check(["S"], "5", "17"); + check(["yyyy", "-", "MM", "-", "dd"], "2020-09-01", "1996-12-13"); + assert.strictEqual( + defFormat(["Z", "/", "ZZ"])(Date.UTC(2021, 0), true), + "-00:00/Z" + ); + assert.strictEqual(defFormat(["\\yyyy"])(0), "yyyy"); + }, - timecode: () => { - assert.strictEqual( - defTimecode(30)(HOUR + 2 * MINUTE + 3 * SECOND + (4 * 1000) / 30), - "01:02:03:04" - ); - assert.strictEqual( - defTimecode(30)(4 * DAY + HOUR + 2 * MINUTE + 3 * SECOND + 999), - "04:01:02:03:29" - ); - assert.strictEqual( - defTimecode(30, ["d ", "h ", "' ", '" '])( - 4 * DAY + HOUR + 2 * MINUTE + 3 * SECOND + 999 - ), - "04d 01h 02' 03\" 29" - ); - }, + timecode: () => { + assert.strictEqual( + defTimecode(30)(HOUR + 2 * MINUTE + 3 * SECOND + (4 * 1000) / 30), + "01:02:03:04" + ); + assert.strictEqual( + defTimecode(30)(4 * DAY + HOUR + 2 * MINUTE + 3 * SECOND + 999), + "04:01:02:03:29" + ); + assert.strictEqual( + defTimecode(30, ["d ", "h ", "' ", '" '])( + 4 * DAY + HOUR + 2 * MINUTE + 3 * SECOND + 999 + ), + "04d 01h 02' 03\" 29" + ); + }, }); diff --git a/packages/date/test/i18n.ts b/packages/date/test/i18n.ts index 76c79a59b2..2789c30678 100644 --- a/packages/date/test/i18n.ts +++ b/packages/date/test/i18n.ts @@ -1,129 +1,129 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { - DE_LONG, - EN_LONG, - ES_LONG, - FR_LONG, - IT_LONG, - LocaleSpec, - tense, - units, - unitsLessThan, - withLocale, -} from "../src/index.js" + DE_LONG, + EN_LONG, + ES_LONG, + FR_LONG, + IT_LONG, + LocaleSpec, + tense, + units, + unitsLessThan, + withLocale, +} from "../src/index.js"; interface I18NTestSpec { - year1: string; - in_year1: string; - year2: string; - in_year2: string; - in_less_year1: string; - ago_less_year1: string; - in_less_year2: string; - ago_less_year2: string; + year1: string; + in_year1: string; + year2: string; + in_year2: string; + in_less_year1: string; + ago_less_year1: string; + in_less_year2: string; + ago_less_year2: string; } const check = (locale: LocaleSpec, spec: I18NTestSpec) => { - withLocale(locale, () => { - assert.strictEqual(units(1, "y"), spec.year1, "year1"); - assert.strictEqual(units(2, "y"), spec.year2, "year2"); - assert.strictEqual( - tense(1, units(1, "y", true)), - spec.in_year1, - "in_year1" - ); - assert.strictEqual( - tense(1, units(2, "y", true)), - spec.in_year2, - "in_year2" - ); - assert.strictEqual( - tense(1, unitsLessThan(1, "y", true)), - spec.in_less_year1, - "in_less_year1" - ); - assert.strictEqual( - tense(1, unitsLessThan(2, "y", true)), - spec.in_less_year2, - "in_less_year2" - ); - assert.strictEqual( - tense(-1, unitsLessThan(1, "y", true)), - spec.ago_less_year1, - "ago_less_year1" - ); - assert.strictEqual( - tense(-1, unitsLessThan(2, "y", true)), - spec.ago_less_year2, - "ago_less_year2" - ); - }); + withLocale(locale, () => { + assert.strictEqual(units(1, "y"), spec.year1, "year1"); + assert.strictEqual(units(2, "y"), spec.year2, "year2"); + assert.strictEqual( + tense(1, units(1, "y", true)), + spec.in_year1, + "in_year1" + ); + assert.strictEqual( + tense(1, units(2, "y", true)), + spec.in_year2, + "in_year2" + ); + assert.strictEqual( + tense(1, unitsLessThan(1, "y", true)), + spec.in_less_year1, + "in_less_year1" + ); + assert.strictEqual( + tense(1, unitsLessThan(2, "y", true)), + spec.in_less_year2, + "in_less_year2" + ); + assert.strictEqual( + tense(-1, unitsLessThan(1, "y", true)), + spec.ago_less_year1, + "ago_less_year1" + ); + assert.strictEqual( + tense(-1, unitsLessThan(2, "y", true)), + spec.ago_less_year2, + "ago_less_year2" + ); + }); }; group("i18n", { - DE_LONG: () => { - check(DE_LONG, { - year1: "1 Jahr", - in_year1: "in 1 Jahr", - year2: "2 Jahre", - in_year2: "in 2 Jahren", - in_less_year1: "in weniger als 1 Jahr", - ago_less_year1: "vor weniger als 1 Jahr", - in_less_year2: "in weniger als 2 Jahren", - ago_less_year2: "vor weniger als 2 Jahren", - }); - }, + DE_LONG: () => { + check(DE_LONG, { + year1: "1 Jahr", + in_year1: "in 1 Jahr", + year2: "2 Jahre", + in_year2: "in 2 Jahren", + in_less_year1: "in weniger als 1 Jahr", + ago_less_year1: "vor weniger als 1 Jahr", + in_less_year2: "in weniger als 2 Jahren", + ago_less_year2: "vor weniger als 2 Jahren", + }); + }, - EN_LONG: () => { - check(EN_LONG, { - year1: "1 year", - in_year1: "in 1 year", - year2: "2 years", - in_year2: "in 2 years", - in_less_year1: "in less than 1 year", - ago_less_year1: "less than 1 year ago", - in_less_year2: "in less than 2 years", - ago_less_year2: "less than 2 years ago", - }); - }, + EN_LONG: () => { + check(EN_LONG, { + year1: "1 year", + in_year1: "in 1 year", + year2: "2 years", + in_year2: "in 2 years", + in_less_year1: "in less than 1 year", + ago_less_year1: "less than 1 year ago", + in_less_year2: "in less than 2 years", + ago_less_year2: "less than 2 years ago", + }); + }, - ES_LONG: () => { - check(ES_LONG, { - year1: "1 año", - in_year1: "en 1 año", - year2: "2 años", - in_year2: "en 2 años", - in_less_year1: "en menos de 1 año", - ago_less_year1: "hace menos de 1 año", - in_less_year2: "en menos de 2 años", - ago_less_year2: "hace menos de 2 años", - }); - }, + ES_LONG: () => { + check(ES_LONG, { + year1: "1 año", + in_year1: "en 1 año", + year2: "2 años", + in_year2: "en 2 años", + in_less_year1: "en menos de 1 año", + ago_less_year1: "hace menos de 1 año", + in_less_year2: "en menos de 2 años", + ago_less_year2: "hace menos de 2 años", + }); + }, - FR_LONG: () => { - check(FR_LONG, { - year1: "1 année", - in_year1: "dans 1 an", - year2: "2 ans", - in_year2: "dans 2 ans", - in_less_year1: "dans moins de 1 an", - ago_less_year1: "il y a moins de 1 an", - in_less_year2: "dans moins de 2 ans", - ago_less_year2: "il y a moins de 2 ans", - }); - }, + FR_LONG: () => { + check(FR_LONG, { + year1: "1 année", + in_year1: "dans 1 an", + year2: "2 ans", + in_year2: "dans 2 ans", + in_less_year1: "dans moins de 1 an", + ago_less_year1: "il y a moins de 1 an", + in_less_year2: "dans moins de 2 ans", + ago_less_year2: "il y a moins de 2 ans", + }); + }, - IT_LONG: () => { - check(IT_LONG, { - year1: "1 anno", - in_year1: "in 1 anno", - year2: "2 anni", - in_year2: "in 2 anni", - in_less_year1: "in meno di 1 anno", - ago_less_year1: "meno di 1 anno fa", - in_less_year2: "in meno di 2 anni", - ago_less_year2: "meno di 2 anni fa", - }); - }, + IT_LONG: () => { + check(IT_LONG, { + year1: "1 anno", + in_year1: "in 1 anno", + year2: "2 anni", + in_year2: "in 2 anni", + in_less_year1: "in meno di 1 anno", + ago_less_year1: "meno di 1 anno fa", + in_less_year2: "in meno di 2 anni", + ago_less_year2: "meno di 2 anni fa", + }); + }, }); diff --git a/packages/date/test/iterators.ts b/packages/date/test/iterators.ts index d748bf0db9..216eee9fea 100644 --- a/packages/date/test/iterators.ts +++ b/packages/date/test/iterators.ts @@ -1,147 +1,147 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { - dateTime, - days, - defFormat, - FMT_yyyyMMdd, - hours, - months, - YEAR, - years, -} from "../src/index.js" + dateTime, + days, + defFormat, + FMT_yyyyMMdd, + hours, + months, + YEAR, + years, +} from "../src/index.js"; group("iterators", { - hours: () => { - const fmt = defFormat(["dd", " ", "h", ":", "mm", " ", "A"]); - assert.deepStrictEqual( - [ - ...hours( - Date.UTC(2019, 11, 30, 15, 1), - Date.UTC(2019, 11, 31, 15, 1) - ), - ].map((x) => fmt(x, true)), - [ - "30 4:00 PM", - "30 5:00 PM", - "30 6:00 PM", - "30 7:00 PM", - "30 8:00 PM", - "30 9:00 PM", - "30 10:00 PM", - "30 11:00 PM", - "31 12:00 AM", - "31 1:00 AM", - "31 2:00 AM", - "31 3:00 AM", - "31 4:00 AM", - "31 5:00 AM", - "31 6:00 AM", - "31 7:00 AM", - "31 8:00 AM", - "31 9:00 AM", - "31 10:00 AM", - "31 11:00 AM", - "31 12:00 PM", - "31 1:00 PM", - "31 2:00 PM", - "31 3:00 PM", - ] - ); - }, + hours: () => { + const fmt = defFormat(["dd", " ", "h", ":", "mm", " ", "A"]); + assert.deepStrictEqual( + [ + ...hours( + Date.UTC(2019, 11, 30, 15, 1), + Date.UTC(2019, 11, 31, 15, 1) + ), + ].map((x) => fmt(x, true)), + [ + "30 4:00 PM", + "30 5:00 PM", + "30 6:00 PM", + "30 7:00 PM", + "30 8:00 PM", + "30 9:00 PM", + "30 10:00 PM", + "30 11:00 PM", + "31 12:00 AM", + "31 1:00 AM", + "31 2:00 AM", + "31 3:00 AM", + "31 4:00 AM", + "31 5:00 AM", + "31 6:00 AM", + "31 7:00 AM", + "31 8:00 AM", + "31 9:00 AM", + "31 10:00 AM", + "31 11:00 AM", + "31 12:00 PM", + "31 1:00 PM", + "31 2:00 PM", + "31 3:00 PM", + ] + ); + }, - days: () => { - assert.deepStrictEqual( - [...days(Date.UTC(2019, 11, 30, 15), Date.UTC(2020, 1, 1, 1))].map( - (x) => FMT_yyyyMMdd(x) - ), - [ - "2019-12-31", - "2020-01-01", - "2020-01-02", - "2020-01-03", - "2020-01-04", - "2020-01-05", - "2020-01-06", - "2020-01-07", - "2020-01-08", - "2020-01-09", - "2020-01-10", - "2020-01-11", - "2020-01-12", - "2020-01-13", - "2020-01-14", - "2020-01-15", - "2020-01-16", - "2020-01-17", - "2020-01-18", - "2020-01-19", - "2020-01-20", - "2020-01-21", - "2020-01-22", - "2020-01-23", - "2020-01-24", - "2020-01-25", - "2020-01-26", - "2020-01-27", - "2020-01-28", - "2020-01-29", - "2020-01-30", - "2020-01-31", - "2020-02-01", - ] - ); - }, + days: () => { + assert.deepStrictEqual( + [...days(Date.UTC(2019, 11, 30, 15), Date.UTC(2020, 1, 1, 1))].map( + (x) => FMT_yyyyMMdd(x) + ), + [ + "2019-12-31", + "2020-01-01", + "2020-01-02", + "2020-01-03", + "2020-01-04", + "2020-01-05", + "2020-01-06", + "2020-01-07", + "2020-01-08", + "2020-01-09", + "2020-01-10", + "2020-01-11", + "2020-01-12", + "2020-01-13", + "2020-01-14", + "2020-01-15", + "2020-01-16", + "2020-01-17", + "2020-01-18", + "2020-01-19", + "2020-01-20", + "2020-01-21", + "2020-01-22", + "2020-01-23", + "2020-01-24", + "2020-01-25", + "2020-01-26", + "2020-01-27", + "2020-01-28", + "2020-01-29", + "2020-01-30", + "2020-01-31", + "2020-02-01", + ] + ); + }, - months: () => { - assert.deepStrictEqual( - [ - ...months(Date.UTC(2019, 10, 30, 15), Date.UTC(2020, 10, 1, 1)), - ].map((x) => FMT_yyyyMMdd(x)), - [ - "2019-12-01", - "2020-01-01", - "2020-02-01", - "2020-03-01", - "2020-04-01", - "2020-05-01", - "2020-06-01", - "2020-07-01", - "2020-08-01", - "2020-09-01", - "2020-10-01", - "2020-11-01", - ] - ); - }, + months: () => { + assert.deepStrictEqual( + [ + ...months(Date.UTC(2019, 10, 30, 15), Date.UTC(2020, 10, 1, 1)), + ].map((x) => FMT_yyyyMMdd(x)), + [ + "2019-12-01", + "2020-01-01", + "2020-02-01", + "2020-03-01", + "2020-04-01", + "2020-05-01", + "2020-06-01", + "2020-07-01", + "2020-08-01", + "2020-09-01", + "2020-10-01", + "2020-11-01", + ] + ); + }, - years: () => { - assert.deepStrictEqual( - [ - ...years(Date.UTC(1996, 10, 30, 15), Date.UTC(2005, 10, 1, 1)), - ].map((x) => FMT_yyyyMMdd(x)), - [ - "1997-01-01", - "1998-01-01", - "1999-01-01", - "2000-01-01", - "2001-01-01", - "2002-01-01", - "2003-01-01", - "2004-01-01", - "2005-01-01", - ] - ); - }, + years: () => { + assert.deepStrictEqual( + [ + ...years(Date.UTC(1996, 10, 30, 15), Date.UTC(2005, 10, 1, 1)), + ].map((x) => FMT_yyyyMMdd(x)), + [ + "1997-01-01", + "1998-01-01", + "1999-01-01", + "2000-01-01", + "2001-01-01", + "2002-01-01", + "2003-01-01", + "2004-01-01", + "2005-01-01", + ] + ); + }, - "arg coercion": () => { - assert.deepStrictEqual( - [...years(dateTime(0), dateTime(YEAR))].map((x) => FMT_yyyyMMdd(x)), - ["1970-01-01", "1971-01-01"] - ); - assert.deepStrictEqual( - [...years(new Date(0), new Date(YEAR))].map((x) => FMT_yyyyMMdd(x)), - ["1970-01-01", "1971-01-01"] - ); - }, + "arg coercion": () => { + assert.deepStrictEqual( + [...years(dateTime(0), dateTime(YEAR))].map((x) => FMT_yyyyMMdd(x)), + ["1970-01-01", "1971-01-01"] + ); + assert.deepStrictEqual( + [...years(new Date(0), new Date(YEAR))].map((x) => FMT_yyyyMMdd(x)), + ["1970-01-01", "1971-01-01"] + ); + }, }); diff --git a/packages/date/test/relative.ts b/packages/date/test/relative.ts index ad557e42f4..7dc65fc511 100644 --- a/packages/date/test/relative.ts +++ b/packages/date/test/relative.ts @@ -3,94 +3,94 @@ import * as assert from "assert"; import { DateTime, dateTime, parseRelative } from "../src/index.js"; const checkDate = (offset: string, base: DateTime, expected: number) => { - const d = parseRelative(offset, base); - assert.ok(!!d, `couldn't parse ${offset}`); - assert.ok( - d.equiv(expected), - `no match (past): ${d.toISOString()} => ${dateTime( - expected - ).toISOString()} (${offset})` - ); + const d = parseRelative(offset, base); + assert.ok(!!d, `couldn't parse ${offset}`); + assert.ok( + d.equiv(expected), + `no match (past): ${d.toISOString()} => ${dateTime( + expected + ).toISOString()} (${offset})` + ); }; const check = ( - period: string[], - ref: number, - past: number, - future: number, - num = 5 + period: string[], + ref: number, + past: number, + future: number, + num = 5 ) => { - const base = dateTime(ref); - for (let p of period) { - checkDate(`-${num} ${p}`, base, past); - checkDate(`-${num} ${p} ago`, base, future); - checkDate(`${num} ${p} ago`, base, past); - checkDate(`${num} ${p}`, base, future); - checkDate(`+${num} ${p}`, base, future); - } + const base = dateTime(ref); + for (let p of period) { + checkDate(`-${num} ${p}`, base, past); + checkDate(`-${num} ${p} ago`, base, future); + checkDate(`${num} ${p} ago`, base, past); + checkDate(`${num} ${p}`, base, future); + checkDate(`+${num} ${p}`, base, future); + } }; group("relative", { - parse: () => { - const base = Date.UTC(2021, 0, 1); - check( - ["ms", "milli", "millis", "millisecond", "milliseconds"], - base, - Date.UTC(2020, 11, 31, 23, 59, 59, 995), - Date.UTC(2021, 0, 1, 0, 0, 0, 5) - ); - check( - ["s", "sec", "secs", "second", "seconds"], - base, - Date.UTC(2020, 11, 31, 23, 59, 55), - Date.UTC(2021, 0, 1, 0, 0, 5) - ); - check( - ["min", "mins", "minute", "minutes"], - base, - Date.UTC(2020, 11, 31, 23, 55), - Date.UTC(2021, 0, 1, 0, 5) - ); - check( - ["h", "hour", "hours"], - base, - Date.UTC(2020, 11, 31, 19), - Date.UTC(2021, 0, 1, 5) - ); - check( - ["d", "day", "days"], - base, - Date.UTC(2020, 11, 27), - Date.UTC(2021, 0, 6) - ); - check( - ["w", "week", "weeks"], - base, - Date.UTC(2020, 10, 27), - Date.UTC(2021, 1, 5) - ); - check( - ["mo", "month", "months"], - base, - Date.UTC(2020, 7, 1), - Date.UTC(2021, 5, 1) - ); - check( - ["y", "year", "years"], - base, - Date.UTC(2016, 0, 1), - Date.UTC(2026, 0, 1) - ); - }, + parse: () => { + const base = Date.UTC(2021, 0, 1); + check( + ["ms", "milli", "millis", "millisecond", "milliseconds"], + base, + Date.UTC(2020, 11, 31, 23, 59, 59, 995), + Date.UTC(2021, 0, 1, 0, 0, 0, 5) + ); + check( + ["s", "sec", "secs", "second", "seconds"], + base, + Date.UTC(2020, 11, 31, 23, 59, 55), + Date.UTC(2021, 0, 1, 0, 0, 5) + ); + check( + ["min", "mins", "minute", "minutes"], + base, + Date.UTC(2020, 11, 31, 23, 55), + Date.UTC(2021, 0, 1, 0, 5) + ); + check( + ["h", "hour", "hours"], + base, + Date.UTC(2020, 11, 31, 19), + Date.UTC(2021, 0, 1, 5) + ); + check( + ["d", "day", "days"], + base, + Date.UTC(2020, 11, 27), + Date.UTC(2021, 0, 6) + ); + check( + ["w", "week", "weeks"], + base, + Date.UTC(2020, 10, 27), + Date.UTC(2021, 1, 5) + ); + check( + ["mo", "month", "months"], + base, + Date.UTC(2020, 7, 1), + Date.UTC(2021, 5, 1) + ); + check( + ["y", "year", "years"], + base, + Date.UTC(2016, 0, 1), + Date.UTC(2026, 0, 1) + ); + }, - "parse (weekday)": () => { - const base = dateTime(Date.UTC(2021, 0, 1)); - checkDate("sat", base, Date.UTC(2021, 0, 2)); - checkDate("sunday", base, Date.UTC(2021, 0, 3)); - checkDate("thu", base, Date.UTC(2021, 0, 7)); - checkDate("friday", base, Date.UTC(2021, 0, 8)); - checkDate("today", base, Date.UTC(2021, 0, 1)); - checkDate("tomorrow", base, Date.UTC(2021, 0, 2)); - checkDate("yesterday", base, Date.UTC(2020, 11, 31)); - }, + "parse (weekday)": () => { + const base = dateTime(Date.UTC(2021, 0, 1)); + checkDate("sat", base, Date.UTC(2021, 0, 2)); + checkDate("sunday", base, Date.UTC(2021, 0, 3)); + checkDate("thu", base, Date.UTC(2021, 0, 7)); + checkDate("friday", base, Date.UTC(2021, 0, 8)); + checkDate("today", base, Date.UTC(2021, 0, 1)); + checkDate("tomorrow", base, Date.UTC(2021, 0, 2)); + checkDate("yesterday", base, Date.UTC(2020, 11, 31)); + }, }); diff --git a/packages/date/tsconfig.json b/packages/date/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/date/tsconfig.json +++ b/packages/date/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/dcons/api-extractor.json b/packages/dcons/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/dcons/api-extractor.json +++ b/packages/dcons/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/dcons/package.json b/packages/dcons/package.json index 5eeeaf38e5..8f93d6bf4b 100644 --- a/packages/dcons/package.json +++ b/packages/dcons/package.json @@ -1,98 +1,98 @@ { - "name": "@thi.ng/dcons", - "version": "3.2.7", - "description": "Double-linked lists with comprehensive set of operations (incl. optional self-organizing behaviors)", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dcons#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/compare": "^2.1.8", - "@thi.ng/equiv": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/random": "^3.3.3", - "@thi.ng/transducers": "^8.3.7" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "acceleration", - "datastructure", - "heuristic", - "iterator", - "linkedlist", - "list", - "queue", - "self-organization", - "stack", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./alist": { - "default": "./alist.js" - }, - "./api": { - "default": "./api.js" - }, - "./dcons": { - "default": "./dcons.js" - }, - "./ring": { - "default": "./ring.js" - }, - "./sol": { - "default": "./sol.js" - } - }, - "thi.ng": { - "year": 2017 - } + "name": "@thi.ng/dcons", + "version": "3.2.7", + "description": "Double-linked lists with comprehensive set of operations (incl. optional self-organizing behaviors)", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dcons#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/compare": "^2.1.8", + "@thi.ng/equiv": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/random": "^3.3.3", + "@thi.ng/transducers": "^8.3.7" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "acceleration", + "datastructure", + "heuristic", + "iterator", + "linkedlist", + "list", + "queue", + "self-organization", + "stack", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./alist": { + "default": "./alist.js" + }, + "./api": { + "default": "./api.js" + }, + "./dcons": { + "default": "./dcons.js" + }, + "./ring": { + "default": "./ring.js" + }, + "./sol": { + "default": "./sol.js" + } + }, + "thi.ng": { + "year": 2017 + } } diff --git a/packages/dcons/src/alist.ts b/packages/dcons/src/alist.ts index 4ac0d982f5..114ef8aa68 100644 --- a/packages/dcons/src/alist.ts +++ b/packages/dcons/src/alist.ts @@ -1,14 +1,14 @@ import type { - Comparator, - Fn, - IClear, - ICopy, - IEmpty, - IEquiv, - IInto, - ILength, - IRelease, - Predicate, + Comparator, + Fn, + IClear, + ICopy, + IEmpty, + IEquiv, + IInto, + ILength, + IRelease, + Predicate, } from "@thi.ng/api"; import { isArrayLike } from "@thi.ng/checks/is-arraylike"; import { compare } from "@thi.ng/compare"; @@ -18,290 +18,290 @@ import { IReducible, isReduced, ReductionFn } from "@thi.ng/transducers"; import type { ConsCell } from "./api.js"; export abstract class AList, T> - implements - IClear, - ICopy, - IEmpty, - IEquiv, - IInto, - Iterable, - ILength, - IReducible, - IRelease + implements + IClear, + ICopy, + IEmpty, + IEquiv, + IInto, + Iterable, + ILength, + IReducible, + IRelease { - _head: ConsCell | undefined; - - protected _length: number = 0; - - constructor(src?: Iterable) { - src && this.into(src); - } - - get length() { - return this._length; - } - - get head() { - return this._head; - } - - abstract get tail(): ConsCell | undefined; - - [Symbol.iterator]() { - return _iterate("next", this._head); - } - - reverseIterator() { - return _iterate("prev", this.tail); - } - - abstract append(n: T): ConsCell; - - clear() { - this.release(); - } - - compare(o: L, cmp: Comparator = compare) { - let n = this._length; - if (n < o._length) { - return -1; - } else if (n > o._length) { - return 1; - } else if (n === 0) { - return 0; - } else { - let ca = this._head; - let cb = o._head; - let res = 0; - for (; n-- > 0 && res === 0; ) { - res = cmp(ca!.value, cb!.value); - ca = ca!.next; - cb = cb!.next; - } - return res; - } - } - - concat(...slices: Iterable[]) { - const res = this.copy(); - for (let slice of slices) { - res.into(slice); - } - return res; - } - - abstract copy(): L; - - abstract drop(): T | undefined; - - abstract empty(): L; - - equiv(o: any) { - if ( - !(o instanceof AList || isArrayLike(o)) || - this._length !== o.length - ) { - return false; - } - if (!this._length || this === o) return true; - const iter: Iterator = (o)[Symbol.iterator](); - let cell = this._head; - for (let n = this._length; n-- > 0; ) { - if (!equiv(cell!.value, iter.next().value)) { - return false; - } - cell = cell!.next; - } - return true; - } - - filter(fn: Predicate) { - const res = this.empty(); - this.traverse((x) => (fn(x.value) && res.append(x.value), true)); - return res; - } - - find(value: T) { - return this.traverse((x) => x.value !== value); - } - - findWith(fn: Predicate) { - return this.traverse((x) => !fn(x.value)); - } - - first() { - return this._head && this._head.value; - } - - abstract insertAfter(cell: ConsCell, value: T): ConsCell; - - abstract insertBefore(cell: ConsCell, value: T): ConsCell; - - insertSorted(value: T, cmp?: Comparator) { - cmp = cmp || compare; - for (let cell = this._head, n = this._length; n-- > 0; ) { - if (cmp(value, cell!.value) <= 0) { - return this.insertBefore(cell!, value); - } - cell = cell!.next; - } - return this.append(value); - } - - into(src: Iterable): L { - for (let x of src) { - this.append(x); - } - return this; - } - - nth(n: number, notFound?: T) { - const cell = this.nthCell(n); - return cell ? cell.value : notFound; - } - - abstract nthCell(n: number): ConsCell | undefined; - - nthCellUnsafe(n: number) { - let cell: ConsCell | undefined; - let dir: keyof ConsCell; - if (n <= this._length >>> 1) { - cell = this._head; - dir = "next"; - } else { - cell = this.tail; - dir = "prev"; - n = this._length - n - 1; - } - while (n-- > 0 && cell) { - cell = cell[dir]; - } - return cell; - } - - peek() { - return this.tail && this.tail.value; - } - - abstract prepend(n: T): ConsCell; - - /** {@inheritDoc @thi.ng/transducers#IReducible.$reduce} */ - $reduce(rfn: ReductionFn, acc: any) { - let cell = this._head; - for (let n = this._length; n-- > 0 && !isReduced(acc); ) { - acc = rfn(acc, cell!.value); - cell = cell!.next; - } - return acc; - } - - reduce(rfn: ReductionFn, initial: R): R { - return this.$reduce(rfn, initial); - } - - release() { - let cell = this._head; - if (!cell) return true; - let next; - for (let i = this._length; i-- > 0; ) { - next = cell!.next; - delete (cell).value; - delete cell!.prev; - delete cell!.next; - cell = next; - } - this._head = undefined; - this._length = 0; - return true; - } - - reverse() { - let head = this._head; - let tail = this.tail; - let n = (this._length >>> 1) + (this._length & 1); - while (head && tail && n > 0) { - const t = head.value; - head.value = tail.value; - tail.value = t; - head = head.next; - tail = tail.prev; - n--; - } - return this; - } - - setHead(v: T) { - const cell = this._head; - if (cell) { - cell.value = v; - return cell; - } - return this.prepend(v); - } - - setNth(n: number, v: T) { - const cell = this.nthCell(n); - !cell && outOfBounds(n); - cell!.value = v; - return cell; - } - - setTail(v: T) { - const cell = this.tail; - if (cell) { - cell.value = v; - return cell; - } - return this.append(v); - } - - swap(a: ConsCell, b: ConsCell): this { - if (a !== b) { - const t = a.value; - a.value = b.value; - b.value = t; - } - return this; - } - - toArray(out: T[] = []) { - this.traverse((x) => (out.push(x.value), true)); - return out; - } - - toJSON() { - return this.toArray(); - } - - toString() { - let res: any = []; - this.traverse((x) => (res.push(String(x.value)), true)); - return res.join(", "); - } - - traverse( - fn: Fn, boolean | number>, - start: ConsCell | undefined = this._head, - end?: ConsCell | undefined - ) { - if (!this._head) return; - let cell = start!; - do { - if (!fn(cell)) break; - cell = cell.next!; - } while (cell !== end); - return cell; - } - - protected _map, V>(res: R, fn: Fn) { - this.traverse((x) => (res.append(fn(x.value)), true)); - return res; - } + _head: ConsCell | undefined; + + protected _length: number = 0; + + constructor(src?: Iterable) { + src && this.into(src); + } + + get length() { + return this._length; + } + + get head() { + return this._head; + } + + abstract get tail(): ConsCell | undefined; + + [Symbol.iterator]() { + return _iterate("next", this._head); + } + + reverseIterator() { + return _iterate("prev", this.tail); + } + + abstract append(n: T): ConsCell; + + clear() { + this.release(); + } + + compare(o: L, cmp: Comparator = compare) { + let n = this._length; + if (n < o._length) { + return -1; + } else if (n > o._length) { + return 1; + } else if (n === 0) { + return 0; + } else { + let ca = this._head; + let cb = o._head; + let res = 0; + for (; n-- > 0 && res === 0; ) { + res = cmp(ca!.value, cb!.value); + ca = ca!.next; + cb = cb!.next; + } + return res; + } + } + + concat(...slices: Iterable[]) { + const res = this.copy(); + for (let slice of slices) { + res.into(slice); + } + return res; + } + + abstract copy(): L; + + abstract drop(): T | undefined; + + abstract empty(): L; + + equiv(o: any) { + if ( + !(o instanceof AList || isArrayLike(o)) || + this._length !== o.length + ) { + return false; + } + if (!this._length || this === o) return true; + const iter: Iterator = (o)[Symbol.iterator](); + let cell = this._head; + for (let n = this._length; n-- > 0; ) { + if (!equiv(cell!.value, iter.next().value)) { + return false; + } + cell = cell!.next; + } + return true; + } + + filter(fn: Predicate) { + const res = this.empty(); + this.traverse((x) => (fn(x.value) && res.append(x.value), true)); + return res; + } + + find(value: T) { + return this.traverse((x) => x.value !== value); + } + + findWith(fn: Predicate) { + return this.traverse((x) => !fn(x.value)); + } + + first() { + return this._head && this._head.value; + } + + abstract insertAfter(cell: ConsCell, value: T): ConsCell; + + abstract insertBefore(cell: ConsCell, value: T): ConsCell; + + insertSorted(value: T, cmp?: Comparator) { + cmp = cmp || compare; + for (let cell = this._head, n = this._length; n-- > 0; ) { + if (cmp(value, cell!.value) <= 0) { + return this.insertBefore(cell!, value); + } + cell = cell!.next; + } + return this.append(value); + } + + into(src: Iterable): L { + for (let x of src) { + this.append(x); + } + return this; + } + + nth(n: number, notFound?: T) { + const cell = this.nthCell(n); + return cell ? cell.value : notFound; + } + + abstract nthCell(n: number): ConsCell | undefined; + + nthCellUnsafe(n: number) { + let cell: ConsCell | undefined; + let dir: keyof ConsCell; + if (n <= this._length >>> 1) { + cell = this._head; + dir = "next"; + } else { + cell = this.tail; + dir = "prev"; + n = this._length - n - 1; + } + while (n-- > 0 && cell) { + cell = cell[dir]; + } + return cell; + } + + peek() { + return this.tail && this.tail.value; + } + + abstract prepend(n: T): ConsCell; + + /** {@inheritDoc @thi.ng/transducers#IReducible.$reduce} */ + $reduce(rfn: ReductionFn, acc: any) { + let cell = this._head; + for (let n = this._length; n-- > 0 && !isReduced(acc); ) { + acc = rfn(acc, cell!.value); + cell = cell!.next; + } + return acc; + } + + reduce(rfn: ReductionFn, initial: R): R { + return this.$reduce(rfn, initial); + } + + release() { + let cell = this._head; + if (!cell) return true; + let next; + for (let i = this._length; i-- > 0; ) { + next = cell!.next; + delete (cell).value; + delete cell!.prev; + delete cell!.next; + cell = next; + } + this._head = undefined; + this._length = 0; + return true; + } + + reverse() { + let head = this._head; + let tail = this.tail; + let n = (this._length >>> 1) + (this._length & 1); + while (head && tail && n > 0) { + const t = head.value; + head.value = tail.value; + tail.value = t; + head = head.next; + tail = tail.prev; + n--; + } + return this; + } + + setHead(v: T) { + const cell = this._head; + if (cell) { + cell.value = v; + return cell; + } + return this.prepend(v); + } + + setNth(n: number, v: T) { + const cell = this.nthCell(n); + !cell && outOfBounds(n); + cell!.value = v; + return cell; + } + + setTail(v: T) { + const cell = this.tail; + if (cell) { + cell.value = v; + return cell; + } + return this.append(v); + } + + swap(a: ConsCell, b: ConsCell): this { + if (a !== b) { + const t = a.value; + a.value = b.value; + b.value = t; + } + return this; + } + + toArray(out: T[] = []) { + this.traverse((x) => (out.push(x.value), true)); + return out; + } + + toJSON() { + return this.toArray(); + } + + toString() { + let res: any = []; + this.traverse((x) => (res.push(String(x.value)), true)); + return res.join(", "); + } + + traverse( + fn: Fn, boolean | number>, + start: ConsCell | undefined = this._head, + end?: ConsCell | undefined + ) { + if (!this._head) return; + let cell = start!; + do { + if (!fn(cell)) break; + cell = cell.next!; + } while (cell !== end); + return cell; + } + + protected _map, V>(res: R, fn: Fn) { + this.traverse((x) => (res.append(fn(x.value)), true)); + return res; + } } function* _iterate(dir: "next" | "prev", cell?: ConsCell) { - while (cell) { - yield cell.value; - cell = cell[dir]!; - } + while (cell) { + yield cell.value; + cell = cell[dir]!; + } } diff --git a/packages/dcons/src/api.ts b/packages/dcons/src/api.ts index 322ce46466..4bb4dcd0c2 100644 --- a/packages/dcons/src/api.ts +++ b/packages/dcons/src/api.ts @@ -1,12 +1,12 @@ export interface ConsCell { - value: T; - next: ConsCell | undefined; - prev: ConsCell | undefined; + value: T; + next: ConsCell | undefined; + prev: ConsCell | undefined; } export interface IList { - append(value: T): ConsCell; - prepend(value: T): ConsCell; - insertAfter(cell: ConsCell, value: T): ConsCell; - insertBefore(cell: ConsCell, value: T): ConsCell; + append(value: T): ConsCell; + prepend(value: T): ConsCell; + insertAfter(cell: ConsCell, value: T): ConsCell; + insertBefore(cell: ConsCell, value: T): ConsCell; } diff --git a/packages/dcons/src/dcons.ts b/packages/dcons/src/dcons.ts index 4470a37aa8..64172e538c 100644 --- a/packages/dcons/src/dcons.ts +++ b/packages/dcons/src/dcons.ts @@ -1,11 +1,11 @@ import type { - Comparator, - Fn, - ICompare, - IEmpty, - ISeq, - ISeqable, - IStack, + Comparator, + Fn, + ICompare, + IEmpty, + ISeq, + ISeqable, + IStack, } from "@thi.ng/api"; import { compare } from "@thi.ng/compare/compare"; import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; @@ -16,400 +16,400 @@ import { AList } from "./alist.js"; import type { ConsCell } from "./api.js"; export class DCons - extends AList, T> - implements - ICompare>, - IEmpty>, - IStack>, - ISeqable + extends AList, T> + implements + ICompare>, + IEmpty>, + IStack>, + ISeqable { - protected _tail: ConsCell | undefined; + protected _tail: ConsCell | undefined; - get tail() { - return this._tail; - } + get tail() { + return this._tail; + } - append(value: T): ConsCell { - if (this._tail) { - const cell = >{ value, prev: this._tail }; - this._tail.next = cell; - this._tail = cell; - this._length++; - return cell; - } else { - return this.prepend(value); - } - } + append(value: T): ConsCell { + if (this._tail) { + const cell = >{ value, prev: this._tail }; + this._tail.next = cell; + this._tail = cell; + this._length++; + return cell; + } else { + return this.prepend(value); + } + } - asHead(cell: ConsCell) { - if (cell === this._head) { - return this; - } - this.remove(cell); - this._head!.prev = cell; - cell.next = this._head; - cell.prev = undefined; - this._head = cell; - this._length++; - return this; - } + asHead(cell: ConsCell) { + if (cell === this._head) { + return this; + } + this.remove(cell); + this._head!.prev = cell; + cell.next = this._head; + cell.prev = undefined; + this._head = cell; + this._length++; + return this; + } - asTail(cell: ConsCell) { - if (cell === this._tail) { - return this; - } - this.remove(cell); - this._tail!.next = cell; - cell.prev = this._tail; - cell.next = undefined; - this._tail = cell; - this._length++; - return this; - } + asTail(cell: ConsCell) { + if (cell === this._tail) { + return this; + } + this.remove(cell); + this._tail!.next = cell; + cell.prev = this._tail; + cell.next = undefined; + this._tail = cell; + this._length++; + return this; + } - /** @deprecated use {@link DCons.prepend} */ - cons(value: T): DCons { - this.prepend(value); - return this; - } + /** @deprecated use {@link DCons.prepend} */ + cons(value: T): DCons { + this.prepend(value); + return this; + } - copy() { - return new DCons(this); - } + copy() { + return new DCons(this); + } - *cycle() { - while (true) { - yield* this; - } - } + *cycle() { + while (true) { + yield* this; + } + } - drop() { - const cell = this._head; - if (cell) { - this._head = cell.next; - if (this._head) { - this._head.prev = undefined; - } else { - this._tail = undefined; - } - this._length--; - return cell.value; - } - } + drop() { + const cell = this._head; + if (cell) { + this._head = cell.next; + if (this._head) { + this._head.prev = undefined; + } else { + this._tail = undefined; + } + this._length--; + return cell.value; + } + } - empty() { - return new DCons(); - } + empty() { + return new DCons(); + } - insertAfter(cell: ConsCell, value: T) { - const newCell = >{ value, next: cell.next, prev: cell }; - if (cell.next) { - cell.next.prev = newCell; - } else { - this._tail = newCell; - } - cell.next = newCell; - this._length++; - return newCell; - } + insertAfter(cell: ConsCell, value: T) { + const newCell = >{ value, next: cell.next, prev: cell }; + if (cell.next) { + cell.next.prev = newCell; + } else { + this._tail = newCell; + } + cell.next = newCell; + this._length++; + return newCell; + } - insertAfterNth(n: number, x: T) { - if (n < 0) { - n += this._length; - } - if (n >= this._length - 1) { - return this.append(x); - } else { - ensureIndex(n, 0, this._length); - return this.insertAfter(this.nthCellUnsafe(n)!, x); - } - } + insertAfterNth(n: number, x: T) { + if (n < 0) { + n += this._length; + } + if (n >= this._length - 1) { + return this.append(x); + } else { + ensureIndex(n, 0, this._length); + return this.insertAfter(this.nthCellUnsafe(n)!, x); + } + } - insertBefore(cell: ConsCell, value: T) { - const newCell = >{ value, next: cell, prev: cell.prev }; - if (cell.prev) { - cell.prev.next = newCell; - } else { - this._head = newCell; - } - cell.prev = newCell; - this._length++; - return newCell; - } + insertBefore(cell: ConsCell, value: T) { + const newCell = >{ value, next: cell, prev: cell.prev }; + if (cell.prev) { + cell.prev.next = newCell; + } else { + this._head = newCell; + } + cell.prev = newCell; + this._length++; + return newCell; + } - insertBeforeNth(n: number, x: T) { - if (n < 0) { - n += this._length; - } - if (n <= 0) { - return this.prepend(x); - } else { - ensureIndex(n, 0, this._length); - return this.insertBefore(this.nthCellUnsafe(n)!, x); - } - } + insertBeforeNth(n: number, x: T) { + if (n < 0) { + n += this._length; + } + if (n <= 0) { + return this.prepend(x); + } else { + ensureIndex(n, 0, this._length); + return this.insertBefore(this.nthCellUnsafe(n)!, x); + } + } - map(fn: Fn) { - return this._map(new DCons(), fn); - } + map(fn: Fn) { + return this._map(new DCons(), fn); + } - nth(n: number, notFound?: T) { - const cell = this.nthCell(n); - return cell ? cell.value : notFound; - } + nth(n: number, notFound?: T) { + const cell = this.nthCell(n); + return cell ? cell.value : notFound; + } - nthCell(n: number) { - if (n < 0) { - n += this._length; - } - if (n < 0 || n >= this._length) { - return; - } - return this.nthCellUnsafe(n); - } + nthCell(n: number) { + if (n < 0) { + n += this._length; + } + if (n < 0 || n >= this._length) { + return; + } + return this.nthCellUnsafe(n); + } - pop() { - const cell = this._tail; - if (!cell) { - return; - } - this._tail = cell.prev; - if (this._tail) { - this._tail.next = undefined; - } else { - this._head = undefined; - } - this._length--; - return cell.value; - } + pop() { + const cell = this._tail; + if (!cell) { + return; + } + this._tail = cell.prev; + if (this._tail) { + this._tail.next = undefined; + } else { + this._head = undefined; + } + this._length--; + return cell.value; + } - prepend(value: T): ConsCell { - const cell = >{ value, next: this._head }; - if (this._head) { - this._head.prev = cell; - } else { - this._tail = cell; - } - this._head = cell; - this._length++; - return cell; - } + prepend(value: T): ConsCell { + const cell = >{ value, next: this._head }; + if (this._head) { + this._head.prev = cell; + } else { + this._tail = cell; + } + this._head = cell; + this._length++; + return cell; + } - push(value: T): DCons { - this.append(value); - return this; - } + push(value: T): DCons { + this.append(value); + return this; + } - release() { - this._tail = undefined; - return super.release(); - } + release() { + this._tail = undefined; + return super.release(); + } - remove(cell: ConsCell) { - if (cell.prev) { - cell.prev.next = cell.next; - } else { - this._head = cell.next; - } - if (cell.next) { - cell.next.prev = cell.prev; - } else { - this._tail = cell.prev; - } - this._length--; - return this; - } + remove(cell: ConsCell) { + if (cell.prev) { + cell.prev.next = cell.next; + } else { + this._head = cell.next; + } + if (cell.next) { + cell.next.prev = cell.prev; + } else { + this._tail = cell.prev; + } + this._length--; + return this; + } - rotateLeft() { - switch (this._length) { - case 0: - case 1: - return this; - case 2: - return this.swap(this._head!, this._tail!); - default: - return this.push(this.drop()!); - } - } + rotateLeft() { + switch (this._length) { + case 0: + case 1: + return this; + case 2: + return this.swap(this._head!, this._tail!); + default: + return this.push(this.drop()!); + } + } - rotateRight() { - switch (this._length) { - case 0: - case 1: - return this; - case 2: - return this.swap(this._head!, this._tail!); - default: - const x = this.peek(); - this.pop(); - this.prepend(x!); - return this; - } - } + rotateRight() { + switch (this._length) { + case 0: + case 1: + return this; + case 2: + return this.swap(this._head!, this._tail!); + default: + const x = this.peek(); + this.pop(); + this.prepend(x!); + return this; + } + } - /** {@inheritDoc @thi.ng/api#ISeqable.seq} */ - seq(start = 0, end = this.length) { - if (start >= end || start < 0) return; - let cell = this.nthCell(start); - const last = this.nthCell(end - 1); - const $seq = (cell: ConsCell): ISeq => ({ - first() { - return cell.value; - }, - next() { - return cell !== last && cell.next ? $seq(cell.next) : undefined; - }, - }); - return cell ? $seq(cell) : undefined; - } + /** {@inheritDoc @thi.ng/api#ISeqable.seq} */ + seq(start = 0, end = this.length) { + if (start >= end || start < 0) return; + let cell = this.nthCell(start); + const last = this.nthCell(end - 1); + const $seq = (cell: ConsCell): ISeq => ({ + first() { + return cell.value; + }, + next() { + return cell !== last && cell.next ? $seq(cell.next) : undefined; + }, + }); + return cell ? $seq(cell) : undefined; + } - /** - * Shuffles list by probabilistically moving cells to head or tail - * positions. - * - * @remarks - * Supports configurable iterations and custom PRNG via - * {@link @thi.ng/random#IRandom} (default: - * {@link @thi.ng/random#SYSTEM}). - * - * Default iterations: `ceil(3/2 * log2(n))` - * - * @param iter - - * @param rnd - - */ - shuffle(iter?: number, rnd: IRandom = SYSTEM) { - if (this._length < 2) return this; - for ( - iter = - iter !== undefined - ? iter - : Math.ceil(1.5 * Math.log2(this._length)); - iter > 0; - iter-- - ) { - let cell = this._head; - while (cell) { - const next = cell.next; - rnd.float() < 0.5 ? this.asHead(cell) : this.asTail(cell); - cell = next; - } - } - return this; - } + /** + * Shuffles list by probabilistically moving cells to head or tail + * positions. + * + * @remarks + * Supports configurable iterations and custom PRNG via + * {@link @thi.ng/random#IRandom} (default: + * {@link @thi.ng/random#SYSTEM}). + * + * Default iterations: `ceil(3/2 * log2(n))` + * + * @param iter - + * @param rnd - + */ + shuffle(iter?: number, rnd: IRandom = SYSTEM) { + if (this._length < 2) return this; + for ( + iter = + iter !== undefined + ? iter + : Math.ceil(1.5 * Math.log2(this._length)); + iter > 0; + iter-- + ) { + let cell = this._head; + while (cell) { + const next = cell.next; + rnd.float() < 0.5 ? this.asHead(cell) : this.asTail(cell); + cell = next; + } + } + return this; + } - slice(from = 0, to = this.length) { - let a = from < 0 ? from + this._length : from; - let b = to < 0 ? to + this._length : to; - if (a < 0 || b < 0) { - illegalArgs("invalid indices: ${from} / ${to}"); - } - const res = new DCons(); - let cell = this.nthCell(a); - while (cell && ++a <= b) { - res.push(cell.value); - cell = cell.next; - } - return res; - } + slice(from = 0, to = this.length) { + let a = from < 0 ? from + this._length : from; + let b = to < 0 ? to + this._length : to; + if (a < 0 || b < 0) { + illegalArgs("invalid indices: ${from} / ${to}"); + } + const res = new DCons(); + let cell = this.nthCell(a); + while (cell && ++a <= b) { + res.push(cell.value); + cell = cell.next; + } + return res; + } - /** - * Merge sort implementation based on Simon Tatham's algorithm: - * https://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html - * - * @remarks - * Uses {@link @thi.ng/compare#compare} as default comparator. - * - * @param cmp - - */ - sort(cmp: Comparator = compare) { - if (!this._length) return this; - let inSize = 1; - while (true) { - let p = this._head; - this._head = undefined; - this._tail = undefined; - let numMerges = 0; - while (p) { - numMerges++; - let q: ConsCell | undefined = p; - let psize = 0; - for (let i = 0; i < inSize; i++) { - psize++; - q = q!.next; - if (!q) break; - } - let qsize = inSize; - while (psize > 0 || (qsize > 0 && q)) { - let e: ConsCell | undefined; - if (psize === 0) { - e = q; - q = q!.next; - qsize--; - } else if (!q || qsize === 0) { - e = p; - p = p!.next; - psize--; - } else if (cmp(p!.value, q!.value) <= 0) { - e = p; - p = p!.next; - psize--; - } else { - e = q; - q = q!.next; - qsize--; - } - if (this._tail) { - this._tail!.next = e; - } else { - this._head = e; - } - e!.prev = this._tail; - this._tail = e; - } - p = q; - } - this._tail!.next = undefined; - if (numMerges <= 1) { - return this; - } - inSize *= 2; - } - } + /** + * Merge sort implementation based on Simon Tatham's algorithm: + * https://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html + * + * @remarks + * Uses {@link @thi.ng/compare#compare} as default comparator. + * + * @param cmp - + */ + sort(cmp: Comparator = compare) { + if (!this._length) return this; + let inSize = 1; + while (true) { + let p = this._head; + this._head = undefined; + this._tail = undefined; + let numMerges = 0; + while (p) { + numMerges++; + let q: ConsCell | undefined = p; + let psize = 0; + for (let i = 0; i < inSize; i++) { + psize++; + q = q!.next; + if (!q) break; + } + let qsize = inSize; + while (psize > 0 || (qsize > 0 && q)) { + let e: ConsCell | undefined; + if (psize === 0) { + e = q; + q = q!.next; + qsize--; + } else if (!q || qsize === 0) { + e = p; + p = p!.next; + psize--; + } else if (cmp(p!.value, q!.value) <= 0) { + e = p; + p = p!.next; + psize--; + } else { + e = q; + q = q!.next; + qsize--; + } + if (this._tail) { + this._tail!.next = e; + } else { + this._head = e; + } + e!.prev = this._tail; + this._tail = e; + } + p = q; + } + this._tail!.next = undefined; + if (numMerges <= 1) { + return this; + } + inSize *= 2; + } + } - splice(at: ConsCell | number, del = 0, insert?: Iterable): DCons { - let cell: ConsCell | undefined; - if (typeof at === "number") { - if (at < 0) { - at += this._length; - } - ensureIndex(at, 0, this._length); - cell = this.nthCellUnsafe(at); - } else { - cell = at; - } - const removed = new DCons(); - if (del > 0) { - while (cell && del-- > 0) { - this.remove(cell); - removed.push(cell.value); - cell = cell.next; - } - } else if (cell) { - cell = cell.next; - } - if (insert) { - if (cell) { - for (let i of insert) { - this.insertBefore(cell, i); - } - } else { - for (let i of insert) { - this.push(i); - } - } - } - return removed; - } + splice(at: ConsCell | number, del = 0, insert?: Iterable): DCons { + let cell: ConsCell | undefined; + if (typeof at === "number") { + if (at < 0) { + at += this._length; + } + ensureIndex(at, 0, this._length); + cell = this.nthCellUnsafe(at); + } else { + cell = at; + } + const removed = new DCons(); + if (del > 0) { + while (cell && del-- > 0) { + this.remove(cell); + removed.push(cell.value); + cell = cell.next; + } + } else if (cell) { + cell = cell.next; + } + if (insert) { + if (cell) { + for (let i of insert) { + this.insertBefore(cell, i); + } + } else { + for (let i of insert) { + this.push(i); + } + } + } + return removed; + } } /** diff --git a/packages/dcons/src/ring.ts b/packages/dcons/src/ring.ts index 1357b35de8..4780c691a8 100644 --- a/packages/dcons/src/ring.ts +++ b/packages/dcons/src/ring.ts @@ -7,140 +7,140 @@ import type { ConsCell } from "./api.js"; * as {@link DCons}. */ export class DRing - extends AList, T> - implements ICopy>, ICompare>, IEmpty> + extends AList, T> + implements ICopy>, ICompare>, IEmpty> { - get tail() { - return this._head ? this._head.prev : undefined; - } - - append(value: T) { - if (!this._head) return this.prepend(value); - const prev = this._head.prev!; - const cell = >{ - value, - prev, - next: this._head, - }; - prev.next = cell; - this._head.prev = cell; - this._length++; - return cell; - } - - copy() { - return new DRing(this); - } - - drop() { - if (this._head) { - const res = this._head.value; - this.remove(this._head); - return res; - } - } - - empty() { - return new DRing(); - } - - insertBefore(cell: ConsCell, value: T) { - const newCell = >{ value, prev: cell.prev, next: cell }; - cell.prev!.next = newCell; - cell.prev = newCell; - if (cell === this._head) { - this._head = newCell; - } - this._length++; - return newCell; - } - - insertAfter(cell: ConsCell, value: T) { - const newCell = >{ value, prev: cell, next: cell.next }; - cell.next!.prev = newCell; - cell.next = newCell; - this._length++; - return newCell; - } - - map(fn: Fn) { - return this._map(new DRing(), fn); - } - - nth(n: number, notFound?: T) { - const cell = this.nthCell(n); - return cell ? cell.value : notFound; - } - - nthCell(n: number) { - const len = this._length; - return len - ? this.nthCellUnsafe(n - Math.floor(n / len) * len) - : undefined; - } - - prepend(value: T) { - let cell: ConsCell; - if (this._head) { - const tail = this.tail!; - cell = { value, prev: tail, next: this._head }; - tail.next = cell; - this._head.prev = cell; - this._head = cell; - } else { - cell = >{ value }; - cell.prev = cell; - cell.next = cell; - this._head = cell; - } - this._length++; - return cell; - } - - remove(v: ConsCell) { - if (this._head === v) { - if (this._length === 1) { - this._head = undefined; - this._length = 0; - return; - } - this._head = v.next; - } - v.prev!.next = v.next; - v.next!.prev = v.prev; - this._length--; - } - - rotateLeft() { - this._head && (this._head = this._head.next); - return this; - } - - rotateRight() { - this._head && (this._head = this._head.prev); - return this; - } - - /** {@inheritDoc @thi.ng/api#ISeqable.seq} */ - seq() { - const $seq = (cell: ConsCell): ISeq => ({ - first() { - return cell.value; - }, - next() { - return $seq(cell.next!); - }, - }); - return this._head ? $seq(this._head) : undefined; - } - - traverse( - fn: Fn, boolean | number>, - start: ConsCell | undefined = this._head, - end: ConsCell | undefined = start - ) { - return super.traverse(fn, start, end); - } + get tail() { + return this._head ? this._head.prev : undefined; + } + + append(value: T) { + if (!this._head) return this.prepend(value); + const prev = this._head.prev!; + const cell = >{ + value, + prev, + next: this._head, + }; + prev.next = cell; + this._head.prev = cell; + this._length++; + return cell; + } + + copy() { + return new DRing(this); + } + + drop() { + if (this._head) { + const res = this._head.value; + this.remove(this._head); + return res; + } + } + + empty() { + return new DRing(); + } + + insertBefore(cell: ConsCell, value: T) { + const newCell = >{ value, prev: cell.prev, next: cell }; + cell.prev!.next = newCell; + cell.prev = newCell; + if (cell === this._head) { + this._head = newCell; + } + this._length++; + return newCell; + } + + insertAfter(cell: ConsCell, value: T) { + const newCell = >{ value, prev: cell, next: cell.next }; + cell.next!.prev = newCell; + cell.next = newCell; + this._length++; + return newCell; + } + + map(fn: Fn) { + return this._map(new DRing(), fn); + } + + nth(n: number, notFound?: T) { + const cell = this.nthCell(n); + return cell ? cell.value : notFound; + } + + nthCell(n: number) { + const len = this._length; + return len + ? this.nthCellUnsafe(n - Math.floor(n / len) * len) + : undefined; + } + + prepend(value: T) { + let cell: ConsCell; + if (this._head) { + const tail = this.tail!; + cell = { value, prev: tail, next: this._head }; + tail.next = cell; + this._head.prev = cell; + this._head = cell; + } else { + cell = >{ value }; + cell.prev = cell; + cell.next = cell; + this._head = cell; + } + this._length++; + return cell; + } + + remove(v: ConsCell) { + if (this._head === v) { + if (this._length === 1) { + this._head = undefined; + this._length = 0; + return; + } + this._head = v.next; + } + v.prev!.next = v.next; + v.next!.prev = v.prev; + this._length--; + } + + rotateLeft() { + this._head && (this._head = this._head.next); + return this; + } + + rotateRight() { + this._head && (this._head = this._head.prev); + return this; + } + + /** {@inheritDoc @thi.ng/api#ISeqable.seq} */ + seq() { + const $seq = (cell: ConsCell): ISeq => ({ + first() { + return cell.value; + }, + next() { + return $seq(cell.next!); + }, + }); + return this._head ? $seq(this._head) : undefined; + } + + traverse( + fn: Fn, boolean | number>, + start: ConsCell | undefined = this._head, + end: ConsCell | undefined = start + ) { + return super.traverse(fn, start, end); + } } /** diff --git a/packages/dcons/src/sol.ts b/packages/dcons/src/sol.ts index f153395ffe..cc6621e703 100644 --- a/packages/dcons/src/sol.ts +++ b/packages/dcons/src/sol.ts @@ -26,49 +26,49 @@ type SOFn = Fn2, ConsCell, ConsCell>; * - https://en.wikipedia.org/wiki/Self-organizing_list */ export class SOL extends DCons { - constructor(protected _reorder: SOFn, src?: Iterable) { - super(src); - } + constructor(protected _reorder: SOFn, src?: Iterable) { + super(src); + } - copy() { - return new SOL(this._reorder, this); - } + copy() { + return new SOL(this._reorder, this); + } - empty() { - return new SOL(this._reorder); - } + empty() { + return new SOL(this._reorder); + } - find(value: T) { - const cell = super.find(value); - return cell ? this._reorder(this, cell) : undefined; - } + find(value: T) { + const cell = super.find(value); + return cell ? this._reorder(this, cell) : undefined; + } - findWith(fn: Predicate) { - const cell = super.findWith(fn); - return cell ? this._reorder(this, cell) : undefined; - } + findWith(fn: Predicate) { + const cell = super.findWith(fn); + return cell ? this._reorder(this, cell) : undefined; + } - nth(n: number, notFound?: T) { - const cell = super.nthCell(n); - return cell ? this._reorder(this, cell).value : notFound; - } + nth(n: number, notFound?: T) { + const cell = super.nthCell(n); + return cell ? this._reorder(this, cell).value : notFound; + } - setNth(n: number, v: T) { - const cell = this.nthCell(n); - !cell && outOfBounds(n); - this._reorder(this, cell!).value = v; - return cell; - } + setNth(n: number, v: T) { + const cell = this.nthCell(n); + !cell && outOfBounds(n); + this._reorder(this, cell!).value = v; + return cell; + } - setTail(value: T) { - const cell = this._tail; - if (cell) { - cell.value = value; - this._reorder(this, cell); - return cell; - } - return this.prepend(value); - } + setTail(value: T) { + const cell = this._tail; + if (cell) { + cell.value = value; + this._reorder(this, cell); + return cell; + } + return this.prepend(value); + } } /** @@ -79,7 +79,7 @@ export class SOL extends DCons { * - https://en.wikipedia.org/wiki/Self-organizing_list#Move_to_front_method_(MTF) */ export const defMTF = (src?: Iterable) => - new SOL((list, cell) => (list.asHead(cell), cell), src); + new SOL((list, cell) => (list.asHead(cell), cell), src); /** * Created self-organizing list using Swap-With-Neighbor (transpose) strategy. @@ -89,8 +89,8 @@ export const defMTF = (src?: Iterable) => * - https://en.wikipedia.org/wiki/Self-organizing_list#Transpose_method */ export const defTranspose = (src?: Iterable) => - new SOL( - (list, cell) => - cell.prev ? (list.swap(cell.prev, cell), cell.prev) : cell, - src - ); + new SOL( + (list, cell) => + cell.prev ? (list.swap(cell.prev, cell), cell.prev) : cell, + src + ); diff --git a/packages/dcons/test/index.ts b/packages/dcons/test/index.ts index 1f20068eb9..5b1d6e8c8d 100644 --- a/packages/dcons/test/index.ts +++ b/packages/dcons/test/index.ts @@ -8,98 +8,98 @@ import { AList, DCons, defDCons } from "../src/index.js"; let a: DCons, src: number[]; group( - "DCons", - { - "is instanceof": () => { - assert.ok(a instanceof AList); - assert.ok(a instanceof DCons); - }, + "DCons", + { + "is instanceof": () => { + assert.ok(a instanceof AList); + assert.ok(a instanceof DCons); + }, - "has length": () => { - assert.strictEqual(a.length, 5); - a = defDCons(); - assert.strictEqual(a.length, 0); - }, + "has length": () => { + assert.strictEqual(a.length, 5); + a = defDCons(); + assert.strictEqual(a.length, 0); + }, - "is iterable": () => { - assert.deepStrictEqual([...a], src); - }, + "is iterable": () => { + assert.deepStrictEqual([...a], src); + }, - "is seqable": () => { - assert.strictEqual(a.seq()!.first(), 1); - // prettier-ignore - assert.strictEqual(a.seq()!.next()!.first(), 2); - // prettier-ignore - assert.strictEqual(a.seq(3)!.first(), 4); - // prettier-ignore - assert.strictEqual(a.seq(3)!.next()!.first(), 5); - // prettier-ignore - assert.strictEqual(a.seq(3)!.next()!.next(), undefined); - assert.strictEqual(a.seq(2, 2), undefined); - assert.strictEqual(a.seq(2, 3)!.first(), 3); - assert.strictEqual(a.seq(2, 3)!.next(), undefined); - }, + "is seqable": () => { + assert.strictEqual(a.seq()!.first(), 1); + // prettier-ignore + assert.strictEqual(a.seq()!.next()!.first(), 2); + // prettier-ignore + assert.strictEqual(a.seq(3)!.first(), 4); + // prettier-ignore + assert.strictEqual(a.seq(3)!.next()!.first(), 5); + // prettier-ignore + assert.strictEqual(a.seq(3)!.next()!.next(), undefined); + assert.strictEqual(a.seq(2, 2), undefined); + assert.strictEqual(a.seq(2, 3)!.first(), 3); + assert.strictEqual(a.seq(2, 3)!.next(), undefined); + }, - shuffle: () => { - assert.deepStrictEqual( - [...a.shuffle(undefined, new XsAdd(0x12345678))], - [3, 5, 1, 4, 2] - ); - assert.deepStrictEqual( - [ - ...defDCons(range(10)).shuffle( - undefined, - new XsAdd(0x12345678) - ), - ], - [3, 0, 7, 8, 5, 2, 9, 1, 6, 4] - ); - assert.deepStrictEqual([...defDCons().shuffle()], []); - assert.deepStrictEqual([...defDCons([1]).shuffle()], [1]); - }, + shuffle: () => { + assert.deepStrictEqual( + [...a.shuffle(undefined, new XsAdd(0x12345678))], + [3, 5, 1, 4, 2] + ); + assert.deepStrictEqual( + [ + ...defDCons(range(10)).shuffle( + undefined, + new XsAdd(0x12345678) + ), + ], + [3, 0, 7, 8, 5, 2, 9, 1, 6, 4] + ); + assert.deepStrictEqual([...defDCons().shuffle()], []); + assert.deepStrictEqual([...defDCons([1]).shuffle()], [1]); + }, - sort: () => { - assert.deepStrictEqual([...defDCons().sort()], []); - assert.deepStrictEqual([...defDCons([1]).sort()], [1]); - assert.deepStrictEqual([...defDCons([1, -1]).sort()], [-1, 1]); - assert.deepStrictEqual( - [...defDCons([8, -1, 17, 5, 8, 3, 11]).sort()], - [-1, 3, 5, 8, 8, 11, 17] - ); - assert.deepStrictEqual( - [...defDCons([8, -1, 17, 5, 8, 3, 11]).sort(compareNumDesc)], - [17, 11, 8, 8, 5, 3, -1] - ); - }, + sort: () => { + assert.deepStrictEqual([...defDCons().sort()], []); + assert.deepStrictEqual([...defDCons([1]).sort()], [1]); + assert.deepStrictEqual([...defDCons([1, -1]).sort()], [-1, 1]); + assert.deepStrictEqual( + [...defDCons([8, -1, 17, 5, 8, 3, 11]).sort()], + [-1, 3, 5, 8, 8, 11, 17] + ); + assert.deepStrictEqual( + [...defDCons([8, -1, 17, 5, 8, 3, 11]).sort(compareNumDesc)], + [17, 11, 8, 8, 5, 3, -1] + ); + }, - "works as stack": () => { - assert.strictEqual(a.push(10).pop(), 10); - assert.strictEqual(a.pop(), 5); - a = defDCons(); - assert.strictEqual(a.pop(), undefined); - }, + "works as stack": () => { + assert.strictEqual(a.push(10).pop(), 10); + assert.strictEqual(a.pop(), 5); + a = defDCons(); + assert.strictEqual(a.pop(), undefined); + }, - "works as queue": () => { - assert.strictEqual(a.push(10).drop(), 1); - assert.strictEqual(a.drop(), 2); - assert.strictEqual(a.drop(), 3); - assert.strictEqual(a.drop(), 4); - assert.strictEqual(a.drop(), 5); - assert.strictEqual(a.drop(), 10); - assert.strictEqual(a.drop(), undefined); - }, + "works as queue": () => { + assert.strictEqual(a.push(10).drop(), 1); + assert.strictEqual(a.drop(), 2); + assert.strictEqual(a.drop(), 3); + assert.strictEqual(a.drop(), 4); + assert.strictEqual(a.drop(), 5); + assert.strictEqual(a.drop(), 10); + assert.strictEqual(a.drop(), undefined); + }, - toString: () => { - assert.strictEqual( - defDCons([, null, 0, 1, ["a", "b"], "ab"]).toString(), - "undefined, null, 0, 1, a,b, ab" - ); - }, - }, - { - beforeEach: () => { - src = [1, 2, 3, 4, 5]; - a = defDCons(src); - }, - } + toString: () => { + assert.strictEqual( + defDCons([, null, 0, 1, ["a", "b"], "ab"]).toString(), + "undefined, null, 0, 1, a,b, ab" + ); + }, + }, + { + beforeEach: () => { + src = [1, 2, 3, 4, 5]; + a = defDCons(src); + }, + } ); diff --git a/packages/dcons/test/sol.ts b/packages/dcons/test/sol.ts index 09dff47417..8fd1098182 100644 --- a/packages/dcons/test/sol.ts +++ b/packages/dcons/test/sol.ts @@ -1,41 +1,41 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { defMTF, defTranspose } from "../src/index.js" +import { defMTF, defTranspose } from "../src/index.js"; group("dcons (self-organizing)", { - "mtf (n=5)": () => { - const a = defMTF([1, 2, 3, 4, 5]); - a.nth(3); - assert.deepStrictEqual([...a], [4, 1, 2, 3, 5]); - a.nth(3); - assert.deepStrictEqual([...a], [3, 4, 1, 2, 5]); - assert.strictEqual(a.find(3)!.value, 3); - assert.deepStrictEqual([...a], [3, 4, 1, 2, 5]); - assert.strictEqual(a.find(1)!.value, 1); - assert.deepStrictEqual([...a], [1, 3, 4, 2, 5]); - assert.strictEqual(a.findWith((x) => x === 2)!.value, 2); - assert.deepStrictEqual([...a], [2, 1, 3, 4, 5]); - a.setNth(4, 50); - assert.deepStrictEqual([...a], [50, 2, 1, 3, 4]); - a.setTail(40); - assert.deepStrictEqual([...a], [40, 50, 2, 1, 3]); - }, + "mtf (n=5)": () => { + const a = defMTF([1, 2, 3, 4, 5]); + a.nth(3); + assert.deepStrictEqual([...a], [4, 1, 2, 3, 5]); + a.nth(3); + assert.deepStrictEqual([...a], [3, 4, 1, 2, 5]); + assert.strictEqual(a.find(3)!.value, 3); + assert.deepStrictEqual([...a], [3, 4, 1, 2, 5]); + assert.strictEqual(a.find(1)!.value, 1); + assert.deepStrictEqual([...a], [1, 3, 4, 2, 5]); + assert.strictEqual(a.findWith((x) => x === 2)!.value, 2); + assert.deepStrictEqual([...a], [2, 1, 3, 4, 5]); + a.setNth(4, 50); + assert.deepStrictEqual([...a], [50, 2, 1, 3, 4]); + a.setTail(40); + assert.deepStrictEqual([...a], [40, 50, 2, 1, 3]); + }, - "transpose (n=5)": () => { - const a = defTranspose([1, 2, 3, 4, 5]); - a.nth(3); - assert.deepStrictEqual([...a], [1, 2, 4, 3, 5]); - a.nth(3); - assert.deepStrictEqual([...a], [1, 2, 3, 4, 5]); - assert.strictEqual(a.find(3)!.value, 3); - assert.deepStrictEqual([...a], [1, 3, 2, 4, 5]); - assert.strictEqual(a.find(1)!.value, 1); - assert.deepStrictEqual([...a], [1, 3, 2, 4, 5]); - assert.strictEqual(a.findWith((x) => x === 2)!.value, 2); - assert.deepStrictEqual([...a], [1, 2, 3, 4, 5]); - a.setNth(4, 50); - assert.deepStrictEqual([...a], [1, 2, 3, 50, 4]); - a.setTail(40); - assert.deepStrictEqual([...a], [1, 2, 3, 40, 50]); - }, + "transpose (n=5)": () => { + const a = defTranspose([1, 2, 3, 4, 5]); + a.nth(3); + assert.deepStrictEqual([...a], [1, 2, 4, 3, 5]); + a.nth(3); + assert.deepStrictEqual([...a], [1, 2, 3, 4, 5]); + assert.strictEqual(a.find(3)!.value, 3); + assert.deepStrictEqual([...a], [1, 3, 2, 4, 5]); + assert.strictEqual(a.find(1)!.value, 1); + assert.deepStrictEqual([...a], [1, 3, 2, 4, 5]); + assert.strictEqual(a.findWith((x) => x === 2)!.value, 2); + assert.deepStrictEqual([...a], [1, 2, 3, 4, 5]); + a.setNth(4, 50); + assert.deepStrictEqual([...a], [1, 2, 3, 50, 4]); + a.setTail(40); + assert.deepStrictEqual([...a], [1, 2, 3, 40, 50]); + }, }); diff --git a/packages/dcons/tsconfig.json b/packages/dcons/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/dcons/tsconfig.json +++ b/packages/dcons/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/defmulti/api-extractor.json b/packages/defmulti/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/defmulti/api-extractor.json +++ b/packages/defmulti/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/defmulti/package.json b/packages/defmulti/package.json index 5258a2deed..9e2263e562 100644 --- a/packages/defmulti/package.json +++ b/packages/defmulti/package.json @@ -1,93 +1,93 @@ { - "name": "@thi.ng/defmulti", - "version": "2.1.8", - "description": "Dynamic, extensible multiple dispatch via user supplied dispatch function.", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/defmulti#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/logger": "^1.1.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "agent", - "arity", - "clojure", - "functional", - "hierarchy", - "inheritance", - "multiple-dispatch", - "polymorphic", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./defmulti-n": { - "default": "./defmulti-n.js" - }, - "./defmulti": { - "default": "./defmulti.js" - }, - "./impls": { - "default": "./impls.js" - }, - "./logger": { - "default": "./logger.js" - } - }, - "thi.ng": { - "year": 2018 - } + "name": "@thi.ng/defmulti", + "version": "2.1.8", + "description": "Dynamic, extensible multiple dispatch via user supplied dispatch function.", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/defmulti#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/logger": "^1.1.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "agent", + "arity", + "clojure", + "functional", + "hierarchy", + "inheritance", + "multiple-dispatch", + "polymorphic", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./defmulti-n": { + "default": "./defmulti-n.js" + }, + "./defmulti": { + "default": "./defmulti.js" + }, + "./impls": { + "default": "./impls.js" + }, + "./logger": { + "default": "./logger.js" + } + }, + "thi.ng": { + "year": 2018 + } } diff --git a/packages/defmulti/src/api.ts b/packages/defmulti/src/api.ts index ebfb31d3fd..bb4554d5e8 100644 --- a/packages/defmulti/src/api.ts +++ b/packages/defmulti/src/api.ts @@ -1,15 +1,15 @@ import type { - Fn, - Fn2, - Fn3, - Fn4, - Fn5, - Fn6, - Fn7, - Fn8, - FnAny, - IObjectOf, - Pair, + Fn, + Fn2, + Fn3, + Fn4, + Fn5, + Fn6, + Fn7, + Fn8, + FnAny, + IObjectOf, + Pair, } from "@thi.ng/api"; export type DispatchFn = FnAny; @@ -21,72 +21,72 @@ export type DispatchFn3 = Fn3; export type DispatchFn3O = (a: A, b: B, c: C, d?: D) => PropertyKey; export type DispatchFn4 = Fn4; export type DispatchFn4O = ( - a: A, - b: B, - c: C, - d: D, - e?: E + a: A, + b: B, + c: C, + d: D, + e?: E ) => PropertyKey; export type DispatchFn5 = Fn5; export type DispatchFn5O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f?: F + a: A, + b: B, + c: C, + d: D, + e: E, + f?: F ) => PropertyKey; export type DispatchFn6 = Fn6; export type DispatchFn6O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g?: G + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g?: G ) => PropertyKey; export type DispatchFn7 = Fn7< - A, - B, - C, - D, - E, - F, - G, - PropertyKey + A, + B, + C, + D, + E, + F, + G, + PropertyKey >; export type DispatchFn7O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h?: H + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h?: H ) => PropertyKey; export type DispatchFn8 = Fn8< - A, - B, - C, - D, - E, - F, - G, - H, - PropertyKey + A, + B, + C, + D, + E, + F, + G, + H, + PropertyKey >; export type DispatchFn8O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i?: I + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i?: I ) => PropertyKey; export type Implementation = FnAny; @@ -98,227 +98,227 @@ export type Implementation3 = Fn3; export type Implementation3O = (a: A, b: B, c: C, d?: D) => T; export type Implementation4 = Fn4; export type Implementation4O = ( - a: A, - b: B, - c: C, - d: D, - e?: E + a: A, + b: B, + c: C, + d: D, + e?: E ) => T; export type Implementation5 = Fn5; export type Implementation5O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f?: F + a: A, + b: B, + c: C, + d: D, + e: E, + f?: F ) => T; export type Implementation6 = Fn6; export type Implementation6O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g?: G + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g?: G ) => T; export type Implementation7 = Fn7< - A, - B, - C, - D, - E, - F, - G, - T + A, + B, + C, + D, + E, + F, + G, + T >; export type Implementation7O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h?: H + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h?: H ) => T; export type Implementation8 = Fn8< - A, - B, - C, - D, - E, - F, - G, - H, - T + A, + B, + C, + D, + E, + F, + G, + H, + T >; export type Implementation8O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i?: I + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i?: I ) => T; export interface MultiFnBase { - /** - * Registers implementation for dispatch value `id`. Returns true, - * if successful. Returns false if an implementation already exists - * (and does nothing in this case). - * - * @param id - implementation ID (dispatch value) - * @param impl - implementation - */ - add(id: PropertyKey, impl: I): boolean; - /** - * Takes an object of dispatch values and their implementations and - * calls `.add()` for each KV pair. Returns true, if all impls were - * added successfully. Note: Only numbers or strings are accepted as - * dispatch values here. - * - * @param impls - object of implementations - */ - addAll(impls: IObjectOf): boolean; - /** - * Sets default/fallback implementation. - * - * @remarks - * Same as `.add(DEFAULT, impl)` - * - * @param impl - - */ - setDefault(impl: I): boolean; - /** - * Removes implementation for dispatch value `id`. Returns true, if - * successful. - * - * @param id - implementation ID - */ - remove(id: PropertyKey): boolean; - /** - * Returns true, if the function is callable (has a valid - * implementation) for given arguments. - * - * @param args - arguments to find impl for - */ - callable(...args: any[]): boolean; - /** - * Returns a set of all registered dispatch values. - */ - impls(): Set; - /** - * Updates dispatch hierarchy by declaring dispatch value `id` to - * delegate to `parent`'s implementation. I.e. in terms of dispatch - * logic, `id` is considered the same as `parent. - * - * @param id - implementation ID - * @param parent -parent implementation ID - */ - isa(id: PropertyKey, parent: PropertyKey): boolean; - /** - * Returns all known dispatch relationships. This is an object with - * all registered dispatch values as keys, each with a set of parent - * dispatch values. - */ - rels(): IObjectOf>; - /** - * Returns a set of immediate parent dispatch values for given - * dispatch value `id`. - * - * @param id - implementation ID - */ - parents(id: PropertyKey): Set; - /** - * Similar to {@link MultiFnBase.parents}, but includes all - * transitive parent dispatch values for given dispatch value `id`. - * @param id - implementation ID - */ - ancestors(id: PropertyKey): Set; - /** - * Returns iterator of all known dispatch values and their dependencies in - * the hierarchy of dispatch values. Each dependency is encoded as `[value, - * parent-value?]`. If `parent-value` is undefined, the dispatch value has - * no parent. - */ - dependencies(): IterableIterator< - Pair - >; + /** + * Registers implementation for dispatch value `id`. Returns true, + * if successful. Returns false if an implementation already exists + * (and does nothing in this case). + * + * @param id - implementation ID (dispatch value) + * @param impl - implementation + */ + add(id: PropertyKey, impl: I): boolean; + /** + * Takes an object of dispatch values and their implementations and + * calls `.add()` for each KV pair. Returns true, if all impls were + * added successfully. Note: Only numbers or strings are accepted as + * dispatch values here. + * + * @param impls - object of implementations + */ + addAll(impls: IObjectOf): boolean; + /** + * Sets default/fallback implementation. + * + * @remarks + * Same as `.add(DEFAULT, impl)` + * + * @param impl - + */ + setDefault(impl: I): boolean; + /** + * Removes implementation for dispatch value `id`. Returns true, if + * successful. + * + * @param id - implementation ID + */ + remove(id: PropertyKey): boolean; + /** + * Returns true, if the function is callable (has a valid + * implementation) for given arguments. + * + * @param args - arguments to find impl for + */ + callable(...args: any[]): boolean; + /** + * Returns a set of all registered dispatch values. + */ + impls(): Set; + /** + * Updates dispatch hierarchy by declaring dispatch value `id` to + * delegate to `parent`'s implementation. I.e. in terms of dispatch + * logic, `id` is considered the same as `parent. + * + * @param id - implementation ID + * @param parent -parent implementation ID + */ + isa(id: PropertyKey, parent: PropertyKey): boolean; + /** + * Returns all known dispatch relationships. This is an object with + * all registered dispatch values as keys, each with a set of parent + * dispatch values. + */ + rels(): IObjectOf>; + /** + * Returns a set of immediate parent dispatch values for given + * dispatch value `id`. + * + * @param id - implementation ID + */ + parents(id: PropertyKey): Set; + /** + * Similar to {@link MultiFnBase.parents}, but includes all + * transitive parent dispatch values for given dispatch value `id`. + * @param id - implementation ID + */ + ancestors(id: PropertyKey): Set; + /** + * Returns iterator of all known dispatch values and their dependencies in + * the hierarchy of dispatch values. Each dependency is encoded as `[value, + * parent-value?]`. If `parent-value` is undefined, the dispatch value has + * no parent. + */ + dependencies(): IterableIterator< + Pair + >; } export interface MultiFn - extends Implementation, - MultiFnBase> {} + extends Implementation, + MultiFnBase> {} export interface MultiFn1 - extends Implementation1, - MultiFnBase> {} + extends Implementation1, + MultiFnBase> {} export interface MultiFn1O - extends Implementation1O, - MultiFnBase> {} + extends Implementation1O, + MultiFnBase> {} export interface MultiFn2 - extends Implementation2, - MultiFnBase> {} + extends Implementation2, + MultiFnBase> {} export interface MultiFn2O - extends Implementation2O, - MultiFnBase> {} + extends Implementation2O, + MultiFnBase> {} export interface MultiFn3 - extends Implementation3, - MultiFnBase> {} + extends Implementation3, + MultiFnBase> {} export interface MultiFn3O - extends Implementation3O, - MultiFnBase> {} + extends Implementation3O, + MultiFnBase> {} export interface MultiFn4 - extends Implementation4, - MultiFnBase> {} + extends Implementation4, + MultiFnBase> {} export interface MultiFn4O - extends Implementation4O, - MultiFnBase> {} + extends Implementation4O, + MultiFnBase> {} export interface MultiFn5 - extends Implementation5, - MultiFnBase> {} + extends Implementation5, + MultiFnBase> {} export interface MultiFn5O - extends Implementation5O, - MultiFnBase> {} + extends Implementation5O, + MultiFnBase> {} export interface MultiFn6 - extends Implementation6, - MultiFnBase> {} + extends Implementation6, + MultiFnBase> {} export interface MultiFn6O - extends Implementation6O, - MultiFnBase> {} + extends Implementation6O, + MultiFnBase> {} export interface MultiFn7 - extends Implementation7, - MultiFnBase> {} + extends Implementation7, + MultiFnBase> {} export interface MultiFn7O - extends Implementation7O, - MultiFnBase> {} + extends Implementation7O, + MultiFnBase> {} export interface MultiFn8 - extends Implementation8, - MultiFnBase> {} + extends Implementation8, + MultiFnBase> {} export interface MultiFn8O - extends Implementation8O, - MultiFnBase> {} + extends Implementation8O, + MultiFnBase> {} export type AncestorDefs = IObjectOf< - PropertyKey | PropertyKey[] | Set + PropertyKey | PropertyKey[] | Set >; diff --git a/packages/defmulti/src/defmulti-n.ts b/packages/defmulti/src/defmulti-n.ts index 410b5f1eab..7c6a20bee0 100644 --- a/packages/defmulti/src/defmulti-n.ts +++ b/packages/defmulti/src/defmulti-n.ts @@ -44,13 +44,13 @@ import { DEFAULT, defmulti } from "./defmulti.js"; * @param fallback - fallback implementation */ export const defmultiN = ( - impls: { [id: number]: Implementation }, - fallback?: Implementation + impls: { [id: number]: Implementation }, + fallback?: Implementation ) => { - const fn = defmulti((...args: any[]) => args.length); - fn.add(DEFAULT, fallback || ((...args) => illegalArity(args.length))); - for (let id in impls) { - fn.add(id, impls[id]); - } - return fn; + const fn = defmulti((...args: any[]) => args.length); + fn.add(DEFAULT, fallback || ((...args) => illegalArity(args.length))); + for (let id in impls) { + fn.add(id, impls[id]); + } + return fn; }; diff --git a/packages/defmulti/src/defmulti.ts b/packages/defmulti/src/defmulti.ts index 78013ad6cd..cd8f2e4e52 100644 --- a/packages/defmulti/src/defmulti.ts +++ b/packages/defmulti/src/defmulti.ts @@ -1,58 +1,58 @@ import type { IObjectOf, Pair } from "@thi.ng/api"; import { unsupported } from "@thi.ng/errors/unsupported"; import type { - AncestorDefs, - DispatchFn, - DispatchFn1, - DispatchFn1O, - DispatchFn2, - DispatchFn2O, - DispatchFn3, - DispatchFn3O, - DispatchFn4, - DispatchFn4O, - DispatchFn5, - DispatchFn5O, - DispatchFn6, - DispatchFn6O, - DispatchFn7, - DispatchFn7O, - DispatchFn8, - DispatchFn8O, - Implementation, - Implementation1, - Implementation1O, - Implementation2, - Implementation2O, - Implementation3, - Implementation3O, - Implementation4, - Implementation4O, - Implementation5, - Implementation5O, - Implementation6, - Implementation6O, - Implementation7, - Implementation7O, - Implementation8, - Implementation8O, - MultiFn, - MultiFn1, - MultiFn1O, - MultiFn2, - MultiFn2O, - MultiFn3, - MultiFn3O, - MultiFn4, - MultiFn4O, - MultiFn5, - MultiFn5O, - MultiFn6, - MultiFn6O, - MultiFn7, - MultiFn7O, - MultiFn8, - MultiFn8O, + AncestorDefs, + DispatchFn, + DispatchFn1, + DispatchFn1O, + DispatchFn2, + DispatchFn2O, + DispatchFn3, + DispatchFn3O, + DispatchFn4, + DispatchFn4O, + DispatchFn5, + DispatchFn5O, + DispatchFn6, + DispatchFn6O, + DispatchFn7, + DispatchFn7O, + DispatchFn8, + DispatchFn8O, + Implementation, + Implementation1, + Implementation1O, + Implementation2, + Implementation2O, + Implementation3, + Implementation3O, + Implementation4, + Implementation4O, + Implementation5, + Implementation5O, + Implementation6, + Implementation6O, + Implementation7, + Implementation7O, + Implementation8, + Implementation8O, + MultiFn, + MultiFn1, + MultiFn1O, + MultiFn2, + MultiFn2O, + MultiFn3, + MultiFn3O, + MultiFn4, + MultiFn4O, + MultiFn5, + MultiFn5O, + MultiFn6, + MultiFn6O, + MultiFn7, + MultiFn7O, + MultiFn8, + MultiFn8O, } from "./api.js"; import { LOGGER } from "./logger.js"; @@ -83,204 +83,204 @@ export const DEFAULT: unique symbol = Symbol(); * the returned function. Each returns `true` if the operation was successful. */ export function defmulti( - f: DispatchFn, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn; export function defmulti( - f: DispatchFn1, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn1, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn1; export function defmulti( - f: DispatchFn2, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn2, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn2; export function defmulti( - f: DispatchFn1O, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn1O, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn1O; export function defmulti( - f: DispatchFn3, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn3, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn3; export function defmulti( - f: DispatchFn2O, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn2O, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn2O; export function defmulti( - f: DispatchFn4, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn4, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn4; export function defmulti( - f: DispatchFn3O, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn3O, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn3O; export function defmulti( - f: DispatchFn5, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn5, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn5; export function defmulti( - f: DispatchFn4O, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn4O, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn4O; export function defmulti( - f: DispatchFn6, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn6, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn6; export function defmulti( - f: DispatchFn5O, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn5O, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn5O; export function defmulti( - f: DispatchFn7, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn7, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn7; export function defmulti( - f: DispatchFn6O, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn6O, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn6O; export function defmulti( - f: DispatchFn8, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn8, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn8; export function defmulti( - f: DispatchFn7O, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn7O, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn7O; export function defmulti( - f: DispatchFn8O, - rels?: AncestorDefs, - impls?: IObjectOf> + f: DispatchFn8O, + rels?: AncestorDefs, + impls?: IObjectOf> ): MultiFn8O; export function defmulti( - f: any, - _rels?: AncestorDefs, - _impls?: IObjectOf> + f: any, + _rels?: AncestorDefs, + _impls?: IObjectOf> ) { - const impls: IObjectOf> = {}; - const rels: IObjectOf> = _rels ? makeRels(_rels) : {}; - const fn: any = (...args: any[]) => { - const id = f(...args); - const g = impls[id] || findImpl(impls, rels, id) || impls[DEFAULT]; - return g - ? g(...args) - : unsupported(`missing implementation for: "${id.toString()}"`); - }; - fn.add = (id: PropertyKey, _impl: Implementation) => { - if (impls[id]) { - LOGGER.warn(`overwriting '${id.toString()}' impl`); - } - impls[id] = _impl; - return true; - }; - fn.addAll = (_impls: IObjectOf>) => { - let ok = true; - for (let id in _impls) { - ok = fn.add(id, _impls[id]) && ok; - } - DEFAULT in _impls && fn.setDefault(_impls[DEFAULT]); - return ok; - }; - fn.setDefault = (impl: Implementation) => fn.add(DEFAULT, impl); - fn.remove = (id: PropertyKey) => { - if (!impls[id]) return false; - delete impls[id]; - return true; - }; - fn.callable = (...args: any[]) => { - const id = f(...args); - return !!( - impls[id] || - findImpl(impls, rels, id) || - impls[DEFAULT] - ); - }; - fn.isa = (id: PropertyKey, parent: PropertyKey) => { - let val = rels[id]; - !val && (rels[id] = val = new Set()); - val.add(parent); - }; - fn.impls = () => { - const res = new Set(Object.keys(impls)); - for (let id in rels) { - findImpl(impls, rels, id) && res.add(id); - } - impls[DEFAULT] && res.add(DEFAULT); - return res; - }; - fn.rels = () => rels; - fn.parents = (id: PropertyKey) => rels[id]; - fn.ancestors = (id: PropertyKey) => - new Set(findAncestors([], rels, id)); - fn.dependencies = function* (): IterableIterator< - Pair - > { - for (let a in rels) { - for (let b of rels[a]) yield [a, b]; - } - for (let id in impls) { - !rels[id] && (yield [id, undefined]); - } - }; + const impls: IObjectOf> = {}; + const rels: IObjectOf> = _rels ? makeRels(_rels) : {}; + const fn: any = (...args: any[]) => { + const id = f(...args); + const g = impls[id] || findImpl(impls, rels, id) || impls[DEFAULT]; + return g + ? g(...args) + : unsupported(`missing implementation for: "${id.toString()}"`); + }; + fn.add = (id: PropertyKey, _impl: Implementation) => { + if (impls[id]) { + LOGGER.warn(`overwriting '${id.toString()}' impl`); + } + impls[id] = _impl; + return true; + }; + fn.addAll = (_impls: IObjectOf>) => { + let ok = true; + for (let id in _impls) { + ok = fn.add(id, _impls[id]) && ok; + } + DEFAULT in _impls && fn.setDefault(_impls[DEFAULT]); + return ok; + }; + fn.setDefault = (impl: Implementation) => fn.add(DEFAULT, impl); + fn.remove = (id: PropertyKey) => { + if (!impls[id]) return false; + delete impls[id]; + return true; + }; + fn.callable = (...args: any[]) => { + const id = f(...args); + return !!( + impls[id] || + findImpl(impls, rels, id) || + impls[DEFAULT] + ); + }; + fn.isa = (id: PropertyKey, parent: PropertyKey) => { + let val = rels[id]; + !val && (rels[id] = val = new Set()); + val.add(parent); + }; + fn.impls = () => { + const res = new Set(Object.keys(impls)); + for (let id in rels) { + findImpl(impls, rels, id) && res.add(id); + } + impls[DEFAULT] && res.add(DEFAULT); + return res; + }; + fn.rels = () => rels; + fn.parents = (id: PropertyKey) => rels[id]; + fn.ancestors = (id: PropertyKey) => + new Set(findAncestors([], rels, id)); + fn.dependencies = function* (): IterableIterator< + Pair + > { + for (let a in rels) { + for (let b of rels[a]) yield [a, b]; + } + for (let id in impls) { + !rels[id] && (yield [id, undefined]); + } + }; - _impls && fn.addAll(_impls); - return fn; + _impls && fn.addAll(_impls); + return fn; } const findImpl = ( - impls: IObjectOf>, - rels: IObjectOf>, - id: PropertyKey + impls: IObjectOf>, + rels: IObjectOf>, + id: PropertyKey ) => { - const parents = rels[id]; - if (!parents) return; - for (let p of parents) { - let impl: Implementation = - impls[p] || findImpl(impls, rels, p); - if (impl) return impl; - } + const parents = rels[id]; + if (!parents) return; + for (let p of parents) { + let impl: Implementation = + impls[p] || findImpl(impls, rels, p); + if (impl) return impl; + } }; const findAncestors = ( - acc: PropertyKey[], - rels: IObjectOf>, - id: PropertyKey + acc: PropertyKey[], + rels: IObjectOf>, + id: PropertyKey ) => { - const parents = rels[id]; - if (parents) { - for (let p of parents) { - acc.push(p); - findAncestors(acc, rels, p); - } - } - return acc; + const parents = rels[id]; + if (parents) { + for (let p of parents) { + acc.push(p); + findAncestors(acc, rels, p); + } + } + return acc; }; const makeRels = (spec: AncestorDefs) => { - const rels: IObjectOf> = {}; - for (let k in spec) { - const val = spec[k]; - rels[k] = - val instanceof Set - ? val - : Array.isArray(val) - ? new Set(val) - : new Set([val]); - } - return rels; + const rels: IObjectOf> = {}; + for (let k in spec) { + const val = spec[k]; + rels[k] = + val instanceof Set + ? val + : Array.isArray(val) + ? new Set(val) + : new Set([val]); + } + return rels; }; diff --git a/packages/defmulti/src/impls.ts b/packages/defmulti/src/impls.ts index 9e77727ff0..fcfa8d2f75 100644 --- a/packages/defmulti/src/impls.ts +++ b/packages/defmulti/src/impls.ts @@ -59,20 +59,20 @@ import type { Implementation, MultiFn } from "./api.js"; * @param impls - implementations */ export const implementations = ( - id: PropertyKey, - rels: IObjectOf[]>, - ...impls: (MultiFn | Implementation)[] + id: PropertyKey, + rels: IObjectOf[]>, + ...impls: (MultiFn | Implementation)[] ) => { - impls.length & 1 && - illegalArgs("expected an even number of implementation items"); - if (rels) { - for (let parent in rels) { - for (let fn of rels[parent]) { - fn.isa(id, parent); - } - } - } - for (let i = 0; i < impls.length; i += 2) { - (>impls[i]).add(id, impls[i + 1]); - } + impls.length & 1 && + illegalArgs("expected an even number of implementation items"); + if (rels) { + for (let parent in rels) { + for (let fn of rels[parent]) { + fn.isa(id, parent); + } + } + } + for (let i = 0; i < impls.length; i += 2) { + (>impls[i]).add(id, impls[i + 1]); + } }; diff --git a/packages/defmulti/test/index.ts b/packages/defmulti/test/index.ts index f73aae5e9a..bd44018700 100644 --- a/packages/defmulti/test/index.ts +++ b/packages/defmulti/test/index.ts @@ -2,224 +2,224 @@ import { ConsoleLogger } from "@thi.ng/logger"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { - DEFAULT, - defmulti, - defmultiN, - implementations, - setLogger, + DEFAULT, + defmulti, + defmultiN, + implementations, + setLogger, } from "../src/index.js"; group("defmulti", { - flatten: () => { - const flatten = defmulti((x) => - Object.prototype.toString.call(x) - ); - assert.ok( - flatten.add( - "[object Array]", - (x, acc: any[]) => (x.forEach((y: any) => flatten(y, acc)), acc) - ) - ); - assert.ok( - flatten.add("[object Object]", (x, acc: any[]) => { - for (let k in x) flatten([k, x[k]], acc); - return acc; - }) - ); - assert.ok(flatten.add("[object Null]", (_, acc) => acc)); - assert.ok( - flatten.add( - DEFAULT, - (x, acc: any[]) => (acc.push(x.toString()), acc) - ) - ); - - assert.deepStrictEqual( - flatten([{ a: 1, b: ["foo", "bar", null, 42] }], []), - ["a", "1", "b", "foo", "bar", "42"] - ); - assert.ok(flatten.remove(DEFAULT)); - assert.ok(!flatten.remove(DEFAULT)); - assert.throws(() => - flatten([{ a: 1, b: ["foo", "bar", null, 42] }], []) - ); - }, - - sexpr: () => { - const exec = defmulti((x) => - Array.isArray(x) ? x[0] : typeof x - ); - assert.ok( - exec.add("+", ([_, ...args]) => - args.reduce((acc: number, n: any) => acc + exec(n), 0) - ) - ); - assert.ok( - exec.add("*", ([_, ...args]) => - args.reduce((acc: number, n: any) => acc * exec(n), 1) - ) - ); - assert.ok(exec.add("number", (x) => x)); - assert.ok( - exec.add(DEFAULT, (x) => { - throw new Error(`invalid expr: ${x}`); - }) - ); - - assert.strictEqual(exec(["+", ["*", 10, ["+", 1, 2, 3]], 6]), 66); - - setLogger(new ConsoleLogger("defmulti")); - assert.ok(exec.add("number", (x) => x * 2)); - assert.strictEqual( - exec(["+", ["*", 10, ["+", 1, 2, 3]], 6]), - (1 * 2 + 2 * 2 + 3 * 2) * 10 * 2 + 6 * 2 - ); - - assert.throws(() => exec("")); - }, - - apr: () => { - const apr = defmulti( - ({ type, balance }) => - `${type}-${ - balance < 1e4 ? "low" : balance < 5e4 ? "med" : "high" - }`, - {}, - { - "current-low": ({ balance }) => balance * 0.005, - "current-med": ({ balance }) => balance * 0.01, - "current-high": ({ balance }) => balance * 0.01, - "savings-low": ({ balance }) => balance * 0.01, - "savings-med": ({ balance }) => balance * 0.025, - "savings-high": ({ balance }) => balance * 0.035, - [DEFAULT]: (x: any) => { - throw new Error(`invalid account type: ${x.type}`); - }, - } - ); - - assert.strictEqual(~~apr({ type: "current", balance: 5000 }), 25); - assert.strictEqual(~~apr({ type: "current", balance: 10000 }), 100); - assert.strictEqual(~~apr({ type: "current", balance: 50000 }), 500); - assert.strictEqual(~~apr({ type: "savings", balance: 5000 }), 50); - assert.strictEqual(~~apr({ type: "savings", balance: 10000 }), 250); - assert.strictEqual(~~apr({ type: "savings", balance: 100000 }), 3500); - assert.throws(() => apr({ type: "isa", balance: 10000 })); - }, - - defmultiN: () => { - const foo = defmultiN({ - 0: () => "zero", - 1: (x) => `one: ${x}`, - 3: (x, y, z) => `three: ${x}, ${y}, ${z}`, - }); - - assert.strictEqual(foo(), "zero"); - assert.strictEqual(foo(23), "one: 23"); - assert.strictEqual(foo(1, 2, 3), "three: 1, 2, 3"); - assert.throws(() => foo(1, 2)); - }, - - isa: () => { - const foo = defmulti((x) => x); - foo.isa(23, "odd"); - foo.isa(42, "even"); - foo.isa("odd", "number"); - foo.isa("even", "number"); - foo.add("odd", () => "odd"); - foo.add("number", () => "number"); - assert.deepStrictEqual(foo.parents(23), new Set(["odd"]), "parents 23"); - assert.deepStrictEqual( - foo.parents(42), - new Set(["even"]), - "parents 42" - ); - assert.deepStrictEqual( - foo.ancestors(23), - new Set(["odd", "number"]), - "ancestors 23" - ); - assert.deepStrictEqual( - foo.ancestors(42), - new Set(["even", "number"]), - "ancestors 42" - ); - assert.deepStrictEqual( - foo.rels(), - { - 23: new Set(["odd"]), - 42: new Set(["even"]), - odd: new Set(["number"]), - even: new Set(["number"]), - }, - "foo rels" - ); - assert.strictEqual(foo(23), "odd"); - assert.strictEqual(foo(42), "number"); - assert.ok(foo.callable(23)); - assert.ok(foo.callable(42)); - assert.ok(!foo.callable(66)); - assert.throws(() => foo(66), "no default"); - foo.add(DEFAULT, (x) => -x); - assert.strictEqual(foo(66), -66); - assert.deepStrictEqual( - foo.impls(), - new Set([DEFAULT, "odd", "even", "number", "23", "42"]) - ); - - const bar = defmulti((x) => x, { - 23: "odd", - 42: "even", - odd: ["number"], - even: new Set(["number"]), - }); - assert.deepStrictEqual( - bar.rels(), - { - 23: new Set(["odd"]), - 42: new Set(["even"]), - odd: new Set(["number"]), - even: new Set(["number"]), - }, - "bar rels" - ); - }, - - implementations: () => { - const foo = defmulti((x) => x.id); - const bar = defmulti((x) => x.id); - - implementations( - "a", - {}, - foo, - (x) => `foo: ${x.val}`, - bar, - (x) => `bar: ${x.val.toUpperCase()}` - ); - - assert.strictEqual(foo({ id: "a", val: "alice" }), "foo: alice"); - assert.strictEqual(bar({ id: "a", val: "alice" }), "bar: ALICE"); - }, - - dependencies: () => { - const a = defmulti((x) => x); - assert.deepStrictEqual([...a.dependencies()], []); - a.add("a", () => {}); - assert.deepStrictEqual([...a.dependencies()], [["a", undefined]]); - a.add("d", () => {}); - a.isa("b", "a"); - a.isa("c", "b"); - a.isa("e", "b"); - assert.deepStrictEqual( - new Set([...a.dependencies()]), - new Set([ - ["b", "a"], - ["c", "b"], - ["e", "b"], - ["a", undefined], - ["d", undefined], - ]) - ); - }, + flatten: () => { + const flatten = defmulti((x) => + Object.prototype.toString.call(x) + ); + assert.ok( + flatten.add( + "[object Array]", + (x, acc: any[]) => (x.forEach((y: any) => flatten(y, acc)), acc) + ) + ); + assert.ok( + flatten.add("[object Object]", (x, acc: any[]) => { + for (let k in x) flatten([k, x[k]], acc); + return acc; + }) + ); + assert.ok(flatten.add("[object Null]", (_, acc) => acc)); + assert.ok( + flatten.add( + DEFAULT, + (x, acc: any[]) => (acc.push(x.toString()), acc) + ) + ); + + assert.deepStrictEqual( + flatten([{ a: 1, b: ["foo", "bar", null, 42] }], []), + ["a", "1", "b", "foo", "bar", "42"] + ); + assert.ok(flatten.remove(DEFAULT)); + assert.ok(!flatten.remove(DEFAULT)); + assert.throws(() => + flatten([{ a: 1, b: ["foo", "bar", null, 42] }], []) + ); + }, + + sexpr: () => { + const exec = defmulti((x) => + Array.isArray(x) ? x[0] : typeof x + ); + assert.ok( + exec.add("+", ([_, ...args]) => + args.reduce((acc: number, n: any) => acc + exec(n), 0) + ) + ); + assert.ok( + exec.add("*", ([_, ...args]) => + args.reduce((acc: number, n: any) => acc * exec(n), 1) + ) + ); + assert.ok(exec.add("number", (x) => x)); + assert.ok( + exec.add(DEFAULT, (x) => { + throw new Error(`invalid expr: ${x}`); + }) + ); + + assert.strictEqual(exec(["+", ["*", 10, ["+", 1, 2, 3]], 6]), 66); + + setLogger(new ConsoleLogger("defmulti")); + assert.ok(exec.add("number", (x) => x * 2)); + assert.strictEqual( + exec(["+", ["*", 10, ["+", 1, 2, 3]], 6]), + (1 * 2 + 2 * 2 + 3 * 2) * 10 * 2 + 6 * 2 + ); + + assert.throws(() => exec("")); + }, + + apr: () => { + const apr = defmulti( + ({ type, balance }) => + `${type}-${ + balance < 1e4 ? "low" : balance < 5e4 ? "med" : "high" + }`, + {}, + { + "current-low": ({ balance }) => balance * 0.005, + "current-med": ({ balance }) => balance * 0.01, + "current-high": ({ balance }) => balance * 0.01, + "savings-low": ({ balance }) => balance * 0.01, + "savings-med": ({ balance }) => balance * 0.025, + "savings-high": ({ balance }) => balance * 0.035, + [DEFAULT]: (x: any) => { + throw new Error(`invalid account type: ${x.type}`); + }, + } + ); + + assert.strictEqual(~~apr({ type: "current", balance: 5000 }), 25); + assert.strictEqual(~~apr({ type: "current", balance: 10000 }), 100); + assert.strictEqual(~~apr({ type: "current", balance: 50000 }), 500); + assert.strictEqual(~~apr({ type: "savings", balance: 5000 }), 50); + assert.strictEqual(~~apr({ type: "savings", balance: 10000 }), 250); + assert.strictEqual(~~apr({ type: "savings", balance: 100000 }), 3500); + assert.throws(() => apr({ type: "isa", balance: 10000 })); + }, + + defmultiN: () => { + const foo = defmultiN({ + 0: () => "zero", + 1: (x) => `one: ${x}`, + 3: (x, y, z) => `three: ${x}, ${y}, ${z}`, + }); + + assert.strictEqual(foo(), "zero"); + assert.strictEqual(foo(23), "one: 23"); + assert.strictEqual(foo(1, 2, 3), "three: 1, 2, 3"); + assert.throws(() => foo(1, 2)); + }, + + isa: () => { + const foo = defmulti((x) => x); + foo.isa(23, "odd"); + foo.isa(42, "even"); + foo.isa("odd", "number"); + foo.isa("even", "number"); + foo.add("odd", () => "odd"); + foo.add("number", () => "number"); + assert.deepStrictEqual(foo.parents(23), new Set(["odd"]), "parents 23"); + assert.deepStrictEqual( + foo.parents(42), + new Set(["even"]), + "parents 42" + ); + assert.deepStrictEqual( + foo.ancestors(23), + new Set(["odd", "number"]), + "ancestors 23" + ); + assert.deepStrictEqual( + foo.ancestors(42), + new Set(["even", "number"]), + "ancestors 42" + ); + assert.deepStrictEqual( + foo.rels(), + { + 23: new Set(["odd"]), + 42: new Set(["even"]), + odd: new Set(["number"]), + even: new Set(["number"]), + }, + "foo rels" + ); + assert.strictEqual(foo(23), "odd"); + assert.strictEqual(foo(42), "number"); + assert.ok(foo.callable(23)); + assert.ok(foo.callable(42)); + assert.ok(!foo.callable(66)); + assert.throws(() => foo(66), "no default"); + foo.add(DEFAULT, (x) => -x); + assert.strictEqual(foo(66), -66); + assert.deepStrictEqual( + foo.impls(), + new Set([DEFAULT, "odd", "even", "number", "23", "42"]) + ); + + const bar = defmulti((x) => x, { + 23: "odd", + 42: "even", + odd: ["number"], + even: new Set(["number"]), + }); + assert.deepStrictEqual( + bar.rels(), + { + 23: new Set(["odd"]), + 42: new Set(["even"]), + odd: new Set(["number"]), + even: new Set(["number"]), + }, + "bar rels" + ); + }, + + implementations: () => { + const foo = defmulti((x) => x.id); + const bar = defmulti((x) => x.id); + + implementations( + "a", + {}, + foo, + (x) => `foo: ${x.val}`, + bar, + (x) => `bar: ${x.val.toUpperCase()}` + ); + + assert.strictEqual(foo({ id: "a", val: "alice" }), "foo: alice"); + assert.strictEqual(bar({ id: "a", val: "alice" }), "bar: ALICE"); + }, + + dependencies: () => { + const a = defmulti((x) => x); + assert.deepStrictEqual([...a.dependencies()], []); + a.add("a", () => {}); + assert.deepStrictEqual([...a.dependencies()], [["a", undefined]]); + a.add("d", () => {}); + a.isa("b", "a"); + a.isa("c", "b"); + a.isa("e", "b"); + assert.deepStrictEqual( + new Set([...a.dependencies()]), + new Set([ + ["b", "a"], + ["c", "b"], + ["e", "b"], + ["a", undefined], + ["d", undefined], + ]) + ); + }, }); diff --git a/packages/defmulti/tsconfig.json b/packages/defmulti/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/defmulti/tsconfig.json +++ b/packages/defmulti/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/dgraph-dot/api-extractor.json b/packages/dgraph-dot/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/dgraph-dot/api-extractor.json +++ b/packages/dgraph-dot/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/dgraph-dot/package.json b/packages/dgraph-dot/package.json index 042194350e..267c861859 100644 --- a/packages/dgraph-dot/package.json +++ b/packages/dgraph-dot/package.json @@ -1,78 +1,78 @@ { - "name": "@thi.ng/dgraph-dot", - "version": "2.1.12", - "description": "Customizable Graphviz DOT serialization for @thi.ng/dgraph", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dgraph-dot#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/dgraph": "^2.1.12", - "@thi.ng/dot": "^2.1.9" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "dependency", - "export", - "fileformat", - "graph", - "graphviz", - "typescript", - "visualization" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/dgraph", - "year": 2020 - } + "name": "@thi.ng/dgraph-dot", + "version": "2.1.12", + "description": "Customizable Graphviz DOT serialization for @thi.ng/dgraph", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dgraph-dot#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/dgraph": "^2.1.12", + "@thi.ng/dot": "^2.1.9" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "dependency", + "export", + "fileformat", + "graph", + "graphviz", + "typescript", + "visualization" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/dgraph", + "year": 2020 + } } diff --git a/packages/dgraph-dot/src/index.ts b/packages/dgraph-dot/src/index.ts index 2ac22bed45..dad48b648d 100644 --- a/packages/dgraph-dot/src/index.ts +++ b/packages/dgraph-dot/src/index.ts @@ -3,27 +3,27 @@ import type { DGraph } from "@thi.ng/dgraph"; import { Edge, Graph, GraphAttribs, Node, serializeGraph } from "@thi.ng/dot"; export interface DGraphDotOpts { - /** - * Graph visualization attributes. See - * {@link @thi.ng/dot#GraphAttribs} for details. - */ - attribs?: Partial; - /** - * Required mapping function applied to each node in the dependency - * graph. Must return a unique ID per node. - */ - id: Fn; - /** - * Optional label function applied to each node to provide a - * potentially more human readable string than that of `id`. - * Not used if `spec` option is given. - */ - label?: Fn; - /** - * Optional function applied to each node to provide an object of - * visualization options. See {@link @thi.ng/dot#Node} for details. - */ - spec?: Fn>; + /** + * Graph visualization attributes. See + * {@link @thi.ng/dot#GraphAttribs} for details. + */ + attribs?: Partial; + /** + * Required mapping function applied to each node in the dependency + * graph. Must return a unique ID per node. + */ + id: Fn; + /** + * Optional label function applied to each node to provide a + * potentially more human readable string than that of `id`. + * Not used if `spec` option is given. + */ + label?: Fn; + /** + * Optional function applied to each node to provide an object of + * visualization options. See {@link @thi.ng/dot#Node} for details. + */ + spec?: Fn>; } /** @@ -35,22 +35,22 @@ export interface DGraphDotOpts { * @param opts - */ export const toDot = (src: DGraph, opts: DGraphDotOpts) => { - const nodes: IObjectOf> = {}; - const edges: Edge[] = []; - const label = opts.label || opts.id; - const spec = opts.spec; - for (let n of src.nodes()) { - const id = opts.id(n); - nodes[id] = spec ? spec(n) : { label: label(n) }; - for (let d of src.immediateDependencies(n)) { - edges.push({ src: id, dest: opts.id(d) }); - } - } - const graph: Graph = { - attribs: opts.attribs, - directed: true, - edges, - nodes, - }; - return serializeGraph(graph); + const nodes: IObjectOf> = {}; + const edges: Edge[] = []; + const label = opts.label || opts.id; + const spec = opts.spec; + for (let n of src.nodes()) { + const id = opts.id(n); + nodes[id] = spec ? spec(n) : { label: label(n) }; + for (let d of src.immediateDependencies(n)) { + edges.push({ src: id, dest: opts.id(d) }); + } + } + const graph: Graph = { + attribs: opts.attribs, + directed: true, + edges, + nodes, + }; + return serializeGraph(graph); }; diff --git a/packages/dgraph-dot/test/index.ts b/packages/dgraph-dot/test/index.ts index df82ff05a0..d5696aae33 100644 --- a/packages/dgraph-dot/test/index.ts +++ b/packages/dgraph-dot/test/index.ts @@ -1,33 +1,33 @@ import { defDGraph } from "@thi.ng/dgraph"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { toDot } from "../src/index.js" +import { toDot } from "../src/index.js"; group("dgraph-dot", { - basic: () => { - const g = defDGraph([ - ["a", "b"], - ["a", "c"], - ["b", "d"], - ["c", "d"], - ["c", "e"], - ]); - const dot = toDot(g, { - id: (node) => node, - attribs: { - node: { - style: "filled", - fillcolor: "black", - fontcolor: "white", - }, - edge: { - arrowsize: 0.75, - }, - }, - }); - assert.strictEqual( - dot, - `digraph g { + basic: () => { + const g = defDGraph([ + ["a", "b"], + ["a", "c"], + ["b", "d"], + ["c", "d"], + ["c", "e"], + ]); + const dot = toDot(g, { + id: (node) => node, + attribs: { + node: { + style: "filled", + fillcolor: "black", + fontcolor: "white", + }, + edge: { + arrowsize: 0.75, + }, + }, + }); + assert.strictEqual( + dot, + `digraph g { node[style="filled", fillcolor="black", fontcolor="white"]; edge[arrowsize="0.75"]; "b"[label="b"]; @@ -41,6 +41,6 @@ edge[arrowsize="0.75"]; "a" -> "b"; "a" -> "c"; }` - ); - }, + ); + }, }); diff --git a/packages/dgraph-dot/tsconfig.json b/packages/dgraph-dot/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/dgraph-dot/tsconfig.json +++ b/packages/dgraph-dot/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/dgraph/api-extractor.json b/packages/dgraph/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/dgraph/api-extractor.json +++ b/packages/dgraph/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/dgraph/package.json b/packages/dgraph/package.json index bf423f0548..bafc1409a4 100644 --- a/packages/dgraph/package.json +++ b/packages/dgraph/package.json @@ -1,85 +1,85 @@ { - "name": "@thi.ng/dgraph", - "version": "2.1.12", - "description": "Type-agnostic directed acyclic graph (DAG) & graph operations", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dgraph#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/associative": "^6.2.0", - "@thi.ng/equiv": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/transducers": "^8.3.7" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "adjacency", - "dag", - "datastructure", - "dependency", - "graph", - "query", - "sort", - "topology", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "related": [ - "adjacency", - "dot", - "system" - ], - "year": 2015 - } + "name": "@thi.ng/dgraph", + "version": "2.1.12", + "description": "Type-agnostic directed acyclic graph (DAG) & graph operations", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dgraph#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/associative": "^6.2.0", + "@thi.ng/equiv": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/transducers": "^8.3.7" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "adjacency", + "dag", + "datastructure", + "dependency", + "graph", + "query", + "sort", + "topology", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "related": [ + "adjacency", + "dot", + "system" + ], + "year": 2015 + } } diff --git a/packages/dgraph/src/index.ts b/packages/dgraph/src/index.ts index 6d94af8579..a2d00faf9a 100644 --- a/packages/dgraph/src/index.ts +++ b/packages/dgraph/src/index.ts @@ -12,164 +12,164 @@ import { reduce, reducer } from "@thi.ng/transducers/reduce"; * each pair is a `[node, parent]` tuple, or using `[node, null]` to merely * register a node in the graph (without dependencies). * - * @param edges - + * @param edges - */ export const defDGraph = (edges?: Iterable>>) => - new DGraph(edges); + new DGraph(edges); export class DGraph implements Iterable, ICopy> { - dependencies: EquivMap>; - dependents: EquivMap>; - - constructor(edges?: Iterable>>) { - this.dependencies = new EquivMap>(); - this.dependents = new EquivMap>(); - if (edges) { - for (let [a, b] of edges) { - b != null ? this.addDependency(a, b) : this.addNode(a); - } - } - } - - *[Symbol.iterator]() { - yield* this.sort(); - } - - get [Symbol.species]() { - return DGraph; - } - - copy() { - const g = new DGraph(); - for (let e of this.dependencies) { - g.dependencies.set(e[0], e[1].copy()); - } - for (let e of this.dependents) { - g.dependents.set(e[0], e[1].copy()); - } - return g; - } - - addNode(node: T) { - !this.dependencies.has(node) && - this.dependencies.set(node, new ArraySet()); - return this; - } - - addDependency(node: T, dep: T) { - if (equiv(node, dep) || this.depends(dep, node)) { - illegalArgs(`Circular dependency between: ${node} & ${dep}`); - } - let deps = this.dependencies.get(node); - this.dependencies.set(node, deps ? deps.add(dep) : new ArraySet([dep])); - deps = this.dependents.get(dep); - this.dependents.set(dep, deps ? deps.add(node) : new ArraySet([node])); - return this; - } - - addDependencies(node: T, deps: Iterable) { - for (let d of deps) { - this.addDependency(node, d); - } - } - - removeEdge(node: T, dep: T) { - let deps = this.dependencies.get(node); - if (deps) { - deps.delete(dep); - } - deps = this.dependents.get(dep); - if (deps) { - deps.delete(node); - } - return this; - } - - removeNode(x: T) { - this.dependencies.delete(x); - return this; - } - - depends(x: T, y: T) { - return this.transitiveDependencies(x).has(y); - } - - dependent(x: T, y: T) { - return this.transitiveDependents(x).has(y); - } - - immediateDependencies(x: T): Set { - return this.dependencies.get(x) || new ArraySet(); - } - - immediateDependents(x: T): Set { - return this.dependents.get(x) || new ArraySet(); - } - - isLeaf(x: T) { - return this.immediateDependents(x).size === 0; - } - - isRoot(x: T) { - return this.immediateDependencies(x).size === 0; - } - - nodes(): Set { - return union( - new ArraySet(this.dependencies.keys()), - new ArraySet(this.dependents.keys()) - ); - } - - leaves() { - return filter((node: T) => this.isLeaf(node), this.nodes()); - } - - roots() { - return filter((node: T) => this.isRoot(node), this.nodes()); - } - - transitiveDependencies(x: T) { - return transitive(this.dependencies, x); - } - - transitiveDependents(x: T) { - return transitive(this.dependents, x); - } - - sort() { - const sorted: T[] = []; - const g = this.copy(); - let queue = new ArraySet(g.leaves()); - while (true) { - if (!queue.size) { - return sorted.reverse(); - } - const node = queue.first()!; - queue.delete(node); - for (let d of [...g.immediateDependencies(node)]) { - g.removeEdge(node, d); - if (g.isLeaf(d)) { - queue.add(d); - } - } - sorted.push(node); - g.removeNode(node); - } - } + dependencies: EquivMap>; + dependents: EquivMap>; + + constructor(edges?: Iterable>>) { + this.dependencies = new EquivMap>(); + this.dependents = new EquivMap>(); + if (edges) { + for (let [a, b] of edges) { + b != null ? this.addDependency(a, b) : this.addNode(a); + } + } + } + + *[Symbol.iterator]() { + yield* this.sort(); + } + + get [Symbol.species]() { + return DGraph; + } + + copy() { + const g = new DGraph(); + for (let e of this.dependencies) { + g.dependencies.set(e[0], e[1].copy()); + } + for (let e of this.dependents) { + g.dependents.set(e[0], e[1].copy()); + } + return g; + } + + addNode(node: T) { + !this.dependencies.has(node) && + this.dependencies.set(node, new ArraySet()); + return this; + } + + addDependency(node: T, dep: T) { + if (equiv(node, dep) || this.depends(dep, node)) { + illegalArgs(`Circular dependency between: ${node} & ${dep}`); + } + let deps = this.dependencies.get(node); + this.dependencies.set(node, deps ? deps.add(dep) : new ArraySet([dep])); + deps = this.dependents.get(dep); + this.dependents.set(dep, deps ? deps.add(node) : new ArraySet([node])); + return this; + } + + addDependencies(node: T, deps: Iterable) { + for (let d of deps) { + this.addDependency(node, d); + } + } + + removeEdge(node: T, dep: T) { + let deps = this.dependencies.get(node); + if (deps) { + deps.delete(dep); + } + deps = this.dependents.get(dep); + if (deps) { + deps.delete(node); + } + return this; + } + + removeNode(x: T) { + this.dependencies.delete(x); + return this; + } + + depends(x: T, y: T) { + return this.transitiveDependencies(x).has(y); + } + + dependent(x: T, y: T) { + return this.transitiveDependents(x).has(y); + } + + immediateDependencies(x: T): Set { + return this.dependencies.get(x) || new ArraySet(); + } + + immediateDependents(x: T): Set { + return this.dependents.get(x) || new ArraySet(); + } + + isLeaf(x: T) { + return this.immediateDependents(x).size === 0; + } + + isRoot(x: T) { + return this.immediateDependencies(x).size === 0; + } + + nodes(): Set { + return union( + new ArraySet(this.dependencies.keys()), + new ArraySet(this.dependents.keys()) + ); + } + + leaves() { + return filter((node: T) => this.isLeaf(node), this.nodes()); + } + + roots() { + return filter((node: T) => this.isRoot(node), this.nodes()); + } + + transitiveDependencies(x: T) { + return transitive(this.dependencies, x); + } + + transitiveDependents(x: T) { + return transitive(this.dependents, x); + } + + sort() { + const sorted: T[] = []; + const g = this.copy(); + let queue = new ArraySet(g.leaves()); + while (true) { + if (!queue.size) { + return sorted.reverse(); + } + const node = queue.first()!; + queue.delete(node); + for (let d of [...g.immediateDependencies(node)]) { + g.removeEdge(node, d); + if (g.isLeaf(d)) { + queue.add(d); + } + } + sorted.push(node); + g.removeNode(node); + } + } } const transitive = (nodes: EquivMap>, x: T): Set => { - const deps: ArraySet = nodes.get(x)!; - if (deps) { - return reduce( - reducer( - null, - (acc, k: T) => >union(acc, transitive(nodes, k)) - ), - deps, - deps - ); - } - return new ArraySet(); + const deps: ArraySet = nodes.get(x)!; + if (deps) { + return reduce( + reducer( + null, + (acc, k: T) => >union(acc, transitive(nodes, k)) + ), + deps, + deps + ); + } + return new ArraySet(); }; diff --git a/packages/dgraph/test/index.ts b/packages/dgraph/test/index.ts index 50047477f4..ee92c47d0d 100644 --- a/packages/dgraph/test/index.ts +++ b/packages/dgraph/test/index.ts @@ -1,94 +1,94 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { DGraph } from "../src/index.js" +import { DGraph } from "../src/index.js"; let g: DGraph; group( - "dgraph", - { - depends: () => { - assert.ok(g.depends([1, 2], [10, 20])); - assert.ok(!g.depends([10, 20], [1, 2])); - }, + "dgraph", + { + depends: () => { + assert.ok(g.depends([1, 2], [10, 20])); + assert.ok(!g.depends([10, 20], [1, 2])); + }, - dependent: () => { - assert.ok(g.dependent([10, 20], [1, 2])); - assert.ok(!g.dependent([1, 2], [10, 20])); - }, + dependent: () => { + assert.ok(g.dependent([10, 20], [1, 2])); + assert.ok(!g.dependent([1, 2], [10, 20])); + }, - isLeaf: () => { - assert.ok(g.isLeaf([1, 2])); - assert.ok(!g.isLeaf([10, 20])); - assert.ok(!g.isLeaf([3, 4])); - }, + isLeaf: () => { + assert.ok(g.isLeaf([1, 2])); + assert.ok(!g.isLeaf([10, 20])); + assert.ok(!g.isLeaf([3, 4])); + }, - isRoot: () => { - assert.ok(g.isRoot([10, 20])); - assert.ok(g.isRoot([30, 40])); - assert.ok(!g.isRoot([3, 4])); - }, + isRoot: () => { + assert.ok(g.isRoot([10, 20])); + assert.ok(g.isRoot([30, 40])); + assert.ok(!g.isRoot([3, 4])); + }, - cyclic: () => { - assert.throws(() => g.addDependency([10, 20], [1, 2])); - assert.throws(() => g.addDependency([1, 2], [1, 2])); - }, + cyclic: () => { + assert.throws(() => g.addDependency([10, 20], [1, 2])); + assert.throws(() => g.addDependency([1, 2], [1, 2])); + }, - sort: () => { - assert.deepStrictEqual(g.sort(), [ - [30, 40], - [3, 4], - [10, 20], - [1, 2], - ]); - g.addDependency([30, 40], [50, 60]); - assert.deepStrictEqual(g.sort(), [ - [50, 60], - [30, 40], - [3, 4], - [10, 20], - [1, 2], - ]); - }, + sort: () => { + assert.deepStrictEqual(g.sort(), [ + [30, 40], + [3, 4], + [10, 20], + [1, 2], + ]); + g.addDependency([30, 40], [50, 60]); + assert.deepStrictEqual(g.sort(), [ + [50, 60], + [30, 40], + [3, 4], + [10, 20], + [1, 2], + ]); + }, - iterator: () => { - assert.deepStrictEqual( - [...g], - [ - [30, 40], - [3, 4], - [10, 20], - [1, 2], - ] - ); - assert.deepStrictEqual( - [...g], - [ - [30, 40], - [3, 4], - [10, 20], - [1, 2], - ] - ); - }, + iterator: () => { + assert.deepStrictEqual( + [...g], + [ + [30, 40], + [3, 4], + [10, 20], + [1, 2], + ] + ); + assert.deepStrictEqual( + [...g], + [ + [30, 40], + [3, 4], + [10, 20], + [1, 2], + ] + ); + }, - "separate nodes": () => { - g = new DGraph(); - g.addNode([1, 2]); - g.addNode([3, 4]); - g.addNode([3, 4]); - assert.deepStrictEqual(g.sort(), [ - [3, 4], - [1, 2], - ]); - }, - }, - { - beforeEach: () => { - g = new DGraph(); - g.addDependency([1, 2], [10, 20]); - g.addDependency([3, 4], [30, 40]); - g.addDependency([1, 2], [3, 4]); - }, - } + "separate nodes": () => { + g = new DGraph(); + g.addNode([1, 2]); + g.addNode([3, 4]); + g.addNode([3, 4]); + assert.deepStrictEqual(g.sort(), [ + [3, 4], + [1, 2], + ]); + }, + }, + { + beforeEach: () => { + g = new DGraph(); + g.addDependency([1, 2], [10, 20]); + g.addDependency([3, 4], [30, 40]); + g.addDependency([1, 2], [3, 4]); + }, + } ); diff --git a/packages/dgraph/tsconfig.json b/packages/dgraph/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/dgraph/tsconfig.json +++ b/packages/dgraph/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/diff/api-extractor.json b/packages/diff/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/diff/api-extractor.json +++ b/packages/diff/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/diff/package.json b/packages/diff/package.json index 2720be7c44..bef4a77658 100644 --- a/packages/diff/package.json +++ b/packages/diff/package.json @@ -1,79 +1,79 @@ { - "name": "@thi.ng/diff", - "version": "5.1.8", - "description": "Customizable diff implementations for arrays (sequential) & objects (associative), with or without linear edit logs", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/diff#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/equiv": "^2.1.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "array", - "diff", - "editlog", - "object", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./array": { - "default": "./array.js" - }, - "./object": { - "default": "./object.js" - } - } + "name": "@thi.ng/diff", + "version": "5.1.8", + "description": "Customizable diff implementations for arrays (sequential) & objects (associative), with or without linear edit logs", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/diff#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/equiv": "^2.1.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "array", + "diff", + "editlog", + "object", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./array": { + "default": "./array.js" + }, + "./object": { + "default": "./object.js" + } + } } diff --git a/packages/diff/src/api.ts b/packages/diff/src/api.ts index 4bc2b9a5b5..921913addd 100644 --- a/packages/diff/src/api.ts +++ b/packages/diff/src/api.ts @@ -1,26 +1,26 @@ import type { IObjectOf } from "@thi.ng/api"; export type DiffMode = - | "only-distance" - | "only-distance-linear" - | "minimal" - | "full"; + | "only-distance" + | "only-distance-linear" + | "minimal" + | "full"; export type DiffKeyMap = IObjectOf; export interface ArrayDiff { - distance: number; - adds?: DiffKeyMap; - dels?: DiffKeyMap; - const?: DiffKeyMap; - linear?: EditLog; + distance: number; + adds?: DiffKeyMap; + dels?: DiffKeyMap; + const?: DiffKeyMap; + linear?: EditLog; } export interface ObjectDiff { - distance: number; - adds?: string[]; - dels?: string[]; - edits?: EditLog; + distance: number; + adds?: string[]; + dels?: string[]; + edits?: EditLog; } export type EditLog = (K | T)[]; diff --git a/packages/diff/src/array.ts b/packages/diff/src/array.ts index 5fbd672e3b..d0c9ebca57 100644 --- a/packages/diff/src/array.ts +++ b/packages/diff/src/array.ts @@ -9,44 +9,44 @@ let _cachedEPC: number[] = []; let _cachedPathPos: number[] = []; const cachedFP = (size: number) => - _cachedFP && _cachedFP.length >= size - ? _cachedFP - : (_cachedFP = new Int32Array(size)); + _cachedFP && _cachedFP.length >= size + ? _cachedFP + : (_cachedFP = new Int32Array(size)); const cachedPath = (size: number) => - _cachedPath && _cachedPath.length >= size - ? _cachedPath - : (_cachedPath = new Int32Array(size)); + _cachedPath && _cachedPath.length >= size + ? _cachedPath + : (_cachedPath = new Int32Array(size)); export const clearCache = () => { - _cachedFP = _cachedPath = undefined; - _cachedEPC.length = _cachedPathPos.length = 0; + _cachedFP = _cachedPath = undefined; + _cachedEPC.length = _cachedPathPos.length = 0; }; const simpleDiff = ( - state: ArrayDiff, - src: ArrayLike, - key: "adds" | "dels", - logDir: number, - mode: DiffMode + state: ArrayDiff, + src: ArrayLike, + key: "adds" | "dels", + logDir: number, + mode: DiffMode ) => { - const n = src.length; - const linear = >state.linear; - state.distance = n; - if (mode !== "only-distance") { - for (let i = 0, j = 0; i < n; i++, j += 3) { - linear[j] = logDir; - linear[j + 1] = i; - linear[j + 2] = src[i]; - } - if (mode === "full") { - const _state = >state[key]; - for (let i = 0; i < n; i++) { - _state[i] = src[i]; - } - } - } - return state; + const n = src.length; + const linear = >state.linear; + state.distance = n; + if (mode !== "only-distance") { + for (let i = 0, j = 0; i < n; i++, j += 3) { + linear[j] = logDir; + linear[j + 1] = i; + linear[j + 2] = src[i]; + } + if (mode === "full") { + const _state = >state[key]; + for (let i = 0; i < n; i++) { + _state[i] = src[i]; + } + } + } + return state; }; /** @@ -67,192 +67,192 @@ const simpleDiff = ( * @param equiv - equality predicate function */ export const diffArray = ( - a: ArrayLike | undefined | null, - b: ArrayLike | undefined | null, - mode: DiffMode = "full", - equiv = _equiv + a: ArrayLike | undefined | null, + b: ArrayLike | undefined | null, + mode: DiffMode = "full", + equiv = _equiv ) => { - const state = >{ - distance: 0, - adds: {}, - dels: {}, - const: {}, - linear: [], - }; + const state = >{ + distance: 0, + adds: {}, + dels: {}, + const: {}, + linear: [], + }; - if (a === b || (a == null && b == null)) { - return state; - } else if (a == null || a.length === 0) { - return simpleDiff(state, b!, "adds", 1, mode); - } else if (b == null || b.length === 0) { - return simpleDiff(state, a, "dels", -1, mode); - } + if (a === b || (a == null && b == null)) { + return state; + } else if (a == null || a.length === 0) { + return simpleDiff(state, b!, "adds", 1, mode); + } else if (b == null || b.length === 0) { + return simpleDiff(state, a, "dels", -1, mode); + } - const reverse = a.length >= b.length; - let _a: ArrayLike, _b: ArrayLike, na: number, nb: number; + const reverse = a.length >= b.length; + let _a: ArrayLike, _b: ArrayLike, na: number, nb: number; - if (reverse) { - _a = b; - _b = a; - } else { - _a = a; - _b = b; - } - na = _a.length; - nb = _b.length; + if (reverse) { + _a = b; + _b = a; + } else { + _a = a; + _b = b; + } + na = _a.length; + nb = _b.length; - const offset = na + 1; - const delta = nb - na; - const doff = delta + offset; - const size = na + nb + 3; - const path = cachedPath(size).fill(-1, 0, size); - const fp = cachedFP(size).fill(-1, 0, size); - const epc = _cachedEPC; - const pathPos = _cachedPathPos; + const offset = na + 1; + const delta = nb - na; + const doff = delta + offset; + const size = na + nb + 3; + const path = cachedPath(size).fill(-1, 0, size); + const fp = cachedFP(size).fill(-1, 0, size); + const epc = _cachedEPC; + const pathPos = _cachedPathPos; - const snake: FnU3 = (k, p, pp) => { - let r: number, y: number; - if (p > pp) { - r = path[k - 1]; - y = p; - } else { - r = path[k + 1]; - y = pp; - } - let x = y - (k - offset); - while (x < na && y < nb && equiv(_a[x], _b[y])) { - x++; - y++; - } - path[k] = pathPos.length / 3; - pathPos.push(x, y, r); - fp[k] = y; - }; + const snake: FnU3 = (k, p, pp) => { + let r: number, y: number; + if (p > pp) { + r = path[k - 1]; + y = p; + } else { + r = path[k + 1]; + y = pp; + } + let x = y - (k - offset); + while (x < na && y < nb && equiv(_a[x], _b[y])) { + x++; + y++; + } + path[k] = pathPos.length / 3; + pathPos.push(x, y, r); + fp[k] = y; + }; - let p = -1; - let k: number; - do { - p++; - for (k = -p + offset; k < doff; k++) { - snake(k, fp[k - 1] + 1, fp[k + 1]); - } - for (k = doff + p; k > doff; k--) { - snake(k, fp[k - 1] + 1, fp[k + 1]); - } - snake(doff, fp[doff - 1] + 1, fp[doff + 1]); - } while (fp[doff] !== nb); + let p = -1; + let k: number; + do { + p++; + for (k = -p + offset; k < doff; k++) { + snake(k, fp[k - 1] + 1, fp[k + 1]); + } + for (k = doff + p; k > doff; k--) { + snake(k, fp[k - 1] + 1, fp[k + 1]); + } + snake(doff, fp[doff - 1] + 1, fp[doff + 1]); + } while (fp[doff] !== nb); - state.distance = delta + 2 * p; + state.distance = delta + 2 * p; - if (mode !== "only-distance") { - p = path[doff] * 3; - while (p >= 0) { - epc.push(p); - p = pathPos[p + 2] * 3; - } + if (mode !== "only-distance") { + p = path[doff] * 3; + while (p >= 0) { + epc.push(p); + p = pathPos[p + 2] * 3; + } - if (mode === "full") { - buildFullLog(epc, pathPos, state, _a, _b, reverse); - } else { - buildLinearLog( - epc, - pathPos, - state, - _a, - _b, - reverse, - mode === "only-distance-linear" - ); - } - } - epc.length = 0; - pathPos.length = 0; - return state; + if (mode === "full") { + buildFullLog(epc, pathPos, state, _a, _b, reverse); + } else { + buildLinearLog( + epc, + pathPos, + state, + _a, + _b, + reverse, + mode === "only-distance-linear" + ); + } + } + epc.length = 0; + pathPos.length = 0; + return state; }; const buildFullLog = ( - epc: any[], - pathPos: any[], - state: ArrayDiff, - a: ArrayLike, - b: ArrayLike, - reverse: boolean + epc: any[], + pathPos: any[], + state: ArrayDiff, + a: ArrayLike, + b: ArrayLike, + reverse: boolean ) => { - const linear = >state.linear; - const _const = >state.const; - let i = epc.length; - let px = 0; - let py = 0; - let adds: DiffKeyMap; - let dels: DiffKeyMap; - let aID: number; - let dID: number; - if (reverse) { - adds = >state.dels; - dels = >state.adds; - aID = -1; - dID = 1; - } else { - adds = >state.adds; - dels = >state.dels; - aID = 1; - dID = -1; - } - for (; i-- > 0; ) { - const e = epc[i]; - const ppx = pathPos[e]; - const ppy = pathPos[e + 1]; - const d = ppy - ppx; - while (px < ppx || py < ppy) { - const dp = py - px; - if (d > dp) { - linear.push(aID, py, (adds[py] = b[py])); - py++; - } else if (d < dp) { - linear.push(dID, px, (dels[px] = a[px])); - px++; - } else { - linear.push(0, px, (_const[px] = a[px])); - px++; - py++; - } - } - } + const linear = >state.linear; + const _const = >state.const; + let i = epc.length; + let px = 0; + let py = 0; + let adds: DiffKeyMap; + let dels: DiffKeyMap; + let aID: number; + let dID: number; + if (reverse) { + adds = >state.dels; + dels = >state.adds; + aID = -1; + dID = 1; + } else { + adds = >state.adds; + dels = >state.dels; + aID = 1; + dID = -1; + } + for (; i-- > 0; ) { + const e = epc[i]; + const ppx = pathPos[e]; + const ppy = pathPos[e + 1]; + const d = ppy - ppx; + while (px < ppx || py < ppy) { + const dp = py - px; + if (d > dp) { + linear.push(aID, py, (adds[py] = b[py])); + py++; + } else if (d < dp) { + linear.push(dID, px, (dels[px] = a[px])); + px++; + } else { + linear.push(0, px, (_const[px] = a[px])); + px++; + py++; + } + } + } }; const buildLinearLog = ( - epc: any[], - pathPos: any[], - state: ArrayDiff, - a: ArrayLike, - b: ArrayLike, - reverse: boolean, - inclConst: boolean + epc: any[], + pathPos: any[], + state: ArrayDiff, + a: ArrayLike, + b: ArrayLike, + reverse: boolean, + inclConst: boolean ) => { - const linear = >state.linear; - const aID = reverse ? -1 : 1; - const dID = reverse ? 1 : -1; - let i = epc.length, - px = 0, - py = 0; - for (; i-- > 0; ) { - const e = epc[i]; - const ppx = pathPos[e]; - const ppy = pathPos[e + 1]; - const d = ppy - ppx; - while (px < ppx || py < ppy) { - const dp = py - px; - if (d > dp) { - linear.push(aID, py, b[py]); - py++; - } else if (d < dp) { - linear.push(dID, px, a[px]); - px++; - } else { - inclConst && linear.push(0, px, a[px]); - px++; - py++; - } - } - } + const linear = >state.linear; + const aID = reverse ? -1 : 1; + const dID = reverse ? 1 : -1; + let i = epc.length, + px = 0, + py = 0; + for (; i-- > 0; ) { + const e = epc[i]; + const ppx = pathPos[e]; + const ppy = pathPos[e + 1]; + const d = ppy - ppx; + while (px < ppx || py < ppy) { + const dp = py - px; + if (d > dp) { + linear.push(aID, py, b[py]); + py++; + } else if (d < dp) { + linear.push(dID, px, a[px]); + px++; + } else { + inclConst && linear.push(0, px, a[px]); + px++; + py++; + } + } + } }; diff --git a/packages/diff/src/object.ts b/packages/diff/src/object.ts index 5c2f64c9ca..e41e737872 100644 --- a/packages/diff/src/object.ts +++ b/packages/diff/src/object.ts @@ -3,61 +3,61 @@ import { equiv } from "@thi.ng/equiv"; import type { ObjectDiff } from "./api.js"; export const diffObject = ( - a: IObjectOf | undefined | null, - b: IObjectOf | undefined | null, - mode: "full" | "only-distance" = "full", - _equiv: Predicate2 = equiv + a: IObjectOf | undefined | null, + b: IObjectOf | undefined | null, + mode: "full" | "only-distance" = "full", + _equiv: Predicate2 = equiv ): ObjectDiff => - a === b - ? { distance: 0 } - : mode === "only-distance" - ? diffObjectDist(a, b, _equiv) - : diffObjectFull(a, b, _equiv); + a === b + ? { distance: 0 } + : mode === "only-distance" + ? diffObjectDist(a, b, _equiv) + : diffObjectFull(a, b, _equiv); const diffObjectDist = ( - a: IObjectOf | undefined | null, - b: IObjectOf | undefined | null, - _equiv: Predicate2 + a: IObjectOf | undefined | null, + b: IObjectOf | undefined | null, + _equiv: Predicate2 ) => { - if (!a) a = {}; - if (!b) b = {}; - let d = 0; - for (let k in a) { - const vb = b[k]; - (vb === undefined || !_equiv(a[k], vb)) && d++; - } - for (let k in b) { - !(k in a) && d++; - } - return { distance: d }; + if (!a) a = {}; + if (!b) b = {}; + let d = 0; + for (let k in a) { + const vb = b[k]; + (vb === undefined || !_equiv(a[k], vb)) && d++; + } + for (let k in b) { + !(k in a) && d++; + } + return { distance: d }; }; const diffObjectFull = ( - a: IObjectOf | undefined | null, - b: IObjectOf | undefined | null, - _equiv: Predicate2 + a: IObjectOf | undefined | null, + b: IObjectOf | undefined | null, + _equiv: Predicate2 ) => { - if (!a) a = {}; - if (!b) b = {}; - let d = 0; - const adds = []; - const dels = []; - const edits = []; - for (let k in a) { - const vb = b[k]; - if (vb === undefined) { - dels.push(k); - d++; - } else if (!_equiv(a[k], vb)) { - edits.push(k, vb); - d++; - } - } - for (let k in b) { - if (!(k in a)) { - adds.push(k); - d++; - } - } - return { distance: d, adds, dels, edits }; + if (!a) a = {}; + if (!b) b = {}; + let d = 0; + const adds = []; + const dels = []; + const edits = []; + for (let k in a) { + const vb = b[k]; + if (vb === undefined) { + dels.push(k); + d++; + } else if (!_equiv(a[k], vb)) { + edits.push(k, vb); + d++; + } + } + for (let k in b) { + if (!(k in a)) { + adds.push(k); + d++; + } + } + return { distance: d, adds, dels, edits }; }; diff --git a/packages/diff/test/array.ts b/packages/diff/test/array.ts index d66ff99ded..292d423055 100644 --- a/packages/diff/test/array.ts +++ b/packages/diff/test/array.ts @@ -1,84 +1,84 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { ArrayDiff, diffArray } from "../src/index.js" +import { ArrayDiff, diffArray } from "../src/index.js"; const state = >{ - distance: 0, - adds: {}, - dels: {}, - const: {}, - linear: [], + distance: 0, + adds: {}, + dels: {}, + const: {}, + linear: [], }; group("array", { - "simple (null,null)": () => { - assert.deepStrictEqual(diffArray(null, null), state); - }, + "simple (null,null)": () => { + assert.deepStrictEqual(diffArray(null, null), state); + }, - "simple (null,arr)": () => { - assert.deepStrictEqual(diffArray(null, [1, 2, 3]), >{ - ...state, - distance: 3, - adds: { 0: 1, 1: 2, 2: 3 }, - linear: [1, 0, 1, 1, 1, 2, 1, 2, 3], - }); - }, + "simple (null,arr)": () => { + assert.deepStrictEqual(diffArray(null, [1, 2, 3]), >{ + ...state, + distance: 3, + adds: { 0: 1, 1: 2, 2: 3 }, + linear: [1, 0, 1, 1, 1, 2, 1, 2, 3], + }); + }, - "simple (arr, null)": () => { - assert.deepStrictEqual(diffArray([1, 2, 3], null), >{ - ...state, - distance: 3, - dels: { 0: 1, 1: 2, 2: 3 }, - linear: [-1, 0, 1, -1, 1, 2, -1, 2, 3], - }); - }, + "simple (arr, null)": () => { + assert.deepStrictEqual(diffArray([1, 2, 3], null), >{ + ...state, + distance: 3, + dels: { 0: 1, 1: 2, 2: 3 }, + linear: [-1, 0, 1, -1, 1, 2, -1, 2, 3], + }); + }, - "diff last": () => { - assert.deepStrictEqual(diffArray([1, 2, 3], [1, 2, 4]), < - ArrayDiff - >{ - distance: 2, - adds: { 2: 4 }, - dels: { 2: 3 }, - const: { 0: 1, 1: 2 }, - linear: [0, 0, 1, 0, 1, 2, -1, 2, 3, 1, 2, 4], - }); - }, + "diff last": () => { + assert.deepStrictEqual(diffArray([1, 2, 3], [1, 2, 4]), < + ArrayDiff + >{ + distance: 2, + adds: { 2: 4 }, + dels: { 2: 3 }, + const: { 0: 1, 1: 2 }, + linear: [0, 0, 1, 0, 1, 2, -1, 2, 3, 1, 2, 4], + }); + }, - "diff 2nd last": () => { - assert.deepStrictEqual(diffArray([1, 2, 3, 4], [1, 2, 5, 4]), < - ArrayDiff - >{ - distance: 2, - adds: { 2: 5 }, - dels: { 2: 3 }, - const: { 0: 1, 1: 2, 3: 4 }, - linear: [0, 0, 1, 0, 1, 2, -1, 2, 3, 1, 2, 5, 0, 3, 4], - }); - }, + "diff 2nd last": () => { + assert.deepStrictEqual(diffArray([1, 2, 3, 4], [1, 2, 5, 4]), < + ArrayDiff + >{ + distance: 2, + adds: { 2: 5 }, + dels: { 2: 3 }, + const: { 0: 1, 1: 2, 3: 4 }, + linear: [0, 0, 1, 0, 1, 2, -1, 2, 3, 1, 2, 5, 0, 3, 4], + }); + }, - "diff insert 2nd last": () => { - assert.deepStrictEqual(diffArray([1, 2, 3, 4], [1, 2, 3, 5, 4]), < - ArrayDiff - >{ - distance: 1, - adds: { 3: 5 }, - dels: {}, - const: { 0: 1, 1: 2, 2: 3, 3: 4 }, - linear: [0, 0, 1, 0, 1, 2, 0, 2, 3, 1, 3, 5, 0, 3, 4], - }); - }, + "diff insert 2nd last": () => { + assert.deepStrictEqual(diffArray([1, 2, 3, 4], [1, 2, 3, 5, 4]), < + ArrayDiff + >{ + distance: 1, + adds: { 3: 5 }, + dels: {}, + const: { 0: 1, 1: 2, 2: 3, 3: 4 }, + linear: [0, 0, 1, 0, 1, 2, 0, 2, 3, 1, 3, 5, 0, 3, 4], + }); + }, - "diff insert 2nd last (changes only)": () => { - assert.deepStrictEqual( - diffArray([1, 2, 3, 4], [1, 2, 3, 5, 4], "minimal"), - >{ - distance: 1, - adds: {}, - dels: {}, - const: {}, - linear: [1, 3, 5], - } - ); - }, + "diff insert 2nd last (changes only)": () => { + assert.deepStrictEqual( + diffArray([1, 2, 3, 4], [1, 2, 3, 5, 4], "minimal"), + >{ + distance: 1, + adds: {}, + dels: {}, + const: {}, + linear: [1, 3, 5], + } + ); + }, }); diff --git a/packages/diff/tsconfig.json b/packages/diff/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/diff/tsconfig.json +++ b/packages/diff/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/distance-transform/api-extractor.json b/packages/distance-transform/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/distance-transform/api-extractor.json +++ b/packages/distance-transform/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/distance-transform/package.json b/packages/distance-transform/package.json index 135582a5fe..d4755c0a49 100644 --- a/packages/distance-transform/package.json +++ b/packages/distance-transform/package.json @@ -1,83 +1,83 @@ { - "name": "@thi.ng/distance-transform", - "version": "0.1.6", - "description": "Binary image to Distance Field transformation", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/distance-transform#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/pixel": "^3.4.7", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=14" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./metric": { - "default": "./metric.js" - }, - "./transform": { - "default": "./transform.js" - } - }, - "thi.ng": { - "status": "alpha", - "year": 2021 - } + "name": "@thi.ng/distance-transform", + "version": "0.1.6", + "description": "Binary image to Distance Field transformation", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/distance-transform#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/pixel": "^3.4.7", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=14" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./metric": { + "default": "./metric.js" + }, + "./transform": { + "default": "./transform.js" + } + }, + "thi.ng": { + "status": "alpha", + "year": 2021 + } } diff --git a/packages/distance-transform/src/api.ts b/packages/distance-transform/src/api.ts index 1a111a024d..76221aced8 100644 --- a/packages/distance-transform/src/api.ts +++ b/packages/distance-transform/src/api.ts @@ -1,16 +1,16 @@ import type { FnN, FnN3, FnN4 } from "@thi.ng/api"; export interface DTMetric { - /** - * Distance metric - */ - dist: FnN3; - /** - * Separation function - */ - sep: FnN4; - /** - * Result post-processing fn, e.g. Math.sqrt() for {@link EUCLEDIAN} - */ - post?: FnN; + /** + * Distance metric + */ + dist: FnN3; + /** + * Separation function + */ + sep: FnN4; + /** + * Result post-processing fn, e.g. Math.sqrt() for {@link EUCLEDIAN} + */ + post?: FnN; } diff --git a/packages/distance-transform/src/metric.ts b/packages/distance-transform/src/metric.ts index 257b08a8ef..3fc3bf1bb3 100644 --- a/packages/distance-transform/src/metric.ts +++ b/packages/distance-transform/src/metric.ts @@ -1,30 +1,30 @@ import type { DTMetric } from "./api.js"; export const EUCLEDIAN_SQ: DTMetric = { - dist: (x, i, gi) => (x - i) * (x - i) + gi * gi, - sep: (i, u, gi, gu) => - Math.floor((u * u - i * i + gu * gu - gi * gi) / (2 * (u - i))), + dist: (x, i, gi) => (x - i) * (x - i) + gi * gi, + sep: (i, u, gi, gu) => + Math.floor((u * u - i * i + gu * gu - gi * gi) / (2 * (u - i))), }; export const EUCLEDIAN: DTMetric = { - ...EUCLEDIAN_SQ, - post: Math.sqrt, + ...EUCLEDIAN_SQ, + post: Math.sqrt, }; export const MANHATTAN: DTMetric = { - dist: (x, i, gi) => Math.abs(x - i) + gi, - sep: (i, u, gi, gu) => - gu >= gi + u - i - ? Infinity - : gi > gu + u - i - ? -Infinity - : Math.floor((gu - gi + u + i) / 2), + dist: (x, i, gi) => Math.abs(x - i) + gi, + sep: (i, u, gi, gu) => + gu >= gi + u - i + ? Infinity + : gi > gu + u - i + ? -Infinity + : Math.floor((gu - gi + u + i) / 2), }; export const CHEBYSHEV: DTMetric = { - dist: (x, i, gi) => Math.max(Math.abs(x - i), gi), - sep: (i, u, gi, gu) => - gi <= gu - ? Math.max(i + gu, (i + u) >> 1) - : Math.min(u - gi, (i + u) >> 1), + dist: (x, i, gi) => Math.max(Math.abs(x - i), gi), + sep: (i, u, gi, gu) => + gi <= gu + ? Math.max(i + gu, (i + u) >> 1) + : Math.min(u - gi, (i + u) >> 1), }; diff --git a/packages/distance-transform/src/transform.ts b/packages/distance-transform/src/transform.ts index 9e57aca68d..3628813c8f 100644 --- a/packages/distance-transform/src/transform.ts +++ b/packages/distance-transform/src/transform.ts @@ -38,77 +38,77 @@ import { EUCLEDIAN } from "./metric.js"; * dtImg.blitCanvas(canvas2d(img.width, img.height, document.body).canvas); * ``` * - * @param grid - - * @param metric - - * @param normalize - + * @param grid - + * @param metric - + * @param normalize - */ export const distanceTransform = ( - { - data: spix, - size: [width, height], - stride: [sx, sy], - offset, - }: IGrid2D, - { dist, sep, post }: DTMetric = EUCLEDIAN, - normalize = 1 + { + data: spix, + size: [width, height], + stride: [sx, sy], + offset, + }: IGrid2D, + { dist, sep, post }: DTMetric = EUCLEDIAN, + normalize = 1 ) => { - post = post || ((x) => x); - const dest = new Float32Array(width * height); - const g = new Uint32Array(width * height); - const s = new Uint32Array(width); - const t = new Uint32Array(width); - const MAX = 0xffffffff; + post = post || ((x) => x); + const dest = new Float32Array(width * height); + const g = new Uint32Array(width * height); + const s = new Uint32Array(width); + const t = new Uint32Array(width); + const MAX = 0xffffffff; - for (let x = 0; x < width; x++) { - g[x] = spix[offset + x * sx] === 0 ? MAX : 0; - for (let y = 1; y < height; y++) { - const i = y * width + x; - g[i] = - spix[offset + y * sy + x * sx] === 0 - ? Math.min(MAX, 1 + g[i - width]) - : 0; - } - for (let y = height - 1; y-- > 0; ) { - const i = x + y * width; - const q = g[i + width]; - q < g[i] && (g[i] = Math.min(MAX, 1 + q)); - } - } + for (let x = 0; x < width; x++) { + g[x] = spix[offset + x * sx] === 0 ? MAX : 0; + for (let y = 1; y < height; y++) { + const i = y * width + x; + g[i] = + spix[offset + y * sy + x * sx] === 0 + ? Math.min(MAX, 1 + g[i - width]) + : 0; + } + for (let y = height - 1; y-- > 0; ) { + const i = x + y * width; + const q = g[i + width]; + q < g[i] && (g[i] = Math.min(MAX, 1 + q)); + } + } - let maxD = -Infinity; - for (let y = 0; y < height; y++) { - const I = y * width; - let q = 0; - s[0] = t[0] = 0; - for (let u = 1; u < width; u++) { - while ( - q >= 0 && - dist(t[q], s[q], g[I + s[q]]) > dist(t[q], u, g[I + u]) - ) - q--; - if (q < 0) { - q = 0; - s[0] = u; - } else { - const w = 1 + sep(s[q], u, g[I + s[q]], g[I + u]); - if (w < width) { - q++; - s[q] = u; - t[q] = w; - } - } - } - for (let u = width; u-- > 0; ) { - let d = (dest[u + I] = post(dist(u, s[q], g[I + s[q]]))); - d > maxD && (maxD = d); - u === t[q] && q--; - } - } + let maxD = -Infinity; + for (let y = 0; y < height; y++) { + const I = y * width; + let q = 0; + s[0] = t[0] = 0; + for (let u = 1; u < width; u++) { + while ( + q >= 0 && + dist(t[q], s[q], g[I + s[q]]) > dist(t[q], u, g[I + u]) + ) + q--; + if (q < 0) { + q = 0; + s[0] = u; + } else { + const w = 1 + sep(s[q], u, g[I + s[q]], g[I + u]); + if (w < width) { + q++; + s[q] = u; + t[q] = w; + } + } + } + for (let u = width; u-- > 0; ) { + let d = (dest[u + I] = post(dist(u, s[q], g[I + s[q]]))); + d > maxD && (maxD = d); + u === t[q] && q--; + } + } - if (normalize > 0 && maxD > 1e-6) { - maxD = normalize / maxD; - for (let i = dest.length; i-- > 0; ) dest[i] *= maxD; - } + if (normalize > 0 && maxD > 1e-6) { + maxD = normalize / maxD; + for (let i = dest.length; i-- > 0; ) dest[i] *= maxD; + } - return dest; + return dest; }; diff --git a/packages/distance-transform/test/index.ts b/packages/distance-transform/test/index.ts index 024a604bdb..df0f95bd09 100644 --- a/packages/distance-transform/test/index.ts +++ b/packages/distance-transform/test/index.ts @@ -4,32 +4,32 @@ import * as assert from "assert"; import { CHEBYSHEV, distanceTransform, MANHATTAN } from "../src/index.js"; group("distance-transform", { - manhattan: () => { - const buf = intBuffer(4, 4, GRAY8); - buf.data[0] = 1; - assert.deepStrictEqual( - [...distanceTransform(buf, MANHATTAN, 0)], - // prettier-ignore - [ + manhattan: () => { + const buf = intBuffer(4, 4, GRAY8); + buf.data[0] = 1; + assert.deepStrictEqual( + [...distanceTransform(buf, MANHATTAN, 0)], + // prettier-ignore + [ 0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6 ] - ); - }, - chebyshev: () => { - const buf = intBuffer(4, 4, GRAY8); - buf.data[0] = 1; - assert.deepStrictEqual( - [...distanceTransform(buf, CHEBYSHEV, 0)], - // prettier-ignore - [ + ); + }, + chebyshev: () => { + const buf = intBuffer(4, 4, GRAY8); + buf.data[0] = 1; + assert.deepStrictEqual( + [...distanceTransform(buf, CHEBYSHEV, 0)], + // prettier-ignore + [ 0, 1, 2, 3, 1, 1, 2, 3, 2, 2, 2, 3, 3, 3, 3, 3 ] - ); - }, + ); + }, }); diff --git a/packages/distance-transform/tools/index.ts b/packages/distance-transform/tools/index.ts index 13a80e3233..08520472a6 100644 --- a/packages/distance-transform/tools/index.ts +++ b/packages/distance-transform/tools/index.ts @@ -1,8 +1,8 @@ import { - CHEBYSHEV, - distanceTransform, - EUCLEDIAN, - MANHATTAN, + CHEBYSHEV, + distanceTransform, + EUCLEDIAN, + MANHATTAN, } from "@thi.ng/distance-transform"; import { floatBuffer, FLOAT_GRAY, GRAY8, intBuffer } from "@thi.ng/pixel"; import { asPGM } from "@thi.ng/pixel-io-netpbm"; @@ -18,9 +18,9 @@ drawLine(src, W / 5, W / 2, (W * 4) / 5, W / 2, 255); const all = floatBuffer(W * 3, W, FLOAT_GRAY); [EUCLEDIAN, MANHATTAN, CHEBYSHEV].forEach((m, i) => - floatBuffer(W, W, FLOAT_GRAY, distanceTransform(src, m)).blit(all, { - dx: i * W, - }) + floatBuffer(W, W, FLOAT_GRAY, distanceTransform(src, m)).blit(all, { + dx: i * W, + }) ); all.forEach((x) => [x[0] ** 0.4545]); diff --git a/packages/distance-transform/tsconfig.json b/packages/distance-transform/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/distance-transform/tsconfig.json +++ b/packages/distance-transform/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/distance/api-extractor.json b/packages/distance/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/distance/api-extractor.json +++ b/packages/distance/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/distance/package.json b/packages/distance/package.json index feb25d5844..0cc74c3552 100644 --- a/packages/distance/package.json +++ b/packages/distance/package.json @@ -1,112 +1,112 @@ { - "name": "@thi.ng/distance", - "version": "2.1.16", - "description": "N-dimensional distance metrics & K-nearest neighborhoods for point queries", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/distance#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/heaps": "^2.1.8", - "@thi.ng/math": "^5.3.4", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "distance", - "metric", - "k-nearest", - "nd", - "neighborhood", - "points", - "query", - "region", - "spatial", - "typescript", - "vector" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./argmin": { - "default": "./argmin.js" - }, - "./eucledian": { - "default": "./eucledian.js" - }, - "./haversine": { - "default": "./haversine.js" - }, - "./knearest": { - "default": "./knearest.js" - }, - "./manhattan": { - "default": "./manhattan.js" - }, - "./nearest": { - "default": "./nearest.js" - }, - "./squared": { - "default": "./squared.js" - } - }, - "thi.ng": { - "related": [ - "geom-accel", - "k-means", - "vectors" - ], - "year": 2021 - } + "name": "@thi.ng/distance", + "version": "2.1.16", + "description": "N-dimensional distance metrics & K-nearest neighborhoods for point queries", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/distance#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/heaps": "^2.1.8", + "@thi.ng/math": "^5.3.4", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "distance", + "metric", + "k-nearest", + "nd", + "neighborhood", + "points", + "query", + "region", + "spatial", + "typescript", + "vector" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./argmin": { + "default": "./argmin.js" + }, + "./eucledian": { + "default": "./eucledian.js" + }, + "./haversine": { + "default": "./haversine.js" + }, + "./knearest": { + "default": "./knearest.js" + }, + "./manhattan": { + "default": "./manhattan.js" + }, + "./nearest": { + "default": "./nearest.js" + }, + "./squared": { + "default": "./squared.js" + } + }, + "thi.ng": { + "related": [ + "geom-accel", + "k-means", + "vectors" + ], + "year": 2021 + } } diff --git a/packages/distance/src/api.ts b/packages/distance/src/api.ts index e828926fe5..7bcde6ad87 100644 --- a/packages/distance/src/api.ts +++ b/packages/distance/src/api.ts @@ -14,58 +14,58 @@ export type Neighbor = Pair; * Distance metric implementation & conversions from/to raw distances. */ export interface IDistance { - /** - * The actual distance function metric. - */ - readonly metric: Metric; + /** + * The actual distance function metric. + */ + readonly metric: Metric; - /** - * Converts Eucledian distance `x` into the metric of this instance. - * - * @param x - - */ - to(x: number): number; + /** + * Converts Eucledian distance `x` into the metric of this instance. + * + * @param x - + */ + to(x: number): number; - /** - * Converts `x` from the metric of this instance into an Eucledian value. - * - * @param x - - */ - from(x: number): number; + /** + * Converts `x` from the metric of this instance into an Eucledian value. + * + * @param x - + */ + from(x: number): number; } export interface INeighborhood extends IReset { - /** - * The distance metric used by this neighborhood - */ - readonly dist: IDistance

; - /** - * The neighborhood's target position / centroid - */ - readonly target: P; - /** - * The neighborhood's original radius (Eucledian metric) - */ - readonly radius: number; + /** + * The distance metric used by this neighborhood + */ + readonly dist: IDistance

; + /** + * The neighborhood's target position / centroid + */ + readonly target: P; + /** + * The neighborhood's original radius (Eucledian metric) + */ + readonly radius: number; - /** - * Returns true, if distance `d` is <= current radius of this neighborhood. - * If `eucledian` is true (default: true), then `d` will first be converted - * into the metric used by this neighborhood using {@link IDistance.to}, - * otherwise it is expected to be already in that metric space. - * - * @param d - - * @param eucledian - - */ - includesDistance(d: number, eucledian?: boolean): boolean; + /** + * Returns true, if distance `d` is <= current radius of this neighborhood. + * If `eucledian` is true (default: true), then `d` will first be converted + * into the metric used by this neighborhood using {@link IDistance.to}, + * otherwise it is expected to be already in that metric space. + * + * @param d - + * @param eucledian - + */ + includesDistance(d: number, eucledian?: boolean): boolean; - /** - * Computes distance metric between `pos` and this neighborhood's target - * pos. If result distance is <= current radius, adds `val` to neighborhood - * and shrinks neighborhood radius to new distance. Returns distance metric. - * - * @param pos - - * @param val - - */ - consider(pos: P, val: T): number; + /** + * Computes distance metric between `pos` and this neighborhood's target + * pos. If result distance is <= current radius, adds `val` to neighborhood + * and shrinks neighborhood radius to new distance. Returns distance metric. + * + * @param pos - + * @param val - + */ + consider(pos: P, val: T): number; } diff --git a/packages/distance/src/argmin.ts b/packages/distance/src/argmin.ts index 22dc798e98..86b6c12b22 100644 --- a/packages/distance/src/argmin.ts +++ b/packages/distance/src/argmin.ts @@ -15,44 +15,44 @@ import { DIST_SQ, DIST_SQ1 } from "./squared.js"; * * https://en.wikipedia.org/wiki/Arg_max#Arg_min * - * @param p - - * @param samples - - * @param dist - + * @param p - + * @param samples - + * @param dist - */ export const argmin = ( - p: ReadonlyVec, - samples: ReadonlyVec[], - dist: Metric | IDistance = DIST_SQ + p: ReadonlyVec, + samples: ReadonlyVec[], + dist: Metric | IDistance = DIST_SQ ) => { - const distFn = isFunction(dist) ? dist : dist.metric; - let minD = Infinity; - let minArg = -1; - for (let i = 0, n = samples.length; i < n; i++) { - const d = distFn(p, samples[i]); - if (d < minD) { - minD = d; - minArg = i; - } - } - return minArg; + const distFn = isFunction(dist) ? dist : dist.metric; + let minD = Infinity; + let minArg = -1; + for (let i = 0, n = samples.length; i < n; i++) { + const d = distFn(p, samples[i]); + if (d < minD) { + minD = d; + minArg = i; + } + } + return minArg; }; export const argminN = ( - p: number, - samples: NumericArray, - dist: Metric | IDistance = DIST_SQ1 + p: number, + samples: NumericArray, + dist: Metric | IDistance = DIST_SQ1 ) => { - const distFn = isFunction(dist) ? dist : dist.metric; - let minD = Infinity; - let minArg = -1; - for (let i = 0, n = samples.length; i < n; i++) { - const d = distFn(p, samples[i]); - if (d < minD) { - minD = d; - minArg = i; - } - } - return minArg; + const distFn = isFunction(dist) ? dist : dist.metric; + let minD = Infinity; + let minArg = -1; + for (let i = 0, n = samples.length; i < n; i++) { + const d = distFn(p, samples[i]); + if (d < minD) { + minD = d; + minArg = i; + } + } + return minArg; }; /** @@ -64,51 +64,51 @@ export const argminN = ( * @reference * Use {@link argmin} if all items are vectors already. * - * @param p - - * @param samples - - * @param key - - * @param dist - + * @param p - + * @param samples - + * @param key - + * @param dist - */ export const argminT = ( - p: T, - samples: T[], - key: Fn, - dist?: Metric | IDistance + p: T, + samples: T[], + key: Fn, + dist?: Metric | IDistance ) => argmin(key(p), samples.map(key), dist); /** * Similar to {@link argmin}, but for k-nearest queries. * - * @param k - - * @param p - - * @param samples - - * @param dist - + * @param k - + * @param p - + * @param samples - + * @param dist - */ export const argminK = ( - k: number, - p: ReadonlyVec, - samples: ReadonlyVec[], - dist: IDistance = DIST_SQ + k: number, + p: ReadonlyVec, + samples: ReadonlyVec[], + dist: IDistance = DIST_SQ ) => { - const neighborhood = knearest(p, k, Infinity, dist); - for (let i = 0, n = samples.length; i < n; i++) { - neighborhood.consider(samples[i], i); - } - return neighborhood.values(); + const neighborhood = knearest(p, k, Infinity, dist); + for (let i = 0, n = samples.length; i < n; i++) { + neighborhood.consider(samples[i], i); + } + return neighborhood.values(); }; /** * Similar to {@link argminT}, but for k-nearest queries. * - * @param k - - * @param p - - * @param samples - - * @param dist - + * @param k - + * @param p - + * @param samples - + * @param dist - */ export const argminKT = ( - k: number, - p: T, - samples: T[], - key: Fn, - dist?: IDistance + k: number, + p: T, + samples: T[], + key: Fn, + dist?: IDistance ) => argminK(k, key(p), samples.map(key), dist); diff --git a/packages/distance/src/eucledian.ts b/packages/distance/src/eucledian.ts index 904670b520..c26c7607e8 100644 --- a/packages/distance/src/eucledian.ts +++ b/packages/distance/src/eucledian.ts @@ -3,15 +3,15 @@ import { dist, dist2, dist3 } from "@thi.ng/vectors/dist"; import type { IDistance, Metric } from "./api.js"; export class Eucledian implements IDistance { - constructor(public readonly metric: Metric) {} + constructor(public readonly metric: Metric) {} - to(x: number) { - return x; - } + to(x: number) { + return x; + } - from(x: number) { - return x; - } + from(x: number) { + return x; + } } /** diff --git a/packages/distance/src/haversine.ts b/packages/distance/src/haversine.ts index 072f5615a6..d4a71bf046 100644 --- a/packages/distance/src/haversine.ts +++ b/packages/distance/src/haversine.ts @@ -1,7 +1,7 @@ import type { ReadonlyVec } from "@thi.ng/vectors"; import { - distHaversineLatLon, - distHaversineLonLat, + distHaversineLatLon, + distHaversineLonLat, } from "@thi.ng/vectors/dist-haversine"; import { Eucledian } from "./eucledian.js"; diff --git a/packages/distance/src/knearest.ts b/packages/distance/src/knearest.ts index 5a2ef8e294..5fd8aae604 100644 --- a/packages/distance/src/knearest.ts +++ b/packages/distance/src/knearest.ts @@ -22,70 +22,70 @@ import { DIST_SQ, DIST_SQ1, DIST_SQ2, DIST_SQ3 } from "./squared.js"; * @typeParam T - indexed value */ export class KNearest - implements INeighborhood, IDeref[]> + implements INeighborhood, IDeref[]> { - readonly radius; - protected _currR!: number; - protected _heap = new Heap>(null, { - compare: (a, b) => b[0] - a[0], - }); + readonly radius; + protected _currR!: number; + protected _heap = new Heap>(null, { + compare: (a, b) => b[0] - a[0], + }); - constructor( - public readonly dist: IDistance, - public readonly target: D, - public readonly k: number, - radius = Infinity, - public sorted = false - ) { - assert(k > 0, `invalid k (must be > 0)`); - this.radius = clamp0(radius); - this.reset(); - } + constructor( + public readonly dist: IDistance, + public readonly target: D, + public readonly k: number, + radius = Infinity, + public sorted = false + ) { + assert(k > 0, `invalid k (must be > 0)`); + this.radius = clamp0(radius); + this.reset(); + } - reset() { - this._currR = this.dist.to(this.radius); - this._heap.clear(); - return this; - } + reset() { + this._currR = this.dist.to(this.radius); + this._heap.clear(); + return this; + } - /** - * Returns an array of current nearest neighbor result tuples (each `[dist, - * val]`). The array will contain at most `k` items and if the `sorted` ctor - * arg was true, will be sorted by distance. - * - * @remarks - * Use {@link KNearest.values} to obtain result values **without** their distance - * metrics. - */ - deref() { - return this.sorted ? this._heap.max() : this._heap.values; - } + /** + * Returns an array of current nearest neighbor result tuples (each `[dist, + * val]`). The array will contain at most `k` items and if the `sorted` ctor + * arg was true, will be sorted by distance. + * + * @remarks + * Use {@link KNearest.values} to obtain result values **without** their distance + * metrics. + */ + deref() { + return this.sorted ? this._heap.max() : this._heap.values; + } - /** - * Similar to {@link KNearest.deref}, but returns array of result values **without** - * their distance metrics. - */ - values() { - return this.deref().map((x) => x[1]); - } + /** + * Similar to {@link KNearest.deref}, but returns array of result values **without** + * their distance metrics. + */ + values() { + return this.deref().map((x) => x[1]); + } - includesDistance(d: number, eucledian = true) { - return (eucledian ? this.dist.to(d) : d) <= this._currR; - } + includesDistance(d: number, eucledian = true) { + return (eucledian ? this.dist.to(d) : d) <= this._currR; + } - consider(pos: D, val: T) { - const d = this.dist.metric(this.target, pos); - if (d <= this._currR) { - const heap = this._heap; - if (heap.length === this.k) { - heap.pushPop([d, val]); - this._currR = heap.peek()![0]; - } else { - heap.push([d, val]); - } - } - return d; - } + consider(pos: D, val: T) { + const d = this.dist.metric(this.target, pos); + if (d <= this._currR) { + const heap = this._heap; + if (heap.length === this.k) { + heap.pushPop([d, val]); + this._currR = heap.peek()![0]; + } else { + heap.push([d, val]); + } + } + return d; + } } /** @@ -93,70 +93,70 @@ export class KNearest * and, by default, using an infinite region radius and {@link DIST_SQ} distance * metric. * - * @param p - - * @param k - - * @param r - - * @param dist - - * @param sorted - + * @param p - + * @param k - + * @param r - + * @param dist - + * @param sorted - */ export const knearest = ( - p: ReadonlyVec, - k: number, - r?: number, - dist = DIST_SQ, - sorted?: boolean + p: ReadonlyVec, + k: number, + r?: number, + dist = DIST_SQ, + sorted?: boolean ) => new KNearest(dist, p, k, r, sorted); /** * Defines a {@link KNearest} instance for 2D vector positions and, by default, * using an infinite region radius and {@link DIST_SQ2} distance metric. * - * @param p - - * @param k - - * @param r - - * @param dist - - * @param sorted - + * @param p - + * @param k - + * @param r - + * @param dist - + * @param sorted - */ export const knearest2 = ( - p: ReadonlyVec, - k: number, - r?: number, - dist = DIST_SQ2, - sorted?: boolean + p: ReadonlyVec, + k: number, + r?: number, + dist = DIST_SQ2, + sorted?: boolean ) => new KNearest(dist, p, k, r, sorted); /** * Defines a {@link KNearest} instance for 3D vector positions, by default, * using an infinite region radius and {@link DIST_SQ3} distance metric. * - * @param p - - * @param k - - * @param r - - * @param dist - - * @param sorted - + * @param p - + * @param k - + * @param r - + * @param dist - + * @param sorted - */ export const knearest3 = ( - p: ReadonlyVec, - k: number, - r?: number, - dist = DIST_SQ3, - sorted?: boolean + p: ReadonlyVec, + k: number, + r?: number, + dist = DIST_SQ3, + sorted?: boolean ) => new KNearest(dist, p, k, r, sorted); /** * Defines a {@link KNearest} instance for numeric positions and, by default, * using an infinite region radius and {@link DIST_SQ1} distance metric. * - * @param p - - * @param k - - * @param r - - * @param dist - - * @param sorted - + * @param p - + * @param k - + * @param r - + * @param dist - + * @param sorted - */ export const knearestN = ( - p: number, - k: number, - r?: number, - dist = DIST_SQ1, - sorted?: boolean + p: number, + k: number, + r?: number, + dist = DIST_SQ1, + sorted?: boolean ) => new KNearest(dist, p, k, r, sorted); diff --git a/packages/distance/src/manhattan.ts b/packages/distance/src/manhattan.ts index b1e4f51eaf..5a0dfefa48 100644 --- a/packages/distance/src/manhattan.ts +++ b/packages/distance/src/manhattan.ts @@ -1,8 +1,8 @@ import type { ReadonlyVec } from "@thi.ng/vectors"; import { - distManhattan, - distManhattan2, - distManhattan3, + distManhattan, + distManhattan2, + distManhattan3, } from "@thi.ng/vectors/dist-manhattan"; import type { IDistance, Metric } from "./api.js"; @@ -49,29 +49,29 @@ import type { IDistance, Metric } from "./api.js"; * ``` */ export class Manhattan implements IDistance { - protected _invD: number; + protected _invD: number; - constructor( - public readonly dim: number, - public readonly metric: Metric - ) { - this._invD = this.dim / Math.sqrt(dim); - } + constructor( + public readonly dim: number, + public readonly metric: Metric + ) { + this._invD = this.dim / Math.sqrt(dim); + } - to(x: number) { - return x * this._invD; - } + to(x: number) { + return x * this._invD; + } - from(x: number) { - return Math.sqrt((x / this.dim) ** 2 * this.dim); - } + from(x: number) { + return Math.sqrt((x / this.dim) ** 2 * this.dim); + } } /** * Returns a new Manhattan distance metric for n-D vectors of dimension `dim`. */ export const defManhattan = (dim: number) => - new Manhattan(dim, distManhattan); + new Manhattan(dim, distManhattan); /** * Manhattan distance metric for 2d vectors. diff --git a/packages/distance/src/nearest.ts b/packages/distance/src/nearest.ts index c170c785b9..60a3cb8e1c 100644 --- a/packages/distance/src/nearest.ts +++ b/packages/distance/src/nearest.ts @@ -13,49 +13,49 @@ import { DIST_SQ, DIST_SQ1, DIST_SQ2, DIST_SQ3 } from "./squared.js"; * @typeParam T - indexed value */ export class Nearest - implements INeighborhood, IDeref | undefined> + implements INeighborhood, IDeref | undefined> { - readonly radius; - protected _currR!: number; - value?: T; + readonly radius; + protected _currR!: number; + value?: T; - constructor( - public readonly dist: IDistance, - public readonly target: D, - radius = Infinity - ) { - this.radius = clamp0(radius); - this.reset(); - } + constructor( + public readonly dist: IDistance, + public readonly target: D, + radius = Infinity + ) { + this.radius = clamp0(radius); + this.reset(); + } - reset() { - this._currR = this.dist.to(this.radius); - this.value = undefined; - return this; - } + reset() { + this._currR = this.dist.to(this.radius); + this.value = undefined; + return this; + } - /** - * Returns current nearest neighbor result tuple (`[dist, val]`) or - * undefined, if no such result exists (yet). - */ - deref() { - return this.value != undefined - ? >[this._currR, this.value] - : undefined; - } + /** + * Returns current nearest neighbor result tuple (`[dist, val]`) or + * undefined, if no such result exists (yet). + */ + deref() { + return this.value != undefined + ? >[this._currR, this.value] + : undefined; + } - includesDistance(d: number, eucledian = true) { - return (eucledian ? this.dist.to(d) : d) <= this._currR; - } + includesDistance(d: number, eucledian = true) { + return (eucledian ? this.dist.to(d) : d) <= this._currR; + } - consider(pos: D, val: T) { - const d = this.dist.metric(this.target, pos); - if (d <= this._currR) { - this._currR = d; - this.value = val; - } - return d; - } + consider(pos: D, val: T) { + const d = this.dist.metric(this.target, pos); + if (d <= this._currR) { + this._currR = d; + this.value = val; + } + return d; + } } /** @@ -63,42 +63,42 @@ export class Nearest * default, using an infinite region radius and {@link DIST_SQ} distance * metric. * - * @param p - - * @param r - - * @param dist - + * @param p - + * @param r - + * @param dist - */ export const nearest = (p: ReadonlyVec, r?: number, dist = DIST_SQ) => - new Nearest(dist, p, r); + new Nearest(dist, p, r); /** * Defines a {@link Nearest} instance for 2D vector positions, by default, * using an infinite region radius and {@link DIST_SQ2} distance metric. * - * @param p - - * @param r - - * @param dist - + * @param p - + * @param r - + * @param dist - */ export const nearest2 = (p: ReadonlyVec, r?: number, dist = DIST_SQ2) => - new Nearest(dist, p, r); + new Nearest(dist, p, r); /** * Defines a {@link Nearest} instance for 3D vector positions, by default, * using an infinite region radius and {@link DIST_SQ3} distance metric. * - * @param p - - * @param r - - * @param dist - + * @param p - + * @param r - + * @param dist - */ export const nearest3 = (p: ReadonlyVec, r?: number, dist = DIST_SQ3) => - new Nearest(dist, p, r); + new Nearest(dist, p, r); /** * Defines a {@link Nearest} instance for numeric positions and, by default, * using an infinite region radius and {@link DIST_SQ1} distance metric. * - * @param p - - * @param r - - * @param dist - + * @param p - + * @param r - + * @param dist - */ export const nearestN = (p: number, r?: number, dist = DIST_SQ1) => - new Nearest(dist, p, r); + new Nearest(dist, p, r); diff --git a/packages/distance/src/squared.ts b/packages/distance/src/squared.ts index 718bd8c6d1..05bddf2cfd 100644 --- a/packages/distance/src/squared.ts +++ b/packages/distance/src/squared.ts @@ -3,15 +3,15 @@ import { distSq, distSq2, distSq3 } from "@thi.ng/vectors/distsq"; import type { IDistance, Metric } from "./api.js"; export class Squared implements IDistance { - constructor(public readonly metric: Metric) {} + constructor(public readonly metric: Metric) {} - to(x: number) { - return x * x; - } + to(x: number) { + return x * x; + } - from(x: number) { - return Math.sqrt(x); - } + from(x: number) { + return Math.sqrt(x); + } } export const DIST_SQ = new Squared(distSq); diff --git a/packages/distance/test/index.ts b/packages/distance/test/index.ts index 70e795db9d..c7c059f862 100644 --- a/packages/distance/test/index.ts +++ b/packages/distance/test/index.ts @@ -2,96 +2,96 @@ import { group } from "@thi.ng/testament"; import { dist, distSq2, distSq3 } from "@thi.ng/vectors"; import * as assert from "assert"; import { - DIST_SQ1, - DIST_SQ2, - DIST_SQ3, - EUCLEDIAN1, - EUCLEDIAN2, - EUCLEDIAN3, - knearestN, - nearestN, -} from "../src/index.js" + DIST_SQ1, + DIST_SQ2, + DIST_SQ3, + EUCLEDIAN1, + EUCLEDIAN2, + EUCLEDIAN3, + knearestN, + nearestN, +} from "../src/index.js"; group("distance", { - eucledian1: () => { - assert.strictEqual(EUCLEDIAN1.to(10), 10); - assert.strictEqual(EUCLEDIAN1.from(10), 10); - assert.strictEqual(EUCLEDIAN1.metric(5, 10), 5); - }, + eucledian1: () => { + assert.strictEqual(EUCLEDIAN1.to(10), 10); + assert.strictEqual(EUCLEDIAN1.from(10), 10); + assert.strictEqual(EUCLEDIAN1.metric(5, 10), 5); + }, - eucledian2: () => { - assert.strictEqual( - EUCLEDIAN2.metric([5, 10], [-5, -10]), - dist([5, 10], [-5, -10]) - ); - }, + eucledian2: () => { + assert.strictEqual( + EUCLEDIAN2.metric([5, 10], [-5, -10]), + dist([5, 10], [-5, -10]) + ); + }, - eucledian3: () => { - assert.strictEqual( - EUCLEDIAN3.metric([5, 10, -20], [-5, -10, 20]), - dist([5, 10, -20], [-5, -10, 20]) - ); - }, + eucledian3: () => { + assert.strictEqual( + EUCLEDIAN3.metric([5, 10, -20], [-5, -10, 20]), + dist([5, 10, -20], [-5, -10, 20]) + ); + }, - squared1: () => { - assert.strictEqual(DIST_SQ1.to(10), 100); - assert.strictEqual(DIST_SQ1.from(100), 10); - assert.strictEqual(DIST_SQ1.metric(5, 10), 25); - }, + squared1: () => { + assert.strictEqual(DIST_SQ1.to(10), 100); + assert.strictEqual(DIST_SQ1.from(100), 10); + assert.strictEqual(DIST_SQ1.metric(5, 10), 25); + }, - squared2: () => { - assert.strictEqual( - DIST_SQ2.metric([5, 10], [-5, -10]), - distSq2([5, 10], [-5, -10]) - ); - }, + squared2: () => { + assert.strictEqual( + DIST_SQ2.metric([5, 10], [-5, -10]), + distSq2([5, 10], [-5, -10]) + ); + }, - squared3: () => { - assert.strictEqual( - DIST_SQ3.metric([5, 10, -20], [-5, -10, 20]), - distSq3([5, 10, -20], [-5, -10, 20]) - ); - }, + squared3: () => { + assert.strictEqual( + DIST_SQ3.metric([5, 10, -20], [-5, -10, 20]), + distSq3([5, 10, -20], [-5, -10, 20]) + ); + }, - "nearestN (inf)": () => { - const a = nearestN(10, Infinity, DIST_SQ1); - assert.deepStrictEqual( - [5, 9, 12, 11].map((x) => a.consider(x, x)), - [25, 1, 4, 1] - ); - assert.deepStrictEqual(a.deref(), [1, 11]); - }, + "nearestN (inf)": () => { + const a = nearestN(10, Infinity, DIST_SQ1); + assert.deepStrictEqual( + [5, 9, 12, 11].map((x) => a.consider(x, x)), + [25, 1, 4, 1] + ); + assert.deepStrictEqual(a.deref(), [1, 11]); + }, - "nearestN (radius)": () => { - const a = nearestN(10, 2, DIST_SQ1); - assert.deepStrictEqual( - [5, 9, 12, 11].map((x) => a.consider(x, x)), - [25, 1, 4, 1] - ); - assert.deepStrictEqual(a.deref(), [1, 11]); - }, + "nearestN (radius)": () => { + const a = nearestN(10, 2, DIST_SQ1); + assert.deepStrictEqual( + [5, 9, 12, 11].map((x) => a.consider(x, x)), + [25, 1, 4, 1] + ); + assert.deepStrictEqual(a.deref(), [1, 11]); + }, - "knearestN (inf)": () => { - const a = knearestN(10, 2, Infinity, DIST_SQ1, true); - assert.deepStrictEqual( - [5, 8, 13, 11].map((x) => a.consider(x, x)), - [25, 4, 9, 1] - ); - assert.deepStrictEqual(a.deref(), [ - [1, 11], - [4, 8], - ]); - }, + "knearestN (inf)": () => { + const a = knearestN(10, 2, Infinity, DIST_SQ1, true); + assert.deepStrictEqual( + [5, 8, 13, 11].map((x) => a.consider(x, x)), + [25, 4, 9, 1] + ); + assert.deepStrictEqual(a.deref(), [ + [1, 11], + [4, 8], + ]); + }, - "knearestN (radius)": () => { - const a = knearestN(10, 2, 2, DIST_SQ1, true); - assert.deepStrictEqual( - [5, 8, 13, 11].map((x) => a.consider(x, x)), - [25, 4, 9, 1] - ); - assert.deepStrictEqual(a.deref(), [ - [1, 11], - [4, 8], - ]); - }, + "knearestN (radius)": () => { + const a = knearestN(10, 2, 2, DIST_SQ1, true); + assert.deepStrictEqual( + [5, 8, 13, 11].map((x) => a.consider(x, x)), + [25, 4, 9, 1] + ); + assert.deepStrictEqual(a.deref(), [ + [1, 11], + [4, 8], + ]); + }, }); diff --git a/packages/distance/tsconfig.json b/packages/distance/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/distance/tsconfig.json +++ b/packages/distance/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/dl-asset/api-extractor.json b/packages/dl-asset/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/dl-asset/api-extractor.json +++ b/packages/dl-asset/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/dl-asset/package.json b/packages/dl-asset/package.json index cb405d9c5d..feccc995c2 100644 --- a/packages/dl-asset/package.json +++ b/packages/dl-asset/package.json @@ -1,92 +1,92 @@ { - "name": "@thi.ng/dl-asset", - "version": "2.3.6", - "description": "Canvas, video recording & file asset download helpers for web apps", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/download#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/mime": "^2.2.3" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "browser", - "canvas", - "download", - "file", - "mime", - "mediarecorder", - "typescript", - "video" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./canvas": { - "default": "./canvas.js" - }, - "./download": { - "default": "./download.js" - }, - "./raw": { - "default": "./raw.js" - } - }, - "thi.ng": { - "related": [ - "mime" - ], - "year": 2020 - } + "name": "@thi.ng/dl-asset", + "version": "2.3.6", + "description": "Canvas, video recording & file asset download helpers for web apps", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/download#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/mime": "^2.2.3" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "browser", + "canvas", + "download", + "file", + "mime", + "mediarecorder", + "typescript", + "video" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./canvas": { + "default": "./canvas.js" + }, + "./download": { + "default": "./download.js" + }, + "./raw": { + "default": "./raw.js" + } + }, + "thi.ng": { + "related": [ + "mime" + ], + "year": 2020 + } } diff --git a/packages/dl-asset/src/api.ts b/packages/dl-asset/src/api.ts index 448f9c5eab..4feabc7447 100644 --- a/packages/dl-asset/src/api.ts +++ b/packages/dl-asset/src/api.ts @@ -1,39 +1,39 @@ export interface DownloadOpts { - /** - * MIME type. If not given, attempts to derive MIME type from given - * filename extension (e.g. `.svg`) and if that fails, falls back to - * default value. - * - * @defaultValue application/octet-stream - */ - mime: string; - /** - * If true, converts source string to UTF-8. Only used if input is a - * string. - * - * @defaultValue false - */ - utf8: boolean; - /** - * Expiry time for generated object URL. Use value < 0 to disable. - * - * @defaultValue 10000 - */ - expire: number; + /** + * MIME type. If not given, attempts to derive MIME type from given + * filename extension (e.g. `.svg`) and if that fails, falls back to + * default value. + * + * @defaultValue application/octet-stream + */ + mime: string; + /** + * If true, converts source string to UTF-8. Only used if input is a + * string. + * + * @defaultValue false + */ + utf8: boolean; + /** + * Expiry time for generated object URL. Use value < 0 to disable. + * + * @defaultValue 10000 + */ + expire: number; } /** * User options for {@link canvasRecorder}. */ export interface CanvasRecorderOpts - extends Pick< - MediaRecorderOptions, - "mimeType" | "bitsPerSecond" | "videoBitsPerSecond" - > { - /** - * Recording frame rate (fps) - * - * @defaultValue 60 - */ - fps: number; + extends Pick< + MediaRecorderOptions, + "mimeType" | "bitsPerSecond" | "videoBitsPerSecond" + > { + /** + * Recording frame rate (fps) + * + * @defaultValue 60 + */ + fps: number; } diff --git a/packages/dl-asset/src/canvas.ts b/packages/dl-asset/src/canvas.ts index 6872d65ce5..7eea3e1d43 100644 --- a/packages/dl-asset/src/canvas.ts +++ b/packages/dl-asset/src/canvas.ts @@ -6,26 +6,26 @@ import { downloadWithMime } from "./raw.js"; * then triggers download via {@link downloadWithMime}. Default file type is * `png`. Default quality is 0.95 (only used for JPEG/WebP). * - * @param canvas - - * @param baseName - - * @param type - - * @param quality - + * @param canvas - + * @param baseName - + * @param type - + * @param quality - */ export const downloadCanvas = ( - canvas: HTMLCanvasElement, - baseName: string, - type: "png" | "jpeg" | "webp" = "png", - quality = 0.95 + canvas: HTMLCanvasElement, + baseName: string, + type: "png" | "jpeg" | "webp" = "png", + quality = 0.95 ) => { - const mime = `image/${type}`; - canvas.toBlob( - (blob) => - blob - ? downloadWithMime(`${baseName}.${type}`, blob, { mime }) - : console.warn("can't download canvas"), - mime, - quality - ); + const mime = `image/${type}`; + canvas.toBlob( + (blob) => + blob + ? downloadWithMime(`${baseName}.${type}`, blob, { mime }) + : console.warn("can't download canvas"), + mime, + quality + ); }; /** @@ -38,33 +38,33 @@ export const downloadCanvas = ( * The default recording format is WebM (VP9 codec) @ 60 fps, using the browser * defined default bit rate. * - * @param canvas - - * @param fileName - - * @param opts - + * @param canvas - + * @param fileName - + * @param opts - */ export const canvasRecorder = ( - canvas: HTMLCanvasElement, - fileName: string, - opts?: Partial + canvas: HTMLCanvasElement, + fileName: string, + opts?: Partial ) => { - opts = { fps: 60, mimeType: "video/webm; codecs=vp9", ...opts }; - const stream = canvas.captureStream(opts.fps); - const recorder = new MediaRecorder(stream, { - mimeType: opts.mimeType, - }); - let blobs: Blob[] = []; - recorder.ondataavailable = (e) => { - if (e.data.size > 0) { - blobs.push(e.data); - downloadWithMime( - fileName, - new Blob(blobs, { type: opts!.mimeType }), - { - mime: opts!.mimeType!, - } - ); - blobs = []; - } - }; - return recorder; + opts = { fps: 60, mimeType: "video/webm; codecs=vp9", ...opts }; + const stream = canvas.captureStream(opts.fps); + const recorder = new MediaRecorder(stream, { + mimeType: opts.mimeType, + }); + let blobs: Blob[] = []; + recorder.ondataavailable = (e) => { + if (e.data.size > 0) { + blobs.push(e.data); + downloadWithMime( + fileName, + new Blob(blobs, { type: opts!.mimeType }), + { + mime: opts!.mimeType!, + } + ); + blobs = []; + } + }; + return recorder; }; diff --git a/packages/dl-asset/src/download.ts b/packages/dl-asset/src/download.ts index 4bb259f7ea..478c7c6a1d 100644 --- a/packages/dl-asset/src/download.ts +++ b/packages/dl-asset/src/download.ts @@ -9,18 +9,18 @@ import { downloadWithMime } from "./raw.js"; * derive it from the given filename's extension (e.g. `.svg`) and if * that fails, falls back to default value. * - * @param name - - * @param src - - * @param opts - + * @param name - + * @param src - + * @param opts - */ export const download = ( - name: string, - src: string | TypedArray | ArrayBuffer | Blob, - opts: Partial = {} + name: string, + src: string | TypedArray | ArrayBuffer | Blob, + opts: Partial = {} ) => { - if (opts.mime === undefined) { - const match = /\.(\w+)$/.exec(name); - opts.mime = preferredType(match ? match[1] : "bin"); - } - return downloadWithMime(name, src, opts); + if (opts.mime === undefined) { + const match = /\.(\w+)$/.exec(name); + opts.mime = preferredType(match ? match[1] : "bin"); + } + return downloadWithMime(name, src, opts); }; diff --git a/packages/dl-asset/src/raw.ts b/packages/dl-asset/src/raw.ts index 8e6791bbf9..3cd6ec0147 100644 --- a/packages/dl-asset/src/raw.ts +++ b/packages/dl-asset/src/raw.ts @@ -20,34 +20,34 @@ import type { DownloadOpts } from "./api.js"; * again after `expire` millseconds (default: 10000) to free up memory. * The URL won't be expired if `expire <= 0`. * - * @param name - - * @param src - - * @param opts - + * @param name - + * @param src - + * @param opts - */ export const downloadWithMime = ( - name: string, - src: string | TypedArray | ArrayBuffer | Blob, - opts: Partial & { mime: string } + name: string, + src: string | TypedArray | ArrayBuffer | Blob, + opts: Partial & { mime: string } ) => { - const _opts = { - expire: 1e4, - utf8: false, - ...opts, - }; - if (isString(src) && _opts.utf8) { - src = new TextEncoder().encode(src); - _opts.mime += ";charset=UTF-8"; - } - const uri = URL.createObjectURL( - !(src instanceof Blob) ? new Blob([src], { type: _opts.mime }) : src - ); - const link = document.createElement("a"); - link.setAttribute("download", name); - link.setAttribute("href", uri); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - if (_opts.expire > 0) { - setTimeout(() => URL.revokeObjectURL(uri), _opts.expire); - } + const _opts = { + expire: 1e4, + utf8: false, + ...opts, + }; + if (isString(src) && _opts.utf8) { + src = new TextEncoder().encode(src); + _opts.mime += ";charset=UTF-8"; + } + const uri = URL.createObjectURL( + !(src instanceof Blob) ? new Blob([src], { type: _opts.mime }) : src + ); + const link = document.createElement("a"); + link.setAttribute("download", name); + link.setAttribute("href", uri); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + if (_opts.expire > 0) { + setTimeout(() => URL.revokeObjectURL(uri), _opts.expire); + } }; diff --git a/packages/dl-asset/tsconfig.json b/packages/dl-asset/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/dl-asset/tsconfig.json +++ b/packages/dl-asset/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/dlogic/api-extractor.json b/packages/dlogic/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/dlogic/api-extractor.json +++ b/packages/dlogic/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/dlogic/package.json b/packages/dlogic/package.json index 4106f63400..485b40267b 100644 --- a/packages/dlogic/package.json +++ b/packages/dlogic/package.json @@ -1,73 +1,73 @@ { - "name": "@thi.ng/dlogic", - "version": "2.1.8", - "description": "Assorted digital logic ops / constructs", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/logic#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "boolean", - "digital", - "functional", - "logic", - "simulation", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "year": 2017 - } + "name": "@thi.ng/dlogic", + "version": "2.1.8", + "description": "Assorted digital logic ops / constructs", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/logic#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "boolean", + "digital", + "functional", + "logic", + "simulation", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "year": 2017 + } } diff --git a/packages/dlogic/src/index.ts b/packages/dlogic/src/index.ts index f831c2c647..0b38f5c989 100644 --- a/packages/dlogic/src/index.ts +++ b/packages/dlogic/src/index.ts @@ -5,8 +5,8 @@ type Op3 = FnU3; type Op4 = FnU4; export interface Sum { - s: T; - c: boolean; + s: T; + c: boolean; } /** @@ -209,7 +209,7 @@ export const oai22: Op4 = (a, b, c, d) => !((a || b) && (c || d)); * @param s - */ export const mux: Op3 = (a: boolean, b: boolean, s: boolean) => - (a && !s) || (b && s); + (a && !s) || (b && s); /** * {@link https://en.wikipedia.org/wiki/NAND_logic#DEMUX} @@ -225,8 +225,8 @@ export const mux: Op3 = (a: boolean, b: boolean, s: boolean) => * @param s - */ export const demux: FnU2 = (i, s) => [ - i && !s, - i && s, + i && !s, + i && s, ]; /** @@ -236,8 +236,8 @@ export const demux: FnU2 = (i, s) => [ * @param b - */ export const hadd1: FnU2> = (a, b) => ({ - s: a !== b, - c: a && b, + s: a !== b, + c: a && b, }); /** @@ -248,8 +248,8 @@ export const hadd1: FnU2> = (a, b) => ({ * @param c - */ export const fadd1: FnU3> = (a, b, c) => ({ - s: (a !== b) !== c, - c: (a !== b && c) || (a && b), + s: (a !== b) !== c, + c: (a !== b && c) || (a && b), }); /** @@ -260,13 +260,13 @@ export const fadd1: FnU3> = (a, b, c) => ({ * @param c - */ export const rca = (a: boolean[], b: boolean[], c: boolean): Sum => { - const s: boolean[] = []; - for (let n = a.length, i = 0; i < n; i++) { - const r = fadd1(a[i], b[i], c); - s.push(r.s); - c = r.c; - } - return { s, c }; + const s: boolean[] = []; + for (let n = a.length, i = 0; i < n; i++) { + const r = fadd1(a[i], b[i], c); + s.push(r.s); + c = r.c; + } + return { s, c }; }; /** @@ -277,14 +277,14 @@ export const rca = (a: boolean[], b: boolean[], c: boolean): Sum => { * @param n - */ export const delay = (n: number) => { - const buf = new Array(n).fill(false); - let i = 0; - return n > 0 - ? (x: boolean) => { - const y = buf[i]; - buf[i++] = x; - i %= n; - return y; - } - : (x: boolean) => x; + const buf = new Array(n).fill(false); + let i = 0; + return n > 0 + ? (x: boolean) => { + const y = buf[i]; + buf[i++] = x; + i %= n; + return y; + } + : (x: boolean) => x; }; diff --git a/packages/dlogic/tsconfig.json b/packages/dlogic/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/dlogic/tsconfig.json +++ b/packages/dlogic/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/dot/api-extractor.json b/packages/dot/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/dot/api-extractor.json +++ b/packages/dot/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/dot/package.json b/packages/dot/package.json index dd50b342fc..c65fa92d01 100644 --- a/packages/dot/package.json +++ b/packages/dot/package.json @@ -1,87 +1,87 @@ { - "name": "@thi.ng/dot", - "version": "2.1.9", - "description": "Graphviz document abstraction & serialization to DOT format", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dot#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "export", - "fileformat", - "graph", - "graphviz", - "style", - "typescript", - "visualization" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./serialize": { - "default": "./serialize.js" - } - }, - "thi.ng": { - "related": [ - "adjacency", - "dgraph" - ], - "status": "beta", - "year": 2018 - } + "name": "@thi.ng/dot", + "version": "2.1.9", + "description": "Graphviz document abstraction & serialization to DOT format", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dot#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "export", + "fileformat", + "graph", + "graphviz", + "style", + "typescript", + "visualization" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./serialize": { + "default": "./serialize.js" + } + }, + "thi.ng": { + "related": [ + "adjacency", + "dgraph" + ], + "status": "beta", + "year": 2018 + } } diff --git a/packages/dot/src/api.ts b/packages/dot/src/api.ts index 3806cb6250..46380c1803 100644 --- a/packages/dot/src/api.ts +++ b/packages/dot/src/api.ts @@ -1,109 +1,109 @@ import type { IObjectOf } from "@thi.ng/api"; export type NodeShape = - | "box" - | "circle" - | "diamond" - | "doublecircle" - | "doubleoctagon" - | "egg" - | "ellipse" - | "hexagon" - | "house" - | "invhouse" - | "invtrapezium" - | "invtriangle" - | "Mcircle" - | "Mdiamond" - | "Mrecord" - | "Msquare" - | "none" - | "octagon" - | "parallelogram" - | "plaintext" - | "point" - | "polygon" - | "record" - | "trapezium" - | "triangle" - | "triple_octagon"; + | "box" + | "circle" + | "diamond" + | "doublecircle" + | "doubleoctagon" + | "egg" + | "ellipse" + | "hexagon" + | "house" + | "invhouse" + | "invtrapezium" + | "invtriangle" + | "Mcircle" + | "Mdiamond" + | "Mrecord" + | "Msquare" + | "none" + | "octagon" + | "parallelogram" + | "plaintext" + | "point" + | "polygon" + | "record" + | "trapezium" + | "triangle" + | "triple_octagon"; export type Color = string | number[]; export interface GraphAttribs { - bgcolor: Color; - clusterrank: "global" | "local" | "none"; - color: Color; - compound: boolean; - concentrate: boolean; - dpi: number; - edge: Partial; - fillcolor: Color; - fontcolor: Color; - fontname: string; - fontsize: number; - label: string; - labeljust: "l" | "r" | "c"; - labelloc: "t" | "b"; - landscape: boolean; - margin: number; - node: Partial; - nodesep: number; - orientation: "portrait" | "landscape"; - rank: "same" | "min" | "max" | "source" | "sink"; - rankdir: "LR" | "TB"; - ranksep: number; - ratio: string; - [id: string]: any; + bgcolor: Color; + clusterrank: "global" | "local" | "none"; + color: Color; + compound: boolean; + concentrate: boolean; + dpi: number; + edge: Partial; + fillcolor: Color; + fontcolor: Color; + fontname: string; + fontsize: number; + label: string; + labeljust: "l" | "r" | "c"; + labelloc: "t" | "b"; + landscape: boolean; + margin: number; + node: Partial; + nodesep: number; + orientation: "portrait" | "landscape"; + rank: "same" | "min" | "max" | "source" | "sink"; + rankdir: "LR" | "TB"; + ranksep: number; + ratio: string; + [id: string]: any; } export interface Graph { - attribs?: Partial; - directed?: boolean; - edges: Edge[]; - id?: string; - nodes: IObjectOf>; - sub?: Graph[]; - include?: string; + attribs?: Partial; + directed?: boolean; + edges: Edge[]; + id?: string; + nodes: IObjectOf>; + sub?: Graph[]; + include?: string; } export interface NodeAttribs { - color: Color; - fillcolor: Color; - fontcolor: Color; - fontname: string; - fontsize: number; - penwidth: number; - peripheries: number; - shape: NodeShape; - sides: number; - skew: number; - style: string; - [id: string]: any; + color: Color; + fillcolor: Color; + fontcolor: Color; + fontname: string; + fontsize: number; + penwidth: number; + peripheries: number; + shape: NodeShape; + sides: number; + skew: number; + style: string; + [id: string]: any; } export interface Node extends NodeAttribs { - ins: IObjectOf; - outs: IObjectOf; - label: string; - tooltip: string; - target: string; - url: string; + ins: IObjectOf; + outs: IObjectOf; + label: string; + tooltip: string; + target: string; + url: string; } export interface EdgeAttribs { - color: Color; - fontcolor: Color; - fontname: string; - fontsize: number; - label: string; - style: string; - [id: string]: any; + color: Color; + fontcolor: Color; + fontname: string; + fontsize: number; + label: string; + style: string; + [id: string]: any; } export interface Edge extends Partial { - src: PropertyKey; - dest: PropertyKey; - srcPort?: PropertyKey; - destPort?: PropertyKey; + src: PropertyKey; + dest: PropertyKey; + srcPort?: PropertyKey; + destPort?: PropertyKey; } diff --git a/packages/dot/src/serialize.ts b/packages/dot/src/serialize.ts index 87a4289b27..9973bc5ffa 100644 --- a/packages/dot/src/serialize.ts +++ b/packages/dot/src/serialize.ts @@ -9,119 +9,119 @@ const nextSubgraphID = () => "cluster" + nextID++; const wrapQ = (x: any) => `"${x}"`; const escape = (x: any) => - String(x).replace(/\"/g, `\\"`).replace(/\n/g, "\\n"); + String(x).replace(/\"/g, `\\"`).replace(/\n/g, "\\n"); const formatGraphAttribs = (attribs: Partial, acc: string[]) => { - for (let a in attribs) { - let v = attribs[a]; - switch (a) { - case "bgcolor": - case "color": - case "fillcolor": - case "fontcolor": - isArray(v) && (v = v.join(",")); - break; - case "edge": - acc.push(`edge[${formatAttribs(v)}];`); - continue; - case "node": - acc.push(`node[${formatAttribs(v)}];`); - continue; - default: - break; - } - acc.push(`${a}="${escape(v)}";`); - } - return acc; + for (let a in attribs) { + let v = attribs[a]; + switch (a) { + case "bgcolor": + case "color": + case "fillcolor": + case "fontcolor": + isArray(v) && (v = v.join(",")); + break; + case "edge": + acc.push(`edge[${formatAttribs(v)}];`); + continue; + case "node": + acc.push(`node[${formatAttribs(v)}];`); + continue; + default: + break; + } + acc.push(`${a}="${escape(v)}";`); + } + return acc; }; const formatAttribs = (attribs: Partial) => { - const acc: string[] = []; - for (let a in attribs) { - let v = attribs[a]; - switch (a) { - case "color": - case "fillcolor": - case "fontcolor": - isArray(v) && (v = v.join(",")); - break; - case "label": - if ((attribs).ins || (attribs).outs) { - v = formatPortLabel(attribs, v); - } - break; - case "url": - a = "URL"; - break; - case "ins": - case "outs": - case "src": - case "dest": - case "srcPort": - case "destPort": - continue; - default: - } - acc.push(`${a}="${escape(v)}"`); - } - return acc.join(", "); + const acc: string[] = []; + for (let a in attribs) { + let v = attribs[a]; + switch (a) { + case "color": + case "fillcolor": + case "fontcolor": + isArray(v) && (v = v.join(",")); + break; + case "label": + if ((attribs).ins || (attribs).outs) { + v = formatPortLabel(attribs, v); + } + break; + case "url": + a = "URL"; + break; + case "ins": + case "outs": + case "src": + case "dest": + case "srcPort": + case "destPort": + continue; + default: + } + acc.push(`${a}="${escape(v)}"`); + } + return acc.join(", "); }; const formatPorts = (ports: IObjectOf) => { - const acc: string[] = []; - for (let i in ports) { - acc.push(`<${i}> ${escape(ports[i])}`); - } - return `{ ${acc.join(" | ")} }`; + const acc: string[] = []; + for (let i in ports) { + acc.push(`<${i}> ${escape(ports[i])}`); + } + return `{ ${acc.join(" | ")} }`; }; const formatPortLabel = (node: Partial, label: string) => { - const acc: string[] = []; - node.ins && acc.push(formatPorts(node.ins)); - acc.push(escape(label)); - node.outs && acc.push(formatPorts(node.outs)); - return acc.join(" | "); + const acc: string[] = []; + node.ins && acc.push(formatPorts(node.ins)); + acc.push(escape(label)); + node.outs && acc.push(formatPorts(node.outs)); + return acc.join(" | "); }; export const serializeNode = (id: string, n: Partial) => { - const attribs = formatAttribs(n); - return attribs.length ? `"${id}"[${attribs}];` : `"${id}";`; + const attribs = formatAttribs(n); + return attribs.length ? `"${id}"[${attribs}];` : `"${id}";`; }; export const serializeEdge = (e: Edge, directed = true) => { - const acc: string[] = [wrapQ(e.src)]; - e.srcPort != null && acc.push(":", wrapQ(e.srcPort)); - acc.push(directed ? " -> " : " -- "); - acc.push(wrapQ(e.dest)); - e.destPort != null && acc.push(":", wrapQ(e.destPort)); - const attribs = formatAttribs(e); - attribs.length && acc.push("[", attribs, "]"); - acc.push(";"); - return acc.join(""); + const acc: string[] = [wrapQ(e.src)]; + e.srcPort != null && acc.push(":", wrapQ(e.srcPort)); + acc.push(directed ? " -> " : " -- "); + acc.push(wrapQ(e.dest)); + e.destPort != null && acc.push(":", wrapQ(e.destPort)); + const attribs = formatAttribs(e); + attribs.length && acc.push("[", attribs, "]"); + acc.push(";"); + return acc.join(""); }; export const serializeGraph = (graph: Graph, isSub = false) => { - const directed = graph.directed !== false; - const acc = isSub - ? [`subgraph ${graph.id || nextSubgraphID()} {`] - : [`${directed ? "di" : ""}graph ${graph.id || "g"} {`]; - if (graph.include) { - acc.push(graph.include); - } - if (graph.attribs) { - formatGraphAttribs(graph.attribs, acc); - } - for (let id in graph.nodes) { - acc.push(serializeNode(id, graph.nodes[id])); - } - for (let e of graph.edges) { - acc.push(serializeEdge(e, directed)); - } - if (graph.sub) { - for (let sub of graph.sub) { - acc.push(serializeGraph(sub, true)); - } - } - acc.push("}"); - return acc.join("\n"); + const directed = graph.directed !== false; + const acc = isSub + ? [`subgraph ${graph.id || nextSubgraphID()} {`] + : [`${directed ? "di" : ""}graph ${graph.id || "g"} {`]; + if (graph.include) { + acc.push(graph.include); + } + if (graph.attribs) { + formatGraphAttribs(graph.attribs, acc); + } + for (let id in graph.nodes) { + acc.push(serializeNode(id, graph.nodes[id])); + } + for (let e of graph.edges) { + acc.push(serializeEdge(e, directed)); + } + if (graph.sub) { + for (let sub of graph.sub) { + acc.push(serializeGraph(sub, true)); + } + } + acc.push("}"); + return acc.join("\n"); }; diff --git a/packages/dot/test/example.ts b/packages/dot/test/example.ts index ab3ec215e4..3508792e7e 100644 --- a/packages/dot/test/example.ts +++ b/packages/dot/test/example.ts @@ -1,73 +1,73 @@ import * as fs from "fs"; -import * as dot from "../src/index.js" +import * as dot from "../src/index.js"; // node type style presets const terminal: Partial = { - color: "black", - fontcolor: "white", + color: "black", + fontcolor: "white", }; // operator nodes use "Mrecord" shape // with input and output port declarations const operator: Partial = { - fillcolor: "yellow", - shape: "Mrecord", - ins: { 0: "a", 1: "b" }, - outs: { out: "out" }, + fillcolor: "yellow", + shape: "Mrecord", + ins: { 0: "a", 1: "b" }, + outs: { out: "out" }, }; try { - fs.mkdirSync("export"); + fs.mkdirSync("export"); } catch (e) {} fs.writeFileSync( - "export/dot-example.dot", - dot.serializeGraph({ - directed: true, // default - // graph attributes - attribs: { - rankdir: "LR", - fontname: "Inconsolata", - fontsize: 9, - fontcolor: "gray", - label: "Generated with @thi.ng/dot", - labeljust: "l", - labelloc: "b", - // node defaults - node: { - style: "filled", - fontname: "Inconsolata", - fontsize: 11, - }, - // edge defaults - edge: { - arrowsize: 0.75, - fontname: "Inconsolata", - fontsize: 9, - }, - }, - // graph nodes (the keys are used as node IDs) - // use spread operator to inject style presets - nodes: { - x: { ...terminal, label: "x (12)" }, - y: { ...terminal, label: "y (23)" }, - res: { ...terminal, label: "res (8050)", peripheries: 2 }, - op1: { ...operator, fillcolor: "green", label: "op1\n(+)" }, - op2: { ...operator, label: "op2\n(*)" }, - }, - // graph edges (w/ optional ports & extra attribs) - edges: [ - { src: "x", dest: "op1", destPort: 1 }, - { src: "y", dest: "op1", destPort: 0 }, - { - src: "y", - dest: "op2", - destPort: 0, - label: "xform", - color: "blue", - }, - { src: "op1", srcPort: "out", dest: "op2", destPort: 1 }, - { src: "op2", srcPort: "out", dest: "res" }, - ], - }) + "export/dot-example.dot", + dot.serializeGraph({ + directed: true, // default + // graph attributes + attribs: { + rankdir: "LR", + fontname: "Inconsolata", + fontsize: 9, + fontcolor: "gray", + label: "Generated with @thi.ng/dot", + labeljust: "l", + labelloc: "b", + // node defaults + node: { + style: "filled", + fontname: "Inconsolata", + fontsize: 11, + }, + // edge defaults + edge: { + arrowsize: 0.75, + fontname: "Inconsolata", + fontsize: 9, + }, + }, + // graph nodes (the keys are used as node IDs) + // use spread operator to inject style presets + nodes: { + x: { ...terminal, label: "x (12)" }, + y: { ...terminal, label: "y (23)" }, + res: { ...terminal, label: "res (8050)", peripheries: 2 }, + op1: { ...operator, fillcolor: "green", label: "op1\n(+)" }, + op2: { ...operator, label: "op2\n(*)" }, + }, + // graph edges (w/ optional ports & extra attribs) + edges: [ + { src: "x", dest: "op1", destPort: 1 }, + { src: "y", dest: "op1", destPort: 0 }, + { + src: "y", + dest: "op2", + destPort: 0, + label: "xform", + color: "blue", + }, + { src: "op1", srcPort: "out", dest: "op2", destPort: 1 }, + { src: "op2", srcPort: "out", dest: "res" }, + ], + }) ); diff --git a/packages/dot/tsconfig.json b/packages/dot/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/dot/tsconfig.json +++ b/packages/dot/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/dsp-io-wav/api-extractor.json b/packages/dsp-io-wav/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/dsp-io-wav/api-extractor.json +++ b/packages/dsp-io-wav/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/dsp-io-wav/package.json b/packages/dsp-io-wav/package.json index e2b6a1dbad..6850ae90ed 100644 --- a/packages/dsp-io-wav/package.json +++ b/packages/dsp-io-wav/package.json @@ -1,92 +1,92 @@ { - "name": "@thi.ng/dsp-io-wav", - "version": "2.1.12", - "description": "WAV file format generation", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dsp-io-wav#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/binary": "^3.3.0", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/transducers-binary": "^2.1.12" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "8bit", - "16bit", - "24bit", - "32bit", - "audio", - "binary", - "channel", - "dsp", - "export", - "fileformat", - "mono", - "stereo", - "typescript", - "wav" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./write": { - "default": "./write.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/dsp", - "year": 2020 - } + "name": "@thi.ng/dsp-io-wav", + "version": "2.1.12", + "description": "WAV file format generation", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dsp-io-wav#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/binary": "^3.3.0", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/transducers-binary": "^2.1.12" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "8bit", + "16bit", + "24bit", + "32bit", + "audio", + "binary", + "channel", + "dsp", + "export", + "fileformat", + "mono", + "stereo", + "typescript", + "wav" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./write": { + "default": "./write.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/dsp", + "year": 2020 + } } diff --git a/packages/dsp-io-wav/src/api.ts b/packages/dsp-io-wav/src/api.ts index 68c395fe1d..a1739c92c5 100644 --- a/packages/dsp-io-wav/src/api.ts +++ b/packages/dsp-io-wav/src/api.ts @@ -1,18 +1,18 @@ export interface WavSpec { - /** - * Sample rate in Hz. - */ - sampleRate: number; - /** - * Number of channels. - */ - channels: number; - /** - * Number of samples (irrespective of channel count) - */ - length: number; - /** - * Bits per sample - */ - bits: 8 | 16 | 24 | 32; + /** + * Sample rate in Hz. + */ + sampleRate: number; + /** + * Number of channels. + */ + channels: number; + /** + * Number of samples (irrespective of channel count) + */ + length: number; + /** + * Bits per sample + */ + bits: 8 | 16 | 24 | 32; } diff --git a/packages/dsp-io-wav/src/write.ts b/packages/dsp-io-wav/src/write.ts index 639acb5870..c0883f6faf 100644 --- a/packages/dsp-io-wav/src/write.ts +++ b/packages/dsp-io-wav/src/write.ts @@ -3,12 +3,12 @@ import { f32u16, f32u24, f32u32, f32u8 } from "@thi.ng/binary/float"; import { assert } from "@thi.ng/errors/assert"; import type { BinStructItem } from "@thi.ng/transducers-binary"; import { - asBytes, - bytes, - u16, - u24, - u32, - u8, + asBytes, + bytes, + u16, + u24, + u32, + u8, } from "@thi.ng/transducers-binary/bytes"; import { comp } from "@thi.ng/transducers/comp"; import { concat } from "@thi.ng/transducers/concat"; @@ -21,31 +21,31 @@ import type { WavSpec } from "./api.js"; const HEADER_SIZE = 44; const CONVERTERS: IObjectOf> = { - 8: (x: number) => u8(f32u8(x)), - 16: (x: number) => u16(f32u16(x), true), - 24: (x: number) => u24(f32u24(x), true), - 32: (x: number) => u32(f32u32(x), true), + 8: (x: number) => u8(f32u8(x)), + 16: (x: number) => u16(f32u16(x), true), + 24: (x: number) => u24(f32u24(x), true), + 32: (x: number) => u32(f32u32(x), true), }; export const wavHeader = (spec: WavSpec): BinStructItem[] => { - const bytesPerSample = spec.bits >> 3; - const blockAlign = spec.channels * bytesPerSample; - const dataLength = spec.length * blockAlign; - return [ - u32(0x52494646, false), // 'RIFF' - u32(dataLength + HEADER_SIZE - 8, true), // riff len - u32(0x57415645, false), // 'WAVE' - u32(0x666d7420, false), // 'fmt ' - u32(16, true), // fmt len, - u16(1, true), // audio format id - u16(spec.channels, true), - u32(spec.sampleRate, true), - u32(spec.sampleRate * blockAlign, true), // byte rate - u16(blockAlign, true), - u16(spec.bits, true), - u32(0x64617461, false), // 'data' - u32(dataLength, true), - ]; + const bytesPerSample = spec.bits >> 3; + const blockAlign = spec.channels * bytesPerSample; + const dataLength = spec.length * blockAlign; + return [ + u32(0x52494646, false), // 'RIFF' + u32(dataLength + HEADER_SIZE - 8, true), // riff len + u32(0x57415645, false), // 'WAVE' + u32(0x666d7420, false), // 'fmt ' + u32(16, true), // fmt len, + u16(1, true), // audio format id + u16(spec.channels, true), + u32(spec.sampleRate, true), + u32(spec.sampleRate * blockAlign, true), // byte rate + u16(blockAlign, true), + u16(spec.bits, true), + u32(0x64617461, false), // 'data' + u32(dataLength, true), + ]; }; /** @@ -77,38 +77,38 @@ export const wavHeader = (spec: WavSpec): BinStructItem[] => { * ) * ``` * - * @param spec - - * @param src - + * @param spec - + * @param src - */ export const wavByteArray = (spec: WavSpec, src: Iterable) => { - const convert = CONVERTERS[spec.bits]; - assert(!!convert, `unsupported bits/sample: ${spec.bits}`); - return reduce( - bytes(), - new Uint8Array( - HEADER_SIZE + spec.length * spec.channels * (spec.bits >> 3) - ), - concat( - wavHeader(spec), - iterator(comp(take(spec.length * spec.channels), map(convert)), src) - ) - ); + const convert = CONVERTERS[spec.bits]; + assert(!!convert, `unsupported bits/sample: ${spec.bits}`); + return reduce( + bytes(), + new Uint8Array( + HEADER_SIZE + spec.length * spec.channels * (spec.bits >> 3) + ), + concat( + wavHeader(spec), + iterator(comp(take(spec.length * spec.channels), map(convert)), src) + ) + ); }; /** * Similar to {@link wavByteArray}, but yields an iterator of the result * bytes, not an actual byte array. * - * @param spec - - * @param src - + * @param spec - + * @param src - */ export const wavBytes = (spec: WavSpec, src: Iterable) => { - const convert = CONVERTERS[spec.bits]; - assert(!!convert, `unsupported bits/sample: ${spec.bits}`); - return asBytes( - concat( - wavHeader(spec), - iterator(comp(take(spec.length * spec.channels), map(convert)), src) - ) - ); + const convert = CONVERTERS[spec.bits]; + assert(!!convert, `unsupported bits/sample: ${spec.bits}`); + return asBytes( + concat( + wavHeader(spec), + iterator(comp(take(spec.length * spec.channels), map(convert)), src) + ) + ); }; diff --git a/packages/dsp-io-wav/test/index.ts b/packages/dsp-io-wav/test/index.ts index 741a5ae81f..332909c124 100644 --- a/packages/dsp-io-wav/test/index.ts +++ b/packages/dsp-io-wav/test/index.ts @@ -1,18 +1,18 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { wavByteArray } from "../src/index.js" +import { wavByteArray } from "../src/index.js"; group("dsp-io-wav", { - "mono 48kHz/16 bits": () => { - assert.deepStrictEqual( - [ - ...wavByteArray( - { sampleRate: 48000, channels: 1, length: 4, bits: 16 }, - [-1, -0.5, 0, 1] - ), - ], - // prettier-ignore - [ + "mono 48kHz/16 bits": () => { + assert.deepStrictEqual( + [ + ...wavByteArray( + { sampleRate: 48000, channels: 1, length: 4, bits: 16 }, + [-1, -0.5, 0, 1] + ), + ], + // prettier-ignore + [ 0x52, 0x49, 0x46, 0x46, 0x2c, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, @@ -22,6 +22,6 @@ group("dsp-io-wav", { 0x00, 0x00, 0x01, 0x80, 0x01, 0xc0, 0x00, 0x00, 0xff, 0x7f ] - ); - }, + ); + }, }); diff --git a/packages/dsp-io-wav/tsconfig.json b/packages/dsp-io-wav/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/dsp-io-wav/tsconfig.json +++ b/packages/dsp-io-wav/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/dsp/api-extractor.json b/packages/dsp/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/dsp/api-extractor.json +++ b/packages/dsp/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/dsp/package.json b/packages/dsp/package.json index d01e0d8dc1..249f13602e 100644 --- a/packages/dsp/package.json +++ b/packages/dsp/package.json @@ -1,275 +1,275 @@ { - "name": "@thi.ng/dsp", - "version": "4.2.6", - "description": "Composable signal generators, oscillators, filters, FFT, spectrum, windowing & related DSP utils", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dsp#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/math": "^5.3.4", - "@thi.ng/random": "^3.3.3", - "@thi.ng/transducers": "^8.3.7" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "allpass", - "analysis", - "audio", - "biquad", - "channel", - "clipping", - "composition", - "datastructure", - "delay", - "dsp", - "envelope", - "feedback", - "fft", - "filter", - "generator", - "impulse", - "iterator", - "lfo", - "math", - "noise", - "oscillator", - "signal", - "svf", - "synthesis", - "typescript", - "waveform", - "waveshaper", - "windowing" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "internal" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./add": { - "default": "./add.js" - }, - "./addg": { - "default": "./addg.js" - }, - "./adsr": { - "default": "./adsr.js" - }, - "./agen": { - "default": "./agen.js" - }, - "./allpass": { - "default": "./allpass.js" - }, - "./alt": { - "default": "./alt.js" - }, - "./anti-alias": { - "default": "./anti-alias.js" - }, - "./api": { - "default": "./api.js" - }, - "./aproc": { - "default": "./aproc.js" - }, - "./biquad": { - "default": "./biquad.js" - }, - "./bounce": { - "default": "./bounce.js" - }, - "./complex": { - "default": "./complex.js" - }, - "./const": { - "default": "./const.js" - }, - "./convert": { - "default": "./convert.js" - }, - "./cosine": { - "default": "./cosine.js" - }, - "./curve": { - "default": "./curve.js" - }, - "./dcblock": { - "default": "./dcblock.js" - }, - "./delay": { - "default": "./delay.js" - }, - "./feedback-delay": { - "default": "./feedback-delay.js" - }, - "./fft": { - "default": "./fft.js" - }, - "./filter-response": { - "default": "./filter-response.js" - }, - "./foldback": { - "default": "./foldback.js" - }, - "./impulse-train": { - "default": "./impulse-train.js" - }, - "./impulse": { - "default": "./impulse.js" - }, - "./integrator": { - "default": "./integrator.js" - }, - "./iterable": { - "default": "./iterable.js" - }, - "./line": { - "default": "./line.js" - }, - "./madd": { - "default": "./madd.js" - }, - "./mapg": { - "default": "./mapg.js" - }, - "./mix": { - "default": "./mix.js" - }, - "./mul": { - "default": "./mul.js" - }, - "./multiplex": { - "default": "./multiplex.js" - }, - "./onepole": { - "default": "./onepole.js" - }, - "./osc-additive": { - "default": "./osc-additive.js" - }, - "./osc-cos": { - "default": "./osc-cos.js" - }, - "./osc-dsf": { - "default": "./osc-dsf.js" - }, - "./osc-mix": { - "default": "./osc-mix.js" - }, - "./osc-parabolic": { - "default": "./osc-parabolic.js" - }, - "./osc-rect": { - "default": "./osc-rect.js" - }, - "./osc-saw": { - "default": "./osc-saw.js" - }, - "./osc-sin": { - "default": "./osc-sin.js" - }, - "./osc-tri": { - "default": "./osc-tri.js" - }, - "./osc-wavetable": { - "default": "./osc-wavetable.js" - }, - "./osc": { - "default": "./osc.js" - }, - "./pink-noise": { - "default": "./pink-noise.js" - }, - "./pipe": { - "default": "./pipe.js" - }, - "./power": { - "default": "./power.js" - }, - "./product": { - "default": "./product.js" - }, - "./reciprocal": { - "default": "./reciprocal.js" - }, - "./serial": { - "default": "./serial.js" - }, - "./sincos": { - "default": "./sincos.js" - }, - "./sum": { - "default": "./sum.js" - }, - "./svf": { - "default": "./svf.js" - }, - "./sweep": { - "default": "./sweep.js" - }, - "./waveshaper": { - "default": "./waveshaper.js" - }, - "./white-noise": { - "default": "./white-noise.js" - }, - "./window": { - "default": "./window.js" - } - }, - "thi.ng": { - "related": [ - "math" - ], - "year": 2015 - } + "name": "@thi.ng/dsp", + "version": "4.2.6", + "description": "Composable signal generators, oscillators, filters, FFT, spectrum, windowing & related DSP utils", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/dsp#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/math": "^5.3.4", + "@thi.ng/random": "^3.3.3", + "@thi.ng/transducers": "^8.3.7" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "allpass", + "analysis", + "audio", + "biquad", + "channel", + "clipping", + "composition", + "datastructure", + "delay", + "dsp", + "envelope", + "feedback", + "fft", + "filter", + "generator", + "impulse", + "iterator", + "lfo", + "math", + "noise", + "oscillator", + "signal", + "svf", + "synthesis", + "typescript", + "waveform", + "waveshaper", + "windowing" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "internal" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./add": { + "default": "./add.js" + }, + "./addg": { + "default": "./addg.js" + }, + "./adsr": { + "default": "./adsr.js" + }, + "./agen": { + "default": "./agen.js" + }, + "./allpass": { + "default": "./allpass.js" + }, + "./alt": { + "default": "./alt.js" + }, + "./anti-alias": { + "default": "./anti-alias.js" + }, + "./api": { + "default": "./api.js" + }, + "./aproc": { + "default": "./aproc.js" + }, + "./biquad": { + "default": "./biquad.js" + }, + "./bounce": { + "default": "./bounce.js" + }, + "./complex": { + "default": "./complex.js" + }, + "./const": { + "default": "./const.js" + }, + "./convert": { + "default": "./convert.js" + }, + "./cosine": { + "default": "./cosine.js" + }, + "./curve": { + "default": "./curve.js" + }, + "./dcblock": { + "default": "./dcblock.js" + }, + "./delay": { + "default": "./delay.js" + }, + "./feedback-delay": { + "default": "./feedback-delay.js" + }, + "./fft": { + "default": "./fft.js" + }, + "./filter-response": { + "default": "./filter-response.js" + }, + "./foldback": { + "default": "./foldback.js" + }, + "./impulse-train": { + "default": "./impulse-train.js" + }, + "./impulse": { + "default": "./impulse.js" + }, + "./integrator": { + "default": "./integrator.js" + }, + "./iterable": { + "default": "./iterable.js" + }, + "./line": { + "default": "./line.js" + }, + "./madd": { + "default": "./madd.js" + }, + "./mapg": { + "default": "./mapg.js" + }, + "./mix": { + "default": "./mix.js" + }, + "./mul": { + "default": "./mul.js" + }, + "./multiplex": { + "default": "./multiplex.js" + }, + "./onepole": { + "default": "./onepole.js" + }, + "./osc-additive": { + "default": "./osc-additive.js" + }, + "./osc-cos": { + "default": "./osc-cos.js" + }, + "./osc-dsf": { + "default": "./osc-dsf.js" + }, + "./osc-mix": { + "default": "./osc-mix.js" + }, + "./osc-parabolic": { + "default": "./osc-parabolic.js" + }, + "./osc-rect": { + "default": "./osc-rect.js" + }, + "./osc-saw": { + "default": "./osc-saw.js" + }, + "./osc-sin": { + "default": "./osc-sin.js" + }, + "./osc-tri": { + "default": "./osc-tri.js" + }, + "./osc-wavetable": { + "default": "./osc-wavetable.js" + }, + "./osc": { + "default": "./osc.js" + }, + "./pink-noise": { + "default": "./pink-noise.js" + }, + "./pipe": { + "default": "./pipe.js" + }, + "./power": { + "default": "./power.js" + }, + "./product": { + "default": "./product.js" + }, + "./reciprocal": { + "default": "./reciprocal.js" + }, + "./serial": { + "default": "./serial.js" + }, + "./sincos": { + "default": "./sincos.js" + }, + "./sum": { + "default": "./sum.js" + }, + "./svf": { + "default": "./svf.js" + }, + "./sweep": { + "default": "./sweep.js" + }, + "./waveshaper": { + "default": "./waveshaper.js" + }, + "./white-noise": { + "default": "./white-noise.js" + }, + "./window": { + "default": "./window.js" + } + }, + "thi.ng": { + "related": [ + "math" + ], + "year": 2015 + } } diff --git a/packages/dsp/src/add.ts b/packages/dsp/src/add.ts index 7fc93ff8e0..4f71d236ea 100644 --- a/packages/dsp/src/add.ts +++ b/packages/dsp/src/add.ts @@ -7,35 +7,35 @@ import { AGen } from "./agen.js"; * given, the resulting output will be clamped to that value (min or max depends * on sign of `start - clamp`). * - * @param step - - * @param start - - * @param clamp - + * @param step - + * @param start - + * @param clamp - */ export const add = (step?: number, start?: number, clamp?: number) => - new Add(step, start, clamp); + new Add(step, start, clamp); export class Add extends AGen implements IReset { - constructor( - protected _step = 1, - protected _start = 0, - protected _clamp?: number - ) { - super(0); - this.reset(); - } + constructor( + protected _step = 1, + protected _start = 0, + protected _clamp?: number + ) { + super(0); + this.reset(); + } - reset() { - this._val = this._start - this._step; - return this; - } + reset() { + this._val = this._start - this._step; + return this; + } - next() { - let v = this._val + this._step; - return (this._val = - this._clamp !== undefined - ? this._start < this._clamp - ? Math.min(v, this._clamp) - : Math.max(v, this._clamp) - : v); - } + next() { + let v = this._val + this._step; + return (this._val = + this._clamp !== undefined + ? this._start < this._clamp + ? Math.min(v, this._clamp) + : Math.max(v, this._clamp) + : v); + } } diff --git a/packages/dsp/src/addg.ts b/packages/dsp/src/addg.ts index 07d160031d..51ef38d77b 100644 --- a/packages/dsp/src/addg.ts +++ b/packages/dsp/src/addg.ts @@ -16,8 +16,8 @@ import { MapG1 } from "./mapg.js"; * // [ 10, 11, 12, 13, 14 ] * ``` * - * @param step - - * @param start - + * @param step - + * @param start - */ export const addG = (step: IGen, start = 0): IGen => - new MapG1((a, b) => a + b, step, start - step.deref()); + new MapG1((a, b) => a + b, step, start - step.deref()); diff --git a/packages/dsp/src/adsr.ts b/packages/dsp/src/adsr.ts index 2c17587e9b..d9796eaead 100644 --- a/packages/dsp/src/adsr.ts +++ b/packages/dsp/src/adsr.ts @@ -6,50 +6,50 @@ import type { IGen } from "./api.js"; import { curve } from "./curve.js"; const enum EnvPhase { - ATTACK, - DECAY, - SUSTAIN, - RELEASE, - IDLE, + ATTACK, + DECAY, + SUSTAIN, + RELEASE, + IDLE, } export interface ADSROpts { - /** - * Attack time (in samples). Default: 0 - */ - a: number; - /** - * Decay time (in samples). Default: 0 - */ - d: number; - /** - * Sustain level/gain (in [0..1] range). Default: 1 - */ - s: number; - /** - * Release time (in samples). Default: 0 - */ - r: number; - /** - * Attack curvature. Recommended range [0.0001 .. 10000] - * (curved -> linear). Default: 0.1 - */ - acurve: number; - /** - * Decay & release curvature. Recommended range [0.0001 .. 10000] - * (curved -> linear). Default: 0.1 - */ - dcurve: number; - /** - * Sustain phase duration (in samples). Default: Infinity. If a - * finite value, then release phase is triggered automatically, else - * needs to be triggered manually via {@link ADSR.release}. - */ - slen: number; - /** - * Overall envelope gain / multiplier. Default: 1 - */ - gain: number; + /** + * Attack time (in samples). Default: 0 + */ + a: number; + /** + * Decay time (in samples). Default: 0 + */ + d: number; + /** + * Sustain level/gain (in [0..1] range). Default: 1 + */ + s: number; + /** + * Release time (in samples). Default: 0 + */ + r: number; + /** + * Attack curvature. Recommended range [0.0001 .. 10000] + * (curved -> linear). Default: 0.1 + */ + acurve: number; + /** + * Decay & release curvature. Recommended range [0.0001 .. 10000] + * (curved -> linear). Default: 0.1 + */ + dcurve: number; + /** + * Sustain phase duration (in samples). Default: Infinity. If a + * finite value, then release phase is triggered automatically, else + * needs to be triggered manually via {@link ADSR.release}. + */ + slen: number; + /** + * Overall envelope gain / multiplier. Default: 1 + */ + gain: number; } /** @@ -79,143 +79,143 @@ export interface ADSROpts { export const adsr = (opts?: Partial) => new ADSR(opts); export class ADSR extends AGen implements IReset { - protected _phase!: EnvPhase; - protected _curve!: IGen; - protected _atime!: number; - protected _dtime!: number; - protected _rtime!: number; - protected _acurve!: number; - protected _dcurve!: number; - protected _sustain!: number; - protected _speriod!: number; - protected _gain!: number; - - constructor(opts?: Partial) { - super(0); - opts = { - a: 0, - d: 0, - s: 1, - r: 0, - acurve: 0.1, - dcurve: 0.001, - slen: Infinity, - gain: 1, - ...opts, - }; - this.setAttack(opts.a!); - this.setDecay(opts.d!); - this.setRelease(opts.r!); - this.setSustain(opts.s!, opts.slen); - this.setCurveA(opts.acurve!); - this.setCurveD(opts.dcurve!); - this.setGain(opts.gain!); - this.reset(); - } - - reset() { - this._phase = EnvPhase.ATTACK; - this._curve = curve(0, 1, this._atime + 1, this._acurve, true); - this._val = 0; - return this; - } - - release() { - if (this._phase < EnvPhase.RELEASE) { - this._phase = EnvPhase.RELEASE; - this._curve = curve( - this._sustain, - 0, - this._rtime + 1, - this._dcurve, - true - ); - } - } - - isSustained() { - return this._phase === EnvPhase.SUSTAIN; - } - - isDone() { - return this._phase === EnvPhase.IDLE; - } - - next() { - let v: number; - switch (this._phase) { - case EnvPhase.IDLE: - return 0; - case EnvPhase.ATTACK: - v = this._curve.next(); - if (v >= 1) { - v = 1; - this._phase = EnvPhase.DECAY; - this._curve = curve( - 1, - this._sustain, - this._dtime + 1, - this._dcurve, - true - ); - } - break; - case EnvPhase.DECAY: - v = this._curve.next(); - if (v <= this._sustain) { - v = this._sustain; - this._phase = EnvPhase.SUSTAIN; - this._curve = add(1, 1); - } - break; - case EnvPhase.SUSTAIN: - if (this._curve.next() >= this._speriod) { - this.release(); - } - return this._val; - case EnvPhase.RELEASE: - v = this._curve.next(); - if (v < 0) { - v = 0; - this._phase = EnvPhase.IDLE; - } - } - return (this._val = v * this._gain); - } - - setAttack(steps: number) { - this._atime = Math.max(steps, 0); - } - - setDecay(steps: number) { - this._dtime = Math.max(steps, 0); - } - - setRelease(steps: number) { - this._rtime = Math.max(steps, 0); - } - - /** - * Sets sustain level & duration. If the latter is omitted, the - * current value will be retained. - * - * @param level - - * @param duration - - */ - setSustain(level: number, duration?: number) { - this._sustain = clamp01(level); - duration !== undefined && (this._speriod = duration); - } - - setCurveA(ratio: number) { - this._acurve = Math.max(ratio, 1e-9); - } - - setCurveD(ratio: number) { - this._dcurve = Math.max(ratio, 1e-9); - } - - setGain(gain: number) { - this._gain = gain; - } + protected _phase!: EnvPhase; + protected _curve!: IGen; + protected _atime!: number; + protected _dtime!: number; + protected _rtime!: number; + protected _acurve!: number; + protected _dcurve!: number; + protected _sustain!: number; + protected _speriod!: number; + protected _gain!: number; + + constructor(opts?: Partial) { + super(0); + opts = { + a: 0, + d: 0, + s: 1, + r: 0, + acurve: 0.1, + dcurve: 0.001, + slen: Infinity, + gain: 1, + ...opts, + }; + this.setAttack(opts.a!); + this.setDecay(opts.d!); + this.setRelease(opts.r!); + this.setSustain(opts.s!, opts.slen); + this.setCurveA(opts.acurve!); + this.setCurveD(opts.dcurve!); + this.setGain(opts.gain!); + this.reset(); + } + + reset() { + this._phase = EnvPhase.ATTACK; + this._curve = curve(0, 1, this._atime + 1, this._acurve, true); + this._val = 0; + return this; + } + + release() { + if (this._phase < EnvPhase.RELEASE) { + this._phase = EnvPhase.RELEASE; + this._curve = curve( + this._sustain, + 0, + this._rtime + 1, + this._dcurve, + true + ); + } + } + + isSustained() { + return this._phase === EnvPhase.SUSTAIN; + } + + isDone() { + return this._phase === EnvPhase.IDLE; + } + + next() { + let v: number; + switch (this._phase) { + case EnvPhase.IDLE: + return 0; + case EnvPhase.ATTACK: + v = this._curve.next(); + if (v >= 1) { + v = 1; + this._phase = EnvPhase.DECAY; + this._curve = curve( + 1, + this._sustain, + this._dtime + 1, + this._dcurve, + true + ); + } + break; + case EnvPhase.DECAY: + v = this._curve.next(); + if (v <= this._sustain) { + v = this._sustain; + this._phase = EnvPhase.SUSTAIN; + this._curve = add(1, 1); + } + break; + case EnvPhase.SUSTAIN: + if (this._curve.next() >= this._speriod) { + this.release(); + } + return this._val; + case EnvPhase.RELEASE: + v = this._curve.next(); + if (v < 0) { + v = 0; + this._phase = EnvPhase.IDLE; + } + } + return (this._val = v * this._gain); + } + + setAttack(steps: number) { + this._atime = Math.max(steps, 0); + } + + setDecay(steps: number) { + this._dtime = Math.max(steps, 0); + } + + setRelease(steps: number) { + this._rtime = Math.max(steps, 0); + } + + /** + * Sets sustain level & duration. If the latter is omitted, the + * current value will be retained. + * + * @param level - + * @param duration - + */ + setSustain(level: number, duration?: number) { + this._sustain = clamp01(level); + duration !== undefined && (this._speriod = duration); + } + + setCurveA(ratio: number) { + this._acurve = Math.max(ratio, 1e-9); + } + + setCurveD(ratio: number) { + this._dcurve = Math.max(ratio, 1e-9); + } + + setGain(gain: number) { + this._gain = gain; + } } diff --git a/packages/dsp/src/agen.ts b/packages/dsp/src/agen.ts index 46d05765a0..1d544fbc53 100644 --- a/packages/dsp/src/agen.ts +++ b/packages/dsp/src/agen.ts @@ -7,19 +7,19 @@ import { __take } from "./internal/take.js"; * `Iterable` implementations to use gens as ES6 iterables. */ export abstract class AGen implements IGen { - constructor(protected _val: T) {} + constructor(protected _val: T) {} - deref() { - return this._val; - } + deref() { + return this._val; + } - *[Symbol.iterator]() { - while (true) yield this.next(); - } + *[Symbol.iterator]() { + while (true) yield this.next(); + } - take(num: number, out: T[] = [], idx = 0): T[] { - return __take(this, num, out, idx); - } + take(num: number, out: T[] = [], idx = 0): T[] { + return __take(this, num, out, idx); + } - abstract next(): T; + abstract next(): T; } diff --git a/packages/dsp/src/allpass.ts b/packages/dsp/src/allpass.ts index fe58c1deaa..09e7193c59 100644 --- a/packages/dsp/src/allpass.ts +++ b/packages/dsp/src/allpass.ts @@ -11,42 +11,42 @@ import { AProc } from "./aproc.js"; export const allpass = (freq: number) => new AllPass1(freq); export class AllPass1 extends AProc implements IReset { - protected _freq!: number; - protected _coeff!: number; - protected _z1!: number; - - constructor(freq: number) { - super(0); - this.setFreq(freq); - this.reset(); - } - - reset() { - this._z1 = 0; - return this; - } - - next(x: number) { - const { _coeff, _z1 } = this; - x -= _z1 * _coeff; - this._z1 = x; - return x * _coeff + _z1; - } - - low(x: number) { - return (x + this.next(x)) * 0.5; - } - - high(x: number) { - return (x - this.next(x)) * 0.5; - } - - freq() { - return this._freq; - } - - setFreq(freq: number) { - this._freq = clamp05(freq); - this._coeff = Math.tan(freq * PI - QUARTER_PI); - } + protected _freq!: number; + protected _coeff!: number; + protected _z1!: number; + + constructor(freq: number) { + super(0); + this.setFreq(freq); + this.reset(); + } + + reset() { + this._z1 = 0; + return this; + } + + next(x: number) { + const { _coeff, _z1 } = this; + x -= _z1 * _coeff; + this._z1 = x; + return x * _coeff + _z1; + } + + low(x: number) { + return (x + this.next(x)) * 0.5; + } + + high(x: number) { + return (x - this.next(x)) * 0.5; + } + + freq() { + return this._freq; + } + + setFreq(freq: number) { + this._freq = clamp05(freq); + this._coeff = Math.tan(freq * PI - QUARTER_PI); + } } diff --git a/packages/dsp/src/alt.ts b/packages/dsp/src/alt.ts index fd1657c0b0..c1722f9a9d 100644 --- a/packages/dsp/src/alt.ts +++ b/packages/dsp/src/alt.ts @@ -8,18 +8,18 @@ export const altT = (a: T, b: T) => new Alt(a, b); export const altB = (x = true) => new Alt(x, !x); export class Alt extends AGen implements IReset { - protected _flip = true; + protected _flip = true; - constructor(protected _a: T, protected _b: T) { - super(_b); - } + constructor(protected _a: T, protected _b: T) { + super(_b); + } - reset() { - this._flip = true; - return this; - } + reset() { + this._flip = true; + return this; + } - next() { - return (this._val = (this._flip = !this._flip) ? this._b : this._a); - } + next() { + return (this._val = (this._flip = !this._flip) ? this._b : this._a); + } } diff --git a/packages/dsp/src/anti-alias.ts b/packages/dsp/src/anti-alias.ts index 0a9fd61df5..a50458c8ac 100644 --- a/packages/dsp/src/anti-alias.ts +++ b/packages/dsp/src/anti-alias.ts @@ -37,8 +37,8 @@ export const fejer: FnN2 = (k, n) => (n - k) / n; * @param t - normalized phase */ export const polyBLEP: FnN2 = (dt, t) => - t < dt - ? ((t /= dt), t + t - t * t - 1) - : t > 1 - dt - ? ((t = (t - 1) / dt), t * t + t + t + 1) - : 0; + t < dt + ? ((t /= dt), t + t - t * t - 1) + : t > 1 - dt + ? ((t = (t - 1) / dt), t * t + t + t + 1) + : 0; diff --git a/packages/dsp/src/api.ts b/packages/dsp/src/api.ts index 8a4cefdddc..d066f03b40 100644 --- a/packages/dsp/src/api.ts +++ b/packages/dsp/src/api.ts @@ -1,11 +1,11 @@ import type { FnN2, IDeref, NumericArray } from "@thi.ng/api"; export type StatelessOscillator = ( - phase: number, - freq: number, - amp?: number, - dc?: number, - ...opts: any[] + phase: number, + freq: number, + amp?: number, + dc?: number, + ...opts: any[] ) => number; export type ComplexArray = [NumericArray, NumericArray]; @@ -13,55 +13,55 @@ export type ComplexArray = [NumericArray, NumericArray]; export type WindowFn = FnN2; export interface IGen extends Iterable, IDeref { - next(): T; - take(num: number, out?: T[], idx?: number): T[]; + next(): T; + take(num: number, out?: T[], idx?: number): T[]; } export interface IProc extends IDeref { - next(src: A): B; + next(src: A): B; } export interface IProc2 extends IDeref { - next(srcA: A, srcB: B): C; + next(srcA: A, srcB: B): C; } export interface FilterConfig { - zeroes: number[]; - poles: number[]; + zeroes: number[]; + poles: number[]; } export interface FilterResponse { - freq: number; - mag: number; - phase: number; + freq: number; + mag: number; + phase: number; } export interface IFilter { - /** - * Returns this filter's zero & pole position(s). The result object - * can then be passed to {@link filterResponse}. - */ - filterCoeffs(): FilterConfig; + /** + * Returns this filter's zero & pole position(s). The result object + * can then be passed to {@link filterResponse}. + */ + filterCoeffs(): FilterConfig; } export type FilterType = - | "lp" - | "hp" - | "bp" - | "notch" - | "peak" - | "loshelf" - | "hishelf" - | "all"; + | "lp" + | "hp" + | "bp" + | "notch" + | "peak" + | "loshelf" + | "hishelf" + | "all"; export type BiquadType = - | "lp" - | "hp" - | "bp" - | "notch" - | "peak" - | "loshelf" - | "hishelf"; + | "lp" + | "hp" + | "bp" + | "notch" + | "peak" + | "loshelf" + | "hishelf"; export type OnepoleType = "lp" | "hp"; diff --git a/packages/dsp/src/aproc.ts b/packages/dsp/src/aproc.ts index 8da7ce9547..5a09f656f3 100644 --- a/packages/dsp/src/aproc.ts +++ b/packages/dsp/src/aproc.ts @@ -7,28 +7,28 @@ import type { IProc, IProc2 } from "./api.js"; * {@link @thi.ng/api#IDeref} to obtain the processor's current value. */ export abstract class AProc implements IProc, IXform { - constructor(protected _val: B) {} + constructor(protected _val: B) {} - deref() { - return this._val; - } + deref() { + return this._val; + } - abstract next(x: A): B; + abstract next(x: A): B; - xform() { - return map((x: A) => this.next(x)); - } + xform() { + return map((x: A) => this.next(x)); + } } /** * Similar to {@link AProc}, but for processors with 2 inputs. */ export abstract class AProc2 implements IProc2 { - constructor(protected _val: C) {} + constructor(protected _val: C) {} - deref() { - return this._val; - } + deref() { + return this._val; + } - abstract next(a: A, b: B): C; + abstract next(a: A, b: B): C; } diff --git a/packages/dsp/src/biquad.ts b/packages/dsp/src/biquad.ts index b121348ef9..cf23d6acd9 100644 --- a/packages/dsp/src/biquad.ts +++ b/packages/dsp/src/biquad.ts @@ -7,10 +7,10 @@ import { AProc } from "./aproc.js"; import { dbMag } from "./convert.js"; export const biquad = ( - type: BiquadType, - fc: number, - q?: number, - gain?: number + type: BiquadType, + fc: number, + q?: number, + gain?: number ) => new Biquad(type, fc, q, gain); export const biquadLP = (fc: number, q?: number) => new Biquad("lp", fc, q); @@ -20,206 +20,206 @@ export const biquadHP = (fc: number, q?: number) => new Biquad("hp", fc, q); export const biquadBP = (fc: number, q?: number) => new Biquad("bp", fc, q); export const biquadNotch = (fc: number, q?: number) => - new Biquad("notch", fc, q); + new Biquad("notch", fc, q); export const biquadPeak = (fc: number, q?: number, gain = 6) => - new Biquad("peak", fc, q, gain); + new Biquad("peak", fc, q, gain); export const biquadLoShelf = (fc: number, gain = -6) => - new Biquad("loshelf", fc, undefined, gain); + new Biquad("loshelf", fc, undefined, gain); export const biquadHiShelf = (fc: number, gain = -6) => - new Biquad("hishelf", fc, undefined, gain); + new Biquad("hishelf", fc, undefined, gain); export class Biquad extends AProc implements IReset, IFilter { - protected _a0!: number; - protected _a1!: number; - protected _a2!: number; - protected _b1!: number; - protected _b2!: number; - protected _z1!: number; - protected _z2!: number; - - constructor( - protected _type: BiquadType, - protected _freq: number, - protected _q = SQRT2_2, - protected _gain = 0 - ) { - super(0); - this.reset(); - this.calcCoeffs(); - } - - reset() { - this._z1 = this._z2 = this._val = 0; - return this; - } - - next(x: number) { - const out = x * this._a0 + this._z1; - this._z1 = x * this._a1 + this._z2 - this._b1 * out; - this._z2 = x * this._a2 - this._b2 * out; - return (this._val = out); - } - - freq() { - return this._freq; - } - - q() { - return this._q; - } - - gain() { - return this._gain; - } - - set(fc: number, q: number, gain: number) { - this._freq = clamp05(fc); - this._q = q; - this._gain = gain; - this.calcCoeffs(); - } - - setFreq(fc: number) { - this._freq = clamp05(fc); - this.calcCoeffs(); - } - - setQ(q: number) { - this._q = q; - this.calcCoeffs(); - } - - setGain(g: number) { - this._gain = g; - this.calcCoeffs(); - } - - filterCoeffs(): FilterConfig { - return { - zeroes: [this._a0, this._a1, this._a2], - poles: [1, this._b1, this._b2], - }; - } - - protected calcCoeffs() { - const k = Math.tan(PI * this._freq); - const k2 = k * k; - const k22 = 2 * (k2 - 1); - const kq = k / this._q; - const k2kqp1 = 1 + kq + k2; - const k2kqm1 = 1 - kq + k2; - const ksqrt2 = k * SQRT2; - const v = dbMag(Math.abs(this._gain)); - const kvq = k * (v / this._q); - const ksqrt2v = k * Math.sqrt(2 * v); - let norm = 1 / k2kqp1; - switch (this._type) { - case "lp": - this._a0 = k2 * norm; - this._a1 = 2 * this._a0; - this._a2 = this._a0; - this._b1 = k22 * norm; - this._b2 = k2kqm1 * norm; - break; - - case "hp": - this._a0 = norm; - this._a1 = -2 * this._a0; - this._a2 = this._a0; - this._b1 = k22 * norm; - this._b2 = k2kqm1 * norm; - break; - - case "bp": - this._a0 = kq * norm; - this._a1 = 0; - this._a2 = -this._a0; - this._b1 = k22 * norm; - this._b2 = k2kqm1 * norm; - break; - - case "notch": - this._a0 = (1 + k2) * norm; - this._a1 = k22 * norm; - this._a2 = this._a0; - this._b1 = this._a1; - this._b2 = k2kqm1 * norm; - break; - - case "peak": { - const z1 = 1 + kvq + k2; - const z2 = 1 - kvq + k2; - if (this._gain >= 0) { - this._a0 = z1 * norm; - this._a1 = k22 * norm; - this._a2 = z2 * norm; - this._b1 = this._a1; - this._b2 = k2kqm1 * norm; - } else { - norm = 1 / z1; - this._a0 = k2kqp1 * norm; - this._a1 = k22 * norm; - this._a2 = k2kqm1 * norm; - this._b1 = this._a1; - this._b2 = z2 * norm; - } - break; - } - - case "loshelf": { - const z1 = 1 + ksqrt2 + k2; - const z2 = 1 - ksqrt2 + k2; - const vk2 = v * k2; - const y1 = 1 + ksqrt2v + vk2; - const y2 = 1 - ksqrt2v + vk2; - const vk22 = 2 * (vk2 - 1); - if (this._gain >= 0) { - norm = 1 / z1; - this._a0 = y1 * norm; - this._a1 = vk22 * norm; - this._a2 = y2 * norm; - this._b1 = k22 * norm; - this._b2 = z2 * norm; - } else { - norm = 1 / y1; - this._a0 = z1 * norm; - this._a1 = k22 * norm; - this._a2 = z2 * norm; - this._b1 = vk22 * norm; - this._b2 = y2 * norm; - } - break; - } - - case "hishelf": { - const z1 = 1 + ksqrt2 + k2; - const z2 = 1 - ksqrt2 + k2; - const y1 = v + ksqrt2v + k2; - const y2 = v - ksqrt2v + k2; - const vk2 = 2 * (k2 - v); - if (this._gain >= 0) { - norm = 1 / z1; - this._a0 = y1 * norm; - this._a1 = vk2 * norm; - this._a2 = y2 * norm; - this._b1 = k22 * norm; - this._b2 = z2 * norm; - } else { - norm = 1 / y1; - this._a0 = z1 * norm; - this._a1 = k22 * norm; - this._a2 = z2 * norm; - this._b1 = vk2 * norm; - this._b2 = y2 * norm; - } - break; - } - - default: - unsupported(`invalid filter type: ${this._type}`); - } - } + protected _a0!: number; + protected _a1!: number; + protected _a2!: number; + protected _b1!: number; + protected _b2!: number; + protected _z1!: number; + protected _z2!: number; + + constructor( + protected _type: BiquadType, + protected _freq: number, + protected _q = SQRT2_2, + protected _gain = 0 + ) { + super(0); + this.reset(); + this.calcCoeffs(); + } + + reset() { + this._z1 = this._z2 = this._val = 0; + return this; + } + + next(x: number) { + const out = x * this._a0 + this._z1; + this._z1 = x * this._a1 + this._z2 - this._b1 * out; + this._z2 = x * this._a2 - this._b2 * out; + return (this._val = out); + } + + freq() { + return this._freq; + } + + q() { + return this._q; + } + + gain() { + return this._gain; + } + + set(fc: number, q: number, gain: number) { + this._freq = clamp05(fc); + this._q = q; + this._gain = gain; + this.calcCoeffs(); + } + + setFreq(fc: number) { + this._freq = clamp05(fc); + this.calcCoeffs(); + } + + setQ(q: number) { + this._q = q; + this.calcCoeffs(); + } + + setGain(g: number) { + this._gain = g; + this.calcCoeffs(); + } + + filterCoeffs(): FilterConfig { + return { + zeroes: [this._a0, this._a1, this._a2], + poles: [1, this._b1, this._b2], + }; + } + + protected calcCoeffs() { + const k = Math.tan(PI * this._freq); + const k2 = k * k; + const k22 = 2 * (k2 - 1); + const kq = k / this._q; + const k2kqp1 = 1 + kq + k2; + const k2kqm1 = 1 - kq + k2; + const ksqrt2 = k * SQRT2; + const v = dbMag(Math.abs(this._gain)); + const kvq = k * (v / this._q); + const ksqrt2v = k * Math.sqrt(2 * v); + let norm = 1 / k2kqp1; + switch (this._type) { + case "lp": + this._a0 = k2 * norm; + this._a1 = 2 * this._a0; + this._a2 = this._a0; + this._b1 = k22 * norm; + this._b2 = k2kqm1 * norm; + break; + + case "hp": + this._a0 = norm; + this._a1 = -2 * this._a0; + this._a2 = this._a0; + this._b1 = k22 * norm; + this._b2 = k2kqm1 * norm; + break; + + case "bp": + this._a0 = kq * norm; + this._a1 = 0; + this._a2 = -this._a0; + this._b1 = k22 * norm; + this._b2 = k2kqm1 * norm; + break; + + case "notch": + this._a0 = (1 + k2) * norm; + this._a1 = k22 * norm; + this._a2 = this._a0; + this._b1 = this._a1; + this._b2 = k2kqm1 * norm; + break; + + case "peak": { + const z1 = 1 + kvq + k2; + const z2 = 1 - kvq + k2; + if (this._gain >= 0) { + this._a0 = z1 * norm; + this._a1 = k22 * norm; + this._a2 = z2 * norm; + this._b1 = this._a1; + this._b2 = k2kqm1 * norm; + } else { + norm = 1 / z1; + this._a0 = k2kqp1 * norm; + this._a1 = k22 * norm; + this._a2 = k2kqm1 * norm; + this._b1 = this._a1; + this._b2 = z2 * norm; + } + break; + } + + case "loshelf": { + const z1 = 1 + ksqrt2 + k2; + const z2 = 1 - ksqrt2 + k2; + const vk2 = v * k2; + const y1 = 1 + ksqrt2v + vk2; + const y2 = 1 - ksqrt2v + vk2; + const vk22 = 2 * (vk2 - 1); + if (this._gain >= 0) { + norm = 1 / z1; + this._a0 = y1 * norm; + this._a1 = vk22 * norm; + this._a2 = y2 * norm; + this._b1 = k22 * norm; + this._b2 = z2 * norm; + } else { + norm = 1 / y1; + this._a0 = z1 * norm; + this._a1 = k22 * norm; + this._a2 = z2 * norm; + this._b1 = vk22 * norm; + this._b2 = y2 * norm; + } + break; + } + + case "hishelf": { + const z1 = 1 + ksqrt2 + k2; + const z2 = 1 - ksqrt2 + k2; + const y1 = v + ksqrt2v + k2; + const y2 = v - ksqrt2v + k2; + const vk2 = 2 * (k2 - v); + if (this._gain >= 0) { + norm = 1 / z1; + this._a0 = y1 * norm; + this._a1 = vk2 * norm; + this._a2 = y2 * norm; + this._b1 = k22 * norm; + this._b2 = z2 * norm; + } else { + norm = 1 / y1; + this._a0 = z1 * norm; + this._a1 = k22 * norm; + this._a2 = z2 * norm; + this._b1 = vk2 * norm; + this._b2 = y2 * norm; + } + break; + } + + default: + unsupported(`invalid filter type: ${this._type}`); + } + } } diff --git a/packages/dsp/src/bounce.ts b/packages/dsp/src/bounce.ts index 75672d06ea..f6191bafb2 100644 --- a/packages/dsp/src/bounce.ts +++ b/packages/dsp/src/bounce.ts @@ -9,13 +9,13 @@ import { AProc } from "./aproc.js"; export const bounce = () => new Bounce(); export class Bounce extends AProc { - constructor() { - super(0); - } + constructor() { + super(0); + } - next(src: NumericArray) { - let res = 0; - for (let i = src.length; i-- > 0; ) res += src[i]; - return (this._val = res); - } + next(src: NumericArray) { + let res = 0; + for (let i = src.length; i-- > 0; ) res += src[i]; + return (this._val = res); + } } diff --git a/packages/dsp/src/complex.ts b/packages/dsp/src/complex.ts index a68a8ac467..ae85ebd3a9 100644 --- a/packages/dsp/src/complex.ts +++ b/packages/dsp/src/complex.ts @@ -3,5 +3,5 @@ import { isNumber } from "@thi.ng/checks/is-number"; import type { ComplexArray } from "./api.js"; export const isComplex = ( - buf: NumericArray | ComplexArray + buf: NumericArray | ComplexArray ): buf is ComplexArray => !isNumber(buf[0]); diff --git a/packages/dsp/src/const.ts b/packages/dsp/src/const.ts index b422db9c19..85a38bf465 100644 --- a/packages/dsp/src/const.ts +++ b/packages/dsp/src/const.ts @@ -3,12 +3,12 @@ import { AGen } from "./agen.js"; /** * Returns new gen yielding always the same given value `x`. * - * @param x - + * @param x - */ export const constant = (x: T) => new Const(x); export class Const extends AGen { - next() { - return this._val; - } + next() { + return this._val; + } } diff --git a/packages/dsp/src/convert.ts b/packages/dsp/src/convert.ts index 1f9b2f3b6b..6243f94723 100644 --- a/packages/dsp/src/convert.ts +++ b/packages/dsp/src/convert.ts @@ -5,8 +5,8 @@ import { TAU } from "@thi.ng/math/api"; * Returns frequency `f` normalized to sample rate `fs`: * `fnorm = f / fs` * - * @param f - - * @param fs - + * @param f - + * @param fs - */ export const normFreq: FnN2 = (f, fs) => f / fs; @@ -14,23 +14,23 @@ export const normFreq: FnN2 = (f, fs) => f / fs; * Returns frequency `f` in radians, based on sample rate `fs`. * I.e. Nyquist freq = PI * - * @param f - - * @param fs - + * @param f - + * @param fs - */ export const freqRad: FnN2 = (f, fs) => (f / fs) * TAU; /** * Returns period length in milliseconds for given frequency in Hz. * - * @param f - + * @param f - */ export const freqMs: FnN = (f) => 1000 / f; /** * Reverse op of {@link freqRad}. * - * @param rad - - * @param fs - + * @param rad - + * @param fs - */ export const radFreq: FnN2 = (rad, fs) => (rad / TAU) * fs; @@ -45,23 +45,23 @@ export const radFreq: FnN2 = (rad, fs) => (rad / TAU) * fs; * // 882 * ``` * - * @param t - - * @param fs - + * @param t - + * @param fs - */ export const msFrames: FnN2 = (t, fs) => t * 0.001 * fs; /** * Reverse op of {@link msFrames}. * - * @param frames - - * @param fs - + * @param frames - + * @param fs - */ export const framesMs: FnN2 = (frames, fs) => (frames / fs) * 1000; /** * Converts given linear magnitude to dBFS (i.e. `20 * log10(x)`) * - * @param x - + * @param x - */ export const magDb: FnN = (x) => (20 * Math.log(x)) / Math.LN10; @@ -69,6 +69,6 @@ export const magDb: FnN = (x) => (20 * Math.log(x)) / Math.LN10; * Converts given dBFS value to linear magnitude * (i.e. `pow(10, x / 20)`) * - * @param x - + * @param x - */ export const dbMag: FnN = (x) => 10 ** (x / 20); diff --git a/packages/dsp/src/cosine.ts b/packages/dsp/src/cosine.ts index 61070adb64..2f8fbda903 100644 --- a/packages/dsp/src/cosine.ts +++ b/packages/dsp/src/cosine.ts @@ -12,47 +12,47 @@ import { AGen } from "./agen.js"; export const cosine = (freq: number, amp?: number) => new Cosine(freq, amp); export class Cosine extends AGen implements IReset { - protected _cos!: number; - protected _nxt!: number; - - constructor(protected _freq: number, protected _amp = 1) { - super(0); - this.calcCoeffs(); - } - - reset() { - this.calcCoeffs(); - return this; - } - - next() { - const t = this._nxt * this._cos - this._val; - this._val = this._nxt; - this._nxt = t; - return this._val; - } - - freq() { - return this._freq; - } - - setFreq(freq: number) { - this._freq = freq; - this.calcCoeffs(); - } - - amp() { - return this._amp; - } - - setAmp(amp: number) { - this._amp = amp; - this.calcCoeffs(); - } - - protected calcCoeffs() { - this._nxt = this._amp; - this._cos = Math.cos(this._freq * TAU) * 2; - this._val = this._cos * this._amp * 0.5; - } + protected _cos!: number; + protected _nxt!: number; + + constructor(protected _freq: number, protected _amp = 1) { + super(0); + this.calcCoeffs(); + } + + reset() { + this.calcCoeffs(); + return this; + } + + next() { + const t = this._nxt * this._cos - this._val; + this._val = this._nxt; + this._nxt = t; + return this._val; + } + + freq() { + return this._freq; + } + + setFreq(freq: number) { + this._freq = freq; + this.calcCoeffs(); + } + + amp() { + return this._amp; + } + + setAmp(amp: number) { + this._amp = amp; + this.calcCoeffs(); + } + + protected calcCoeffs() { + this._nxt = this._amp; + this._cos = Math.cos(this._freq * TAU) * 2; + this._val = this._cos * this._amp * 0.5; + } } diff --git a/packages/dsp/src/curve.ts b/packages/dsp/src/curve.ts index ad8e77d3e5..eafdb8a8e9 100644 --- a/packages/dsp/src/curve.ts +++ b/packages/dsp/src/curve.ts @@ -38,19 +38,19 @@ import { MAdd } from "./madd.js"; * @param clampEnd - true to clamp curve at end value (default: false) */ export const curve = ( - start: number, - end: number, - num: number, - rate = 0.1, - skipFirst = false, - clampEnd = false + start: number, + end: number, + num: number, + rate = 0.1, + skipFirst = false, + clampEnd = false ) => { - const c = Math.exp(-Math.log((Math.abs(end - start) + rate) / rate) / num); - const offset = (start < end ? end + rate : end - rate) * (1 - c); - return new MAdd( - c, - skipFirst ? offset + start * c : start, - offset, - clampEnd ? end : undefined - ); + const c = Math.exp(-Math.log((Math.abs(end - start) + rate) / rate) / num); + const offset = (start < end ? end + rate : end - rate) * (1 - c); + return new MAdd( + c, + skipFirst ? offset + start * c : start, + offset, + clampEnd ? end : undefined + ); }; diff --git a/packages/dsp/src/dcblock.ts b/packages/dsp/src/dcblock.ts index d398057585..156e05fdcd 100644 --- a/packages/dsp/src/dcblock.ts +++ b/packages/dsp/src/dcblock.ts @@ -3,12 +3,12 @@ import { OnePole } from "./onepole.js"; /** * One-pole DC blocker based on {@link OnePole}. * - * @param freq - + * @param freq - */ export const dcBlock = (freq: number) => new DCBlock("lp", freq); export class DCBlock extends OnePole { - next(x: number) { - return x - super.next(x); - } + next(x: number) { + return x - super.next(x); + } } diff --git a/packages/dsp/src/delay.ts b/packages/dsp/src/delay.ts index 2f166f6e1c..971902f79e 100644 --- a/packages/dsp/src/delay.ts +++ b/packages/dsp/src/delay.ts @@ -6,14 +6,14 @@ import { AProc } from "./aproc.js"; /** * Delay line of length `n` for numeric values. * - * @param n - + * @param n - */ export const delay = (n: number) => new Delay(n, 0); /** * Delay line of length `n` for arbitrary typed values. * - * @param n - + * @param n - */ export const delayT = (n: number, off: T | Fn0) => new Delay(n, off); @@ -22,100 +22,100 @@ export const delayT = (n: number, off: T | Fn0) => new Delay(n, off); * at any delay time (within configured buffer size). */ export class Delay extends AProc implements IClear, ILength, IReset { - protected _buf: T[]; - protected _rpos: number; - protected _wpos: number; + protected _buf: T[]; + protected _rpos: number; + protected _wpos: number; - /** - * Constructs new delay line of size `n` and initializes all - * elements to `empty`. If the latter is a function, the buffer will - * be initialized with the results of that function (called for each - * element). - * - * @param n - - * @param _empty - - */ - constructor(n: number, protected _empty: T | Fn0) { - super(isFunction(_empty) ? _empty() : _empty); - this._wpos = n - 1; - this._rpos = 0; - this._buf = new Array(n); - this.clear(); - } + /** + * Constructs new delay line of size `n` and initializes all + * elements to `empty`. If the latter is a function, the buffer will + * be initialized with the results of that function (called for each + * element). + * + * @param n - + * @param _empty - + */ + constructor(n: number, protected _empty: T | Fn0) { + super(isFunction(_empty) ? _empty() : _empty); + this._wpos = n - 1; + this._rpos = 0; + this._buf = new Array(n); + this.clear(); + } - get length() { - return this._buf.length; - } + get length() { + return this._buf.length; + } - clear() { - const { _buf, _empty } = this; - if (isFunction(_empty)) { - for (let i = _buf.length; i-- > 0; ) { - this._buf[i] = _empty(); - } - } else { - this._buf.fill(_empty); - } - } + clear() { + const { _buf, _empty } = this; + if (isFunction(_empty)) { + for (let i = _buf.length; i-- > 0; ) { + this._buf[i] = _empty(); + } + } else { + this._buf.fill(_empty); + } + } - /** - * Alias for {@link Delay.clear} - */ - reset() { - this.clear(); - return this; - } + /** + * Alias for {@link Delay.clear} + */ + reset() { + this.clear(); + return this; + } - /** - * Returns the delayed value at current read position (i.e. `n` - * samples behind current write pos, where `n` is the length of this - * delay line). - */ - deref(): T { - return this._buf[this._rpos]; - } + /** + * Returns the delayed value at current read position (i.e. `n` + * samples behind current write pos, where `n` is the length of this + * delay line). + */ + deref(): T { + return this._buf[this._rpos]; + } - /** - * Reads value at `t` relative to current write position. `t` should - * be in `(-∞..0)` interval. E.g. `tap(-1)` returns the second - * most recent value written. - * - * @param t - - */ - tap(t: number) { - return this._buf[wrap((t | 0) + this._wpos, 0, this._buf.length - 1)]; - } + /** + * Reads value at `t` relative to current write position. `t` should + * be in `(-∞..0)` interval. E.g. `tap(-1)` returns the second + * most recent value written. + * + * @param t - + */ + tap(t: number) { + return this._buf[wrap((t | 0) + this._wpos, 0, this._buf.length - 1)]; + } - /** - * Takes an array of offsets relative to current write position, - * calls {@link Delay.tap} for each, writes results to `out` and - * returns it. - */ - multiTap(t: ArrayLike, out: T[] = []) { - for (let i = t.length; i-- > 0; ) { - out[i] = this.tap(t[i]); - } - return out; - } + /** + * Takes an array of offsets relative to current write position, + * calls {@link Delay.tap} for each, writes results to `out` and + * returns it. + */ + multiTap(t: ArrayLike, out: T[] = []) { + for (let i = t.length; i-- > 0; ) { + out[i] = this.tap(t[i]); + } + return out; + } - /** - * Progresses read & write pos, stores & returns new value. - * - * @param x - - */ - next(x: T) { - this.step(); - this._buf[this._wpos] = x; - return x; - } + /** + * Progresses read & write pos, stores & returns new value. + * + * @param x - + */ + next(x: T) { + this.step(); + this._buf[this._wpos] = x; + return x; + } - /** - * Moves read & write cursors one step forward. Useful for skipping - * elements and/or to create gaps in the delay line. - */ - step() { - const n = this._buf.length; - ++this._wpos >= n && (this._wpos -= n); - ++this._rpos >= n && (this._rpos -= n); - } + /** + * Moves read & write cursors one step forward. Useful for skipping + * elements and/or to create gaps in the delay line. + */ + step() { + const n = this._buf.length; + ++this._wpos >= n && (this._wpos -= n); + ++this._rpos >= n && (this._rpos -= n); + } } diff --git a/packages/dsp/src/feedback-delay.ts b/packages/dsp/src/feedback-delay.ts index d88879826c..1b7ba8d10d 100644 --- a/packages/dsp/src/feedback-delay.ts +++ b/packages/dsp/src/feedback-delay.ts @@ -9,23 +9,23 @@ import { Delay } from "./delay.js"; * @param feedback - feedback factor (default: 0.5) */ export const feedbackDelay = (n: number, feedback?: number) => - new FeedbackDelay(n, feedback); + new FeedbackDelay(n, feedback); export class FeedbackDelay extends Delay { - constructor(n: number, protected _feedback = 0.5) { - super(n, 0); - this.setFeedback(_feedback); - } + constructor(n: number, protected _feedback = 0.5) { + super(n, 0); + this.setFeedback(_feedback); + } - next(x: number) { - return super.next(x + this._buf[this._rpos] * this._feedback); - } + next(x: number) { + return super.next(x + this._buf[this._rpos] * this._feedback); + } - feedback() { - return this._feedback; - } + feedback() { + return this._feedback; + } - setFeedback(feedback: number) { - this._feedback = clamp01(feedback); - } + setFeedback(feedback: number) { + this._feedback = clamp01(feedback); + } } diff --git a/packages/dsp/src/fft.ts b/packages/dsp/src/fft.ts index 6aad19f25d..4e28451915 100644 --- a/packages/dsp/src/fft.ts +++ b/packages/dsp/src/fft.ts @@ -8,21 +8,21 @@ import { applyWindow } from "./window.js"; /** * Returns a new tuple of real/img F64 buffers of given size. * - * @param n - + * @param n - */ export const complexArray = (n: number): ComplexArray => [ - new Float64Array(n), - new Float64Array(n), + new Float64Array(n), + new Float64Array(n), ]; /** * Creates a deep copy of given {@link ComplexArray}. * - * @param complex - + * @param complex - */ export const copyComplex = (complex: ComplexArray): ComplexArray => [ - complex[0].slice(), - complex[1].slice(), + complex[0].slice(), + complex[1].slice(), ]; /** @@ -66,116 +66,116 @@ export const copyComplex = (complex: ComplexArray): ComplexArray => [ * // ] * ``` * - * @param src - - * @param isImg - + * @param src - + * @param isImg - */ export function conjugate(src: NumericArray, isImg?: boolean): NumericArray; export function conjugate(complex: ComplexArray): ComplexArray; export function conjugate(src: NumericArray | ComplexArray, isImg = true): any { - if (isComplex(src)) { - const n = src[0].length; - const res = complexArray(n * 2); - const [sreal, simg] = src; - const [dreal, dimg] = res; - (dreal).set(sreal); - (dimg).set(simg); - for (let i = 1, j = n * 2 - 1; i < n; i++, j--) { - dreal[j] = sreal[i]; - dimg[j] = -simg[i]; - } - return res; - } else { - const n = src.length; - const dest = new Float64Array(n * 2); - dest.set(src); - for (let i = 1, j = n * 2 - 1; i < n; i++, j--) { - dest[j] = isImg ? -(src)[i] : (src)[i]; - } - return dest; - } + if (isComplex(src)) { + const n = src[0].length; + const res = complexArray(n * 2); + const [sreal, simg] = src; + const [dreal, dimg] = res; + (dreal).set(sreal); + (dimg).set(simg); + for (let i = 1, j = n * 2 - 1; i < n; i++, j--) { + dreal[j] = sreal[i]; + dimg[j] = -simg[i]; + } + return res; + } else { + const n = src.length; + const dest = new Float64Array(n * 2); + dest.set(src); + for (let i = 1, j = n * 2 - 1; i < n; i++, j--) { + dest[j] = isImg ? -(src)[i] : (src)[i]; + } + return dest; + } } const swapR = (real: NumericArray, n: number) => { - const n2 = n >> 1; - let ii: number; - let jj: number; - let k: number; - let t: number; - for (let i = 1, j = 1; i < n; i++) { - if (i < j) { - ii = i - 1; - jj = j - 1; - t = real[jj]; - real[jj] = real[ii]; - real[ii] = t; - } - k = n2; - while (k < j) { - j -= k; - k >>= 1; - } - j += k; - } + const n2 = n >> 1; + let ii: number; + let jj: number; + let k: number; + let t: number; + for (let i = 1, j = 1; i < n; i++) { + if (i < j) { + ii = i - 1; + jj = j - 1; + t = real[jj]; + real[jj] = real[ii]; + real[ii] = t; + } + k = n2; + while (k < j) { + j -= k; + k >>= 1; + } + j += k; + } }; const swapRI = (real: NumericArray, img: NumericArray, n: number) => { - const n2 = n >> 1; - let ii: number; - let jj: number; - let k: number; - let t: number; - for (let i = 1, j = 1; i < n; i++) { - if (i < j) { - ii = i - 1; - jj = j - 1; - t = real[jj]; - real[jj] = real[ii]; - real[ii] = t; - t = img[jj]; - img[jj] = img[ii]; - img[ii] = t; - } - k = n2; - while (k < j) { - j -= k; - k >>= 1; - } - j += k; - } + const n2 = n >> 1; + let ii: number; + let jj: number; + let k: number; + let t: number; + for (let i = 1, j = 1; i < n; i++) { + if (i < j) { + ii = i - 1; + jj = j - 1; + t = real[jj]; + real[jj] = real[ii]; + real[ii] = t; + t = img[jj]; + img[jj] = img[ii]; + img[ii] = t; + } + k = n2; + while (k < j) { + j -= k; + k >>= 1; + } + j += k; + } }; const transform = (real: NumericArray, img: NumericArray, n: number) => { - let step = 1; - let prevStep: number; - let i: number, j: number, ii: number, ip: number; - let tr: number, ti: number; - let ur: number, ui: number; - let wr: number, wi: number; - let t: number; - for (let b = Math.ceil(Math.log2(n)); b-- > 0; ) { - prevStep = step; - step <<= 1; - ur = 1; - ui = 0; - t = Math.PI / prevStep; - wr = Math.cos(t); - wi = -Math.sin(t); - for (j = 1; j <= prevStep; j++) { - for (i = j; i <= n; i += step) { - ip = i + prevStep - 1; - ii = i - 1; - tr = real[ip] * ur - img[ip] * ui; - ti = real[ip] * ui + img[ip] * ur; - real[ip] = real[ii] - tr; - img[ip] = img[ii] - ti; - real[ii] += tr; - img[ii] += ti; - } - t = ur; - ur = t * wr - ui * wi; - ui = t * wi + ui * wr; - } - } + let step = 1; + let prevStep: number; + let i: number, j: number, ii: number, ip: number; + let tr: number, ti: number; + let ur: number, ui: number; + let wr: number, wi: number; + let t: number; + for (let b = Math.ceil(Math.log2(n)); b-- > 0; ) { + prevStep = step; + step <<= 1; + ur = 1; + ui = 0; + t = Math.PI / prevStep; + wr = Math.cos(t); + wi = -Math.sin(t); + for (j = 1; j <= prevStep; j++) { + for (i = j; i <= n; i += step) { + ip = i + prevStep - 1; + ii = i - 1; + tr = real[ip] * ur - img[ip] * ui; + ti = real[ip] * ui + img[ip] * ur; + real[ip] = real[ii] - tr; + img[ip] = img[ii] - ti; + real[ii] += tr; + img[ii] += ti; + } + t = ur; + ur = t * wr - ui * wi; + ui = t * wi + ui * wr; + } + } }; /** @@ -194,34 +194,34 @@ const transform = (real: NumericArray, img: NumericArray, n: number) => { * - https://betterexplained.com/articles/an-interactive-guide-to-the-fourier-transform/ * - http://toxi.co.uk/p5/fftDebug/fft4amit.pde * - * @param complex - - * @param window - + * @param complex - + * @param window - */ export const fft = ( - complex: NumericArray | ComplexArray, - window?: NumericArray + complex: NumericArray | ComplexArray, + window?: NumericArray ): ComplexArray => { - let real: NumericArray, img: NumericArray | undefined; - if (isComplex(complex)) { - real = complex[0]; - img = complex[1]; - } else { - real = complex; - } - if (window) { - applyWindow(real, window); - } - const n = real.length; - if (img) { - swapRI(real, img, n); - } else { - swapR(real, n); - img = new Float64Array(n); - } + let real: NumericArray, img: NumericArray | undefined; + if (isComplex(complex)) { + real = complex[0]; + img = complex[1]; + } else { + real = complex; + } + if (window) { + applyWindow(real, window); + } + const n = real.length; + if (img) { + swapRI(real, img, n); + } else { + swapR(real, n); + img = new Float64Array(n); + } - transform(real, img, n); + transform(real, img, n); - return [real, img]; + return [real, img]; }; /** @@ -233,27 +233,27 @@ export const fft = ( * * - https://www.dsprelated.com/showarticle/800.php (method #3) * - * @param complex - + * @param complex - */ export const ifft = (src: NumericArray | ComplexArray): ComplexArray => { - let complex: ComplexArray = isComplex(src) - ? src - : [new Float64Array(src.length), src]; - fft([complex[1], complex[0]]); - return scaleFFT(complex, 1 / complex[0].length); + let complex: ComplexArray = isComplex(src) + ? src + : [new Float64Array(src.length), src]; + fft([complex[1], complex[0]]); + return scaleFFT(complex, 1 / complex[0].length); }; export const scaleFFT = ( - complex: ComplexArray, - scale: number + complex: ComplexArray, + scale: number ): ComplexArray => { - const [real, img] = complex; - const n = real.length; - for (let i = 0; i < n; i++) { - real[i] *= scale; - img[i] *= scale; - } - return [real, img]; + const [real, img] = complex; + const n = real.length; + for (let i = 0; i < n; i++) { + real[i] *= scale; + img[i] *= scale; + } + return [real, img]; }; /** @@ -268,12 +268,12 @@ export const scaleFFT = ( * References: * - https://holometer.fnal.gov/GH_FFT.pdf * - * @param complex - - * @param window - + * @param complex - + * @param window - */ export const normalizeFFT = ( - complex: ComplexArray, - window: number | NumericArray = 2 / complex[0].length + complex: ComplexArray, + window: number | NumericArray = 2 / complex[0].length ): ComplexArray => scaleFFT(complex, powerScale(window, 2)); /** @@ -288,12 +288,12 @@ export const normalizeFFT = ( * References: * - https://holometer.fnal.gov/GH_FFT.pdf * - * @param complex - - * @param window - + * @param complex - + * @param window - */ export const denormalizeFFT = ( - complex: ComplexArray, - window: number | NumericArray = complex[0].length / 2 + complex: ComplexArray, + window: number | NumericArray = complex[0].length / 2 ): ComplexArray => scaleFFT(complex, invPowerScale(window, 2)); /** @@ -308,17 +308,17 @@ export const denormalizeFFT = ( * References: * - https://www.gaussianwaves.com/2015/11/interpreting-fft-results-obtaining-magnitude-and-phase-information/ * - * @param complex - - * @param eps - + * @param complex - + * @param eps - */ export const thresholdFFT = (complex: ComplexArray, eps = 1e-12) => { - const [real, img] = complex; - for (let i = 0, n = real.length; i < n; i++) { - if (Math.hypot(real[i], img[i]) < eps) { - real[i] = img[i] = 0; - } - } - return complex; + const [real, img] = complex; + for (let i = 0, n = real.length; i < n; i++) { + if (Math.hypot(real[i], img[i]) < eps) { + real[i] = img[i] = 0; + } + } + return complex; }; /** @@ -330,15 +330,15 @@ export const thresholdFFT = (complex: ComplexArray, eps = 1e-12) => { * @param out - result array */ export const spectrumMag = ( - complex: ComplexArray, - n = complex[0].length / 2, - out: NumericArray = [] + complex: ComplexArray, + n = complex[0].length / 2, + out: NumericArray = [] ) => { - const [real, img] = complex; - for (let i = 0; i < n; i++) { - out[i] = Math.hypot(real[i], img[i]); - } - return out; + const [real, img] = complex; + for (let i = 0; i < n; i++) { + out[i] = Math.hypot(real[i], img[i]); + } + return out; }; /** @@ -363,26 +363,26 @@ export const spectrumMag = ( * - https://dsp.stackexchange.com/a/14935 * - https://www.kvraudio.com/forum/viewtopic.php?t=276092 * - * @param complex - - * @param db - - * @param window - - * @param n - - * @param out - + * @param complex - + * @param db - + * @param window - + * @param n - + * @param out - */ export const spectrumPow = ( - complex: ComplexArray, - db = false, - window: number | NumericArray = 2 / complex[0].length, - n = complex[0].length / 2, - out: NumericArray = [] + complex: ComplexArray, + db = false, + window: number | NumericArray = 2 / complex[0].length, + n = complex[0].length / 2, + out: NumericArray = [] ) => { - const [real, img] = complex; - const scale = powerScale(window, 2); - for (let i = 0; i < n; i++) { - const p = real[i] ** 2 + img[i] ** 2; - out[i] = db ? magDb(Math.sqrt(p) * scale) : p * scale; - } - return out; + const [real, img] = complex; + const scale = powerScale(window, 2); + for (let i = 0; i < n; i++) { + const p = real[i] ** 2 + img[i] ** 2; + out[i] = db ? magDb(Math.sqrt(p) * scale) : p * scale; + } + return out; }; /** @@ -398,15 +398,15 @@ export const spectrumPow = ( * @param out - result array */ export const spectrumPhase = ( - complex: ComplexArray, - n = complex[0].length / 2, - out: NumericArray = [] + complex: ComplexArray, + n = complex[0].length / 2, + out: NumericArray = [] ) => { - const [real, img] = complex; - for (let i = 0; i < n; i++) { - out[i] = Math.atan2(img[i], real[i]); - } - return out; + const [real, img] = complex; + for (let i = 0; i < n; i++) { + out[i] = Math.atan2(img[i], real[i]); + } + return out; }; /** @@ -439,9 +439,9 @@ export const binFreq: FnN3 = (bin, fs, n) => (bin * fs) / n; * @param m - number of result values */ export const fftFreq = (n: number, fs: number, m = n / 2) => { - const res = new Float64Array(m); - for (let i = 0; i <= m; i++) { - res[i] = binFreq(i, fs, n); - } - return res; + const res = new Float64Array(m); + for (let i = 0; i <= m; i++) { + res[i] = binFreq(i, fs, n); + } + return res; }; diff --git a/packages/dsp/src/filter-response.ts b/packages/dsp/src/filter-response.ts index 8c0f41961d..e8184a13c8 100644 --- a/packages/dsp/src/filter-response.ts +++ b/packages/dsp/src/filter-response.ts @@ -22,35 +22,35 @@ import { line } from "./line.js"; * @param db - */ export const filterResponseRaw = ( - zeroes: NumericArray, - poles: NumericArray, - freq: number, - db = true + zeroes: NumericArray, + poles: NumericArray, + freq: number, + db = true ): FilterResponse => { - const w0 = TAU * freq; - const [cp, sp] = convolve(poles, w0); - const [cz, sz] = convolve(zeroes, w0); - const mag = Math.sqrt((cz * cz + sz * sz) / (cp * cp + sp * sp)); - const phase = Math.atan2(sp, cp) - Math.atan2(sz, cz); - return { freq, phase, mag: db ? magDb(mag) : mag }; + const w0 = TAU * freq; + const [cp, sp] = convolve(poles, w0); + const [cz, sz] = convolve(zeroes, w0); + const mag = Math.sqrt((cz * cz + sz * sz) / (cp * cp + sp * sp)); + const phase = Math.atan2(sp, cp) - Math.atan2(sz, cz); + return { freq, phase, mag: db ? magDb(mag) : mag }; }; export const filterResponse = ( - coeffs: FilterConfig, - freq: number, - db?: boolean + coeffs: FilterConfig, + freq: number, + db?: boolean ) => filterResponseRaw(coeffs.zeroes, coeffs.poles, freq, db); export const freqRange: FnU3 = (fstart, fend, num) => - line(fstart, fend, num - 1).take(num); + line(fstart, fend, num - 1).take(num); const convolve = (coeffs: NumericArray, w0: number) => { - let c = 0; - let s = 0; - for (let i = coeffs.length; i-- > 0; ) { - const k = cossin(w0 * i, coeffs[i]); - c += k[0]; - s += k[1]; - } - return [c, s]; + let c = 0; + let s = 0; + for (let i = coeffs.length; i-- > 0; ) { + const k = cossin(w0 * i, coeffs[i]); + c += k[0]; + s += k[1]; + } + return [c, s]; }; diff --git a/packages/dsp/src/foldback.ts b/packages/dsp/src/foldback.ts index 0dae5bdde8..439651845d 100644 --- a/packages/dsp/src/foldback.ts +++ b/packages/dsp/src/foldback.ts @@ -13,30 +13,30 @@ import { AProc } from "./aproc.js"; * @param amp - post amplifier */ export const foldback = (thresh?: number, amp?: number) => - new Foldback(thresh, amp); + new Foldback(thresh, amp); export class Foldback extends AProc { - constructor(protected _thresh = 1, protected _amp = 1 / _thresh) { - super(0); - } + constructor(protected _thresh = 1, protected _amp = 1 / _thresh) { + super(0); + } - next(x: number) { - return (this._val = _foldback(this._thresh, x) * this._amp); - } + next(x: number) { + return (this._val = _foldback(this._thresh, x) * this._amp); + } - threshold() { - return this._thresh; - } + threshold() { + return this._thresh; + } - setThreshold(t: number) { - this._thresh = t; - } + setThreshold(t: number) { + this._thresh = t; + } - amp() { - return this._amp; - } + amp() { + return this._amp; + } - setAmp(a: number) { - this._amp = a; - } + setAmp(a: number) { + this._amp = a; + } } diff --git a/packages/dsp/src/impulse-train.ts b/packages/dsp/src/impulse-train.ts index a147bc91cb..9e5d56527a 100644 --- a/packages/dsp/src/impulse-train.ts +++ b/packages/dsp/src/impulse-train.ts @@ -8,41 +8,41 @@ import { AGen } from "./agen.js"; * @param start - */ export const impulseTrain = (period: number, start?: number) => - new ImpulseTrain(1, 0, period, start); + new ImpulseTrain(1, 0, period, start); export const impulseTrainT = ( - on: T, - off: T, - period: number, - start?: number + on: T, + off: T, + period: number, + start?: number ) => new ImpulseTrain(on, off, period, start); export const impulseTrainB = (period: number, start?: number) => - new ImpulseTrain(true, false, period, start); + new ImpulseTrain(true, false, period, start); export class ImpulseTrain extends AGen implements IReset { - protected _startpos: number; + protected _startpos: number; - constructor( - protected _on: T, - protected _off: T, - protected _period: number, - protected _pos = 0 - ) { - super(_off); - this._startpos = --this._pos; - } + constructor( + protected _on: T, + protected _off: T, + protected _period: number, + protected _pos = 0 + ) { + super(_off); + this._startpos = --this._pos; + } - reset() { - this._val = this._off; - this._pos = this._startpos; - return this; - } + reset() { + this._val = this._off; + this._pos = this._startpos; + return this; + } - next() { - return (this._val = - ++this._pos >= this._period - ? ((this._pos = 0), this._on) - : this._off); - } + next() { + return (this._val = + ++this._pos >= this._period + ? ((this._pos = 0), this._on) + : this._off); + } } diff --git a/packages/dsp/src/impulse.ts b/packages/dsp/src/impulse.ts index 2d087d51b8..f4c9847c54 100644 --- a/packages/dsp/src/impulse.ts +++ b/packages/dsp/src/impulse.ts @@ -5,7 +5,7 @@ import { AGen } from "./agen.js"; * Numeric version of {@link impulseT}, using given `on` (default: 1) as * initial value and zero for the remaining values. * - * @param on - + * @param on - */ export const impulse = (on = 1) => new Impulse(on, 0); @@ -22,23 +22,23 @@ export const impulseT = (on: T, off: T) => new Impulse(on, off); * Boolean version of {@link impulseT}, using given `start` (default: * true) as initial value and its inverse for the remaining values. * - * @param start - + * @param start - */ export const impulseB = (start = true) => new Impulse(start, !start); export class Impulse extends AGen implements IReset { - constructor(protected _on: T, protected _off: T) { - super(_on); - } + constructor(protected _on: T, protected _off: T) { + super(_on); + } - reset() { - this._val = this._on; - return this; - } + reset() { + this._val = this._on; + return this; + } - next() { - const x = this._val; - this._val = this._off; - return x; - } + next() { + const x = this._val; + this._val = this._off; + return x; + } } diff --git a/packages/dsp/src/integrator.ts b/packages/dsp/src/integrator.ts index f31c0b5274..8ae2069d3a 100644 --- a/packages/dsp/src/integrator.ts +++ b/packages/dsp/src/integrator.ts @@ -9,27 +9,27 @@ import { AProc } from "./aproc.js"; * @param coeff - leak (default: 1) */ export const integrator = (coeff?: number, start?: number) => - new Integrator(coeff, start); + new Integrator(coeff, start); export class Integrator extends AProc implements IReset { - constructor(protected _coeff = 1, protected _start = 0) { - super(_start); - } + constructor(protected _coeff = 1, protected _start = 0) { + super(_start); + } - reset() { - this._val = this._start; - return this; - } + reset() { + this._val = this._start; + return this; + } - next(x: number) { - return (this._val = this._val * this._coeff + x); - } + next(x: number) { + return (this._val = this._val * this._coeff + x); + } - coeff() { - return this._coeff; - } + coeff() { + return this._coeff; + } - setCoeff(c: number) { - this._coeff = c; - } + setCoeff(c: number) { + this._coeff = c; + } } diff --git a/packages/dsp/src/internal/take.ts b/packages/dsp/src/internal/take.ts index e8a44ecc02..b89e8c78ee 100644 --- a/packages/dsp/src/internal/take.ts +++ b/packages/dsp/src/internal/take.ts @@ -1,13 +1,13 @@ import type { IGen } from "../api.js"; export const __take = ( - src: IGen, - num: number, - out: T[] = [], - idx = 0 + src: IGen, + num: number, + out: T[] = [], + idx = 0 ) => { - for (; num-- > 0; ) { - out[idx++] = src.next(); - } - return out; + for (; num-- > 0; ) { + out[idx++] = src.next(); + } + return out; }; diff --git a/packages/dsp/src/iterable.ts b/packages/dsp/src/iterable.ts index b683e8d261..abfd149547 100644 --- a/packages/dsp/src/iterable.ts +++ b/packages/dsp/src/iterable.ts @@ -12,42 +12,42 @@ import { __take } from "./internal/take.js"; * The `initial` value is required to satisfy `.deref()` and the case where the * iterable doesn't provide a single value. * - * @param src - - * @param initial - + * @param src - + * @param initial - */ export const iterable = (src: Iterable, initial: T) => - new $Iterable(src, initial); + new $Iterable(src, initial); export class $Iterable implements IGen { - protected _iter: Iterator | null; - protected _val: T; + protected _iter: Iterator | null; + protected _val: T; - constructor(src: Iterable, initial: T) { - this._iter = src[Symbol.iterator](); - this._val = initial; - } + constructor(src: Iterable, initial: T) { + this._iter = src[Symbol.iterator](); + this._val = initial; + } - deref() { - return this._val; - } + deref() { + return this._val; + } - *[Symbol.iterator]() { - while (true) yield this.next(); - } + *[Symbol.iterator]() { + while (true) yield this.next(); + } - next() { - if (this._iter) { - const res = this._iter.next(); - if (!res.done) { - this._val = res.value; - } else { - this._iter = null; - } - } - return this._val; - } + next() { + if (this._iter) { + const res = this._iter.next(); + if (!res.done) { + this._val = res.value; + } else { + this._iter = null; + } + } + return this._val; + } - take(num: number, out: T[] = [], idx = 0): T[] { - return __take(this, num, out, idx); - } + take(num: number, out: T[] = [], idx = 0): T[] { + return __take(this, num, out, idx); + } } diff --git a/packages/dsp/src/line.ts b/packages/dsp/src/line.ts index 4dfe7884b1..5bb1b37bac 100644 --- a/packages/dsp/src/line.ts +++ b/packages/dsp/src/line.ts @@ -23,16 +23,16 @@ import { Add } from "./add.js"; * @param clampEnd - true to clamp curve at end value (default: false) */ export const line = ( - start = 0, - end = 1, - num = 10, - skipFirst = false, - clampEnd = false + start = 0, + end = 1, + num = 10, + skipFirst = false, + clampEnd = false ) => { - const dt = (end - start) / num; - return new Add( - dt, - skipFirst ? start + dt : start, - clampEnd ? end : undefined - ); + const dt = (end - start) / num; + return new Add( + dt, + skipFirst ? start + dt : start, + clampEnd ? end : undefined + ); }; diff --git a/packages/dsp/src/madd.ts b/packages/dsp/src/madd.ts index e87fecdf12..807543114c 100644 --- a/packages/dsp/src/madd.ts +++ b/packages/dsp/src/madd.ts @@ -12,35 +12,35 @@ import { AGen } from "./agen.js"; * @param clamp - optional final value */ export const madd = ( - factor?: number, - start?: number, - offset?: number, - clamp?: number + factor?: number, + start?: number, + offset?: number, + clamp?: number ) => new MAdd(factor, start, offset, clamp); export class MAdd extends AGen implements IReset { - constructor( - protected _factor = 1, - protected _start = 1, - protected _offset = 0, - protected _clamp?: number - ) { - super(0); - this.reset(); - } + constructor( + protected _factor = 1, + protected _start = 1, + protected _offset = 0, + protected _clamp?: number + ) { + super(0); + this.reset(); + } - reset() { - this._val = (this._start - this._offset) / this._factor; - return this; - } + reset() { + this._val = (this._start - this._offset) / this._factor; + return this; + } - next() { - let v = this._val * this._factor + this._offset; - return (this._val = - this._clamp !== undefined - ? this._start < this._clamp - ? Math.min(v, this._clamp) - : Math.max(v, this._clamp) - : v); - } + next() { + let v = this._val * this._factor + this._offset; + return (this._val = + this._clamp !== undefined + ? this._start < this._clamp + ? Math.min(v, this._clamp) + : Math.max(v, this._clamp) + : v); + } } diff --git a/packages/dsp/src/mapg.ts b/packages/dsp/src/mapg.ts index 738a054f4b..74348e7e0c 100644 --- a/packages/dsp/src/mapg.ts +++ b/packages/dsp/src/mapg.ts @@ -5,110 +5,110 @@ import type { IGen } from "./api.js"; export function mapG(op: Fn2, a: IGen, init: T): IGen; export function mapG( - op: Fn3, - a: IGen, - b: IGen, - init: T + op: Fn3, + a: IGen, + b: IGen, + init: T ): IGen; export function mapG( - op: Fn4, - a: IGen, - b: IGen, - c: IGen, - init: T + op: Fn4, + a: IGen, + b: IGen, + c: IGen, + init: T ): IGen; export function mapG( - op: Fn5, - a: IGen, - b: IGen, - c: IGen, - d: IGen, - init: T + op: Fn5, + a: IGen, + b: IGen, + c: IGen, + d: IGen, + init: T ): IGen; export function mapG(op: FnAny, ...args: any[]): IGen { - switch (args.length) { - case 2: - return new MapG1(op, args[0], args[1]); - case 3: - return new MapG2(op, args[0], args[1], args[2]); - case 4: - return new MapG3(op, args[0], args[1], args[2], args[3]); - case 5: - return new MapG4(op, args[0], args[1], args[2], args[3], args[4]); - default: - illegalArity(args.length); - } + switch (args.length) { + case 2: + return new MapG1(op, args[0], args[1]); + case 3: + return new MapG2(op, args[0], args[1], args[2]); + case 4: + return new MapG3(op, args[0], args[1], args[2], args[3]); + case 5: + return new MapG4(op, args[0], args[1], args[2], args[3], args[4]); + default: + illegalArity(args.length); + } } export class MapG1 extends AGen { - constructor(protected _op: Fn2, protected _a: IGen, init: T) { - super(init); - } + constructor(protected _op: Fn2, protected _a: IGen, init: T) { + super(init); + } - next() { - return (this._val = this._op(this._a.next(), this._val)); - } + next() { + return (this._val = this._op(this._a.next(), this._val)); + } } export class MapG2 extends AGen { - constructor( - protected _op: Fn3, - protected _a: IGen, - protected _b: IGen, - init: T - ) { - super(init); - } + constructor( + protected _op: Fn3, + protected _a: IGen, + protected _b: IGen, + init: T + ) { + super(init); + } - next() { - return (this._val = this._op( - this._a.next(), - this._b.next(), - this._val - )); - } + next() { + return (this._val = this._op( + this._a.next(), + this._b.next(), + this._val + )); + } } export class MapG3 extends AGen { - constructor( - protected _op: Fn4, - protected _a: IGen, - protected _b: IGen, - protected _c: IGen, - init: T - ) { - super(init); - } + constructor( + protected _op: Fn4, + protected _a: IGen, + protected _b: IGen, + protected _c: IGen, + init: T + ) { + super(init); + } - next() { - return (this._val = this._op( - this._a.next(), - this._b.next(), - this._c.next(), - this._val - )); - } + next() { + return (this._val = this._op( + this._a.next(), + this._b.next(), + this._c.next(), + this._val + )); + } } export class MapG4 extends AGen { - constructor( - protected _op: Fn5, - protected _a: IGen, - protected _b: IGen, - protected _c: IGen, - protected _d: IGen, - init: T - ) { - super(init); - } + constructor( + protected _op: Fn5, + protected _a: IGen, + protected _b: IGen, + protected _c: IGen, + protected _d: IGen, + init: T + ) { + super(init); + } - next() { - return (this._val = this._op( - this._a.next(), - this._b.next(), - this._c.next(), - this._d.next(), - this._val - )); - } + next() { + return (this._val = this._op( + this._a.next(), + this._b.next(), + this._c.next(), + this._d.next(), + this._val + )); + } } diff --git a/packages/dsp/src/mix.ts b/packages/dsp/src/mix.ts index 14c17eb2b6..1ee0d9091d 100644 --- a/packages/dsp/src/mix.ts +++ b/packages/dsp/src/mix.ts @@ -2,19 +2,19 @@ import { clamp01 } from "@thi.ng/math/interval"; import { AProc2 } from "./aproc.js"; export class Mix extends AProc2 { - constructor(protected _t: number) { - super(0); - } + constructor(protected _t: number) { + super(0); + } - get mix() { - return this._t; - } + get mix() { + return this._t; + } - set mix(x: number) { - this._t = clamp01(x); - } + set mix(x: number) { + this._t = clamp01(x); + } - next(a: number, b: number) { - return (this._val = a + (b - a) * this._t); - } + next(a: number, b: number) { + return (this._val = a + (b - a) * this._t); + } } diff --git a/packages/dsp/src/mul.ts b/packages/dsp/src/mul.ts index 637e7ffc4f..9775629418 100644 --- a/packages/dsp/src/mul.ts +++ b/packages/dsp/src/mul.ts @@ -12,30 +12,30 @@ import { AGen } from "./agen.js"; * @param clamp - */ export const mul = (factor?: number, start?: number, clamp?: number) => - new Mul(factor, start, clamp); + new Mul(factor, start, clamp); export class Mul extends AGen implements IReset { - constructor( - protected _factor = 1, - protected _start = 1, - protected _clamp?: number - ) { - super(0); - this.reset(); - } + constructor( + protected _factor = 1, + protected _start = 1, + protected _clamp?: number + ) { + super(0); + this.reset(); + } - reset() { - this._val = this._start / this._factor; - return this; - } + reset() { + this._val = this._start / this._factor; + return this; + } - next() { - let v = this._val * this._factor; - return (this._val = - this._clamp !== undefined - ? this._start < this._clamp - ? Math.min(v, this._clamp) - : Math.max(v, this._clamp) - : v); - } + next() { + let v = this._val * this._factor; + return (this._val = + this._clamp !== undefined + ? this._start < this._clamp + ? Math.min(v, this._clamp) + : Math.max(v, this._clamp) + : v); + } } diff --git a/packages/dsp/src/multiplex.ts b/packages/dsp/src/multiplex.ts index 7a2e59fd6f..bc0ed76dc4 100644 --- a/packages/dsp/src/multiplex.ts +++ b/packages/dsp/src/multiplex.ts @@ -13,37 +13,37 @@ import { AProc } from "./aproc.js"; * * See {@link bounce} for combining results back into a single channel output. * - * @param a - - * @param b - + * @param a - + * @param b - */ export function multiplex( - a: IProc, - b: IProc + a: IProc, + b: IProc ): Multiplex; export function multiplex( - a: IProc, - b: IProc, - c: IProc + a: IProc, + b: IProc, + c: IProc ): Multiplex; export function multiplex( - a: IProc, - b: IProc, - c: IProc, - d: IProc + a: IProc, + b: IProc, + c: IProc, + d: IProc ): Multiplex; export function multiplex(...procs: IProc[]): Multiplex { - return new Multiplex(procs); + return new Multiplex(procs); } export class Multiplex extends AProc { - protected _procs: IProc[]; + protected _procs: IProc[]; - constructor(procs: IProc[]) { - super(procs.map((p) => p.deref())); - this._procs = procs; - } + constructor(procs: IProc[]) { + super(procs.map((p) => p.deref())); + this._procs = procs; + } - next(x: A) { - return (this._val = this._procs.map((p) => p.next(x))); - } + next(x: A) { + return (this._val = this._procs.map((p) => p.next(x))); + } } diff --git a/packages/dsp/src/onepole.ts b/packages/dsp/src/onepole.ts index 72bcc58c8f..48fb6a3f65 100644 --- a/packages/dsp/src/onepole.ts +++ b/packages/dsp/src/onepole.ts @@ -12,45 +12,45 @@ export const onepoleHP = (fc: number) => new OnePole("hp", fc); * https://www.earlevel.com/main/2012/12/15/a-one-pole-filter/ */ export class OnePole - extends AProc - implements IClear, IFilter, IReset + extends AProc + implements IClear, IFilter, IReset { - protected _a0!: number; - protected _b1!: number; - - constructor(protected _type: OnepoleType, protected _freq: number) { - super(0); - this.setFreq(_freq); - } - - clear() { - this._val = 0; - } - - reset() { - this.clear(); - return this; - } - - next(x: number) { - return (this._val = x * this._a0 + this._val * this._b1); - } - - setFreq(fc: number) { - this._freq = fc = clamp05(fc); - if (this._type === "lp") { - this._b1 = Math.exp(-TAU * fc); - this._a0 = 1 - this._b1; - } else { - this._b1 = -Math.exp(-TAU * (0.5 - fc)); - this._a0 = 1 + this._b1; - } - } - - filterCoeffs(): FilterConfig { - return { - zeroes: [this._a0], - poles: [1, this._type === "lp" ? this._b1 : -this._b1], - }; - } + protected _a0!: number; + protected _b1!: number; + + constructor(protected _type: OnepoleType, protected _freq: number) { + super(0); + this.setFreq(_freq); + } + + clear() { + this._val = 0; + } + + reset() { + this.clear(); + return this; + } + + next(x: number) { + return (this._val = x * this._a0 + this._val * this._b1); + } + + setFreq(fc: number) { + this._freq = fc = clamp05(fc); + if (this._type === "lp") { + this._b1 = Math.exp(-TAU * fc); + this._a0 = 1 - this._b1; + } else { + this._b1 = -Math.exp(-TAU * (0.5 - fc)); + this._a0 = 1 + this._b1; + } + } + + filterCoeffs(): FilterConfig { + return { + zeroes: [this._a0], + poles: [1, this._type === "lp" ? this._b1 : -this._b1], + }; + } } diff --git a/packages/dsp/src/osc-additive.ts b/packages/dsp/src/osc-additive.ts index 7d49281602..97fb92d26d 100644 --- a/packages/dsp/src/osc-additive.ts +++ b/packages/dsp/src/osc-additive.ts @@ -12,30 +12,30 @@ import { sin } from "./osc-sin.js"; * and amplitude factors for each of the `n` requested harmonics (given in [i,n] * range). * - * @param osc - - * @param freqFn - - * @param ampFn - - * @param n - + * @param osc - + * @param freqFn - + * @param ampFn - + * @param n - */ export const additive = ( - osc: StatelessOscillator, - freqFn: Fn, - ampFn: Fn, - n: number + osc: StatelessOscillator, + freqFn: Fn, + ampFn: Fn, + n: number ): StatelessOscillator => { - const fcache: number[] = []; - const acache: number[] = []; - for (let i = 1; i <= n; i++) { - fcache.push(freqFn(i)); - acache.push(ampFn(i)); - } - return (phase, freq, amp = 1, dc = 0) => { - let y = 0; - for (let i = 0; i < n; i++) { - y += osc(phase, freq * fcache[i], acache[i]); - } - return dc + amp * y; - }; + const fcache: number[] = []; + const acache: number[] = []; + for (let i = 1; i <= n; i++) { + fcache.push(freqFn(i)); + acache.push(ampFn(i)); + } + return (phase, freq, amp = 1, dc = 0) => { + let y = 0; + for (let i = 0; i < n; i++) { + y += osc(phase, freq * fcache[i], acache[i]); + } + return dc + amp * y; + }; }; /** @@ -45,12 +45,12 @@ export const additive = ( * @param n - number of octaves */ export const squareAdditive = (n = 8) => - additive( - sin, - (i) => 2 * (i - 1) + 1, - (i) => (1 / (2 * (i - 1) + 1)) * gibbs(n, i), - n - ); + additive( + sin, + (i) => 2 * (i - 1) + 1, + (i) => (1 / (2 * (i - 1) + 1)) * gibbs(n, i), + n + ); /** * Interactive graph of this oscillator: @@ -59,9 +59,9 @@ export const squareAdditive = (n = 8) => * @param n - number of octaves */ export const sawAdditive = (n = 8) => - additive( - sin, - (i) => i, - (i) => (1 / i) * gibbs(n, i), - n - ); + additive( + sin, + (i) => i, + (i) => (1 / i) * gibbs(n, i), + n + ); diff --git a/packages/dsp/src/osc-cos.ts b/packages/dsp/src/osc-cos.ts index fc543ce940..b2bda9f1f4 100644 --- a/packages/dsp/src/osc-cos.ts +++ b/packages/dsp/src/osc-cos.ts @@ -2,4 +2,4 @@ import { TAU } from "@thi.ng/math/api"; import type { StatelessOscillator } from "./api.js"; export const cos: StatelessOscillator = (phase, freq, amp = 1, dc = 0) => - dc + amp * Math.cos(phase * freq * TAU); + dc + amp * Math.cos(phase * freq * TAU); diff --git a/packages/dsp/src/osc-dsf.ts b/packages/dsp/src/osc-dsf.ts index cbdd46afce..1bd3323ffb 100644 --- a/packages/dsp/src/osc-dsf.ts +++ b/packages/dsp/src/osc-dsf.ts @@ -22,49 +22,49 @@ import type { StatelessOscillator } from "./api.js"; * - https://www.desmos.com/calculator/klvl9oszfm * - https://ccrma.stanford.edu/files/papers/stanm5.pdf * - * @param phase - - * @param freq - - * @param amp - - * @param dc - - * @param alpha - - * @param beta - + * @param phase - + * @param freq - + * @param amp - + * @param dc - + * @param alpha - + * @param beta - */ export const dsf: StatelessOscillator = ( - phase, - freq, - amp = 1, - dc = 0, - alpha = 0.5, - beta = 1 + phase, + freq, + amp = 1, + dc = 0, + alpha = 0.5, + beta = 1 ) => { - const aa = alpha * alpha; - const a2 = 2 * alpha; - phase *= TAU * freq; - return ( - amp * - (((1 - aa) * Math.sin(phase)) / - (1 + aa - a2 * Math.cos(beta * phase))) + - dc - ); + const aa = alpha * alpha; + const a2 = 2 * alpha; + phase *= TAU * freq; + return ( + amp * + (((1 - aa) * Math.sin(phase)) / + (1 + aa - a2 * Math.cos(beta * phase))) + + dc + ); }; /** * Higher order version of {@link dsf} oscillator with pre-configured * params. Slightly faster, but not dynamically changeable waveform. * - * @param alpha - - * @param beta - + * @param alpha - + * @param beta - */ export const dsfHOF = (alpha = 0.5, beta = 1): StatelessOscillator => { - const aa = alpha * alpha; - const a2 = 2 * alpha; - return (phase, freq, amp = 1, dc = 0) => { - phase *= TAU * freq; - return ( - amp * - (((1 - aa) * Math.sin(phase)) / - (1 + aa - a2 * Math.cos(beta * phase))) + - dc - ); - }; + const aa = alpha * alpha; + const a2 = 2 * alpha; + return (phase, freq, amp = 1, dc = 0) => { + phase *= TAU * freq; + return ( + amp * + (((1 - aa) * Math.sin(phase)) / + (1 + aa - a2 * Math.cos(beta * phase))) + + dc + ); + }; }; diff --git a/packages/dsp/src/osc-mix.ts b/packages/dsp/src/osc-mix.ts index 49ad4c3154..4b1a087908 100644 --- a/packages/dsp/src/osc-mix.ts +++ b/packages/dsp/src/osc-mix.ts @@ -8,30 +8,30 @@ import type { StatelessOscillator } from "./api.js"; * control contributions of either oscillator (default: 0.5 aka 50/50 * ratio). * - * @param osc1 - - * @param osc2 - + * @param osc1 - + * @param osc2 - */ export const mixOsc = - ( - osc1: StatelessOscillator, - osc2: StatelessOscillator - ): StatelessOscillator => - (phase, freq, amp = 1, dc = 0, t = 0.5) => - _mix(osc1(phase, freq, amp, dc), osc2(phase, freq, amp, dc), t); + ( + osc1: StatelessOscillator, + osc2: StatelessOscillator + ): StatelessOscillator => + (phase, freq, amp = 1, dc = 0, t = 0.5) => + _mix(osc1(phase, freq, amp, dc), osc2(phase, freq, amp, dc), t); /** * Similar to {@link mixOsc}, but with `mix` arg ([0..1] range) * directly given to HOF and not changeable after. * - * @param osc1 - - * @param osc2 - - * @param mix - + * @param osc1 - + * @param osc2 - + * @param mix - */ export const mixOscHOF = - ( - osc1: StatelessOscillator, - osc2: StatelessOscillator, - mix = 0.5 - ): StatelessOscillator => - (phase, freq, amp = 1, dc = 0) => - _mix(osc1(phase, freq, amp, dc), osc2(phase, freq, amp, dc), mix); + ( + osc1: StatelessOscillator, + osc2: StatelessOscillator, + mix = 0.5 + ): StatelessOscillator => + (phase, freq, amp = 1, dc = 0) => + _mix(osc1(phase, freq, amp, dc), osc2(phase, freq, amp, dc), mix); diff --git a/packages/dsp/src/osc-parabolic.ts b/packages/dsp/src/osc-parabolic.ts index 93603245ad..e042e00e7c 100644 --- a/packages/dsp/src/osc-parabolic.ts +++ b/packages/dsp/src/osc-parabolic.ts @@ -14,4 +14,4 @@ import type { StatelessOscillator } from "./api.js"; * @param dc - */ export const parabolic: StatelessOscillator = (phase, freq, amp = 1, dc = 0) => - dc + amp * (8 * (fract(phase * freq) - 0.5) ** 2 - 1); + dc + amp * (8 * (fract(phase * freq) - 0.5) ** 2 - 1); diff --git a/packages/dsp/src/osc-rect.ts b/packages/dsp/src/osc-rect.ts index 814bc9b652..144fa2c685 100644 --- a/packages/dsp/src/osc-rect.ts +++ b/packages/dsp/src/osc-rect.ts @@ -2,20 +2,20 @@ import { fract } from "@thi.ng/math/prec"; import type { StatelessOscillator } from "./api.js"; export const rect: StatelessOscillator = ( - phase, - freq, - amp = 1, - dc = 0, - duty = 0.5 + phase, + freq, + amp = 1, + dc = 0, + duty = 0.5 ) => dc + amp * (fract(phase * freq) < duty ? 1 : -1); /** * Higher order version of {@link rect} with pre-configured `duty` width * (in the (0..1) range). * - * @param duty - + * @param duty - */ export const rectHOF = - (duty = 0.5): StatelessOscillator => - (phase, freq, amp, dc) => - rect(phase, freq, amp, dc, duty); + (duty = 0.5): StatelessOscillator => + (phase, freq, amp, dc) => + rect(phase, freq, amp, dc, duty); diff --git a/packages/dsp/src/osc-saw.ts b/packages/dsp/src/osc-saw.ts index 217f175cbf..63b50d9f90 100644 --- a/packages/dsp/src/osc-saw.ts +++ b/packages/dsp/src/osc-saw.ts @@ -2,4 +2,4 @@ import { fract } from "@thi.ng/math/prec"; import type { StatelessOscillator } from "./api.js"; export const saw: StatelessOscillator = (phase, freq, amp = 1, dc = 0) => - dc + amp * (1 - 2 * fract(phase * freq)); + dc + amp * (1 - 2 * fract(phase * freq)); diff --git a/packages/dsp/src/osc-sin.ts b/packages/dsp/src/osc-sin.ts index 1e1a4c97a8..4b9ebe8306 100644 --- a/packages/dsp/src/osc-sin.ts +++ b/packages/dsp/src/osc-sin.ts @@ -2,4 +2,4 @@ import { TAU } from "@thi.ng/math/api"; import type { StatelessOscillator } from "./api.js"; export const sin: StatelessOscillator = (phase, freq, amp = 1, dc = 0) => - dc + amp * Math.sin(phase * freq * TAU); + dc + amp * Math.sin(phase * freq * TAU); diff --git a/packages/dsp/src/osc-tri.ts b/packages/dsp/src/osc-tri.ts index b1a8ca7fe0..37b12b68c1 100644 --- a/packages/dsp/src/osc-tri.ts +++ b/packages/dsp/src/osc-tri.ts @@ -2,4 +2,4 @@ import { fract } from "@thi.ng/math/prec"; import type { StatelessOscillator } from "./api.js"; export const tri: StatelessOscillator = (phase, freq, amp = 1, dc = 0) => - dc + amp * (Math.abs(fract(phase * freq) * 4 - 2) - 1); + dc + amp * (Math.abs(fract(phase * freq) * 4 - 2) - 1); diff --git a/packages/dsp/src/osc-wavetable.ts b/packages/dsp/src/osc-wavetable.ts index 83be7c6d50..66d88999cd 100644 --- a/packages/dsp/src/osc-wavetable.ts +++ b/packages/dsp/src/osc-wavetable.ts @@ -4,15 +4,15 @@ import { fract } from "@thi.ng/math/prec"; import type { StatelessOscillator } from "./api.js"; export const wavetable = ( - table: NumericArray, - interpolate: Fn3 = _mix + table: NumericArray, + interpolate: Fn3 = _mix ): StatelessOscillator => { - const n = table.length; - const n1 = n - 1; - return (phase, freq, amp = 1, dc = 0) => { - phase = fract(phase * freq) * n; - let i = phase | 0; - let j = i < n1 ? i + 1 : 0; - return dc + amp * interpolate(table[i], table[j], phase - i); - }; + const n = table.length; + const n1 = n - 1; + return (phase, freq, amp = 1, dc = 0) => { + phase = fract(phase * freq) * n; + let i = phase | 0; + let j = i < n1 ? i + 1 : 0; + return dc + amp * interpolate(table[i], table[j], phase - i); + }; }; diff --git a/packages/dsp/src/osc.ts b/packages/dsp/src/osc.ts index e87e372f7d..bfe8e385d5 100644 --- a/packages/dsp/src/osc.ts +++ b/packages/dsp/src/osc.ts @@ -28,11 +28,11 @@ import { sum } from "./sum.js"; * @param dc - DC offset / center value */ export const osc = ( - osc: StatelessOscillator, - freq: IGen | number, - amp?: IGen | number, - dc?: number, - phase?: number + osc: StatelessOscillator, + freq: IGen | number, + amp?: IGen | number, + dc?: number, + phase?: number ) => new Osc(osc, freq, amp, dc, phase); /** @@ -58,44 +58,44 @@ export const osc = ( * @param amod` - normalized freq */ export const modOsc = ( - osc: StatelessOscillator, - freq: IGen | number, - fmod: IGen, - amod: IGen | number = 1 + osc: StatelessOscillator, + freq: IGen | number, + fmod: IGen, + amod: IGen | number = 1 ) => new Osc(osc, sum(fmod, isNumber(freq) ? add(freq) : freq), amod); export class Osc extends AGen { - protected _phase!: IGen; - protected _amp!: IGen; + protected _phase!: IGen; + protected _amp!: IGen; - constructor( - protected _osc: StatelessOscillator, - freq: IGen | number, - amp: IGen | number = 1, - protected _dc = 0, - phase = 0 - ) { - super(0); - isNumber(freq) ? this.setFreq(freq, phase) : this.setFreq(freq); - this.setAmp(amp); - } + constructor( + protected _osc: StatelessOscillator, + freq: IGen | number, + amp: IGen | number = 1, + protected _dc = 0, + phase = 0 + ) { + super(0); + isNumber(freq) ? this.setFreq(freq, phase) : this.setFreq(freq); + this.setAmp(amp); + } - next() { - return (this._val = this._osc( - this._phase.next(), - 1, - this._amp.next(), - this._dc - )); - } + next() { + return (this._val = this._osc( + this._phase.next(), + 1, + this._amp.next(), + this._dc + )); + } - setFreq(freq: IGen): void; - setFreq(freq: number, phase?: number): void; - setFreq(freq: number | IGen, phase?: number) { - this._phase = isNumber(freq) ? new Add(freq, phase || 0) : freq; - } + setFreq(freq: IGen): void; + setFreq(freq: number, phase?: number): void; + setFreq(freq: number | IGen, phase?: number) { + this._phase = isNumber(freq) ? new Add(freq, phase || 0) : freq; + } - setAmp(amp: IGen | number) { - this._amp = isNumber(amp) ? new Const(amp) : amp; - } + setAmp(amp: IGen | number) { + this._amp = isNumber(amp) ? new Const(amp) : amp; + } } diff --git a/packages/dsp/src/pink-noise.ts b/packages/dsp/src/pink-noise.ts index 9c3a3396f4..eb6b422424 100644 --- a/packages/dsp/src/pink-noise.ts +++ b/packages/dsp/src/pink-noise.ts @@ -32,49 +32,49 @@ const PROB = [0.00198, 0.0128, 0.049, 0.17, 0.682]; * @param prob - */ export const pinkNoise = ( - gain?: number, - rnd?: IRandom, - amp?: PNoiseCoeffs, - prob?: PNoiseCoeffs + gain?: number, + rnd?: IRandom, + amp?: PNoiseCoeffs, + prob?: PNoiseCoeffs ) => new PinkNoise(gain, rnd, amp, prob); export class PinkNoise extends AGen implements IReset { - protected _bins: number[]; - protected _psum: number[]; + protected _bins: number[]; + protected _psum: number[]; - constructor( - protected _gain = 1, - protected _rnd: IRandom = SYSTEM, - protected _amp: PNoiseCoeffs = AMP, - prob: PNoiseCoeffs = PROB - ) { - super(0); - this._gain /= _amp.reduce((acc, x) => acc + x, 0); - this._psum = prob.reduce( - (acc: number[], x, i) => ( - acc.push(i > 0 ? acc[i - 1] + x : x), acc - ), - [] - ); - this._bins = [0, 0, 0, 0, 0]; - } + constructor( + protected _gain = 1, + protected _rnd: IRandom = SYSTEM, + protected _amp: PNoiseCoeffs = AMP, + prob: PNoiseCoeffs = PROB + ) { + super(0); + this._gain /= _amp.reduce((acc, x) => acc + x, 0); + this._psum = prob.reduce( + (acc: number[], x, i) => ( + acc.push(i > 0 ? acc[i - 1] + x : x), acc + ), + [] + ); + this._bins = [0, 0, 0, 0, 0]; + } - reset() { - this._bins.fill(0); - return this; - } + reset() { + this._bins.fill(0); + return this; + } - next() { - const { _bins, _rnd, _amp, _psum } = this; - const bin = _rnd.float(); - for (let i = 0; i < 5; i++) { - if (bin <= _psum[i]) { - _bins[i] = _rnd.norm(_amp[i]); - break; - } - } - return (this._val = - this._gain * - (_bins[0] + _bins[1] + _bins[2] + _bins[3] + _bins[4])); - } + next() { + const { _bins, _rnd, _amp, _psum } = this; + const bin = _rnd.float(); + for (let i = 0; i < 5; i++) { + if (bin <= _psum[i]) { + _bins[i] = _rnd.norm(_amp[i]); + break; + } + } + return (this._val = + this._gain * + (_bins[0] + _bins[1] + _bins[2] + _bins[3] + _bins[4])); + } } diff --git a/packages/dsp/src/pipe.ts b/packages/dsp/src/pipe.ts index 486185832c..9ab8f7c659 100644 --- a/packages/dsp/src/pipe.ts +++ b/packages/dsp/src/pipe.ts @@ -6,54 +6,54 @@ import { MapG1 } from "./mapg.js"; * Higher order generator. Composes a new {@link IGen} from given source gen and * a number of {@link IProc}s (processed in series, like using {@link serial}). * - * @param src - - * @param proc - + * @param src - + * @param proc - */ export function pipe(src: IGen, proc: IProc): IGen; export function pipe( - src: IGen, - a: IProc, - b: IProc + src: IGen, + a: IProc, + b: IProc ): IGen; export function pipe( - src: IGen, - a: IProc, - b: IProc, - c: IProc + src: IGen, + a: IProc, + b: IProc, + c: IProc ): IGen; export function pipe( - src: IGen, - a: IProc, - b: IProc, - c: IProc, - d: IProc + src: IGen, + a: IProc, + b: IProc, + c: IProc, + d: IProc ): IGen; export function pipe( - src: IGen, - a: IProc, - b: IProc, - c: IProc, - d: IProc, - ...xs: IProc[] + src: IGen, + a: IProc, + b: IProc, + c: IProc, + d: IProc, + ...xs: IProc[] ): IGen; export function pipe(src: IGen, ...procs: IProc[]): IGen { - let fn: FnAny; - const [a, b, c, d] = procs; - switch (procs.length) { - case 1: - fn = (x) => a.next(x); - break; - case 2: - fn = (x) => b.next(a.next(x)); - break; - case 3: - fn = (x) => c.next(b.next(a.next(x))); - break; - case 4: - fn = (x) => d.next(c.next(b.next(a.next(x)))); - break; - default: - fn = (x) => procs.reduce((x, p) => p.next(x), x); - } - return new MapG1(fn, src, null); + let fn: FnAny; + const [a, b, c, d] = procs; + switch (procs.length) { + case 1: + fn = (x) => a.next(x); + break; + case 2: + fn = (x) => b.next(a.next(x)); + break; + case 3: + fn = (x) => c.next(b.next(a.next(x))); + break; + case 4: + fn = (x) => d.next(c.next(b.next(a.next(x)))); + break; + default: + fn = (x) => procs.reduce((x, p) => p.next(x), x); + } + return new MapG1(fn, src, null); } diff --git a/packages/dsp/src/power.ts b/packages/dsp/src/power.ts index 50cfc51093..dd609c6302 100644 --- a/packages/dsp/src/power.ts +++ b/packages/dsp/src/power.ts @@ -6,72 +6,72 @@ import { isComplex } from "./complex.js"; /** * Computes the sum of the given array. * - * @param window - + * @param window - */ export const integralT = (window: NumericArray) => { - let sum = 0; - for (let i = window.length; i-- > 0; ) { - sum += window[i]; - } - return sum; + let sum = 0; + for (let i = window.length; i-- > 0; ) { + sum += window[i]; + } + return sum; }; /** * Computes the squared sum of given array. * - * @param window - + * @param window - */ export const integralTSquared = (window: NumericArray) => { - let sum = 0; - for (let i = window.length; i-- > 0; ) { - sum += window[i] ** 2; - } - return sum; + let sum = 0; + for (let i = window.length; i-- > 0; ) { + sum += window[i] ** 2; + } + return sum; }; /** * Computes the `sum(|c(i)|)` for given complex array. * - * @param window - + * @param window - */ export const integralF = ([real, img]: ComplexArray) => { - let sum = 0; - for (let i = real.length; i-- > 0; ) { - sum += Math.hypot(real[i], img[i]); - } - return sum; + let sum = 0; + for (let i = real.length; i-- > 0; ) { + sum += Math.hypot(real[i], img[i]); + } + return sum; }; /** * Computes the `sum(|c(i)|^2)` for given complex array. * - * @param window - + * @param window - */ export const integralFSquared = ([real, img]: ComplexArray) => { - let sum = 0; - for (let i = real.length; i-- > 0; ) { - sum += real[i] ** 2 + img[i] ** 2; - } - return sum; + let sum = 0; + for (let i = real.length; i-- > 0; ) { + sum += real[i] ** 2 + img[i] ** 2; + } + return sum; }; /** * If `scale` is a number, returns it. Else returns `base / integralT(scale)`. * - * @param scale - - * @param base - + * @param scale - + * @param base - */ export const powerScale = (scale: number | NumericArray, base = 1) => - isNumber(scale) ? scale : base / integralT(scale); + isNumber(scale) ? scale : base / integralT(scale); /** * If `scale` is a number, returns it. Else returns `integralT(scale) / base`. * - * @param scale - - * @param base - + * @param scale - + * @param base - */ export const invPowerScale = (scale: number | NumericArray, base = 1) => - isNumber(scale) ? scale : integralT(scale) / base; + isNumber(scale) ? scale : integralT(scale) / base; /** * Computes sum squared power of given time or frequency domain window. @@ -81,12 +81,12 @@ export const invPowerScale = (scale: number | NumericArray, base = 1) => * - http://www.it.uom.gr/teaching/linearalgebra/NumericalRecipiesInC/c13-4.pdf * - http://www.hep.ucl.ac.uk/~rjn/saltStuff/fftNormalisation.pdf * - * @param window - + * @param window - */ export const powerSumSquared = (window: NumericArray | ComplexArray) => - isComplex(window) - ? integralFSquared(window) / window[0].length - : integralTSquared(window); + isComplex(window) + ? integralFSquared(window) / window[0].length + : integralTSquared(window); /** * Computes mean squared power of given time or frequency domain window. @@ -96,11 +96,11 @@ export const powerSumSquared = (window: NumericArray | ComplexArray) => * - http://www.it.uom.gr/teaching/linearalgebra/NumericalRecipiesInC/c13-4.pdf * - http://www.hep.ucl.ac.uk/~rjn/saltStuff/fftNormalisation.pdf * - * @param window - + * @param window - */ export const powerMeanSquared = (window: NumericArray | ComplexArray) => - powerSumSquared(window) / - (isComplex(window) ? window[0].length : window.length); + powerSumSquared(window) / + (isComplex(window) ? window[0].length : window.length); /** * Computes time-integral squared power of given time or frequency domain @@ -111,11 +111,11 @@ export const powerMeanSquared = (window: NumericArray | ComplexArray) => * - http://www.it.uom.gr/teaching/linearalgebra/NumericalRecipiesInC/c13-4.pdf * - http://www.hep.ucl.ac.uk/~rjn/saltStuff/fftNormalisation.pdf * - * @param window - + * @param window - */ export const powerTimeIntegral = ( - window: NumericArray | ComplexArray, - fs: number + window: NumericArray | ComplexArray, + fs: number ) => - (isComplex(window) ? integralFSquared(window) : integralTSquared(window)) / - fs; + (isComplex(window) ? integralFSquared(window) : integralTSquared(window)) / + fs; diff --git a/packages/dsp/src/product.ts b/packages/dsp/src/product.ts index 48c7113957..aea7ee82f6 100644 --- a/packages/dsp/src/product.ts +++ b/packages/dsp/src/product.ts @@ -11,12 +11,12 @@ import { MapG2, MapG3 } from "./mapg.js"; */ export function product(a: IGen, b: IGen): IGen; export function product( - a: IGen, - b: IGen, - c: IGen + a: IGen, + b: IGen, + c: IGen ): IGen; export function product(a: IGen, b: IGen, c?: IGen) { - return c - ? new MapG3((a, b, c) => a * b * c, a, b, c, 0) - : new MapG2((a, b) => a * b, a, b, 0); + return c + ? new MapG3((a, b, c) => a * b * c, a, b, c, 0) + : new MapG2((a, b) => a * b, a, b, 0); } diff --git a/packages/dsp/src/reciprocal.ts b/packages/dsp/src/reciprocal.ts index 0a731032ba..83f878a9e7 100644 --- a/packages/dsp/src/reciprocal.ts +++ b/packages/dsp/src/reciprocal.ts @@ -4,25 +4,25 @@ import { AGen } from "./agen.js"; /** * Returns a gen which yield sequence `y(t) = 1 / (y(t - 1) + step)`. * - * @param step - + * @param step - */ export const reciprocal = (step?: number) => new Reciprocal(step); export class Reciprocal extends AGen implements IReset { - protected _n!: number; + protected _n!: number; - constructor(protected _step = 1) { - super(1); - this.reset(); - } + constructor(protected _step = 1) { + super(1); + this.reset(); + } - reset() { - this._n = 1 - this._step; - return this; - } + reset() { + this._n = 1 - this._step; + return this; + } - next() { - this._n += this._step; - return (this._val = 1 / this._n); - } + next() { + this._n += this._step; + return (this._val = 1 / this._n); + } } diff --git a/packages/dsp/src/serial.ts b/packages/dsp/src/serial.ts index ff9f1d4479..97e737d24c 100644 --- a/packages/dsp/src/serial.ts +++ b/packages/dsp/src/serial.ts @@ -9,89 +9,89 @@ import { AProc } from "./aproc.js"; * @remarks * Provides optimized (loop free) versions for 2-4 inputs * - * @param a - - * @param b - + * @param a - + * @param b - */ export function serial(a: IProc, b: IProc): IProc; export function serial( - a: IProc, - b: IProc, - c: IProc + a: IProc, + b: IProc, + c: IProc ): IProc; export function serial( - a: IProc, - b: IProc, - c: IProc, - d: IProc + a: IProc, + b: IProc, + c: IProc, + d: IProc ): IProc; export function serial( - a: IProc, - b: IProc, - c: IProc, - d: IProc, - ...xs: IProc[] + a: IProc, + b: IProc, + c: IProc, + d: IProc, + ...xs: IProc[] ): IProc; export function serial(...procs: IProc[]): IProc { - const [a, b, c, d] = procs; - switch (procs.length) { - case 2: - return new Serial2(a, b); - case 3: - return new Serial3(a, b, c); - case 4: - return new Serial4(a, b, c, d); - default: - return new Serial(procs); - } + const [a, b, c, d] = procs; + switch (procs.length) { + case 2: + return new Serial2(a, b); + case 3: + return new Serial3(a, b, c); + case 4: + return new Serial4(a, b, c, d); + default: + return new Serial(procs); + } } export class Serial2 extends AProc { - constructor(protected _a: IProc, protected _b: IProc) { - super(null); - } + constructor(protected _a: IProc, protected _b: IProc) { + super(null); + } - next(x: A) { - return (this._val = this._b.next(this._a.next(x))); - } + next(x: A) { + return (this._val = this._b.next(this._a.next(x))); + } } export class Serial3 extends AProc { - constructor( - protected _a: IProc, - protected _b: IProc, - protected _c: IProc - ) { - super(null); - } + constructor( + protected _a: IProc, + protected _b: IProc, + protected _c: IProc + ) { + super(null); + } - next(x: A) { - return (this._val = this._c.next(this._b.next(this._a.next(x)))); - } + next(x: A) { + return (this._val = this._c.next(this._b.next(this._a.next(x)))); + } } export class Serial4 extends AProc { - constructor( - protected _a: IProc, - protected _b: IProc, - protected _c: IProc, - protected _d: IProc - ) { - super(null); - } + constructor( + protected _a: IProc, + protected _b: IProc, + protected _c: IProc, + protected _d: IProc + ) { + super(null); + } - next(x: A) { - return (this._val = this._d.next( - this._c.next(this._b.next(this._a.next(x))) - )); - } + next(x: A) { + return (this._val = this._d.next( + this._c.next(this._b.next(this._a.next(x))) + )); + } } export class Serial extends AProc { - constructor(protected _procs: IProc[]) { - super(null); - } + constructor(protected _procs: IProc[]) { + super(null); + } - next(x: any) { - return (this._val = this._procs.reduce((x, p) => p.next(x), x)); - } + next(x: any) { + return (this._val = this._procs.reduce((x, p) => p.next(x), x)); + } } diff --git a/packages/dsp/src/sincos.ts b/packages/dsp/src/sincos.ts index aff286e14e..da0c19c3c1 100644 --- a/packages/dsp/src/sincos.ts +++ b/packages/dsp/src/sincos.ts @@ -20,48 +20,48 @@ import { AGen } from "./agen.js"; * @param amp - amplitude (default: 1) */ export class SinCos extends AGen implements IReset { - protected _f!: number; - protected _s!: number; - protected _c!: number; + protected _f!: number; + protected _s!: number; + protected _c!: number; - constructor(protected _freq: number, protected _amp = 1) { - super([0, _amp]); - this.calcCoeffs(); - } + constructor(protected _freq: number, protected _amp = 1) { + super([0, _amp]); + this.calcCoeffs(); + } - reset() { - this.calcCoeffs(); - return this; - } + reset() { + this.calcCoeffs(); + return this; + } - next() { - this._val = [this._s, this._c]; - this._s += this._f * this._c; - this._c -= this._f * this._s; - return this._val; - } + next() { + this._val = [this._s, this._c]; + this._s += this._f * this._c; + this._c -= this._f * this._s; + return this._val; + } - freq() { - return this._freq; - } + freq() { + return this._freq; + } - setFreq(freq: number) { - this._freq = freq; - this.calcCoeffs(); - } + setFreq(freq: number) { + this._freq = freq; + this.calcCoeffs(); + } - amp() { - return this._amp; - } + amp() { + return this._amp; + } - setAmp(amp: number) { - this._amp = amp; - this.calcCoeffs(); - } + setAmp(amp: number) { + this._amp = amp; + this.calcCoeffs(); + } - protected calcCoeffs() { - this._f = TAU * this._freq; - this._s = 0; - this._c = this._amp; - } + protected calcCoeffs() { + this._f = TAU * this._freq; + this._s = 0; + this._c = this._amp; + } } diff --git a/packages/dsp/src/sum.ts b/packages/dsp/src/sum.ts index 1c459aa64d..08c69d135b 100644 --- a/packages/dsp/src/sum.ts +++ b/packages/dsp/src/sum.ts @@ -11,12 +11,12 @@ import { MapG2, MapG3 } from "./mapg.js"; */ export function sum(a: IGen, b: IGen): IGen; export function sum( - a: IGen, - b: IGen, - c: IGen + a: IGen, + b: IGen, + c: IGen ): IGen; export function sum(a: IGen, b: IGen, c?: IGen) { - return c - ? new MapG3((a, b, c) => a + b + c, a, b, c, 0) - : new MapG2((a, b) => a + b, a, b, 0); + return c + ? new MapG3((a, b, c) => a + b + c, a, b, c, 0) + : new MapG2((a, b) => a + b, a, b, 0); } diff --git a/packages/dsp/src/svf.ts b/packages/dsp/src/svf.ts index bfd0a7b3c0..b0950564be 100644 --- a/packages/dsp/src/svf.ts +++ b/packages/dsp/src/svf.ts @@ -26,72 +26,72 @@ export const svfAllpass = (fc: number, q?: number) => new SVF("all", fc, q); * - https://en.wikipedia.org/wiki/Trapezoidal_rule */ export class SVF extends AProc implements IReset { - protected _a1!: number; - protected _a2!: number; - protected _c1!: number; - protected _c2!: number; - protected _g!: number; - protected _k!: number; + protected _a1!: number; + protected _a2!: number; + protected _c1!: number; + protected _c2!: number; + protected _g!: number; + protected _k!: number; - constructor( - protected _type: SVFType, - protected _freq: number, - protected _q = 0.5 - ) { - super(0); - this.reset(); - this.computeCoeffs(); - } + constructor( + protected _type: SVFType, + protected _freq: number, + protected _q = 0.5 + ) { + super(0); + this.reset(); + this.computeCoeffs(); + } - reset() { - this._c1 = this._c2 = this._val = 0; - return this; - } + reset() { + this._c1 = this._c2 = this._val = 0; + return this; + } - next(x: number) { - const { _c1, _c2 } = this; - const x1 = this._a1 * _c1 + this._a2 * (x - _c2); - const x2 = _c2 + this._g * x1; - this._c1 = 2 * x1 - _c1; - this._c2 = 2 * x2 - _c2; - // TODO support type morphing / interpolation? - switch (this._type) { - case "lp": - return (this._val = x2); - case "hp": - return (this._val = x - this._k * x1 - x2); - case "bp": - return (this._val = x1); - case "notch": - return (this._val = x - this._k * x1); - case "peak": - return (this._val = 2 * x2 - x + this._k * x1); - case "all": - return (this._val = x - 2 * this._k * x1); - } - } + next(x: number) { + const { _c1, _c2 } = this; + const x1 = this._a1 * _c1 + this._a2 * (x - _c2); + const x2 = _c2 + this._g * x1; + this._c1 = 2 * x1 - _c1; + this._c2 = 2 * x2 - _c2; + // TODO support type morphing / interpolation? + switch (this._type) { + case "lp": + return (this._val = x2); + case "hp": + return (this._val = x - this._k * x1 - x2); + case "bp": + return (this._val = x1); + case "notch": + return (this._val = x - this._k * x1); + case "peak": + return (this._val = 2 * x2 - x + this._k * x1); + case "all": + return (this._val = x - 2 * this._k * x1); + } + } - set(fc: number, q: number) { - this._freq = fc; - this._q = q; - this.computeCoeffs(); - } + set(fc: number, q: number) { + this._freq = fc; + this._q = q; + this.computeCoeffs(); + } - setFreq(fc: number) { - this._freq = fc; - this.computeCoeffs(); - } + setFreq(fc: number) { + this._freq = fc; + this.computeCoeffs(); + } - setQ(q: number) { - this._q = q; - this.computeCoeffs(); - } + setQ(q: number) { + this._q = q; + this.computeCoeffs(); + } - protected computeCoeffs() { - this._freq = clamp05(this._freq); - const g = (this._g = Math.tan(PI * this._freq)); - this._k = 2 - 2 * this._q; - this._a1 = 1 / (1 + g * (g + this._k)); - this._a2 = g * this._a1; - } + protected computeCoeffs() { + this._freq = clamp05(this._freq); + const g = (this._g = Math.tan(PI * this._freq)); + this._k = 2 - 2 * this._q; + this._a1 = 1 / (1 + g * (g + this._k)); + this._a2 = g * this._a1; + } } diff --git a/packages/dsp/src/sweep.ts b/packages/dsp/src/sweep.ts index a4d2d3c6fd..ddca3daf23 100644 --- a/packages/dsp/src/sweep.ts +++ b/packages/dsp/src/sweep.ts @@ -21,16 +21,16 @@ import { curve } from "./curve.js"; * // [...] * ``` * - * @param start - - * @param end - - * @param steps - - * @param rate - + * @param start - + * @param end - + * @param steps - + * @param rate - * @param clamp - true, if clamp at `end` value */ export const sweep = ( - start: number, - end: number, - steps: number, - rate?: number, - clamp = true + start: number, + end: number, + steps: number, + rate?: number, + clamp = true ) => addG(curve(start, end, steps, rate, false, clamp)); diff --git a/packages/dsp/src/waveshaper.ts b/packages/dsp/src/waveshaper.ts index d5f023baad..67889d8366 100644 --- a/packages/dsp/src/waveshaper.ts +++ b/packages/dsp/src/waveshaper.ts @@ -29,55 +29,55 @@ export type WaveShaperFn = Fn2; * @param amp - post amplifier / autogain flag */ export const waveShaper = ( - thresh?: number, - amp?: number | true, - map?: WaveShaperFn + thresh?: number, + amp?: number | true, + map?: WaveShaperFn ) => new WaveShaper(thresh, amp, map); export class WaveShaper extends AProc { - protected _amp!: number; - protected _autoGain!: boolean; + protected _amp!: number; + protected _autoGain!: boolean; - constructor( - protected _coeff = 3, - amp: number | true = true, - protected _map: WaveShaperFn = waveshapeSigmoid - ) { - super(0); - amp === true ? this.setAutoGain() : this.setAmp(amp); - } + constructor( + protected _coeff = 3, + amp: number | true = true, + protected _map: WaveShaperFn = waveshapeSigmoid + ) { + super(0); + amp === true ? this.setAutoGain() : this.setAmp(amp); + } - next(x: number) { - return (this._val = this._amp * this._map(x, this._coeff)); - } + next(x: number) { + return (this._val = this._amp * this._map(x, this._coeff)); + } - coeff() { - return this._coeff; - } + coeff() { + return this._coeff; + } - setCoeff(t: number) { - this._coeff = Math.max(t, 0); - this._autoGain && this.setAutoGain(); - } + setCoeff(t: number) { + this._coeff = Math.max(t, 0); + this._autoGain && this.setAutoGain(); + } - amp() { - return this._amp; - } + amp() { + return this._amp; + } - setAmp(a: number) { - this._amp = a; - this._autoGain = false; - } + setAmp(a: number) { + this._amp = a; + this._autoGain = false; + } - setAutoGain() { - this._amp = 1 / this._map(1, this._coeff); - this._autoGain = true; - } + setAutoGain() { + this._amp = 1 / this._map(1, this._coeff); + this._autoGain = true; + } } export const waveshapeTan: WaveShaperFn = (x, k) => Math.atan(k * x) / k; export const waveshapeSigmoid: WaveShaperFn = (x, k) => - 2 / (1 + Math.exp(-k * x)) - 1; + 2 / (1 + Math.exp(-k * x)) - 1; export const waveshapeSin: WaveShaperFn = (x, k) => Math.sin((PI / k) * x); diff --git a/packages/dsp/src/white-noise.ts b/packages/dsp/src/white-noise.ts index 916a096513..323c50bb93 100644 --- a/packages/dsp/src/white-noise.ts +++ b/packages/dsp/src/white-noise.ts @@ -11,18 +11,18 @@ import { AGen } from "./agen.js"; * @param rnd - */ export const whiteNoise = (gain?: number, rnd?: IRandom) => - new WhiteNoise(gain, rnd); + new WhiteNoise(gain, rnd); export class WhiteNoise extends AGen implements IReset { - constructor(protected _gain = 1, protected _rnd: IRandom = SYSTEM) { - super(0); - } + constructor(protected _gain = 1, protected _rnd: IRandom = SYSTEM) { + super(0); + } - reset() { - return this; - } + reset() { + return this; + } - next() { - return (this._val = this._rnd.norm(this._gain)); - } + next() { + return (this._val = this._rnd.norm(this._gain)); + } } diff --git a/packages/dsp/src/window.ts b/packages/dsp/src/window.ts index 0a8ecfaee4..3fc628df6e 100644 --- a/packages/dsp/src/window.ts +++ b/packages/dsp/src/window.ts @@ -15,67 +15,67 @@ const cos = Math.cos; * The buffer size MUST be the same as the signal length given to * {@link fft}. * - * @param fn - - * @param lenOfBuf - + * @param fn - + * @param lenOfBuf - */ export const window = (fn: WindowFn, lenOfBuf: number | FloatArray) => { - const buf = isNumber(lenOfBuf) ? new Float64Array(lenOfBuf) : lenOfBuf; - const n = buf.length - 1; - for (let i = 0; i <= n; i++) { - buf[i] = fn(i, n); - } - return buf; + const buf = isNumber(lenOfBuf) ? new Float64Array(lenOfBuf) : lenOfBuf; + const n = buf.length - 1; + for (let i = 0; i <= n; i++) { + buf[i] = fn(i, n); + } + return buf; }; /** * Takes a `signal` and `window` buffer and multiplies both elementwise. Writes * results into `out` (or back into `signal` by default). * - * @param signal - - * @param window - - * @param out - + * @param signal - + * @param window - + * @param out - */ export const applyWindow = ( - signal: NumericArray, - window: NumericArray, - out = signal + signal: NumericArray, + window: NumericArray, + out = signal ) => { - for (let i = signal.length; i-- > 0; ) { - out[i] = signal[i] * window[i]; - } - return out; + for (let i = signal.length; i-- > 0; ) { + out[i] = signal[i] * window[i]; + } + return out; }; export const windowRect: WindowFn = () => 1; export const windowBartlett: WindowFn = (i, n) => - 1 - Math.abs((i - n / 2) / (n / 2)); + 1 - Math.abs((i - n / 2) / (n / 2)); export const windowWelch: WindowFn = (i, n) => 1 - ((i - n / 2) / (n / 2)) ** 2; export const windowSin: WindowFn = (i, n) => sin((PI * i) / n); export const windowSinPow: Fn = (k) => (i, n) => - sin((PI * i) / n) ** k; + sin((PI * i) / n) ** k; export const windowLanczos: WindowFn = (i, n) => { - i = PI * ((2 * i) / n - 1); - return sin(i) / i; + i = PI * ((2 * i) / n - 1); + return sin(i) / i; }; const windowCosSum: Fn = (k) => { - let ik = 1 - k; - return (i, n) => k - ik * cos((TAU * i) / n); + let ik = 1 - k; + return (i, n) => k - ik * cos((TAU * i) / n); }; const windowCosSum3: FnU3 = (k1, k2, k3) => (i, n) => { - i /= n; - return k1 + k2 * cos(TAU * i) + k3 * cos(PI4 * i); + i /= n; + return k1 + k2 * cos(TAU * i) + k3 * cos(PI4 * i); }; const windowCosSum4: FnU4 = (k1, k2, k3, k4) => (i, n) => { - i /= n; - return k1 + k2 * cos(TAU * i) + k3 * cos(PI4 * i) + k4 * cos(PI6 * i); + i /= n; + return k1 + k2 * cos(TAU * i) + k3 * cos(PI4 * i) + k4 * cos(PI6 * i); }; export const windowHann = windowCosSum(0.5); @@ -85,29 +85,29 @@ export const windowHamming = windowCosSum(0.53836); export const windowBlackman = windowCosSum3(0.42, -0.5, 0.08); export const windowBlackmanHarris = windowCosSum4( - 0.35875, - -0.48829, - 0.14128, - 0.01168 + 0.35875, + -0.48829, + 0.14128, + 0.01168 ); export const windowNuttall = windowCosSum4( - 0.355768, - -0.487396, - 0.144232, - -0.012604 + 0.355768, + -0.487396, + 0.144232, + -0.012604 ); export const windowBlackmanNuttall = windowCosSum4( - 0.3635819, - -0.4891775, - 0.1365995, - -0.0106411 + 0.3635819, + -0.4891775, + 0.1365995, + -0.0106411 ); export const windowGauss = - (a = 0.4): WindowFn => - (i, n) => { - n /= 2; - return Math.exp(-0.5 * ((i - n) / (a * n)) ** 2); - }; + (a = 0.4): WindowFn => + (i, n) => { + n /= 2; + return Math.exp(-0.5 * ((i - n) / (a * n)) ** 2); + }; diff --git a/packages/dsp/test/fft.ts b/packages/dsp/test/fft.ts index 2e0c9e0479..fb2fbf51b1 100644 --- a/packages/dsp/test/fft.ts +++ b/packages/dsp/test/fft.ts @@ -3,80 +3,80 @@ import { eqDelta, TAU } from "@thi.ng/math"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { - add, - copyComplex, - cos, - fft, - freqBin, - ifft, - magDb, - normalizeFFT, - osc, - powerMeanSquared, - powerSumSquared, - spectrumMag, - spectrumPhase, - spectrumPow, - window, - windowRect, -} from "../src/index.js" + add, + copyComplex, + cos, + fft, + freqBin, + ifft, + magDb, + normalizeFFT, + osc, + powerMeanSquared, + powerSumSquared, + spectrumMag, + spectrumPhase, + spectrumPow, + window, + windowRect, +} from "../src/index.js"; const deltaEq = (a: NumericArray, b: NumericArray, eps = 1e-3) => { - if (a.length != b.length) return false; - eps **= 2; - for (let i = a.length; --i >= 0; ) { - const diff = (a[i] - b[i]) ** 2; - if (diff > eps) { - console.log("deltaEq: ", i, diff); - return false; - } - } - return true; + if (a.length != b.length) return false; + eps **= 2; + for (let i = a.length; --i >= 0; ) { + const diff = (a[i] - b[i]) ** 2; + if (diff > eps) { + console.log("deltaEq: ", i, diff); + return false; + } + } + return true; }; // const deltaEqComplex = (a: ComplexArray, b: ComplexArray, eps?: number) => // deltaEq(a[0], b[0], eps) && deltaEq(a[1], b[1], eps); group("fft", { - roundtrip: () => { - const src = osc(cos, 64 / 512, 1).take(512); - const rev = ifft(fft([...src])); - assert.ok(deltaEq(rev[0], src)); - }, + roundtrip: () => { + const src = osc(cos, 64 / 512, 1).take(512); + const rev = ifft(fft([...src])); + assert.ok(deltaEq(rev[0], src)); + }, - parseval: () => { - const FC = 64; - const FS = 512; - const A = 0.5; - const N = 2 * FS; - const I = freqBin(FC, FS, N); + parseval: () => { + const FC = 64; + const FS = 512; + const A = 0.5; + const N = 2 * FS; + const I = freqBin(FC, FS, N); - const src = osc(cos, add(FC / FS, 1 / 12), A).take(N); - const win = window(windowRect, N); - const fwd = fft([...src], win); - // parseval's theorem: sum(src[i]^2) = sum(|fft[i]|^2) / N - const sumT = src.reduce((acc, x) => acc + x * x, 0); - const sumF = - (fwd[0]).reduce( - (acc, x, i) => acc + x ** 2 + fwd[1][i] ** 2, - 0 - ) / N; + const src = osc(cos, add(FC / FS, 1 / 12), A).take(N); + const win = window(windowRect, N); + const fwd = fft([...src], win); + // parseval's theorem: sum(src[i]^2) = sum(|fft[i]|^2) / N + const sumT = src.reduce((acc, x) => acc + x * x, 0); + const sumF = + (fwd[0]).reduce( + (acc, x, i) => acc + x ** 2 + fwd[1][i] ** 2, + 0 + ) / N; - assert.ok(eqDelta(powerSumSquared(src), sumT), "sumT1"); - assert.ok(eqDelta(powerSumSquared(fwd), sumF), "sumF1"); - assert.ok(eqDelta(powerMeanSquared(src), sumT / N), "sumT2"); - assert.ok(eqDelta(powerMeanSquared(fwd), sumF / N), "sumF2"); + assert.ok(eqDelta(powerSumSquared(src), sumT), "sumT1"); + assert.ok(eqDelta(powerSumSquared(fwd), sumF), "sumF1"); + assert.ok(eqDelta(powerMeanSquared(src), sumT / N), "sumT2"); + assert.ok(eqDelta(powerMeanSquared(fwd), sumF / N), "sumF2"); - assert.ok(eqDelta(spectrumMag(fwd)[I], 2 * sumF)); - assert.ok(eqDelta(spectrumPow(fwd)[I], sumF)); - assert.ok(eqDelta(spectrumPow(fwd, true)[I], magDb(A))); - assert.ok(eqDelta(spectrumPhase(fwd)[I], (1 / 12) * TAU)); + assert.ok(eqDelta(spectrumMag(fwd)[I], 2 * sumF)); + assert.ok(eqDelta(spectrumPow(fwd)[I], sumF)); + assert.ok(eqDelta(spectrumPow(fwd, true)[I], magDb(A))); + assert.ok(eqDelta(spectrumPhase(fwd)[I], (1 / 12) * TAU)); - const norm = normalizeFFT(copyComplex(fwd), win); + const norm = normalizeFFT(copyComplex(fwd), win); - assert.ok(eqDelta(spectrumMag(norm)[I], A)); - assert.ok(eqDelta(spectrumPow(norm, false, 1)[I], A / 2)); - assert.ok(eqDelta(spectrumPow(norm, true, 1)[I], magDb(A))); - assert.ok(eqDelta(spectrumPhase(norm)[I], (1 / 12) * TAU)); - }, + assert.ok(eqDelta(spectrumMag(norm)[I], A)); + assert.ok(eqDelta(spectrumPow(norm, false, 1)[I], A / 2)); + assert.ok(eqDelta(spectrumPow(norm, true, 1)[I], magDb(A))); + assert.ok(eqDelta(spectrumPhase(norm)[I], (1 / 12) * TAU)); + }, }); diff --git a/packages/dsp/test/osc.ts b/packages/dsp/test/osc.ts index ef8d7cb11a..21bc9c2f00 100644 --- a/packages/dsp/test/osc.ts +++ b/packages/dsp/test/osc.ts @@ -6,14 +6,14 @@ import { cos, osc, sin } from "../src/index.js"; const checkEq = (a: number, b: number) => assert.ok(eqDelta(a, b, 1e-3)); group("osc", { - startPhase: () => { - checkEq(osc(sin, 0.01).next(), 0); - checkEq(osc(sin, 0.01, 0.5, 0, 0.25 /** 1/2π */).next(), 0.5); - checkEq(osc(sin, 0.01, 0.5, 0, 0.5 /** π */).next(), 0); - checkEq(osc(sin, 0.01, 0.5, 0, 0.75 /** 3/2π */).next(), -0.5); - checkEq(osc(cos, 0.01).next(), 1); - checkEq(osc(cos, 0.01, 0.5, 0, 0.25 /** 1/2π */).next(), 0); - checkEq(osc(cos, 0.01, 0.5, 0, 0.5 /** π */).next(), -0.5); - checkEq(osc(cos, 0.01, 0.5, 0, 0.75 /** 3/2π */).next(), 0); - }, + startPhase: () => { + checkEq(osc(sin, 0.01).next(), 0); + checkEq(osc(sin, 0.01, 0.5, 0, 0.25 /** 1/2π */).next(), 0.5); + checkEq(osc(sin, 0.01, 0.5, 0, 0.5 /** π */).next(), 0); + checkEq(osc(sin, 0.01, 0.5, 0, 0.75 /** 3/2π */).next(), -0.5); + checkEq(osc(cos, 0.01).next(), 1); + checkEq(osc(cos, 0.01, 0.5, 0, 0.25 /** 1/2π */).next(), 0); + checkEq(osc(cos, 0.01, 0.5, 0, 0.5 /** π */).next(), -0.5); + checkEq(osc(cos, 0.01, 0.5, 0, 0.75 /** 3/2π */).next(), 0); + }, }); diff --git a/packages/dsp/tools/generate-diagrams.ts b/packages/dsp/tools/generate-diagrams.ts index ea6caed39b..0f61830561 100644 --- a/packages/dsp/tools/generate-diagrams.ts +++ b/packages/dsp/tools/generate-diagrams.ts @@ -1,60 +1,60 @@ import type { Fn, IObjectOf } from "@thi.ng/api"; import { cosineColor, COSINE_GRADIENTS } from "@thi.ng/color"; import { - asSvg, - group, - line, - polyline, - rect as grect, - svgDoc, + asSvg, + group, + line, + polyline, + rect as grect, + svgDoc, } from "@thi.ng/geom"; import type { IHiccupShape } from "@thi.ng/geom-api"; import { fit, fit11, fitClamped, PI } from "@thi.ng/math"; import { map, mapcat, mapIndexed, range, take, zip } from "@thi.ng/transducers"; import { writeFileSync } from "fs"; import { - allpass, - biquadBP, - biquadHiShelf, - biquadHP, - biquadLoShelf, - biquadLP, - biquadNotch, - biquadPeak, - curve, - dcBlock, - dsfHOF, - filterResponse, - foldback, - freqMs, - freqRange, - IGen, - impulseTrainT, - IProc, - mixOscHOF, - modOsc, - msFrames, - normFreq, - onepoleLP, - osc, - parabolic, - pinkNoise, - rect, - saw, - sin, - StatelessOscillator, - svfAllpass, - svfBP, - svfHP, - svfLP, - svfNotch, - svfPeak, - tri, - waveShaper, - waveshapeSin, - waveshapeTan, - wavetable, - whiteNoise, + allpass, + biquadBP, + biquadHiShelf, + biquadHP, + biquadLoShelf, + biquadLP, + biquadNotch, + biquadPeak, + curve, + dcBlock, + dsfHOF, + filterResponse, + foldback, + freqMs, + freqRange, + IGen, + impulseTrainT, + IProc, + mixOscHOF, + modOsc, + msFrames, + normFreq, + onepoleLP, + osc, + parabolic, + pinkNoise, + rect, + saw, + sin, + StatelessOscillator, + svfAllpass, + svfBP, + svfHP, + svfLP, + svfNotch, + svfPeak, + tri, + waveShaper, + waveshapeSin, + waveshapeTan, + wavetable, + whiteNoise, } from "../src"; const FS = 48000; @@ -63,14 +63,14 @@ const FREQS = [5000, 2500, 1000, 500, 250, 125]; const [F1, F2, F3, F4, F5] = FREQS.map((f) => normFreq(f, FS)); const OSC: IObjectOf = { - sin, - saw, - rect, - tri, - parabolic, - recttri: mixOscHOF(rect, tri), - dsf: dsfHOF(0.6, 2.04), - wt: wavetable(curve(1, -1, 127).take(128)), + sin, + saw, + rect, + tri, + parabolic, + recttri: mixOscHOF(rect, tri), + dsf: dsfHOF(0.6, 2.04), + wt: wavetable(curve(1, -1, 127).take(128)), }; const BASE_DIR = "export/"; @@ -79,157 +79,157 @@ const X = 30; const YSCALE = 50; const label = (x: number, y: number, body: string) => - { - toHiccup() { - return ["text", { stroke: "none" }, [x, y + 2], body]; - }, - }; + { + toHiccup() { + return ["text", { stroke: "none" }, [x, y + 2], body]; + }, + }; const color = (i: number) => - cosineColor(COSINE_GRADIENTS["orange-magenta-blue"], 1 - i); + cosineColor(COSINE_GRADIENTS["orange-magenta-blue"], 1 - i); const write = ( - fname: string, - pts: number[][], - labels: string[], - yticks: number[] = [...range(-1, 1.01, 0.25)], - yfmt: Fn = (y) => y.toFixed(2), - num = labels.length + fname: string, + pts: number[][], + labels: string[], + yticks: number[] = [...range(-1, 1.01, 0.25)], + yfmt: Fn = (y) => y.toFixed(2), + num = labels.length ) => - writeFileSync( - BASE_DIR + fname, - asSvg( - svgDoc( - { - viewBox: `-10 -${YSCALE + 10} 570 ${2 * YSCALE + 40}`, - "font-family": "Inconsolata", - "font-size": "8px", - "text-anchor": "end", - // "dominant-baseline": "middle" - }, - // axis & labels - group({ stroke: "#666", fill: "#666", "stroke-width": 0.5 }, [ - line([X - 6, 0], [pts.length * 4 + X + 10, 0]), - line([X, -YSCALE], [X, YSCALE]), - ...mapcat( - ([i, y]) => [ - line( - [i & 1 ? X - 3 : X - 6, y * YSCALE], - [X, y * YSCALE] - ), - line( - [X + 5, y * YSCALE], - [pts.length * 4 + X + 10, y * YSCALE], - { stroke: "#ccc", dash: [1, 2] } - ), - ], - zip(range(), yticks) - ), - ...map((y) => label(X - 12, -y * YSCALE, yfmt(y)), yticks), - ]), - // waveforms - group({ translate: [X + 5, 0] }, [ - ...map( - (id) => - polyline( - [ - ...mapIndexed( - (i, y) => [i * 4, -y[id] * YSCALE], - pts - ), - ], - { - stroke: color(id / num), - "stroke-width": id === 0 ? 1 : 0.5, - } - ), - range(num) - ), - ]), - // legend - grect([-10, YSCALE + 10], [570, 20], { fill: "#fff" }), - ...mapIndexed( - (i, txt) => - group( - { - translate: [X + 10 + i * 70, YSCALE + 20], - "text-anchor": "start", - }, - [ - grect([0, -1], [10, 2], { - fill: color(i / num), - }), - label(12, 0, txt), - ] - ), - labels - ) - ) - ) - ); + writeFileSync( + BASE_DIR + fname, + asSvg( + svgDoc( + { + viewBox: `-10 -${YSCALE + 10} 570 ${2 * YSCALE + 40}`, + "font-family": "Inconsolata", + "font-size": "8px", + "text-anchor": "end", + // "dominant-baseline": "middle" + }, + // axis & labels + group({ stroke: "#666", fill: "#666", "stroke-width": 0.5 }, [ + line([X - 6, 0], [pts.length * 4 + X + 10, 0]), + line([X, -YSCALE], [X, YSCALE]), + ...mapcat( + ([i, y]) => [ + line( + [i & 1 ? X - 3 : X - 6, y * YSCALE], + [X, y * YSCALE] + ), + line( + [X + 5, y * YSCALE], + [pts.length * 4 + X + 10, y * YSCALE], + { stroke: "#ccc", dash: [1, 2] } + ), + ], + zip(range(), yticks) + ), + ...map((y) => label(X - 12, -y * YSCALE, yfmt(y)), yticks), + ]), + // waveforms + group({ translate: [X + 5, 0] }, [ + ...map( + (id) => + polyline( + [ + ...mapIndexed( + (i, y) => [i * 4, -y[id] * YSCALE], + pts + ), + ], + { + stroke: color(id / num), + "stroke-width": id === 0 ? 1 : 0.5, + } + ), + range(num) + ), + ]), + // legend + grect([-10, YSCALE + 10], [570, 20], { fill: "#fff" }), + ...mapIndexed( + (i, txt) => + group( + { + translate: [X + 10 + i * 70, YSCALE + 20], + "text-anchor": "start", + }, + [ + grect([0, -1], [10, 2], { + fill: color(i / num), + }), + label(12, 0, txt), + ] + ), + labels + ) + ) + ) + ); const compute = (gen: IGen, procs: IProc[]) => [ - ...take( - 128, - map((x) => [x, ...map((p) => p.next(x), procs)], gen) - ), + ...take( + 128, + map((x) => [x, ...map((p) => p.next(x), procs)], gen) + ), ]; const withFilters = ( - fname: Fn, - filter: Fn>, - oscFn: Fn> = (x) => osc(x, F3, 0.75), - freqs = [F1, F2, F3, F4, F5], - labels: string[] = ["orig (1kHz)", ...freqs.map((f) => `${(f * FS) | 0}Hz`)] + fname: Fn, + filter: Fn>, + oscFn: Fn> = (x) => osc(x, F3, 0.75), + freqs = [F1, F2, F3, F4, F5], + labels: string[] = ["orig (1kHz)", ...freqs.map((f) => `${(f * FS) | 0}Hz`)] ) => { - for (let id in OSC) { - write( - fname(id), - compute( - oscFn(OSC[id]), - freqs.map((f) => filter(f)) - ), - labels - ); - } + for (let id in OSC) { + write( + fname(id), + compute( + oscFn(OSC[id]), + freqs.map((f) => filter(f)) + ), + labels + ); + } }; withFilters( - (id) => `${id}-wshape-sigmoid.svg`, - (k) => waveShaper(k, true), - undefined, - [0.5, 1, 2, 4, 8], - ["orig", ...[0.5, 1, 2, 4, 8].map((k) => `k=${k}`)] + (id) => `${id}-wshape-sigmoid.svg`, + (k) => waveShaper(k, true), + undefined, + [0.5, 1, 2, 4, 8], + ["orig", ...[0.5, 1, 2, 4, 8].map((k) => `k=${k}`)] ); withFilters( - (id) => `${id}-wshape-sin.svg`, - (k) => waveShaper(k, 1, waveshapeSin), - undefined, - [0.5, 1, 2, 4, 8], - ["orig", ...[0.5, 1, 2, 4, 8].map((k) => `k=${k}`)] + (id) => `${id}-wshape-sin.svg`, + (k) => waveShaper(k, 1, waveshapeSin), + undefined, + [0.5, 1, 2, 4, 8], + ["orig", ...[0.5, 1, 2, 4, 8].map((k) => `k=${k}`)] ); withFilters( - (id) => `${id}-wshape-tan.svg`, - (k) => waveShaper(k, true, waveshapeTan), - undefined, - [0.5, 1, 2, 4, 8], - ["orig", ...[0.5, 1, 2, 4, 8].map((k) => `k=${k}`)] + (id) => `${id}-wshape-tan.svg`, + (k) => waveShaper(k, true, waveshapeTan), + undefined, + [0.5, 1, 2, 4, 8], + ["orig", ...[0.5, 1, 2, 4, 8].map((k) => `k=${k}`)] ); withFilters( - () => `pnoise.svg`, - svfLP, - () => pinkNoise(2), - [12000 / FS, 8000 / FS, F1, F2, F3] + () => `pnoise.svg`, + svfLP, + () => pinkNoise(2), + [12000 / FS, 8000 / FS, F1, F2, F3] ); withFilters( - () => `wnoise.svg`, - svfLP, - () => whiteNoise(), - [12000 / FS, 8000 / FS, F1, F2, F3] + () => `wnoise.svg`, + svfLP, + () => whiteNoise(), + [12000 / FS, 8000 / FS, F1, F2, F3] ); withFilters((id) => `${id}-svf-lpf.svg`, svfLP); @@ -263,57 +263,57 @@ withFilters((id) => `${id}-bq-lsh.svg`, biquadLoShelf); withFilters((id) => `${id}-bq-hsh.svg`, biquadHiShelf); withFilters( - (id) => `${id}-foldback.svg`, - (t) => foldback(t), - undefined, - [0.5, 0.3, 0.15], - ["orig", ...[0.5, 0.3, 0.15].map((t) => `t=${t}`)] + (id) => `${id}-foldback.svg`, + (t) => foldback(t), + undefined, + [0.5, 0.3, 0.15], + ["orig", ...[0.5, 0.3, 0.15].map((t) => `t=${t}`)] ); withFilters( - (id) => `${id}-allpass1.svg`, - (f) => allpass(f) + (id) => `${id}-allpass1.svg`, + (f) => allpass(f) ); withFilters( - (id) => `fmod-${id}-lpf-1pole.svg`, - onepoleLP, - (o) => modOsc(o, F3, osc(sin, F1, 0.1), 0.75) + (id) => `fmod-${id}-lpf-1pole.svg`, + onepoleLP, + (o) => modOsc(o, F3, osc(sin, F1, 0.1), 0.75) ); withFilters( - () => `train-lpf.svg`, - onepoleLP, - () => impulseTrainT(1, -1, msFrames(freqMs(FREQS[2]), FS)) + () => `train-lpf.svg`, + onepoleLP, + () => impulseTrainT(1, -1, msFrames(freqMs(FREQS[2]), FS)) ); withFilters( - () => `fmam-osc.svg`, - onepoleLP, - () => modOsc(saw, F3, osc(saw, F1, 0.3), osc(saw, F4)) + () => `fmam-osc.svg`, + onepoleLP, + () => modOsc(saw, F3, osc(saw, F1, 0.3), osc(saw, F4)) ); withFilters( - (id) => `${id}-allpass-high.svg`, - (f) => { - let flt = allpass(f); - return >{ - next(x: number) { - return flt.high(x); - }, - }; - } + (id) => `${id}-allpass-high.svg`, + (f) => { + let flt = allpass(f); + return >{ + next(x: number) { + return flt.high(x); + }, + }; + } ); const fc = biquadLP(5000 / FS).filterCoeffs(); write( - `bq-lpf-resp.svg`, - freqRange(24 / FS, 0.499, 128).map((f) => { - const r = filterResponse(fc, f, true); - return [fitClamped(r.mag, -60, 0, -1, 1), fit(r.phase, -PI, PI, -1, 1)]; - }), - ["mag", "phase"], - [...range(-1, 1.01, 0.5)], - (y) => (fit11(y, -60, 0) | 0) + " dB" + `bq-lpf-resp.svg`, + freqRange(24 / FS, 0.499, 128).map((f) => { + const r = filterResponse(fc, f, true); + return [fitClamped(r.mag, -60, 0, -1, 1), fit(r.phase, -PI, PI, -1, 1)]; + }), + ["mag", "phase"], + [...range(-1, 1.01, 0.5)], + (y) => (fit11(y, -60, 0) | 0) + " dB" ); diff --git a/packages/dsp/tsconfig.json b/packages/dsp/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/dsp/tsconfig.json +++ b/packages/dsp/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/dual-algebra/api-extractor.json b/packages/dual-algebra/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/dual-algebra/api-extractor.json +++ b/packages/dual-algebra/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/dual-algebra/package.json b/packages/dual-algebra/package.json index 971fa13382..6131d8164e 100644 --- a/packages/dual-algebra/package.json +++ b/packages/dual-algebra/package.json @@ -1,94 +1,94 @@ { - "name": "@thi.ng/dual-algebra", - "version": "0.4.8", - "description": "Multivariate dual number algebra, automatic differentiation", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/dual-algebra#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "algebra", - "datastructure", - "derivative", - "descend", - "differentiation", - "dual", - "gradient", - "math", - "multivariate", - "typescript", - "vector" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./ops": { - "default": "./ops.js" - }, - "./poly": { - "default": "./poly.js" - }, - "./vector": { - "default": "./vector.js" - } - }, - "thi.ng": { - "related": [ - "math" - ], - "status": "alpha", - "year": 2020 - } + "name": "@thi.ng/dual-algebra", + "version": "0.4.8", + "description": "Multivariate dual number algebra, automatic differentiation", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/dual-algebra#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "algebra", + "datastructure", + "derivative", + "descend", + "differentiation", + "dual", + "gradient", + "math", + "multivariate", + "typescript", + "vector" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./ops": { + "default": "./ops.js" + }, + "./poly": { + "default": "./poly.js" + }, + "./vector": { + "default": "./vector.js" + } + }, + "thi.ng": { + "related": [ + "math" + ], + "status": "alpha", + "year": 2020 + } } diff --git a/packages/dual-algebra/src/api.ts b/packages/dual-algebra/src/api.ts index 0a6f38383e..a34f173e97 100644 --- a/packages/dual-algebra/src/api.ts +++ b/packages/dual-algebra/src/api.ts @@ -1,12 +1,12 @@ import type { - Fn, - Fn2, - FnU2, - FnU3, - FnU4, - FnU5, - FnU6, - NumericArray, + Fn, + Fn2, + FnU2, + FnU3, + FnU4, + FnU5, + FnU6, + NumericArray, } from "@thi.ng/api"; export type Dual = NumericArray; diff --git a/packages/dual-algebra/src/ops.ts b/packages/dual-algebra/src/ops.ts index 7e7c4fa23b..465e5b303f 100644 --- a/packages/dual-algebra/src/ops.ts +++ b/packages/dual-algebra/src/ops.ts @@ -8,223 +8,223 @@ import type { Dual, Op1, Op1N, Op2, Op3, Op4 } from "./api.js"; * @param i - variable index (0 < i <= n) */ export const dual = (real: number, n = 1, i = 0): Dual => { - const out = new Array(n + 1).fill(0, 1); - out[0] = real; - i > 0 && (out[i] = 1); - return out; + const out = new Array(n + 1).fill(0, 1); + out[0] = real; + i > 0 && (out[i] = 1); + return out; }; /** * Creates a 1-dual number of `r`. Syntax sugar for {@link dual}. * - * @param r - - * @param i - + * @param r - + * @param i - */ export const $ = (r: number, i = 0) => [r, i === 1 ? 1 : 0]; /** * Creates a 2-dual number of `r`. Syntax sugar for {@link dual}. * - * @param r - - * @param i - + * @param r - + * @param i - */ export const $2 = (r: number, i = 0) => dual(r, 2, i); /** * Creates a 3-dual number of `r`. Syntax sugar for {@link dual}. * - * @param r - - * @param i - + * @param r - + * @param i - */ export const $3 = (r: number, i = 0) => dual(r, 3, i); /** * Creates a 4-dual number of `r`. Syntax sugar for {@link dual}. * - * @param r - - * @param i - + * @param r - + * @param i - */ export const $4 = (r: number, i = 0) => dual(r, 4, i); export const defOp = ( - single: T, - multi: T, - dispatch = 0 + single: T, + multi: T, + dispatch = 0 ): T => - ( - ((...xs: any[]) => - xs[dispatch].length < 3 ? single(...xs) : multi(...xs)) - ); + ( + ((...xs: any[]) => + xs[dispatch].length < 3 ? single(...xs) : multi(...xs)) + ); export const add = defOp( - (a, b) => [a[0] + b[0], a[1] + b[1]], - (a, b) => a.map((x: number, i: number) => x + b[i]) + (a, b) => [a[0] + b[0], a[1] + b[1]], + (a, b) => a.map((x: number, i: number) => x + b[i]) ); export const sub = defOp( - (a, b) => [a[0] - b[0], a[1] - b[1]], - (a, b) => a.map((x: number, i: number) => x - b[i]) + (a, b) => [a[0] - b[0], a[1] - b[1]], + (a, b) => a.map((x: number, i: number) => x - b[i]) ); export const neg = defOp( - (a) => [-a[0], -a[1]], - (a) => a.map((x: number) => (x !== 0 ? -x : 0)) + (a) => [-a[0], -a[1]], + (a) => a.map((x: number) => (x !== 0 ? -x : 0)) ); export const mul = defOp( - ([ar, ad], [br, bd]) => [ar * br, ar * bd + ad * br], - (a, b) => { - const ar = a[0]; - const br = b[0]; - const out = [ar * br]; - for (let i = a.length; i-- > 1; ) { - out[i] = ar * b[i] + a[i] * br; - } - return out; - } + ([ar, ad], [br, bd]) => [ar * br, ar * bd + ad * br], + (a, b) => { + const ar = a[0]; + const br = b[0]; + const out = [ar * br]; + for (let i = a.length; i-- > 1; ) { + out[i] = ar * b[i] + a[i] * br; + } + return out; + } ); export const div = defOp( - ([ar, ad], [br, bd]) => [ar / br, (ad * br - ar * bd) / (br * br)], - (a, b) => { - const ar = a[0]; - const br = b[0]; - const ibr = 1 / (br * br); - const out = [ar / br]; - for (let i = a.length; i-- > 1; ) { - out[i] = (a[i] * br - ar * b[i]) * ibr; - } - return out; - } + ([ar, ad], [br, bd]) => [ar / br, (ad * br - ar * bd) / (br * br)], + (a, b) => { + const ar = a[0]; + const br = b[0]; + const ibr = 1 / (br * br); + const out = [ar / br]; + for (let i = a.length; i-- > 1; ) { + out[i] = (a[i] * br - ar * b[i]) * ibr; + } + return out; + } ); export const abs = defOp( - ([ar, ad]) => [Math.abs(ar), ad * Math.sign(ar)], - (a) => { - const s = Math.sign(a[0]); - const out = [Math.abs(a[0])]; - for (let i = a.length; i-- > 1; ) { - out[i] = s * a[i]; - } - return out; - } + ([ar, ad]) => [Math.abs(ar), ad * Math.sign(ar)], + (a) => { + const s = Math.sign(a[0]); + const out = [Math.abs(a[0])]; + for (let i = a.length; i-- > 1; ) { + out[i] = s * a[i]; + } + return out; + } ); export const sqrt = defOp( - (a) => { - const s = Math.sqrt(a[0]); - return [s, (0.5 * a[1]) / s]; - }, - (a) => { - const s = Math.sqrt(a[0]); - const si = 0.5 / s; - const out = [s]; - for (let i = a.length; i-- > 1; ) { - out[i] = si * a[i]; - } - return out; - } + (a) => { + const s = Math.sqrt(a[0]); + return [s, (0.5 * a[1]) / s]; + }, + (a) => { + const s = Math.sqrt(a[0]); + const si = 0.5 / s; + const out = [s]; + for (let i = a.length; i-- > 1; ) { + out[i] = si * a[i]; + } + return out; + } ); export const exp = defOp( - ([ar, ad]) => { - ar = Math.exp(ar); - return [ar, ad * ar]; - }, - (a) => { - const ar = Math.exp(a[0]); - const out = [ar]; - for (let i = a.length; i-- > 1; ) { - out[i] = ar * a[i]; - } - return out; - } + ([ar, ad]) => { + ar = Math.exp(ar); + return [ar, ad * ar]; + }, + (a) => { + const ar = Math.exp(a[0]); + const out = [ar]; + for (let i = a.length; i-- > 1; ) { + out[i] = ar * a[i]; + } + return out; + } ); export const log = defOp( - ([ar, ad]) => [Math.log(ar), ad / ar], - (a) => { - const ar = Math.log(a[0]); - const iar = 1 / ar; - const out = [ar]; - for (let i = a.length; i-- > 1; ) { - out[i] = iar * a[i]; - } - return out; - } + ([ar, ad]) => [Math.log(ar), ad / ar], + (a) => { + const ar = Math.log(a[0]); + const iar = 1 / ar; + const out = [ar]; + for (let i = a.length; i-- > 1; ) { + out[i] = iar * a[i]; + } + return out; + } ); export const pow = defOp( - ([ar, ad], k) => [ar ** k, ad * k * ar ** (k - 1)], - (a, k) => { - const f = k * a[0] ** (k - 1); - const out = [a[0] ** k]; - for (let i = a.length; i-- > 1; ) { - out[i] = f * a[i]; - } - return out; - } + ([ar, ad], k) => [ar ** k, ad * k * ar ** (k - 1)], + (a, k) => { + const f = k * a[0] ** (k - 1); + const out = [a[0] ** k]; + for (let i = a.length; i-- > 1; ) { + out[i] = f * a[i]; + } + return out; + } ); export const sin = defOp( - ([ar, ad]) => [Math.sin(ar), ad * Math.cos(ar)], - (a) => { - const c = Math.cos(a[0]); - const out = [Math.sin(a[0])]; - for (let i = a.length; i-- > 1; ) { - out[i] = c * a[i]; - } - return out; - } + ([ar, ad]) => [Math.sin(ar), ad * Math.cos(ar)], + (a) => { + const c = Math.cos(a[0]); + const out = [Math.sin(a[0])]; + for (let i = a.length; i-- > 1; ) { + out[i] = c * a[i]; + } + return out; + } ); export const cos = defOp( - ([ar, ad]) => [Math.cos(ar), -ad * Math.sin(ar)], - (a) => { - const s = -Math.sin(a[0]); - const out = [Math.cos(a[0])]; - for (let i = a.length; i-- > 1; ) { - out[i] = s * a[i]; - } - return out; - } + ([ar, ad]) => [Math.cos(ar), -ad * Math.sin(ar)], + (a) => { + const s = -Math.sin(a[0]); + const out = [Math.cos(a[0])]; + for (let i = a.length; i-- > 1; ) { + out[i] = s * a[i]; + } + return out; + } ); export const tan = defOp( - ([ar, ad]) => { - const c = Math.cos(ar); - return [Math.tan(ar), ad / (c * c)]; - }, - (a) => { - const c = Math.cos(a[0]); - const ic = 1 / (c * c); - const out = [Math.tan(a[0])]; - for (let i = a.length; i-- > 1; ) { - out[i] = ic * a[i]; - } - return out; - } + ([ar, ad]) => { + const c = Math.cos(ar); + return [Math.tan(ar), ad / (c * c)]; + }, + (a) => { + const c = Math.cos(a[0]); + const ic = 1 / (c * c); + const out = [Math.tan(a[0])]; + for (let i = a.length; i-- > 1; ) { + out[i] = ic * a[i]; + } + return out; + } ); export const atan = defOp( - ([ar, ad]) => [Math.atan(ar), ad / (1 + ar * ar)], - (a) => { - const ar = a[0]; - const iar = 1 / (1 + ar * ar); - const out = [Math.atan(ar)]; - for (let i = a.length; i-- > 1; ) { - out[i] = iar * a[i]; - } - return out; - } + ([ar, ad]) => [Math.atan(ar), ad / (1 + ar * ar)], + (a) => { + const ar = a[0]; + const iar = 1 / (1 + ar * ar); + const out = [Math.atan(ar)]; + for (let i = a.length; i-- > 1; ) { + out[i] = iar * a[i]; + } + return out; + } ); /** * Linear interpolation for dual numbers: `a + (b - a) * t` * - * @param a - - * @param b - - * @param t - + * @param a - + * @param b - + * @param t - */ export const mix = (a: Dual, b: Dual, t: Dual) => add(a, mul(sub(b, a), t)); @@ -241,24 +241,24 @@ export const mix = (a: Dual, b: Dual, t: Dual) => add(a, mul(sub(b, a), t)); * - index 1 - derivative of first var at `x` * - index 2 - derivative of second var at `y` * - * @param fn - + * @param fn - */ export const evalFn2 = (fn: Op2) => (x: number, y: number) => - fn([x, 1, 0], [y, 0, 1]); + fn([x, 1, 0], [y, 0, 1]); /** * Same as {@link evalFn2}, but 3-multivariate functions. * - * @param fn - + * @param fn - */ export const evalFn3 = (fn: Op3) => (x: number, y: number, z: number) => - fn([x, 1, 0, 0], [y, 0, 1, 0], [z, 0, 0, 1]); + fn([x, 1, 0, 0], [y, 0, 1, 0], [z, 0, 0, 1]); /** * Same as {@link evalFn4}, but 4-multivariate functions. * - * @param fn - + * @param fn - */ export const evalFn4 = - (fn: Op4) => (x: number, y: number, z: number, w: number) => - fn([x, 1, 0, 0, 0], [y, 0, 1, 0, 0], [z, 0, 0, 1, 0], [w, 0, 0, 0, 1]); + (fn: Op4) => (x: number, y: number, z: number, w: number) => + fn([x, 1, 0, 0, 0], [y, 0, 1, 0, 0], [z, 0, 0, 1, 0], [w, 0, 0, 0, 1]); diff --git a/packages/dual-algebra/src/poly.ts b/packages/dual-algebra/src/poly.ts index 2693800c64..a5b43e95d2 100644 --- a/packages/dual-algebra/src/poly.ts +++ b/packages/dual-algebra/src/poly.ts @@ -5,89 +5,89 @@ import { add, mul } from "./ops.js"; /** * Computes: `ax^2 + bx + c`. All args must have same size/arity. * - * @param x - - * @param a - - * @param b - - * @param c - + * @param x - + * @param a - + * @param b - + * @param c - */ export const quadratic: Op4 = (x, a, b, c) => - add(add(mul(a, mul(x, x)), mul(b, x)), c); + add(add(mul(a, mul(x, x)), mul(b, x)), c); /** * Same as {@link quadratic}, but for real/scalar inputs. `x` is treated as * variable `x+1ε`, the rest as `n+0ε`. * - * @param x - - * @param a - - * @param b - - * @param c - + * @param x - + * @param a - + * @param b - + * @param c - */ export const quadraticS: FnU4 = (x, a, b, c) => - quadratic([x, 1], [a, 0], [b, 0], [c, 0]); + quadratic([x, 1], [a, 0], [b, 0], [c, 0]); /** * Computes: `ax^3 + bx^2 + cx + d`. All args must have same size/arity. * - * @param x - - * @param a - - * @param b - - * @param c - - * @param d - + * @param x - + * @param a - + * @param b - + * @param c - + * @param d - */ export const cubic: Op5 = (x, a, b, c, d) => { - const x2 = mul(x, x); - return add(add(add(mul(a, mul(x2, x)), mul(b, x2)), mul(c, x)), d); + const x2 = mul(x, x); + return add(add(add(mul(a, mul(x2, x)), mul(b, x2)), mul(c, x)), d); }; /** * Same as {@link cubic}, but for real/scalar inputs. `x` is treated as variable * `x+1ε`, the rest as `n+0ε`. * - * @param x - - * @param a - - * @param b - - * @param c - - * @param d - + * @param x - + * @param a - + * @param b - + * @param c - + * @param d - */ export const cubicS: FnU5 = (x, a, b, c, d) => - cubic([x, 1], [a, 0], [b, 0], [c, 0], [d, 0]); + cubic([x, 1], [a, 0], [b, 0], [c, 0], [d, 0]); /** * Computes: `ax^4 + bx^3 + cx^2 + dx + e`. All args must have same size/arity. * - * @param x - - * @param a - - * @param b - - * @param c - - * @param d - - * @param e - + * @param x - + * @param a - + * @param b - + * @param c - + * @param d - + * @param e - */ export const quartic = ( - x: Dual, - a: Dual, - b: Dual, - c: Dual, - d: Dual, - e: Dual + x: Dual, + a: Dual, + b: Dual, + c: Dual, + d: Dual, + e: Dual ) => { - const x2 = mul(x, x); - const x3 = mul(x2, x); - return add( - add(add(add(mul(a, mul(x3, x)), mul(b, x3)), mul(c, x2)), mul(d, x)), - e - ); + const x2 = mul(x, x); + const x3 = mul(x2, x); + return add( + add(add(add(mul(a, mul(x3, x)), mul(b, x3)), mul(c, x2)), mul(d, x)), + e + ); }; /** * Same as {@link quartic}, but for real/scalar inputs. `x` is treated as * variable `x+1ε`, the rest as `n+0ε`. * - * @param x - - * @param a - - * @param b - - * @param c - - * @param d - - * @param e - + * @param x - + * @param a - + * @param b - + * @param c - + * @param d - + * @param e - */ export const quarticS: FnU6 = (x, a, b, c, d, e) => - quartic([x, 1], [a, 0], [b, 0], [c, 0], [d, 0], [e, 0]); + quartic([x, 1], [a, 0], [b, 0], [c, 0], [d, 0], [e, 0]); diff --git a/packages/dual-algebra/src/vector.ts b/packages/dual-algebra/src/vector.ts index d53d6e8cd6..30c4e044f6 100644 --- a/packages/dual-algebra/src/vector.ts +++ b/packages/dual-algebra/src/vector.ts @@ -3,16 +3,16 @@ import type { Dual, Op2, OpV2 } from "./api.js"; import { add, div, dual, mul, sub } from "./ops.js"; const defVecOp2 = - (op: Op2): OpV2 => - (a, b) => - a.map((a, i) => op(a, b[i])); + (op: Op2): OpV2 => + (a, b) => + a.map((a, i) => op(a, b[i])); /** * Dual vector addition. Applies {@link add} in a component-wise manner. Returns * new (dual) vector. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const vadd = defVecOp2(add); @@ -20,8 +20,8 @@ export const vadd = defVecOp2(add); * Dual vector subtraction. Applies {@link sub} in a component-wise manner. * Returns new (dual) vector. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const vsub = defVecOp2(sub); @@ -29,8 +29,8 @@ export const vsub = defVecOp2(sub); * Dual vector multiplication. Applies {@link mul} in a component-wise manner. * Returns new (dual) vector. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const vmul = defVecOp2(mul); @@ -38,16 +38,16 @@ export const vmul = defVecOp2(mul); * Dual vector division. Applies {@link div} in a component-wise manner. * Returns new (dual) vector. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const vdiv = defVecOp2(div); /** * Computes dot product of 2 dual vectors. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const dot: FnU2 = (a, b) => - a.reduce((acc, a, i) => add(acc, mul(a, b[i])), dual(0, a[0].length)); + a.reduce((acc, a, i) => add(acc, mul(a, b[i])), dual(0, a[0].length)); diff --git a/packages/dual-algebra/tsconfig.json b/packages/dual-algebra/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/dual-algebra/tsconfig.json +++ b/packages/dual-algebra/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/dynvar/api-extractor.json b/packages/dynvar/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/dynvar/api-extractor.json +++ b/packages/dynvar/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/dynvar/package.json b/packages/dynvar/package.json index fd33b64232..b13da9eba1 100644 --- a/packages/dynvar/package.json +++ b/packages/dynvar/package.json @@ -1,80 +1,80 @@ { - "name": "@thi.ng/dynvar", - "version": "0.3.8", - "description": "Dynamically scoped variable bindings", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/api#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/errors": "^2.1.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "clojure", - "datastructure", - "dynamic", - "scope", - "state", - "typescript", - "variable" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "year": 2016, - "status": "alpha" - } + "name": "@thi.ng/dynvar", + "version": "0.3.8", + "description": "Dynamically scoped variable bindings", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/api#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/errors": "^2.1.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "clojure", + "datastructure", + "dynamic", + "scope", + "state", + "typescript", + "variable" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "year": 2016, + "status": "alpha" + } } diff --git a/packages/dynvar/src/index.ts b/packages/dynvar/src/index.ts index 0ba3fd7e33..7a1796f58f 100644 --- a/packages/dynvar/src/index.ts +++ b/packages/dynvar/src/index.ts @@ -22,85 +22,85 @@ export const dynvar = (x: T) => new DynVar(x); * */ export class DynVar implements IDeref, IBind, ICopy> { - constructor(val: T) { - __private.set(this, [val]); - } + constructor(val: T) { + __private.set(this, [val]); + } - /** - * Returns current bound value, i.e. that of the currently active - * dynamic scope. - */ - deref() { - const v = __private.get(this); - return v[v.length - 1]; - } + /** + * Returns current bound value, i.e. that of the currently active + * dynamic scope. + */ + deref() { + const v = __private.get(this); + return v[v.length - 1]; + } - /** - * Same as {@link DynVar.deref}, but also for `DynVar` to satisfy - * `Object.valueOf()` contract. - * - */ - valueOf() { - return this.deref(); - } + /** + * Same as {@link DynVar.deref}, but also for `DynVar` to satisfy + * `Object.valueOf()` contract. + * + */ + valueOf() { + return this.deref(); + } - /** - * Returns new `DynVar` with this var's current value as its root - * binding. - */ - copy() { - return new DynVar(this.deref()); - } + /** + * Returns new `DynVar` with this var's current value as its root + * binding. + */ + copy() { + return new DynVar(this.deref()); + } - /** - * Starts new dynamic scope in which given `val` will be bound to - * this variable. In most cases, calls to `bind()` should always be - * eventually followed by calls to {@link DynVar.unbind} to restore - * this var's previously scoped value. - * - * @param val - - */ - bind(val: T) { - __private.get(this).push(val); - return true; - } + /** + * Starts new dynamic scope in which given `val` will be bound to + * this variable. In most cases, calls to `bind()` should always be + * eventually followed by calls to {@link DynVar.unbind} to restore + * this var's previously scoped value. + * + * @param val - + */ + bind(val: T) { + __private.get(this).push(val); + return true; + } - /** - * Attempts to end the current scope by restoring this var's bound - * value to that of parent scope. An error is thrown if attempting - * to remove the root binding. - */ - unbind() { - const v = __private.get(this); - assert(v.length > 1, `can't unbind root value`); - v.pop(); - return true; - } + /** + * Attempts to end the current scope by restoring this var's bound + * value to that of parent scope. An error is thrown if attempting + * to remove the root binding. + */ + unbind() { + const v = __private.get(this); + assert(v.length > 1, `can't unbind root value`); + v.pop(); + return true; + } - /** - * Replaces current scope's value with new `val`. - * - * @param val - - */ - set(val: T) { - const v = __private.get(this); - v[v.length - 1] = val; - } + /** + * Replaces current scope's value with new `val`. + * + * @param val - + */ + set(val: T) { + const v = __private.get(this); + v[v.length - 1] = val; + } - /** - * Executes given `body` function in a new scope which has given - * `val` bound to this variable. The new scope is automatically - * removed when the function returns or an error occurred. - * - * @param val - - * @param body - - */ - withBinding(val: T, body: Fn0) { - this.bind(val); - try { - return body(); - } finally { - this.unbind(); - } - } + /** + * Executes given `body` function in a new scope which has given + * `val` bound to this variable. The new scope is automatically + * removed when the function returns or an error occurred. + * + * @param val - + * @param body - + */ + withBinding(val: T, body: Fn0) { + this.bind(val); + try { + return body(); + } finally { + this.unbind(); + } + } } diff --git a/packages/dynvar/test/index.ts b/packages/dynvar/test/index.ts index b4b5469b34..ca7d4a1891 100644 --- a/packages/dynvar/test/index.ts +++ b/packages/dynvar/test/index.ts @@ -1,50 +1,50 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { dynvar } from "../src/index.js" +import { dynvar } from "../src/index.js"; group("dynvar", { - basic: () => { - const a = dynvar(1); - assert.strictEqual(a.deref(), 1); - a.bind(2); - assert.strictEqual(a.deref(), 2); - a.bind(3); - assert.strictEqual(a.deref(), 3); - a.unbind(); - assert.strictEqual(a.deref(), 2); - a.set(4); - assert.strictEqual(a.deref(), 4); - a.unbind(); - assert.strictEqual(a.deref(), 1); - assert.throws(() => a.unbind()); - }, + basic: () => { + const a = dynvar(1); + assert.strictEqual(a.deref(), 1); + a.bind(2); + assert.strictEqual(a.deref(), 2); + a.bind(3); + assert.strictEqual(a.deref(), 3); + a.unbind(); + assert.strictEqual(a.deref(), 2); + a.set(4); + assert.strictEqual(a.deref(), 4); + a.unbind(); + assert.strictEqual(a.deref(), 1); + assert.throws(() => a.unbind()); + }, - withBinding: () => { - const res: number[] = []; - const a = dynvar(1); + withBinding: () => { + const res: number[] = []; + const a = dynvar(1); - const collect = () => { - const x = a.deref(); - res.push(x); - if (x < 4) a.withBinding(x + 1, collect); - res.push(a.deref() * 10); - }; - collect(); + const collect = () => { + const x = a.deref(); + res.push(x); + if (x < 4) a.withBinding(x + 1, collect); + res.push(a.deref() * 10); + }; + collect(); - assert.deepStrictEqual(res, [1, 2, 3, 4, 40, 30, 20, 10]); - assert.throws(() => a.unbind()); - }, + assert.deepStrictEqual(res, [1, 2, 3, 4, 40, 30, 20, 10]); + assert.throws(() => a.unbind()); + }, - "withBinding (error)": () => { - const a = dynvar(1); - a.withBinding(2, () => { - try { - a.withBinding(3, () => { - throw new Error(); - }); - } catch (_) {} - assert.strictEqual(a.deref(), 2); - }); - assert.strictEqual(a.deref(), 1); - }, + "withBinding (error)": () => { + const a = dynvar(1); + a.withBinding(2, () => { + try { + a.withBinding(3, () => { + throw new Error(); + }); + } catch (_) {} + assert.strictEqual(a.deref(), 2); + }); + assert.strictEqual(a.deref(), 1); + }, }); diff --git a/packages/dynvar/tsconfig.json b/packages/dynvar/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/dynvar/tsconfig.json +++ b/packages/dynvar/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/ecs/api-extractor.json b/packages/ecs/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/ecs/api-extractor.json +++ b/packages/ecs/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/ecs/package.json b/packages/ecs/package.json index 149dded5b8..f73d45571a 100644 --- a/packages/ecs/package.json +++ b/packages/ecs/package.json @@ -1,125 +1,125 @@ { - "name": "@thi.ng/ecs", - "version": "0.7.12", - "description": "Entity Component System based around typed arrays & sparse sets", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/ecs#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc caches components groups", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/associative": "^6.2.0", - "@thi.ng/binary": "^3.3.0", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/dcons": "^3.2.7", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/idgen": "^2.1.8", - "@thi.ng/logger": "^1.1.8", - "@thi.ng/malloc": "^6.1.9", - "@thi.ng/transducers": "^8.3.7", - "tslib": "^2.4.0" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/equiv": "^2.1.8", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "acceleration", - "animation", - "aos", - "array", - "cache", - "component", - "data-oriented", - "datastructure", - "entity", - "memory-mapped", - "simd", - "typedarray", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "caches", - "components", - "groups" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./caches/lru": { - "default": "./caches/lru.js" - }, - "./caches/null": { - "default": "./caches/null.js" - }, - "./caches/unbounded": { - "default": "./caches/unbounded.js" - }, - "./components/acomponent": { - "default": "./components/acomponent.js" - }, - "./components/mem-component": { - "default": "./components/mem-component.js" - }, - "./components/object-component": { - "default": "./components/object-component.js" - }, - "./ecs": { - "default": "./ecs.js" - }, - "./groups/group": { - "default": "./groups/group.js" - }, - "./logger": { - "default": "./logger.js" - } - }, - "thi.ng": { - "status": "alpha", - "year": 2019 - } + "name": "@thi.ng/ecs", + "version": "0.7.12", + "description": "Entity Component System based around typed arrays & sparse sets", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/ecs#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc caches components groups", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/associative": "^6.2.0", + "@thi.ng/binary": "^3.3.0", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/dcons": "^3.2.7", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/idgen": "^2.1.8", + "@thi.ng/logger": "^1.1.8", + "@thi.ng/malloc": "^6.1.9", + "@thi.ng/transducers": "^8.3.7", + "tslib": "^2.4.0" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/equiv": "^2.1.8", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "acceleration", + "animation", + "aos", + "array", + "cache", + "component", + "data-oriented", + "datastructure", + "entity", + "memory-mapped", + "simd", + "typedarray", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "caches", + "components", + "groups" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./caches/lru": { + "default": "./caches/lru.js" + }, + "./caches/null": { + "default": "./caches/null.js" + }, + "./caches/unbounded": { + "default": "./caches/unbounded.js" + }, + "./components/acomponent": { + "default": "./components/acomponent.js" + }, + "./components/mem-component": { + "default": "./components/mem-component.js" + }, + "./components/object-component": { + "default": "./components/object-component.js" + }, + "./ecs": { + "default": "./ecs.js" + }, + "./groups/group": { + "default": "./groups/group.js" + }, + "./logger": { + "default": "./logger.js" + } + }, + "thi.ng": { + "status": "alpha", + "year": 2019 + } } diff --git a/packages/ecs/src/api.ts b/packages/ecs/src/api.ts index e1213862fe..737c958640 100644 --- a/packages/ecs/src/api.ts +++ b/packages/ecs/src/api.ts @@ -1,13 +1,13 @@ import type { - ArrayLikeIterable, - Fn0, - IClear, - IID, - INotify, - IRelease, - Type, - TypedArray, - UIntArray, + ArrayLikeIterable, + Fn0, + IClear, + IID, + INotify, + IRelease, + Type, + TypedArray, + UIntArray, } from "@thi.ng/api"; import type { IMemPoolArray } from "@thi.ng/malloc"; @@ -16,96 +16,96 @@ export type ComponentID = keyof S & string; export type ComponentDefaultValue = T | Fn0; export type GroupTuple> = Pick & - IID; + IID; export type GroupInfo> = { - [P in K]: ComponentInfo; + [P in K]: ComponentInfo; }; export interface ComponentInfo> { - values: SPEC[K] extends TypedArray ? SPEC[K] : SPEC[K][]; - size: number; - stride: number; + values: SPEC[K] extends TypedArray ? SPEC[K] : SPEC[K][]; + size: number; + stride: number; } export interface IComponent - extends IID, - INotify { - dense: UIntArray; - sparse: UIntArray; - vals: VALUES; - readonly size: number; - readonly stride: number; - - owner?: IID; - - resize(pool: IMemPoolArray, cap: number): void; - has(id: number): boolean; - add(id: number, val?: SET): boolean; - delete(id: number): boolean; - get(id: number): GET | undefined; - set(i: number, val: SET): boolean; - getIndex(i: number): GET | undefined; - setIndex(i: number, val: SET): boolean; - setIndexUnsafe(i: number, val: SET, notify?: boolean): void; - - keys(): ArrayLikeIterable; - values(): IterableIterator; - - /** - * Swaps slots of `src` & `dest` indices. The given args are NOT - * entity IDs, but indices in the `dense` array. The corresponding - * sparse & value slots are swapped too. Returns true if swap - * happened (false, if `src` and `dest` are equal) - * - * @param src - - * @param dest - - */ - swapIndices(src: number, dest: number): boolean; + extends IID, + INotify { + dense: UIntArray; + sparse: UIntArray; + vals: VALUES; + readonly size: number; + readonly stride: number; + + owner?: IID; + + resize(pool: IMemPoolArray, cap: number): void; + has(id: number): boolean; + add(id: number, val?: SET): boolean; + delete(id: number): boolean; + get(id: number): GET | undefined; + set(i: number, val: SET): boolean; + getIndex(i: number): GET | undefined; + setIndex(i: number, val: SET): boolean; + setIndexUnsafe(i: number, val: SET, notify?: boolean): void; + + keys(): ArrayLikeIterable; + values(): IterableIterator; + + /** + * Swaps slots of `src` & `dest` indices. The given args are NOT + * entity IDs, but indices in the `dense` array. The corresponding + * sparse & value slots are swapped too. Returns true if swap + * happened (false, if `src` and `dest` are equal) + * + * @param src - + * @param dest - + */ + swapIndices(src: number, dest: number): boolean; } export interface MemMappedComponentOpts { - id: ID; - type: Type; - buf?: ArrayBuffer; - byteOffset?: number; - size?: number; - stride?: number; - default?: ComponentDefaultValue>; - cache?: ICache; + id: ID; + type: Type; + buf?: ArrayBuffer; + byteOffset?: number; + size?: number; + stride?: number; + default?: ComponentDefaultValue>; + cache?: ICache; } export interface ObjectComponentOpts { - id: ID; - default?: ComponentDefaultValue; + id: ID; + default?: ComponentDefaultValue; } export interface GroupOpts { - id: string; - cache?: ICache; + id: string; + cache?: ICache; } export interface ICache extends IClear, IRelease { - keys(): Iterable; - set(key: number, val: T): T; - get(key: number): T | undefined; - getSet(key: number, notFound: Fn0): T; - delete(key: number): boolean; + keys(): Iterable; + set(key: number, val: T): T; + get(key: number): T | undefined; + getSet(key: number, notFound: Fn0): T; + delete(key: number): boolean; } export interface ECSOpts { - /** - * Max. number of entities - * - * @defaultValue 1000 - */ - capacity: number; - /** - * Optional {@link @thi.ng/malloc#IMemPoolArray} implementation - * - * @defaultValue {@link @thi.ng/malloc#NativePool} - */ - pool: IMemPoolArray; + /** + * Max. number of entities + * + * @defaultValue 1000 + */ + capacity: number; + /** + * Optional {@link @thi.ng/malloc#IMemPoolArray} implementation + * + * @defaultValue {@link @thi.ng/malloc#NativePool} + */ + pool: IMemPoolArray; } export const EVENT_ADDED = "added"; diff --git a/packages/ecs/src/caches/lru.ts b/packages/ecs/src/caches/lru.ts index ec55fd9dad..aa58376bae 100644 --- a/packages/ecs/src/caches/lru.ts +++ b/packages/ecs/src/caches/lru.ts @@ -6,66 +6,66 @@ import type { ICache } from "../api.js"; type LRUEntry = { k: number; v: T }; export class LRU implements ICache { - items: DCons>; - index: Map>>; - capacity: number; + items: DCons>; + index: Map>>; + capacity: number; - constructor(cap: number) { - this.items = new DCons(); - this.index = new Map(); - this.capacity = cap; - } + constructor(cap: number) { + this.items = new DCons(); + this.index = new Map(); + this.capacity = cap; + } - keys() { - return this.index.keys(); - } + keys() { + return this.index.keys(); + } - clear() { - this.items.release(); - this.index.clear(); - } + clear() { + this.items.release(); + this.index.clear(); + } - release() { - this.clear(); - return true; - } + release() { + this.clear(); + return true; + } - set(key: number, val: T) { - const { items, index } = this; - let node = index.get(key); - if (node) { - node.value.v = val; - items.asHead(node); - } else { - items.prepend({ k: key, v: val }); - index.set(key, items.head!); - while (items.length > this.capacity) { - this.index.delete(this.items.pop()!.k); - } - } - return val; - } + set(key: number, val: T) { + const { items, index } = this; + let node = index.get(key); + if (node) { + node.value.v = val; + items.asHead(node); + } else { + items.prepend({ k: key, v: val }); + index.set(key, items.head!); + while (items.length > this.capacity) { + this.index.delete(this.items.pop()!.k); + } + } + return val; + } - get(key: number) { - const node = this.index.get(key); - if (node) { - this.items.asHead(node); - return node.value.v; - } - } + get(key: number) { + const node = this.index.get(key); + if (node) { + this.items.asHead(node); + return node.value.v; + } + } - getSet(key: number, notFound: Fn0) { - let val = this.get(key); - return val !== undefined ? val : this.set(key, notFound()); - } + getSet(key: number, notFound: Fn0) { + let val = this.get(key); + return val !== undefined ? val : this.set(key, notFound()); + } - delete(key: number) { - const node = this.index.get(key); - if (node) { - this.index.delete(key); - this.items.remove(node); - return true; - } - return false; - } + delete(key: number) { + const node = this.index.get(key); + if (node) { + this.index.delete(key); + this.items.remove(node); + return true; + } + return false; + } } diff --git a/packages/ecs/src/caches/null.ts b/packages/ecs/src/caches/null.ts index ddae0b0afa..c7b2385f59 100644 --- a/packages/ecs/src/caches/null.ts +++ b/packages/ecs/src/caches/null.ts @@ -2,27 +2,27 @@ import type { Fn0 } from "@thi.ng/api"; import type { ICache } from "../api.js"; export class NullCache implements ICache { - clear() {} + clear() {} - release() { - return true; - } + release() { + return true; + } - *keys() {} + *keys() {} - set(_: number, val: T): T { - return val; - } + set(_: number, val: T): T { + return val; + } - get(_: number): T | undefined { - return; - } + get(_: number): T | undefined { + return; + } - getSet(_: number, notFound: Fn0): T { - return notFound(); - } + getSet(_: number, notFound: Fn0): T { + return notFound(); + } - delete(_: number): boolean { - return true; - } + delete(_: number): boolean { + return true; + } } diff --git a/packages/ecs/src/caches/unbounded.ts b/packages/ecs/src/caches/unbounded.ts index 93ac1f6321..3b4b8eebdd 100644 --- a/packages/ecs/src/caches/unbounded.ts +++ b/packages/ecs/src/caches/unbounded.ts @@ -2,40 +2,40 @@ import type { Fn0 } from "@thi.ng/api"; import type { ICache } from "../api.js"; export class UnboundedCache implements ICache { - index: Map; - - constructor() { - this.index = new Map(); - } - - clear() { - this.index.clear(); - } - - release() { - this.clear(); - return true; - } - - keys() { - return this.index.keys(); - } - - set(key: number, val: T): T { - this.index.set(key, val); - return val; - } - - get(key: number): T | undefined { - return this.index.get(key); - } - - getSet(key: number, notFound: Fn0): T { - let val = this.index.get(key); - return val !== undefined ? val : this.set(key, notFound()); - } - - delete(key: number): boolean { - return this.index.delete(key); - } + index: Map; + + constructor() { + this.index = new Map(); + } + + clear() { + this.index.clear(); + } + + release() { + this.clear(); + return true; + } + + keys() { + return this.index.keys(); + } + + set(key: number, val: T): T { + this.index.set(key, val); + return val; + } + + get(key: number): T | undefined { + return this.index.get(key); + } + + getSet(key: number, notFound: Fn0): T { + let val = this.index.get(key); + return val !== undefined ? val : this.set(key, notFound()); + } + + delete(key: number): boolean { + return this.index.delete(key); + } } diff --git a/packages/ecs/src/components/acomponent.ts b/packages/ecs/src/components/acomponent.ts index 0b0d466e88..7c73027c9b 100644 --- a/packages/ecs/src/components/acomponent.ts +++ b/packages/ecs/src/components/acomponent.ts @@ -3,138 +3,138 @@ import { INotifyMixin } from "@thi.ng/api/mixins/inotify"; import { isFunction } from "@thi.ng/checks/is-function"; import type { IMemPoolArray } from "@thi.ng/malloc"; import { - ComponentDefaultValue, - EVENT_ADDED, - EVENT_CHANGED, - EVENT_PRE_DELETE, - IComponent, + ComponentDefaultValue, + EVENT_ADDED, + EVENT_CHANGED, + EVENT_PRE_DELETE, + IComponent, } from "../api.js"; @INotifyMixin export abstract class AComponent - implements IComponent, INotify + implements IComponent, INotify { - readonly id: K; - abstract readonly size: number; - abstract readonly stride: number; - - sparse: UIntArray; - dense: UIntArray; - vals: VALUES; - n: number; - - default?: ComponentDefaultValue; - owner?: IID; - - constructor(id: K, sparse: UIntArray, dense: UIntArray, vals: VALUES) { - this.id = id; - this.sparse = sparse; - this.dense = dense; - this.vals = vals; - this.n = 0; - } - - keys() { - return this.dense.slice(0, this.n); - } - - *values() { - for (let i = this.n; i-- > 0; ) { - yield this.getIndex(i)!; - } - } - - abstract resize(pool: IMemPoolArray, newCap: number): void; - - has(id: number): boolean { - const i = this.sparse[id]; - return i < this.n && this.dense[i] === id; - } - - abstract get(id: number): GET | undefined; - - abstract getIndex(i: number): GET | undefined; - - valueIndexForID(id: number) { - const i = this.sparse[id]; - return i < this.n && this.dense[i] === id ? i * this.stride : -1; - } - - valueIndexForIDUnsafe(id: number) { - return this.sparse[id] * this.stride; - } - - set(id: number, val: SET) { - const i = this.sparse[id]; - if (i < this.n && this.dense[i] === id) { - this.setIndexUnsafe(i, val); - return true; - } - return false; - } - - setIndex(i: number, val: SET) { - const id = this.dense[i]; - if (i < this.n && this.sparse[id] === i) { - this.setIndexUnsafe(i, val); - return true; - } - return false; - } - - abstract setIndexUnsafe(i: number, val: SET, notify?: boolean): void; - - add(id: number, val?: SET) { - const { dense, sparse, n } = this; - const max = dense.length; - const i = sparse[id]; - if (id < max && n < max && !(i < n && dense[i] === id)) { - dense[n] = id; - sparse[id] = n; - this.n++; - const def = this.default; - const initVal = val || (isFunction(def) ? def() : def); - initVal !== undefined && this.setIndexUnsafe(n, initVal, false); - this.notify({ id: EVENT_ADDED, target: this, value: id }); - return true; - } - return false; - } - - delete(id: number) { - let { dense, sparse, n } = this; - let i = sparse[id]; - if (i < n && dense[i] === id) { - // notify listeners prior to removal to allow restructure / swaps - this.notify({ id: EVENT_PRE_DELETE, target: this, value: id }); - // get possibly updated slot - i = sparse[id]; - const j = dense[--n]; - dense[i] = j; - sparse[j] = i; - this.n = n; - this.moveIndex(n, i); - return true; - } - return false; - } - - // @ts-ignore: arguments - addListener(id: string, fn: Listener, scope?: any): boolean {} - - /** {@inheritDoc @thi.ng/api#INotify.removeListener} */ - // @ts-ignore: arguments - removeListener(id: string, fn: Listener, scope?: any): boolean {} - - /** {@inheritDoc @thi.ng/api#INotify.notify} */ - // @ts-ignore: arguments - notify(event: Event) {} - - notifyChange(id: number) { - this.notify({ id: EVENT_CHANGED, target: this, value: id }); - } - - abstract swapIndices(src: number, dest: number): boolean; - - protected abstract moveIndex(src: number, dest: number): void; + readonly id: K; + abstract readonly size: number; + abstract readonly stride: number; + + sparse: UIntArray; + dense: UIntArray; + vals: VALUES; + n: number; + + default?: ComponentDefaultValue; + owner?: IID; + + constructor(id: K, sparse: UIntArray, dense: UIntArray, vals: VALUES) { + this.id = id; + this.sparse = sparse; + this.dense = dense; + this.vals = vals; + this.n = 0; + } + + keys() { + return this.dense.slice(0, this.n); + } + + *values() { + for (let i = this.n; i-- > 0; ) { + yield this.getIndex(i)!; + } + } + + abstract resize(pool: IMemPoolArray, newCap: number): void; + + has(id: number): boolean { + const i = this.sparse[id]; + return i < this.n && this.dense[i] === id; + } + + abstract get(id: number): GET | undefined; + + abstract getIndex(i: number): GET | undefined; + + valueIndexForID(id: number) { + const i = this.sparse[id]; + return i < this.n && this.dense[i] === id ? i * this.stride : -1; + } + + valueIndexForIDUnsafe(id: number) { + return this.sparse[id] * this.stride; + } + + set(id: number, val: SET) { + const i = this.sparse[id]; + if (i < this.n && this.dense[i] === id) { + this.setIndexUnsafe(i, val); + return true; + } + return false; + } + + setIndex(i: number, val: SET) { + const id = this.dense[i]; + if (i < this.n && this.sparse[id] === i) { + this.setIndexUnsafe(i, val); + return true; + } + return false; + } + + abstract setIndexUnsafe(i: number, val: SET, notify?: boolean): void; + + add(id: number, val?: SET) { + const { dense, sparse, n } = this; + const max = dense.length; + const i = sparse[id]; + if (id < max && n < max && !(i < n && dense[i] === id)) { + dense[n] = id; + sparse[id] = n; + this.n++; + const def = this.default; + const initVal = val || (isFunction(def) ? def() : def); + initVal !== undefined && this.setIndexUnsafe(n, initVal, false); + this.notify({ id: EVENT_ADDED, target: this, value: id }); + return true; + } + return false; + } + + delete(id: number) { + let { dense, sparse, n } = this; + let i = sparse[id]; + if (i < n && dense[i] === id) { + // notify listeners prior to removal to allow restructure / swaps + this.notify({ id: EVENT_PRE_DELETE, target: this, value: id }); + // get possibly updated slot + i = sparse[id]; + const j = dense[--n]; + dense[i] = j; + sparse[j] = i; + this.n = n; + this.moveIndex(n, i); + return true; + } + return false; + } + + // @ts-ignore: arguments + addListener(id: string, fn: Listener, scope?: any): boolean {} + + /** {@inheritDoc @thi.ng/api#INotify.removeListener} */ + // @ts-ignore: arguments + removeListener(id: string, fn: Listener, scope?: any): boolean {} + + /** {@inheritDoc @thi.ng/api#INotify.notify} */ + // @ts-ignore: arguments + notify(event: Event) {} + + notifyChange(id: number) { + this.notify({ id: EVENT_CHANGED, target: this, value: id }); + } + + abstract swapIndices(src: number, dest: number): boolean; + + protected abstract moveIndex(src: number, dest: number): void; } diff --git a/packages/ecs/src/components/mem-component.ts b/packages/ecs/src/components/mem-component.ts index a637126b0d..d9276503f0 100644 --- a/packages/ecs/src/components/mem-component.ts +++ b/packages/ecs/src/components/mem-component.ts @@ -1,8 +1,8 @@ import { - Type, - typedArray, - TypedArray, - UIntArray, + Type, + typedArray, + TypedArray, + UIntArray, } from "@thi.ng/api/typedarray"; import { INotifyMixin } from "@thi.ng/api/mixins/inotify"; import { assert } from "@thi.ng/errors/assert"; @@ -12,111 +12,111 @@ import { AComponent } from "./acomponent.js"; @INotifyMixin export class MemMappedComponent extends AComponent< - K, - TypedArray, - TypedArray, - ArrayLike + K, + TypedArray, + TypedArray, + ArrayLike > { - readonly type: Type; - readonly size: number; - readonly stride: number; + readonly type: Type; + readonly size: number; + readonly stride: number; - cache?: ICache; + cache?: ICache; - constructor( - sparse: UIntArray, - dense: UIntArray, - opts: MemMappedComponentOpts - ) { - opts = { - size: 1, - byteOffset: 0, - ...opts, - }; - const size = opts.size!; - const stride = opts.stride || size; - super( - opts.id, - sparse, - dense, - opts.buf - ? typedArray( - opts.type!, - opts.buf, - opts.byteOffset!, - dense.length * stride - ) - : typedArray(opts.type!, dense.length * stride) - ); - this.type = opts.type!; - this.size = size; - this.stride = stride; - this.default = opts.default; - this.cache = opts.cache; - } + constructor( + sparse: UIntArray, + dense: UIntArray, + opts: MemMappedComponentOpts + ) { + opts = { + size: 1, + byteOffset: 0, + ...opts, + }; + const size = opts.size!; + const stride = opts.stride || size; + super( + opts.id, + sparse, + dense, + opts.buf + ? typedArray( + opts.type!, + opts.buf, + opts.byteOffset!, + dense.length * stride + ) + : typedArray(opts.type!, dense.length * stride) + ); + this.type = opts.type!; + this.size = size; + this.stride = stride; + this.default = opts.default; + this.cache = opts.cache; + } - packedValues() { - return this.vals.subarray(0, this.n * this.stride); - } + packedValues() { + return this.vals.subarray(0, this.n * this.stride); + } - resize(pool: IMemPoolArray, cap: number) { - assert(cap >= this.dense.length, "can't decrease capacity"); - if (cap === this.dense.length) return; - const sparse = pool.reallocArray(this.sparse, cap); - const dense = pool.reallocArray(this.dense, cap); - const vals = pool.reallocArray(this.vals, cap * this.stride); - assert( - !!(sparse && dense && vals), - `couldn't resize component: ${this.id}` - ); - this.sparse = sparse!; - this.dense = dense!; - this.vals = vals!; - this.cache && this.cache.clear(); - } + resize(pool: IMemPoolArray, cap: number) { + assert(cap >= this.dense.length, "can't decrease capacity"); + if (cap === this.dense.length) return; + const sparse = pool.reallocArray(this.sparse, cap); + const dense = pool.reallocArray(this.dense, cap); + const vals = pool.reallocArray(this.vals, cap * this.stride); + assert( + !!(sparse && dense && vals), + `couldn't resize component: ${this.id}` + ); + this.sparse = sparse!; + this.dense = dense!; + this.vals = vals!; + this.cache && this.cache.clear(); + } - get(id: number) { - let i = this.sparse[id]; - return this.dense[i] === id ? this.getIndex(i) : undefined; - } + get(id: number) { + let i = this.sparse[id]; + return this.dense[i] === id ? this.getIndex(i) : undefined; + } - getIndex(i: number) { - return i < this.n - ? this.cache - ? this.cache.getSet(i, () => { - i *= this.stride; - return this.vals.subarray(i, i + this.size); - }) - : ((i *= this.stride), this.vals.subarray(i, i + this.size)) - : undefined; - } + getIndex(i: number) { + return i < this.n + ? this.cache + ? this.cache.getSet(i, () => { + i *= this.stride; + return this.vals.subarray(i, i + this.size); + }) + : ((i *= this.stride), this.vals.subarray(i, i + this.size)) + : undefined; + } - setIndexUnsafe(i: number, val: ArrayLike, notify = true) { - this.vals.set(val, i * this.stride); - notify && this.notifyChange(this.dense[i]); - } + setIndexUnsafe(i: number, val: ArrayLike, notify = true) { + this.vals.set(val, i * this.stride); + notify && this.notifyChange(this.dense[i]); + } - swapIndices(src: number, dest: number) { - if (src === dest) return false; - const { dense, sparse, vals, size, stride } = this; - const ss = dense[src]; - const sd = dense[dest]; - dense[src] = sd; - dense[dest] = ss; - sparse[ss] = dest; - sparse[sd] = src; - src *= stride; - dest *= stride; - const tmp = vals.slice(src, src + size); - vals.copyWithin(src, dest, dest + size); - vals.set(tmp, dest); - return true; - } + swapIndices(src: number, dest: number) { + if (src === dest) return false; + const { dense, sparse, vals, size, stride } = this; + const ss = dense[src]; + const sd = dense[dest]; + dense[src] = sd; + dense[dest] = ss; + sparse[ss] = dest; + sparse[sd] = src; + src *= stride; + dest *= stride; + const tmp = vals.slice(src, src + size); + vals.copyWithin(src, dest, dest + size); + vals.set(tmp, dest); + return true; + } - protected moveIndex(src: number, dest: number) { - const s = this.stride; - src *= s; - this.vals.copyWithin(dest * s, src, src + this.size); - this.cache && this.cache.delete(dest); - } + protected moveIndex(src: number, dest: number) { + const s = this.stride; + src *= s; + this.vals.copyWithin(dest * s, src, src + this.size); + this.cache && this.cache.delete(dest); + } } diff --git a/packages/ecs/src/components/object-component.ts b/packages/ecs/src/components/object-component.ts index cb89f5ed75..54563efb2a 100644 --- a/packages/ecs/src/components/object-component.ts +++ b/packages/ecs/src/components/object-component.ts @@ -7,73 +7,73 @@ import { AComponent } from "./acomponent.js"; @INotifyMixin export class ObjectComponent extends AComponent< - K, - T[], - T, - T + K, + T[], + T, + T > { - constructor( - sparse: UIntArray, - dense: UIntArray, - opts: ObjectComponentOpts - ) { - super(opts.id, sparse, dense, new Array(dense.length)); - this.default = opts.default; - } + constructor( + sparse: UIntArray, + dense: UIntArray, + opts: ObjectComponentOpts + ) { + super(opts.id, sparse, dense, new Array(dense.length)); + this.default = opts.default; + } - get size() { - return 1; - } - get stride() { - return 1; - } + get size() { + return 1; + } + get stride() { + return 1; + } - packedValues() { - return this.vals.slice(0, this.n); - } + packedValues() { + return this.vals.slice(0, this.n); + } - resize(pool: IMemPoolArray, cap: number) { - assert(cap >= this.dense.length, "can't decrease capacity"); - if (cap === this.dense.length) return; - const sparse = pool.reallocArray(this.sparse, cap); - const dense = pool.reallocArray(this.dense, cap); - assert(!!(sparse && dense), `couldn't resize component: ${this.id}`); - this.sparse = sparse!; - this.dense = dense!; - } + resize(pool: IMemPoolArray, cap: number) { + assert(cap >= this.dense.length, "can't decrease capacity"); + if (cap === this.dense.length) return; + const sparse = pool.reallocArray(this.sparse, cap); + const dense = pool.reallocArray(this.dense, cap); + assert(!!(sparse && dense), `couldn't resize component: ${this.id}`); + this.sparse = sparse!; + this.dense = dense!; + } - get(id: number) { - let i = this.sparse[id]; - return i < this.n && this.dense[i] === id ? this.vals[i] : undefined; - } + get(id: number) { + let i = this.sparse[id]; + return i < this.n && this.dense[i] === id ? this.vals[i] : undefined; + } - getIndex(i: number) { - return i < this.n ? this.vals[i] : undefined; - } + getIndex(i: number) { + return i < this.n ? this.vals[i] : undefined; + } - setIndexUnsafe(i: number, v: T, notify = true) { - this.vals[i] = v; - notify && this.notifyChange(this.dense[i]); - } + setIndexUnsafe(i: number, v: T, notify = true) { + this.vals[i] = v; + notify && this.notifyChange(this.dense[i]); + } - swapIndices(src: number, dest: number) { - if (src === dest) return false; - const { dense, sparse, vals } = this; - const ss = dense[src]; - const sd = dense[dest]; - dense[src] = sd; - dense[dest] = ss; - sparse[ss] = dest; - sparse[sd] = src; - const tmp = vals[src]; - vals[src] = vals[dest]; - vals[dest] = tmp; - return true; - } + swapIndices(src: number, dest: number) { + if (src === dest) return false; + const { dense, sparse, vals } = this; + const ss = dense[src]; + const sd = dense[dest]; + dense[src] = sd; + dense[dest] = ss; + sparse[ss] = dest; + sparse[sd] = src; + const tmp = vals[src]; + vals[src] = vals[dest]; + vals[dest] = tmp; + return true; + } - protected moveIndex(src: number, dest: number) { - const vals = this.vals; - vals[dest] = vals[src]; - delete vals[src]; - } + protected moveIndex(src: number, dest: number) { + const vals = this.vals; + vals[dest] = vals[src]; + delete vals[src]; + } } diff --git a/packages/ecs/src/ecs.ts b/packages/ecs/src/ecs.ts index 1d2d99c798..49490433ed 100644 --- a/packages/ecs/src/ecs.ts +++ b/packages/ecs/src/ecs.ts @@ -10,14 +10,14 @@ import type { IMemPoolArray } from "@thi.ng/malloc"; import { NativePool } from "@thi.ng/malloc/native"; import { filter } from "@thi.ng/transducers/filter"; import { - ComponentID, - ECSOpts, - EVENT_ADDED, - EVENT_PRE_DELETE, - GroupOpts, - IComponent, - MemMappedComponentOpts, - ObjectComponentOpts, + ComponentID, + ECSOpts, + EVENT_ADDED, + EVENT_PRE_DELETE, + GroupOpts, + IComponent, + MemMappedComponentOpts, + ObjectComponentOpts, } from "./api.js"; import { MemMappedComponent } from "./components/mem-component.js"; import { ObjectComponent } from "./components/object-component.js"; @@ -27,128 +27,128 @@ let NEXT_GROUP_ID = 0; @INotifyMixin export class ECS implements INotify { - idgen: IDGen; - pool: IMemPoolArray; - components: Map< - ComponentID, - IComponent, any, any, any> - >; - groups: Map>; + idgen: IDGen; + pool: IMemPoolArray; + components: Map< + ComponentID, + IComponent, any, any, any> + >; + groups: Map>; - constructor(opts?: Partial) { - opts = { capacity: 1000, pool: new NativePool(), ...opts }; - this.idgen = new IDGen(bitSize(opts.capacity!), 0); - this.pool = opts.pool!; - this.components = new Map(); - this.groups = new Map(); - } + constructor(opts?: Partial) { + opts = { capacity: 1000, pool: new NativePool(), ...opts }; + this.idgen = new IDGen(bitSize(opts.capacity!), 0); + this.pool = opts.pool!; + this.components = new Map(); + this.groups = new Map(); + } - defEntity>( - comps?: K[] | IComponent[] | Partial> - ) { - const id = this.idgen.next(); - if (comps) { - if (isArray(comps)) { - if (!comps.length) return id!; - for (let cid of comps) { - const comp = isString(cid) ? this.components.get(cid) : cid; - assert(!!comp, `unknown component ID: ${cid}`); - comp!.add(id!); - } - } else { - for (let cid in comps) { - const comp = this.components.get(cid); - assert(!!comp, `unknown component ID: ${cid}`); - comp!.add(id!, comps[cid]); - } - } - } - this.notify({ id: EVENT_ADDED, target: this, value: id }); - return id!; - } + defEntity>( + comps?: K[] | IComponent[] | Partial> + ) { + const id = this.idgen.next(); + if (comps) { + if (isArray(comps)) { + if (!comps.length) return id!; + for (let cid of comps) { + const comp = isString(cid) ? this.components.get(cid) : cid; + assert(!!comp, `unknown component ID: ${cid}`); + comp!.add(id!); + } + } else { + for (let cid in comps) { + const comp = this.components.get(cid); + assert(!!comp, `unknown component ID: ${cid}`); + comp!.add(id!, comps[cid]); + } + } + } + this.notify({ id: EVENT_ADDED, target: this, value: id }); + return id!; + } - defComponent>( - opts: MemMappedComponentOpts - ): MemMappedComponent | undefined; - defComponent>( - opts: ObjectComponentOpts - ): ObjectComponent | undefined; - defComponent>(opts: any) { - assert( - !this.components.has(opts.id), - `component '${opts.id}' already existing` - ); - const cap = this.idgen.capacity; - const utype = uintTypeForSize(cap); - const sparse = this.pool.mallocAs(utype, cap); - const dense = this.pool.mallocAs(utype, cap); - if (!(sparse && dense)) return; - const comp: IComponent = - opts.type !== undefined - ? new MemMappedComponent(dense, sparse, opts) - : new ObjectComponent(sparse, dense, opts); - this.components.set(opts.id, comp); - return comp; - } + defComponent>( + opts: MemMappedComponentOpts + ): MemMappedComponent | undefined; + defComponent>( + opts: ObjectComponentOpts + ): ObjectComponent | undefined; + defComponent>(opts: any) { + assert( + !this.components.has(opts.id), + `component '${opts.id}' already existing` + ); + const cap = this.idgen.capacity; + const utype = uintTypeForSize(cap); + const sparse = this.pool.mallocAs(utype, cap); + const dense = this.pool.mallocAs(utype, cap); + if (!(sparse && dense)) return; + const comp: IComponent = + opts.type !== undefined + ? new MemMappedComponent(dense, sparse, opts) + : new ObjectComponent(sparse, dense, opts); + this.components.set(opts.id, comp); + return comp; + } - defGroup>( - comps: IComponent[], - owned: IComponent[] = comps, - opts: Partial = {} - ) { - opts = { - id: `group${NEXT_GROUP_ID++}`, - ...opts, - }; - assert( - !this.groups.has(opts.id!), - `group '${opts.id}' already existing` - ); - const g = new Group(comps, owned, opts); - this.groups.set(g.id, g); - return g; - } + defGroup>( + comps: IComponent[], + owned: IComponent[] = comps, + opts: Partial = {} + ) { + opts = { + id: `group${NEXT_GROUP_ID++}`, + ...opts, + }; + assert( + !this.groups.has(opts.id!), + `group '${opts.id}' already existing` + ); + const g = new Group(comps, owned, opts); + this.groups.set(g.id, g); + return g; + } - hasID(id: number) { - this.idgen.has(id); - } + hasID(id: number) { + this.idgen.has(id); + } - deleteID(id: number) { - if (this.idgen.free(id)) { - this.notify({ id: EVENT_PRE_DELETE, target: this, value: id }); - for (let c of this.componentsForID(id)) { - c.delete(id); - } - return true; - } - return false; - } + deleteID(id: number) { + if (this.idgen.free(id)) { + this.notify({ id: EVENT_PRE_DELETE, target: this, value: id }); + for (let c of this.componentsForID(id)) { + c.delete(id); + } + return true; + } + return false; + } - componentsForID(id: number) { - return filter((c) => c.has(id), this.components.values()); - } + componentsForID(id: number) { + return filter((c) => c.has(id), this.components.values()); + } - groupsForID(id: number) { - return filter((g) => g.has(id), this.groups.values()); - } + groupsForID(id: number) { + return filter((g) => g.has(id), this.groups.values()); + } - setCapacity(newCap: number) { - this.idgen.capacity = newCap; - const pool = this.pool; - for (let comp of this.components.values()) { - comp.resize(pool, newCap); - } - } + setCapacity(newCap: number) { + this.idgen.capacity = newCap; + const pool = this.pool; + for (let comp of this.components.values()) { + comp.resize(pool, newCap); + } + } - /** {@inheritDoc @thi.ng/api#INotify.addListener} */ - // @ts-ignore: mixin - addListener(id: string, fn: Listener, scope?: any): boolean {} + /** {@inheritDoc @thi.ng/api#INotify.addListener} */ + // @ts-ignore: mixin + addListener(id: string, fn: Listener, scope?: any): boolean {} - /** {@inheritDoc @thi.ng/api#INotify.removeListener} */ - // @ts-ignore: mixin - removeListener(id: string, fn: Listener, scope?: any): boolean {} + /** {@inheritDoc @thi.ng/api#INotify.removeListener} */ + // @ts-ignore: mixin + removeListener(id: string, fn: Listener, scope?: any): boolean {} - /** {@inheritDoc @thi.ng/api#INotify.notify} */ - // @ts-ignore: mixin - notify(event: Event) {} + /** {@inheritDoc @thi.ng/api#INotify.notify} */ + // @ts-ignore: mixin + notify(event: Event) {} } diff --git a/packages/ecs/src/groups/group.ts b/packages/ecs/src/groups/group.ts index ea19c445a1..940e9ec822 100644 --- a/packages/ecs/src/groups/group.ts +++ b/packages/ecs/src/groups/group.ts @@ -4,224 +4,224 @@ import { assert } from "@thi.ng/errors/assert"; import { map } from "@thi.ng/transducers/map"; import { transduce } from "@thi.ng/transducers/transduce"; import { - ComponentID, - EVENT_ADDED, - EVENT_CHANGED, - EVENT_PRE_DELETE, - GroupInfo, - GroupOpts, - GroupTuple, - ICache, - IComponent, + ComponentID, + EVENT_ADDED, + EVENT_CHANGED, + EVENT_PRE_DELETE, + GroupInfo, + GroupOpts, + GroupTuple, + ICache, + IComponent, } from "../api.js"; import { UnboundedCache } from "../caches/unbounded.js"; import { ObjectComponent } from "../components/object-component.js"; import { LOGGER } from "../logger.js"; export class Group> implements IID { - readonly id: string; - - components: IComponent[]; - owned: IComponent[]; - ids: Set; - n: number; - - info: GroupInfo; - cache: ICache>; - - constructor( - comps: IComponent[], - owned: IComponent[] = comps, - opts: GroupOpts - ) { - this.components = comps; - this.ids = new Set(); - this.n = 0; - this.id = opts.id; - this.cache = opts.cache || new UnboundedCache(); - - this.info = comps.reduce((acc: GroupInfo, c) => { - acc[c.id] = { - values: c.vals, - size: c.size, - stride: c.stride, - }; - return acc; - }, {}); - - // update ownerships - owned.forEach((c) => { - assert( - comps.includes(c), - `owned component ${c.id} not in given list` - ); - assert( - !c.owner, - () => `component ${c.id} already owned by ${c.owner!.id}` - ); - c.owner = this; - }); - this.owned = owned; - this.addExisting(); - this.addRemoveListeners(true); - } - - release() { - this.addRemoveListeners(false); - this.cache.release(); - } - - has(id: number) { - return this.ids.has(id); - } - - values() { - return this.isFullyOwning() - ? this.ownedValues() - : this.nonOwnedValues(); - } - - getIndex(i: number) { - this.ensureFullyOwning(); - return i < this.n - ? this.getEntityUnsafe(this.components[0].dense[i]) - : undefined; - } - - getEntity(id: number) { - return this.has(id) ? this.getEntityUnsafe(id) : undefined; - } - - getEntityUnsafe(id: number) { - return this.cache.getSet(id, () => { - const tuple = >{ id: id }; - const comps = this.components; - for (let j = comps.length; j-- > 0; ) { - const c = comps[j]; - tuple[c.id] = c.getIndex(c.sparse[id])!; - } - return tuple; - }); - } - - run(fn: FnO2, number, void>, ...xs: any[]) { - this.ensureFullyOwning(); - fn(this.info, this.n, ...xs); - } - - forEachRaw( - fn: FnO3, number, number, void>, - ...xs: any[] - ) { - this.ensureFullyOwning(); - const info = this.info; - const ref = this.components[0].dense; - for (let i = 0, n = this.n; i < n; i++) { - fn(info, ref[i], i, ...xs); - } - } - - forEach(fn: FnO2, number, void>, ...xs: any[]) { - let i = 0; - for (let id of this.ids) { - fn(this.getEntityUnsafe(id), i++, ...xs); - } - } - - isFullyOwning() { - return this.owned.length === this.components.length; - } - - isValidID(id: number) { - for (let comp of this.components) { - if (!comp.has(id)) return false; - } - return true; - } - - protected onAddListener(e: Event) { - LOGGER.debug(`add ${e.target.id}: ${e.value}`); - this.addID(e.value); - } - - protected onDeleteListener(e: Event) { - LOGGER.debug(`delete ${e.target.id}: ${e.value}`); - this.removeID(e.value); - } - - protected onChangeListener(e: Event) { - if (e.target instanceof ObjectComponent) { - LOGGER.debug(`invalidate ${e.target.id}: ${e.value}`); - this.cache.delete(e.value); - } - } - - protected addExisting() { - const existing: Set = transduce( - map((c) => c.keys()), - intersectionR(), - this.components - ); - for (let id of existing) { - this.addID(id, false); - } - } - - protected addID(id: number, validate = true) { - if (validate && !this.isValidID(id)) return; - this.ids.add(id); - this.reorderOwned(id, this.n++); - } - - protected removeID(id: number, validate = true) { - if (validate && !this.isValidID(id)) return; - this.ids.delete(id); - this.reorderOwned(id, --this.n); - } - - protected reorderOwned(id: number, n: number) { - const owned = this.owned; - if (!owned.length) return; - const id2 = owned[0].dense[n]; - let swapped = false; - for (let i = owned.length; i-- > 0; ) { - const comp = owned[i]; - // console.log(`moving id: ${id} in ${comp.id}...`); - swapped = comp.swapIndices(comp.sparse[id], n) || swapped; - } - if (swapped) { - this.cache.delete(id); - this.cache.delete(id2); - } - } - - protected *ownedValues() { - const comps = this.components; - const ref = comps[0].dense; - for (let i = this.n; i-- > 0; ) { - yield this.getEntityUnsafe(ref[i]); - } - } - - protected *nonOwnedValues() { - for (let id of this.ids) { - yield this.getEntityUnsafe(id); - } - } - - protected ensureFullyOwning() { - assert( - this.isFullyOwning(), - `group ${this.id} isn't fully owning its components` - ); - } - - protected addRemoveListeners(add: boolean) { - const f = add ? "addListener" : "removeListener"; - this.components.forEach((comp) => { - comp[f](EVENT_ADDED, this.onAddListener, this); - comp[f](EVENT_PRE_DELETE, this.onDeleteListener, this); - comp[f](EVENT_CHANGED, this.onChangeListener, this); - }); - } + readonly id: string; + + components: IComponent[]; + owned: IComponent[]; + ids: Set; + n: number; + + info: GroupInfo; + cache: ICache>; + + constructor( + comps: IComponent[], + owned: IComponent[] = comps, + opts: GroupOpts + ) { + this.components = comps; + this.ids = new Set(); + this.n = 0; + this.id = opts.id; + this.cache = opts.cache || new UnboundedCache(); + + this.info = comps.reduce((acc: GroupInfo, c) => { + acc[c.id] = { + values: c.vals, + size: c.size, + stride: c.stride, + }; + return acc; + }, {}); + + // update ownerships + owned.forEach((c) => { + assert( + comps.includes(c), + `owned component ${c.id} not in given list` + ); + assert( + !c.owner, + () => `component ${c.id} already owned by ${c.owner!.id}` + ); + c.owner = this; + }); + this.owned = owned; + this.addExisting(); + this.addRemoveListeners(true); + } + + release() { + this.addRemoveListeners(false); + this.cache.release(); + } + + has(id: number) { + return this.ids.has(id); + } + + values() { + return this.isFullyOwning() + ? this.ownedValues() + : this.nonOwnedValues(); + } + + getIndex(i: number) { + this.ensureFullyOwning(); + return i < this.n + ? this.getEntityUnsafe(this.components[0].dense[i]) + : undefined; + } + + getEntity(id: number) { + return this.has(id) ? this.getEntityUnsafe(id) : undefined; + } + + getEntityUnsafe(id: number) { + return this.cache.getSet(id, () => { + const tuple = >{ id: id }; + const comps = this.components; + for (let j = comps.length; j-- > 0; ) { + const c = comps[j]; + tuple[c.id] = c.getIndex(c.sparse[id])!; + } + return tuple; + }); + } + + run(fn: FnO2, number, void>, ...xs: any[]) { + this.ensureFullyOwning(); + fn(this.info, this.n, ...xs); + } + + forEachRaw( + fn: FnO3, number, number, void>, + ...xs: any[] + ) { + this.ensureFullyOwning(); + const info = this.info; + const ref = this.components[0].dense; + for (let i = 0, n = this.n; i < n; i++) { + fn(info, ref[i], i, ...xs); + } + } + + forEach(fn: FnO2, number, void>, ...xs: any[]) { + let i = 0; + for (let id of this.ids) { + fn(this.getEntityUnsafe(id), i++, ...xs); + } + } + + isFullyOwning() { + return this.owned.length === this.components.length; + } + + isValidID(id: number) { + for (let comp of this.components) { + if (!comp.has(id)) return false; + } + return true; + } + + protected onAddListener(e: Event) { + LOGGER.debug(`add ${e.target.id}: ${e.value}`); + this.addID(e.value); + } + + protected onDeleteListener(e: Event) { + LOGGER.debug(`delete ${e.target.id}: ${e.value}`); + this.removeID(e.value); + } + + protected onChangeListener(e: Event) { + if (e.target instanceof ObjectComponent) { + LOGGER.debug(`invalidate ${e.target.id}: ${e.value}`); + this.cache.delete(e.value); + } + } + + protected addExisting() { + const existing: Set = transduce( + map((c) => c.keys()), + intersectionR(), + this.components + ); + for (let id of existing) { + this.addID(id, false); + } + } + + protected addID(id: number, validate = true) { + if (validate && !this.isValidID(id)) return; + this.ids.add(id); + this.reorderOwned(id, this.n++); + } + + protected removeID(id: number, validate = true) { + if (validate && !this.isValidID(id)) return; + this.ids.delete(id); + this.reorderOwned(id, --this.n); + } + + protected reorderOwned(id: number, n: number) { + const owned = this.owned; + if (!owned.length) return; + const id2 = owned[0].dense[n]; + let swapped = false; + for (let i = owned.length; i-- > 0; ) { + const comp = owned[i]; + // console.log(`moving id: ${id} in ${comp.id}...`); + swapped = comp.swapIndices(comp.sparse[id], n) || swapped; + } + if (swapped) { + this.cache.delete(id); + this.cache.delete(id2); + } + } + + protected *ownedValues() { + const comps = this.components; + const ref = comps[0].dense; + for (let i = this.n; i-- > 0; ) { + yield this.getEntityUnsafe(ref[i]); + } + } + + protected *nonOwnedValues() { + for (let id of this.ids) { + yield this.getEntityUnsafe(id); + } + } + + protected ensureFullyOwning() { + assert( + this.isFullyOwning(), + `group ${this.id} isn't fully owning its components` + ); + } + + protected addRemoveListeners(add: boolean) { + const f = add ? "addListener" : "removeListener"; + this.components.forEach((comp) => { + comp[f](EVENT_ADDED, this.onAddListener, this); + comp[f](EVENT_PRE_DELETE, this.onDeleteListener, this); + comp[f](EVENT_CHANGED, this.onChangeListener, this); + }); + } } diff --git a/packages/ecs/test/component.ts b/packages/ecs/test/component.ts index 2bc80ebc16..79f0a3dafc 100644 --- a/packages/ecs/test/component.ts +++ b/packages/ecs/test/component.ts @@ -1,120 +1,120 @@ import { equiv } from "@thi.ng/equiv"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { ECS, MemMappedComponent } from "../src/index.js" +import { ECS, MemMappedComponent } from "../src/index.js"; let ecs: ECS; group( - "component", - { - "defComponent (minimal)": () => { - const a = ecs.defComponent({ id: "a", type: "f32" }); - assert.ok(a instanceof MemMappedComponent); - assert.ok(a.dense instanceof Uint8Array); - assert.ok(a.sparse instanceof Uint8Array); - assert.ok(a.vals instanceof Float32Array); - assert.strictEqual(a.dense.length, ecs.idgen.capacity); - assert.strictEqual(a.sparse.length, ecs.idgen.capacity); - assert.strictEqual(a.vals.length, ecs.idgen.capacity); - assert.strictEqual(a.size, 1); - assert.strictEqual(a.stride, 1); - }, + "component", + { + "defComponent (minimal)": () => { + const a = ecs.defComponent({ id: "a", type: "f32" }); + assert.ok(a instanceof MemMappedComponent); + assert.ok(a.dense instanceof Uint8Array); + assert.ok(a.sparse instanceof Uint8Array); + assert.ok(a.vals instanceof Float32Array); + assert.strictEqual(a.dense.length, ecs.idgen.capacity); + assert.strictEqual(a.sparse.length, ecs.idgen.capacity); + assert.strictEqual(a.vals.length, ecs.idgen.capacity); + assert.strictEqual(a.size, 1); + assert.strictEqual(a.stride, 1); + }, - "defComponent (w/ type)": () => { - const a = ecs.defComponent({ id: "a", type: "u8" })!; - assert.ok(a.vals instanceof Uint8Array); - assert.strictEqual(a.dense.length, ecs.idgen.capacity); - assert.strictEqual(a.sparse.length, ecs.idgen.capacity); - assert.strictEqual(a.vals.length, ecs.idgen.capacity); - assert.strictEqual(a.size, 1); - assert.strictEqual(a.stride, 1); - }, + "defComponent (w/ type)": () => { + const a = ecs.defComponent({ id: "a", type: "u8" })!; + assert.ok(a.vals instanceof Uint8Array); + assert.strictEqual(a.dense.length, ecs.idgen.capacity); + assert.strictEqual(a.sparse.length, ecs.idgen.capacity); + assert.strictEqual(a.vals.length, ecs.idgen.capacity); + assert.strictEqual(a.size, 1); + assert.strictEqual(a.stride, 1); + }, - "defComponent (w/ size)": () => { - const a = ecs.defComponent({ id: "a", type: "f32", size: 2 })!; - assert.ok(a.vals instanceof Float32Array); - assert.strictEqual(a.vals.length, ecs.idgen.capacity * 2); - assert.strictEqual(a.size, 2); - assert.strictEqual(a.stride, 2); - const b = ecs.defComponent({ - id: "b", - type: "f32", - size: 3, - stride: 4, - })!; - assert.strictEqual(b.vals.length, ecs.idgen.capacity * 4); - assert.strictEqual(b.size, 3); - assert.strictEqual(b.stride, 4); - }, + "defComponent (w/ size)": () => { + const a = ecs.defComponent({ id: "a", type: "f32", size: 2 })!; + assert.ok(a.vals instanceof Float32Array); + assert.strictEqual(a.vals.length, ecs.idgen.capacity * 2); + assert.strictEqual(a.size, 2); + assert.strictEqual(a.stride, 2); + const b = ecs.defComponent({ + id: "b", + type: "f32", + size: 3, + stride: 4, + })!; + assert.strictEqual(b.vals.length, ecs.idgen.capacity * 4); + assert.strictEqual(b.size, 3); + assert.strictEqual(b.stride, 4); + }, - "add (w/ default val)": () => { - const a = ecs.defComponent({ - id: "a", - type: "f32", - size: 2, - default: [1, 2], - })!; - assert.ok(a.add(8)); - assert.ok(a.add(9, [10, 20])); - assert.ok(!a.add(16)); - assert.deepStrictEqual([...a.get(8)!], [1, 2]); - assert.deepStrictEqual([...a.get(9)!], [10, 20]); - assert.ok(!a.add(8, [-1, -2])); - assert.deepStrictEqual([...a.get(8)!], [1, 2]); - }, + "add (w/ default val)": () => { + const a = ecs.defComponent({ + id: "a", + type: "f32", + size: 2, + default: [1, 2], + })!; + assert.ok(a.add(8)); + assert.ok(a.add(9, [10, 20])); + assert.ok(!a.add(16)); + assert.deepStrictEqual([...a.get(8)!], [1, 2]); + assert.deepStrictEqual([...a.get(9)!], [10, 20]); + assert.ok(!a.add(8, [-1, -2])); + assert.deepStrictEqual([...a.get(8)!], [1, 2]); + }, - "values / packedValues": () => { - const a = ecs.defComponent({ - id: "a", - type: "f32", - size: 2, - default: [1, 2], - })!; - assert.ok(a.add(8)); - assert.ok(a.add(9, [10, 20])); - assert.deepStrictEqual([...a.packedValues()], [1, 2, 10, 20]); - assert.ok( - equiv( - [...a.values()], - [ - [10, 20], - [1, 2], - ] - ) - ); - }, + "values / packedValues": () => { + const a = ecs.defComponent({ + id: "a", + type: "f32", + size: 2, + default: [1, 2], + })!; + assert.ok(a.add(8)); + assert.ok(a.add(9, [10, 20])); + assert.deepStrictEqual([...a.packedValues()], [1, 2, 10, 20]); + assert.ok( + equiv( + [...a.values()], + [ + [10, 20], + [1, 2], + ] + ) + ); + }, - resize: () => { - const a = ecs.defComponent({ - id: "a", - type: "f32", - size: 2, - default: [1, 2], - })!; - const b = ecs.defComponent({ id: "b", default: "red" })!; - const g = ecs.defGroup([a, b], [a, b]); - const eid = ecs.defEntity([a, b]); - assert.deepStrictEqual(g.getEntity(eid), { - a: new Float32Array([1, 2]), - b: "red", - id: 0, - }); - assert.strictEqual(a.sparse.length, 16); - assert.strictEqual(b.sparse.length, 16); - ecs.setCapacity(32); - assert.strictEqual(a.sparse.length, 32); - assert.strictEqual(b.sparse.length, 32); - assert.deepStrictEqual(g.getEntity(eid), { - a: new Float32Array([1, 2]), - b: "red", - id: 0, - }); - }, - }, - { - beforeEach: () => { - ecs = new ECS({ capacity: 16 }); - }, - } + resize: () => { + const a = ecs.defComponent({ + id: "a", + type: "f32", + size: 2, + default: [1, 2], + })!; + const b = ecs.defComponent({ id: "b", default: "red" })!; + const g = ecs.defGroup([a, b], [a, b]); + const eid = ecs.defEntity([a, b]); + assert.deepStrictEqual(g.getEntity(eid), { + a: new Float32Array([1, 2]), + b: "red", + id: 0, + }); + assert.strictEqual(a.sparse.length, 16); + assert.strictEqual(b.sparse.length, 16); + ecs.setCapacity(32); + assert.strictEqual(a.sparse.length, 32); + assert.strictEqual(b.sparse.length, 32); + assert.deepStrictEqual(g.getEntity(eid), { + a: new Float32Array([1, 2]), + b: "red", + id: 0, + }); + }, + }, + { + beforeEach: () => { + ecs = new ECS({ capacity: 16 }); + }, + } ); diff --git a/packages/ecs/test/group.ts b/packages/ecs/test/group.ts index 48f243439c..fe7c0fd9bc 100644 --- a/packages/ecs/test/group.ts +++ b/packages/ecs/test/group.ts @@ -1,56 +1,56 @@ import { equiv } from "@thi.ng/equiv"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { ECS, Group } from "../src/index.js" +import { ECS, Group } from "../src/index.js"; const collect = (g: Group) => { - let res: any[] = []; - g.forEach((x) => res.push(x)); - return res; + let res: any[] = []; + g.forEach((x) => res.push(x)); + return res; }; let ecs: ECS; group( - "component", - { - group: () => { - const a = ecs.defComponent({ id: "a", default: () => "a" })!; - const b = ecs.defComponent({ id: "b", type: "f32", size: 2 })!; - const g = ecs.defGroup([a, b]); - ecs.defEntity(["a", "b"]); - ecs.defEntity({ a: "aa", b: [1, 2] }); - ecs.defEntity({ a: "aaa", b: [3, 4] }); - assert.ok(g.has(0)); - assert.ok(g.has(1)); - assert.ok(g.has(2)); - assert.ok(!g.has(3)); - assert.deepStrictEqual([...ecs.componentsForID(2)], [a, b]); - assert.deepStrictEqual([...ecs.groupsForID(2)], [g]); - assert.ok( - equiv(collect(g), [ - { a: "a", b: [0, 0], id: 0 }, - { a: "aa", b: [1, 2], id: 1 }, - { a: "aaa", b: [3, 4], id: 2 }, - ]) - ); + "component", + { + group: () => { + const a = ecs.defComponent({ id: "a", default: () => "a" })!; + const b = ecs.defComponent({ id: "b", type: "f32", size: 2 })!; + const g = ecs.defGroup([a, b]); + ecs.defEntity(["a", "b"]); + ecs.defEntity({ a: "aa", b: [1, 2] }); + ecs.defEntity({ a: "aaa", b: [3, 4] }); + assert.ok(g.has(0)); + assert.ok(g.has(1)); + assert.ok(g.has(2)); + assert.ok(!g.has(3)); + assert.deepStrictEqual([...ecs.componentsForID(2)], [a, b]); + assert.deepStrictEqual([...ecs.groupsForID(2)], [g]); + assert.ok( + equiv(collect(g), [ + { a: "a", b: [0, 0], id: 0 }, + { a: "aa", b: [1, 2], id: 1 }, + { a: "aaa", b: [3, 4], id: 2 }, + ]) + ); - a.delete(0); - assert.ok( - equiv(collect(g), [ - { a: "aa", b: [1, 2], id: 1 }, - { a: "aaa", b: [3, 4], id: 2 }, - ]) - ); - a.delete(2); - assert.ok(equiv(collect(g), [{ a: "aa", b: [1, 2], id: 1 }])); - a.set(1, "hi"); - assert.ok(equiv(collect(g), [{ a: "hi", b: [1, 2], id: 1 }])); - }, - }, - { - beforeEach: () => { - ecs = new ECS({ capacity: 16 }); - }, - } + a.delete(0); + assert.ok( + equiv(collect(g), [ + { a: "aa", b: [1, 2], id: 1 }, + { a: "aaa", b: [3, 4], id: 2 }, + ]) + ); + a.delete(2); + assert.ok(equiv(collect(g), [{ a: "aa", b: [1, 2], id: 1 }])); + a.set(1, "hi"); + assert.ok(equiv(collect(g), [{ a: "hi", b: [1, 2], id: 1 }])); + }, + }, + { + beforeEach: () => { + ecs = new ECS({ capacity: 16 }); + }, + } ); diff --git a/packages/ecs/tsconfig.json b/packages/ecs/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/ecs/tsconfig.json +++ b/packages/ecs/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/egf/api-extractor.json b/packages/egf/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/egf/api-extractor.json +++ b/packages/egf/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/egf/package.json b/packages/egf/package.json index 5b04f9a9b2..968a04e31e 100644 --- a/packages/egf/package.json +++ b/packages/egf/package.json @@ -1,105 +1,105 @@ { - "name": "@thi.ng/egf", - "version": "0.6.13", - "description": "Extensible Graph Format", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/egf#readme", - "funding": { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - }, - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "pub:wip": "yarn npm publish --access public --no-git-tag-version", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/associative": "^6.2.0", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/dot": "^2.1.9", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/logger": "^1.1.8", - "@thi.ng/prefixes": "^2.1.8", - "@thi.ng/strings": "^3.3.6", - "@thi.ng/transducers-binary": "^2.1.12" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/equiv": "^2.1.8", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "conversion", - "datastructure", - "fileformat", - "graph", - "graphviz", - "json", - "linked data", - "lpg", - "rdf", - "semweb", - "tags", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./checks": { - "default": "./checks.js" - }, - "./convert": { - "default": "./convert.js" - }, - "./dot": { - "default": "./dot.js" - }, - "./parser": { - "default": "./parser.js" - }, - "./prefix": { - "default": "./prefix.js" - }, - "./tags": { - "default": "./tags.js" - } - }, - "thi.ng": { - "status": "alpha", - "year": 2020 - } + "name": "@thi.ng/egf", + "version": "0.6.13", + "description": "Extensible Graph Format", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/egf#readme", + "funding": { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + }, + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "pub:wip": "yarn npm publish --access public --no-git-tag-version", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/associative": "^6.2.0", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/dot": "^2.1.9", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/logger": "^1.1.8", + "@thi.ng/prefixes": "^2.1.8", + "@thi.ng/strings": "^3.3.6", + "@thi.ng/transducers-binary": "^2.1.12" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/equiv": "^2.1.8", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "conversion", + "datastructure", + "fileformat", + "graph", + "graphviz", + "json", + "linked data", + "lpg", + "rdf", + "semweb", + "tags", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./checks": { + "default": "./checks.js" + }, + "./convert": { + "default": "./convert.js" + }, + "./dot": { + "default": "./dot.js" + }, + "./parser": { + "default": "./parser.js" + }, + "./prefix": { + "default": "./prefix.js" + }, + "./tags": { + "default": "./tags.js" + } + }, + "thi.ng": { + "status": "alpha", + "year": 2020 + } } diff --git a/packages/egf/src/api.ts b/packages/egf/src/api.ts index 68f94cb569..d42bb6c040 100644 --- a/packages/egf/src/api.ts +++ b/packages/egf/src/api.ts @@ -4,16 +4,16 @@ import { unsupported } from "@thi.ng/errors/unsupported"; import type { ILogger } from "@thi.ng/logger"; export interface Node { - $id: string; - [id: string]: any; + $id: string; + [id: string]: any; } export interface NodeRef extends IDeref, IEquiv { - $ref: string; + $ref: string; } export interface IToEGFConvert { - toEGF(): string; + toEGF(): string; } export type Nodes = Record; @@ -21,58 +21,58 @@ export type Nodes = Record; export type Prefixes = Record; export interface ParseOpts { - /** - * If true, parser will include referenced source files and read tagged - * `#file` values. - * - * @defaultValue true - */ - includes: boolean; - /** - * If true, `#ref`-tagged values will be resolved and used as value instead - * of their referred node ID. Circular dependencies ARE supported. - * - * If false, `#ref` values will be stored as {@link NodeRef} values in the - * result object to allow them to be identified as references. - * - * In both cases, if the `prefixes` option is enabled, the referenced IDs - * will/might first be expanded (iff they match the `prefix:name` pattern). - * - * @defaultValue false - */ - resolve: boolean; - /** - * If true, IDs of the `prefix:name` form, will be expanded using - * pre-declared `@prefix`es. If enabled and a prefix cannot be resolved, the - * parser will throw an error. - * - * Prefixes can be overridden on a per-file basis. If an included file - * defines its own prefixes, they will only be in scope for triples defined - * in that file. - * - * @defaultValue false - */ - prefixes: boolean; - /** - * Only used if `resolve` option is enabled. If true, nodes with only the - * reserved `$id` property (and no other user defined properties) will be - * removed from the graph after parsing is complete. - * - * Such nodes are usually only indirectly created through forward node - * references and they're only removed as top-level items in the result - * graph object. Any references from other nodes will remain intact. - * - * @defaultValue false - */ - prune: boolean; - /** - * If true, tagged `#gpg` values will be decrypted. NodeJS only and requires - * GPG with all keys used to encrypt the values installed on host. - * - * @defaultValue false - */ - decrypt: boolean; - [id: string]: any; + /** + * If true, parser will include referenced source files and read tagged + * `#file` values. + * + * @defaultValue true + */ + includes: boolean; + /** + * If true, `#ref`-tagged values will be resolved and used as value instead + * of their referred node ID. Circular dependencies ARE supported. + * + * If false, `#ref` values will be stored as {@link NodeRef} values in the + * result object to allow them to be identified as references. + * + * In both cases, if the `prefixes` option is enabled, the referenced IDs + * will/might first be expanded (iff they match the `prefix:name` pattern). + * + * @defaultValue false + */ + resolve: boolean; + /** + * If true, IDs of the `prefix:name` form, will be expanded using + * pre-declared `@prefix`es. If enabled and a prefix cannot be resolved, the + * parser will throw an error. + * + * Prefixes can be overridden on a per-file basis. If an included file + * defines its own prefixes, they will only be in scope for triples defined + * in that file. + * + * @defaultValue false + */ + prefixes: boolean; + /** + * Only used if `resolve` option is enabled. If true, nodes with only the + * reserved `$id` property (and no other user defined properties) will be + * removed from the graph after parsing is complete. + * + * Such nodes are usually only indirectly created through forward node + * references and they're only removed as top-level items in the result + * graph object. Any references from other nodes will remain intact. + * + * @defaultValue false + */ + prune: boolean; + /** + * If true, tagged `#gpg` values will be decrypted. NodeJS only and requires + * GPG with all keys used to encrypt the values installed on host. + * + * @defaultValue false + */ + decrypt: boolean; + [id: string]: any; } /** @@ -81,16 +81,16 @@ export interface ParseOpts { export type TagParser = Fn3; export interface ParseContext { - cwd: string; - file: string; - files: string[]; - prefixes: Prefixes; - nodes: Nodes; - index: IObjectOf; - tags: IObjectOf; - defaultTag?: TagParser; - opts: Partial; - logger: ILogger; + cwd: string; + file: string; + files: string[]; + prefixes: Prefixes; + nodes: Nodes; + index: IObjectOf; + tags: IObjectOf; + defaultTag?: TagParser; + opts: Partial; + logger: ILogger; } export const IS_NODE = isNode(); diff --git a/packages/egf/src/checks.ts b/packages/egf/src/checks.ts index 6504e5f483..287d676e42 100644 --- a/packages/egf/src/checks.ts +++ b/packages/egf/src/checks.ts @@ -7,4 +7,4 @@ export const isNode = (x: any): x is Node => isPlainObject(x) && "$id" in x; export const isRef = (x: any): x is NodeRef => isPlainObject(x) && "$ref" in x; export const isToEGF = (x: any): x is IToEGFConvert => - implementsFunction(x, "toEGF"); + implementsFunction(x, "toEGF"); diff --git a/packages/egf/src/convert.ts b/packages/egf/src/convert.ts index 18d0ce40b6..7fe4417a71 100644 --- a/packages/egf/src/convert.ts +++ b/packages/egf/src/convert.ts @@ -11,70 +11,70 @@ import { isNode, isRef, isToEGF } from "./checks.js"; import { defPrefixer } from "./prefix.js"; export const toEGF = ( - nodes: Iterable, - prefixes: Prefixes = {}, - propFn?: Fn2 + nodes: Iterable, + prefixes: Prefixes = {}, + propFn?: Fn2 ) => { - const prefixID = defPrefixer(prefixes); - const res: string[] = []; - for (let id in prefixes) { - res.push(`@prefix ${id}: ${prefixes[id]}`); - } - res.push(""); - for (let node of nodes) { - res.push(toEGFNode(node, prefixID, propFn), ""); - } - return res.join("\n"); + const prefixID = defPrefixer(prefixes); + const res: string[] = []; + for (let id in prefixes) { + res.push(`@prefix ${id}: ${prefixes[id]}`); + } + res.push(""); + for (let node of nodes) { + res.push(toEGFNode(node, prefixID, propFn), ""); + } + return res.join("\n"); }; export const toEGFNode = ( - node: Node, - prefix: Fn, - propFn: Fn2 = toEGFProp + node: Node, + prefix: Fn, + propFn: Fn2 = toEGFProp ) => { - if (isToEGF(node)) return node.toEGF(); - const res: string[] = [prefix(node.$id) || node.$id]; + if (isToEGF(node)) return node.toEGF(); + const res: string[] = [prefix(node.$id) || node.$id]; - const $prop = (p: string, pid: string, v: any) => - res.push( - `\t${pid} ` + - (isNode(v) - ? `-> ${prefix(v.$id) || v.$id}` - : isRef(v) - ? `-> ${prefix(v.$ref) || v.$ref}` - : isToEGF(v) - ? v.toEGF() - : propFn(p, v)) - ); + const $prop = (p: string, pid: string, v: any) => + res.push( + `\t${pid} ` + + (isNode(v) + ? `-> ${prefix(v.$id) || v.$id}` + : isRef(v) + ? `-> ${prefix(v.$ref) || v.$ref}` + : isToEGF(v) + ? v.toEGF() + : propFn(p, v)) + ); - for (let p in node) { - if (p === "$id") continue; - const pid = prefix(p) || p; - const val = node[p]; - if (isArray(val)) { - for (let v of val) { - $prop(p, pid, v); - } - } else { - $prop(p, pid, val); - } - } - return res.join("\n"); + for (let p in node) { + if (p === "$id") continue; + const pid = prefix(p) || p; + const val = node[p]; + if (isArray(val)) { + for (let v of val) { + $prop(p, pid, v); + } + } else { + $prop(p, pid, val); + } + } + return res.join("\n"); }; export const toEGFProp = (_: string, val: any) => - isString(val) - ? val.indexOf("\n") >= 0 - ? `>>>${val}<<<` - : val - : isNumber(val) - ? `#num ${val}` - : isDate(val) - ? `#date ${val.toISOString()}` - : isTypedArray(val) - ? `#base64 ${base64Encode( - new Uint8Array(val.buffer, val.byteOffset, val.byteLength) - )}` - : isArray(val) || isPlainObject(val) - ? `#json ${JSON.stringify(val)}` - : String(val); + isString(val) + ? val.indexOf("\n") >= 0 + ? `>>>${val}<<<` + : val + : isNumber(val) + ? `#num ${val}` + : isDate(val) + ? `#date ${val.toISOString()}` + : isTypedArray(val) + ? `#base64 ${base64Encode( + new Uint8Array(val.buffer, val.byteOffset, val.byteLength) + )}` + : isArray(val) || isPlainObject(val) + ? `#json ${JSON.stringify(val)}` + : String(val); diff --git a/packages/egf/src/dot.ts b/packages/egf/src/dot.ts index ee5c9b8808..e46cf3f2d3 100644 --- a/packages/egf/src/dot.ts +++ b/packages/egf/src/dot.ts @@ -6,45 +6,45 @@ import type { Node, Nodes } from "./api.js"; import { isRef } from "./checks.js"; export interface GraphvizOpts { - /** - * Predicate function called for each property of each node. If the function - * returns false, no edge will be created for that property. - */ - filter: Fn2; - attribs: Partial; + /** + * Predicate function called for each property of each node. If the function + * returns false, no edge will be created for that property. + */ + filter: Fn2; + attribs: Partial; } export const toDot = (graph: Nodes, opts: Partial) => { - const nodes: IObjectOf> = {}; - const edges: Edge[] = []; - const filter = opts.filter || (() => true); + const nodes: IObjectOf> = {}; + const edges: Edge[] = []; + const filter = opts.filter || (() => true); - const addEdge = (src: string, prop: string, val: any) => { - if (isRef(val)) { - edges.push({ src, dest: val.$ref, label: prop }); - } else if (val.$id) { - edges.push({ src, dest: val.$id, label: prop }); - } else { - // FIXME hash string - const id = `lit-${slugify(String(val))}`; - nodes[id] = { label: String(val).replace(/\n/g, "\\n") }; - edges.push({ src, dest: id, label: prop }); - } - }; + const addEdge = (src: string, prop: string, val: any) => { + if (isRef(val)) { + edges.push({ src, dest: val.$ref, label: prop }); + } else if (val.$id) { + edges.push({ src, dest: val.$id, label: prop }); + } else { + // FIXME hash string + const id = `lit-${slugify(String(val))}`; + nodes[id] = { label: String(val).replace(/\n/g, "\\n") }; + edges.push({ src, dest: id, label: prop }); + } + }; - Object.entries(graph).forEach(([id, node]) => { - nodes[id] = { label: node.name || node.$id }; - Object.entries(node).forEach(([prop, val]) => { - if (!filter(prop, node)) return; - isArray(val) - ? val.forEach((v) => addEdge(id, prop, v)) - : addEdge(id, prop, val); - }); - }); + Object.entries(graph).forEach(([id, node]) => { + nodes[id] = { label: node.name || node.$id }; + Object.entries(node).forEach(([prop, val]) => { + if (!filter(prop, node)) return; + isArray(val) + ? val.forEach((v) => addEdge(id, prop, v)) + : addEdge(id, prop, val); + }); + }); - return serializeGraph({ - attribs: opts.attribs, - nodes, - edges, - }); + return serializeGraph({ + attribs: opts.attribs, + nodes, + edges, + }); }; diff --git a/packages/egf/src/parser.ts b/packages/egf/src/parser.ts index 42d09183ac..bd05a48fba 100644 --- a/packages/egf/src/parser.ts +++ b/packages/egf/src/parser.ts @@ -14,222 +14,222 @@ const INCLUDE = "@include "; const PREFIX = "@prefix "; export const parse = (src: string, ctx: ParseContext) => { - const lines = src.split(/\r?\n/); - const nodes = ctx.nodes; - const usePrefixes = ctx.opts.prefixes; - for (let i = 0, n = lines.length; i < n; ) { - let subj = lines[i++]; - if (!subj.length || subj[0] === ";") continue; - if (subj[0] === "@") { - if (subj.startsWith(INCLUDE)) { - parseInclude(subj, ctx); - continue; - } else if (subj.startsWith(PREFIX)) { - usePrefixes && parsePrefix(subj, ctx); - continue; - } - } - subj = unescape(subj); - usePrefixes && (subj = qualifiedID(ctx.prefixes, subj)); - const curr: Node = nodes[subj] || (nodes[subj] = { $id: subj }); - while (i < n) { - let line = lines[i]; - if (line[0] === "\t" || line.startsWith(" ")) { - i = parseProp(curr, ctx, line, lines, i); - } else if (!line.length) { - i++; - break; - } else if (line[0] === ";") { - i++; - } else illegalState(`expected property or comment @ line: ${i}`); - } - } - ctx.opts.resolve && ctx.opts.prune && pruneNodes(ctx); - return ctx; + const lines = src.split(/\r?\n/); + const nodes = ctx.nodes; + const usePrefixes = ctx.opts.prefixes; + for (let i = 0, n = lines.length; i < n; ) { + let subj = lines[i++]; + if (!subj.length || subj[0] === ";") continue; + if (subj[0] === "@") { + if (subj.startsWith(INCLUDE)) { + parseInclude(subj, ctx); + continue; + } else if (subj.startsWith(PREFIX)) { + usePrefixes && parsePrefix(subj, ctx); + continue; + } + } + subj = unescape(subj); + usePrefixes && (subj = qualifiedID(ctx.prefixes, subj)); + const curr: Node = nodes[subj] || (nodes[subj] = { $id: subj }); + while (i < n) { + let line = lines[i]; + if (line[0] === "\t" || line.startsWith(" ")) { + i = parseProp(curr, ctx, line, lines, i); + } else if (!line.length) { + i++; + break; + } else if (line[0] === ";") { + i++; + } else illegalState(`expected property or comment @ line: ${i}`); + } + } + ctx.opts.resolve && ctx.opts.prune && pruneNodes(ctx); + return ctx; }; const parseInclude = (line: string, ctx: ParseContext) => { - const path = unescape(line.substring(INCLUDE.length)); - if (IS_NODE && ctx.opts.includes) { - $parseFile(path, { - ...ctx, - cwd: dirname(ctx.file), - prefixes: { ...ctx.prefixes }, - opts: { ...ctx.opts, prune: false }, - }); - } else { - ctx.logger.debug("skipping include:", path); - } + const path = unescape(line.substring(INCLUDE.length)); + if (IS_NODE && ctx.opts.includes) { + $parseFile(path, { + ...ctx, + cwd: dirname(ctx.file), + prefixes: { ...ctx.prefixes }, + opts: { ...ctx.opts, prune: false }, + }); + } else { + ctx.logger.debug("skipping include:", path); + } }; const RE_PREFIX = /^([a-z0-9-_$]*)$/i; const parsePrefix = (line: string, ctx: ParseContext) => { - const idx = line.indexOf(": ", PREFIX.length); - if (idx > 0) { - const id = unescape(line.substring(PREFIX.length, idx)); - if (RE_PREFIX.test(id)) { - const val = unescape(line.substring(idx + 2).trim()); - if (val.length) { - ctx.logger.debug(`declare prefix: ${id} = ${val}`); - ctx.prefixes[id] = val; - return; - } - } - } - illegalState(`invalid prefix decl: ${line}`); + const idx = line.indexOf(": ", PREFIX.length); + if (idx > 0) { + const id = unescape(line.substring(PREFIX.length, idx)); + if (RE_PREFIX.test(id)) { + const val = unescape(line.substring(idx + 2).trim()); + if (val.length) { + ctx.logger.debug(`declare prefix: ${id} = ${val}`); + ctx.prefixes[id] = val; + return; + } + } + } + illegalState(`invalid prefix decl: ${line}`); }; const parseTag: TagParser = (tag, body, ctx) => { - const parser = ctx.tags[tag] || ctx.defaultTag; - return parser - ? parser(tag, body, ctx) - : unsupported(`missing parser for tag: ${tag}`); + const parser = ctx.tags[tag] || ctx.defaultTag; + return parser + ? parser(tag, body, ctx) + : unsupported(`missing parser for tag: ${tag}`); }; const parseProp = ( - node: Node, - ctx: ParseContext, - line: string, - lines: string[], - i: number + node: Node, + ctx: ParseContext, + line: string, + lines: string[], + i: number ) => { - const idx0 = line[0] === "\t" ? 1 : 4; - if (line[idx0] === ";") return ++i; - let idx = line.indexOf(" ", idx0); - let key = unescape(line.substring(idx0, idx)); - ctx.opts.prefixes && (key = qualifiedID(ctx.prefixes, key)); - let tag: string | undefined; - let body: string; - idx++; - if (line[idx] === "-" && line[idx + 1] === ">") { - addProp( - ctx.index, - node, - key, - parseRef(unescape(line.substring(idx + 2).trim()), ctx) - ); - return ++i; - } else if (line[idx] === "#") { - const tstart = idx + 1; - idx = line.indexOf(" ", tstart); - tag = unescape(line.substring(tstart, idx)); - idx++; - } - if (line[idx] === ">" && line[idx + 1] === ">" && line[idx + 2] === ">") { - body = line.substring(idx + 3); - idx = body.indexOf("<<<"); - if (idx < 0) { - const n = lines.length; - let closed = false; - while (++i < n) { - line = lines[i]; - idx = line.indexOf("<<<"); - if (idx >= 0) { - body += "\n" + line.substring(0, idx); - closed = true; - i++; - break; - } else { - body += "\n" + line; - } - } - !closed && illegalState("unterminated value, EOF reached"); - } else { - body = body.substring(0, idx); - i++; - } - } else { - body = line.substring(idx); - i++; - } - body = body.trim(); - addProp( - ctx.index, - node, - key, - tag ? parseTag(tag, body, ctx) : unescape(body) - ); - return i; + const idx0 = line[0] === "\t" ? 1 : 4; + if (line[idx0] === ";") return ++i; + let idx = line.indexOf(" ", idx0); + let key = unescape(line.substring(idx0, idx)); + ctx.opts.prefixes && (key = qualifiedID(ctx.prefixes, key)); + let tag: string | undefined; + let body: string; + idx++; + if (line[idx] === "-" && line[idx + 1] === ">") { + addProp( + ctx.index, + node, + key, + parseRef(unescape(line.substring(idx + 2).trim()), ctx) + ); + return ++i; + } else if (line[idx] === "#") { + const tstart = idx + 1; + idx = line.indexOf(" ", tstart); + tag = unescape(line.substring(tstart, idx)); + idx++; + } + if (line[idx] === ">" && line[idx + 1] === ">" && line[idx + 2] === ">") { + body = line.substring(idx + 3); + idx = body.indexOf("<<<"); + if (idx < 0) { + const n = lines.length; + let closed = false; + while (++i < n) { + line = lines[i]; + idx = line.indexOf("<<<"); + if (idx >= 0) { + body += "\n" + line.substring(0, idx); + closed = true; + i++; + break; + } else { + body += "\n" + line; + } + } + !closed && illegalState("unterminated value, EOF reached"); + } else { + body = body.substring(0, idx); + i++; + } + } else { + body = line.substring(idx); + i++; + } + body = body.trim(); + addProp( + ctx.index, + node, + key, + tag ? parseTag(tag, body, ctx) : unescape(body) + ); + return i; }; const addProp = ( - index: IObjectOf, - acc: Node, - key: string, - val: any + index: IObjectOf, + acc: Node, + key: string, + val: any ) => { - const exist = acc[key]; - const id = acc.$id + "~" + key; - if (exist !== undefined) { - ++index[id] > 2 ? exist.push(val) : (acc[key] = [exist, val]); - } else { - acc[key] = val; - index[id] = 1; - } + const exist = acc[key]; + const id = acc.$id + "~" + key; + if (exist !== undefined) { + ++index[id] > 2 ? exist.push(val) : (acc[key] = [exist, val]); + } else { + acc[key] = val; + index[id] = 1; + } }; const parseRef = (id: string, ctx: ParseContext) => { - ctx.opts.prefixes && (id = qualifiedID(ctx.prefixes, id)); - return ctx.opts.resolve - ? ctx.nodes[id] || (ctx.nodes[id] = { $id: id }) - : { - $ref: id, - deref() { - return ctx.nodes[id]; - }, - equiv(o: any) { - return o != null && o.$ref === this.$ref; - }, - }; + ctx.opts.prefixes && (id = qualifiedID(ctx.prefixes, id)); + return ctx.opts.resolve + ? ctx.nodes[id] || (ctx.nodes[id] = { $id: id }) + : { + $ref: id, + deref() { + return ctx.nodes[id]; + }, + equiv(o: any) { + return o != null && o.$ref === this.$ref; + }, + }; }; const pruneNodes = ({ nodes, logger }: ParseContext) => { - for (let id in nodes) { - const keys = Object.keys(nodes[id]); - if (keys.length === 1 && keys[0] === "$id") { - logger.debug("pruning node:", id); - delete nodes[id]; - } - } + for (let id in nodes) { + const keys = Object.keys(nodes[id]); + if (keys.length === 1 && keys[0] === "$id") { + logger.debug("pruning node:", id); + delete nodes[id]; + } + } }; const initContext = (ctx: Partial = {}) => { - const opts = { - decrypt: false, - includes: true, - prefixes: false, - prune: false, - resolve: false, - ...ctx.opts, - }; - return { - cwd: ctx.cwd || ".", - file: ctx.file || "", - files: ctx.files || [], - nodes: ctx.nodes || {}, - index: ctx.index || {}, - tags: { ...BUILTINS, ...ctx.tags }, - defaultTag: ctx.defaultTag, - prefixes: ctx.prefixes - ? { ...ctx.prefixes } - : { ...$prefixes, void: $prefixes.VOID }, - logger: ctx.logger || NULL_LOGGER, - opts, - }; + const opts = { + decrypt: false, + includes: true, + prefixes: false, + prune: false, + resolve: false, + ...ctx.opts, + }; + return { + cwd: ctx.cwd || ".", + file: ctx.file || "", + files: ctx.files || [], + nodes: ctx.nodes || {}, + index: ctx.index || {}, + tags: { ...BUILTINS, ...ctx.tags }, + defaultTag: ctx.defaultTag, + prefixes: ctx.prefixes + ? { ...ctx.prefixes } + : { ...$prefixes, void: $prefixes.VOID }, + logger: ctx.logger || NULL_LOGGER, + opts, + }; }; /** @interal */ export const $parseFile = (path: string, ctx?: Partial) => { - const $ctx = initContext(ctx); - $ctx.file = path = resolvePath($ctx.cwd, path); - if ($ctx.files.includes(path)) { - $ctx.logger.warn("file already processed, skipping:", path); - return $ctx; - } - $ctx.files.push(path); - $ctx.logger.debug("loading file:", path); - return parse(readFileSync(path).toString(), $ctx); + const $ctx = initContext(ctx); + $ctx.file = path = resolvePath($ctx.cwd, path); + if ($ctx.files.includes(path)) { + $ctx.logger.warn("file already processed, skipping:", path); + return $ctx; + } + $ctx.files.push(path); + $ctx.logger.debug("loading file:", path); + return parse(readFileSync(path).toString(), $ctx); }; /** @@ -240,8 +240,8 @@ export const $parseFile = (path: string, ctx?: Partial) => { * @param ctx - */ export const parseFile = (path: string, ctx?: Partial) => { - const res = $parseFile(path, ctx); - return { nodes: res.nodes, prefixes: res.prefixes }; + const res = $parseFile(path, ctx); + return { nodes: res.nodes, prefixes: res.prefixes }; }; /** @@ -252,6 +252,6 @@ export const parseFile = (path: string, ctx?: Partial) => { * @param ctx - */ export const parseString = (src: string, ctx?: Partial) => { - const res = parse(src, initContext(ctx)); - return { nodes: res.nodes, prefixes: res.prefixes }; + const res = parse(src, initContext(ctx)); + return { nodes: res.nodes, prefixes: res.prefixes }; }; diff --git a/packages/egf/src/prefix.ts b/packages/egf/src/prefix.ts index 1a957a5653..1d96741006 100644 --- a/packages/egf/src/prefix.ts +++ b/packages/egf/src/prefix.ts @@ -5,33 +5,33 @@ import type { Prefixes } from "./api.js"; const RE_QFN = /^([a-z0-9-_$]*):([a-z0-9-_$.+]+)$/i; export const qualifiedID = (prefixes: Prefixes, id: string) => { - if (id[0] === "<" && id[id.length - 1] === ">") { - return id.substring(1, id.length - 1); - } - if (id.indexOf(":") !== -1) { - const match = RE_QFN.exec(id); - if (match) { - const prefix = prefixes[match[1]]; - return prefix - ? prefix + match[2] - : illegalArgs(`unknown prefix: ${id}`); - } - } - return id; + if (id[0] === "<" && id[id.length - 1] === ">") { + return id.substring(1, id.length - 1); + } + if (id.indexOf(":") !== -1) { + const match = RE_QFN.exec(id); + if (match) { + const prefix = prefixes[match[1]]; + return prefix + ? prefix + match[2] + : illegalArgs(`unknown prefix: ${id}`); + } + } + return id; }; export const defPrefixer = (prefixes: Prefixes) => { - const uriToID = new TrieMap(); - Object.entries(prefixes).forEach(([id, url]) => uriToID.set(url, id)); - return (uri: string) => { - const known = uriToID.knownPrefix(uri); - return known - ? uriToID.get(known)! + ":" + uri.substring(known.length) - : undefined; - }; + const uriToID = new TrieMap(); + Object.entries(prefixes).forEach(([id, url]) => uriToID.set(url, id)); + return (uri: string) => { + const known = uriToID.knownPrefix(uri); + return known + ? uriToID.get(known)! + ":" + uri.substring(known.length) + : undefined; + }; }; export const defVocab = - (uri: string) => - (name = "") => - uri + name; + (uri: string) => + (name = "") => + uri + name; diff --git a/packages/egf/src/tags.ts b/packages/egf/src/tags.ts index b2710fdc39..bf762d9e8d 100644 --- a/packages/egf/src/tags.ts +++ b/packages/egf/src/tags.ts @@ -8,31 +8,31 @@ import { resolve as resolvePath } from "path"; import { IS_NODE, NODE_ONLY, TagParser } from "./api.js"; export const BUILTINS: IObjectOf = { - base64: IS_NODE - ? (_, body) => Buffer.from(body, "base64") - : (_, body) => base64Decode(body), - date: (_, body) => new Date(body), - file: (_, path, ctx) => { - if (IS_NODE && ctx.opts.includes) { - path = resolvePath(ctx.opts.root!, unescape(path)); - ctx.logger.debug("loading value from:", path); - return readFileSync(path).toString(); - } else { - ctx.logger.debug("skipping file value:", path); - return path; - } - }, - gpg: IS_NODE - ? (_, body, ctx) => - (ctx.opts.decrypt - ? execFileSync("gpg", ["--decrypt"], { - input: body, - }).toString() - : body - ).trim() - : NODE_ONLY, - hex: (_, body) => maybeParseInt(body, 0, 16), - json: (_, body) => JSON.parse(unescape(body)), - list: (_, body) => body.split(/[\n\r\t ]+/g).map(unescape), - num: (_, body) => maybeParseFloat(body, 0), + base64: IS_NODE + ? (_, body) => Buffer.from(body, "base64") + : (_, body) => base64Decode(body), + date: (_, body) => new Date(body), + file: (_, path, ctx) => { + if (IS_NODE && ctx.opts.includes) { + path = resolvePath(ctx.opts.root!, unescape(path)); + ctx.logger.debug("loading value from:", path); + return readFileSync(path).toString(); + } else { + ctx.logger.debug("skipping file value:", path); + return path; + } + }, + gpg: IS_NODE + ? (_, body, ctx) => + (ctx.opts.decrypt + ? execFileSync("gpg", ["--decrypt"], { + input: body, + }).toString() + : body + ).trim() + : NODE_ONLY, + hex: (_, body) => maybeParseInt(body, 0, 16), + json: (_, body) => JSON.parse(unescape(body)), + list: (_, body) => body.split(/[\n\r\t ]+/g).map(unescape), + num: (_, body) => maybeParseFloat(body, 0), }; diff --git a/packages/egf/test/escape.ts b/packages/egf/test/escape.ts index 8a8da72f6a..336e3ccfd7 100644 --- a/packages/egf/test/escape.ts +++ b/packages/egf/test/escape.ts @@ -1,92 +1,92 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { ParseContext, parseString } from "../src/index.js" +import { ParseContext, parseString } from "../src/index.js"; const $ctx: Partial = { opts: { prefixes: true } }; group("escape", { - "node id": () => { - assert.deepStrictEqual(parseString(`\\u0046oo`).nodes, { - Foo: { $id: "Foo" }, - }); - }, + "node id": () => { + assert.deepStrictEqual(parseString(`\\u0046oo`).nodes, { + Foo: { $id: "Foo" }, + }); + }, - "node id (qfn)": () => { - assert.deepStrictEqual( - parseString(`@prefix foo: FOO\n\\u0066oo:\\u0062ar`, $ctx).nodes, - { - FOObar: { $id: "FOObar" }, - } - ); - }, + "node id (qfn)": () => { + assert.deepStrictEqual( + parseString(`@prefix foo: FOO\n\\u0066oo:\\u0062ar`, $ctx).nodes, + { + FOObar: { $id: "FOObar" }, + } + ); + }, - "prefix decl": () => { - assert.strictEqual( - parseString(`@prefix \\u0066oo: \\u0046OO`, $ctx).prefixes.foo, - "FOO" - ); - }, + "prefix decl": () => { + assert.strictEqual( + parseString(`@prefix \\u0066oo: \\u0046OO`, $ctx).prefixes.foo, + "FOO" + ); + }, - "prop name (qfn)": () => { - assert.deepStrictEqual( - parseString(`@prefix a: foo\nx\n\t\\u0061:\\u0062ar baz`, $ctx) - .nodes, - { - x: { $id: "x", foobar: "baz" }, - } - ); - }, + "prop name (qfn)": () => { + assert.deepStrictEqual( + parseString(`@prefix a: foo\nx\n\t\\u0061:\\u0062ar baz`, $ctx) + .nodes, + { + x: { $id: "x", foobar: "baz" }, + } + ); + }, - "tag id": () => { - assert.deepStrictEqual(parseString(`a\n\tfoo #\\u006eum 42`).nodes, { - a: { $id: "a", foo: 42 }, - }); - }, + "tag id": () => { + assert.deepStrictEqual(parseString(`a\n\tfoo #\\u006eum 42`).nodes, { + a: { $id: "a", foo: 42 }, + }); + }, - "string value": () => { - assert.deepStrictEqual(parseString(`a\n\tfoo \\u0062ar`).nodes, { - a: { $id: "a", foo: "bar" }, - }); - }, + "string value": () => { + assert.deepStrictEqual(parseString(`a\n\tfoo \\u0062ar`).nodes, { + a: { $id: "a", foo: "bar" }, + }); + }, - "string multi-line value": () => { - assert.deepStrictEqual( - parseString(`a\n\tfoo >>>abc\\ndef\nghi<<<`).nodes, - { - a: { $id: "a", foo: "abc\ndef\nghi" }, - } - ); - }, + "string multi-line value": () => { + assert.deepStrictEqual( + parseString(`a\n\tfoo >>>abc\\ndef\nghi<<<`).nodes, + { + a: { $id: "a", foo: "abc\ndef\nghi" }, + } + ); + }, - "#list multi-line value": () => { - assert.deepStrictEqual( - parseString(`a\n\tfoo #list >>>\nabc\\ndef\nghi<<<`).nodes, - { - a: { $id: "a", foo: ["abc\ndef", "ghi"] }, - } - ); - }, + "#list multi-line value": () => { + assert.deepStrictEqual( + parseString(`a\n\tfoo #list >>>\nabc\\ndef\nghi<<<`).nodes, + { + a: { $id: "a", foo: ["abc\ndef", "ghi"] }, + } + ); + }, - ref: () => { - assert.deepStrictEqual( - parseString(`a\n\tfoo -> \\u0062`, { opts: { resolve: true } }) - .nodes, - { - a: { $id: "a", foo: { $id: "b" } }, - b: { $id: "b" }, - } - ); - }, + ref: () => { + assert.deepStrictEqual( + parseString(`a\n\tfoo -> \\u0062`, { opts: { resolve: true } }) + .nodes, + { + a: { $id: "a", foo: { $id: "b" } }, + b: { $id: "b" }, + } + ); + }, - "ref <>": () => { - assert.deepStrictEqual( - parseString(`a\n\tfoo -> <\\u0062:b>`, { - opts: { prefixes: true, resolve: true }, - }).nodes, - { - a: { $id: "a", foo: { $id: "b:b" } }, - "b:b": { $id: "b:b" }, - } - ); - }, + "ref <>": () => { + assert.deepStrictEqual( + parseString(`a\n\tfoo -> <\\u0062:b>`, { + opts: { prefixes: true, resolve: true }, + }).nodes, + { + a: { $id: "a", foo: { $id: "b:b" } }, + "b:b": { $id: "b:b" }, + } + ); + }, }); diff --git a/packages/egf/test/prefix.ts b/packages/egf/test/prefix.ts index 1c0eec1d83..5f890ade82 100644 --- a/packages/egf/test/prefix.ts +++ b/packages/egf/test/prefix.ts @@ -1,35 +1,35 @@ import * as assert from "assert"; import { group } from "@thi.ng/testament"; -import { ParseContext, parseString, qualifiedID } from "../src/index.js" +import { ParseContext, parseString, qualifiedID } from "../src/index.js"; const $ctx: Partial = { opts: { prefixes: true } }; group("@prefix", { - "@prefix decl": () => { - assert.throws(() => parseString(`@prefix :`, $ctx), "1"); - assert.throws(() => parseString(`@prefix : `, $ctx), "2"); - assert.throws(() => parseString(`@prefix a&b: abc`, $ctx), "3"); - assert.strictEqual( - parseString(`@prefix : abc`, $ctx).prefixes[""], - "abc" - ); - // prettier-ignore - assert.strictEqual(parseString(`@prefix _: abc`, $ctx).prefixes["_"], "abc"); - // prettier-ignore - assert.strictEqual(parseString(`@prefix $1a-b_C: abc`, $ctx).prefixes["$1a-b_C"], "abc"); - }, + "@prefix decl": () => { + assert.throws(() => parseString(`@prefix :`, $ctx), "1"); + assert.throws(() => parseString(`@prefix : `, $ctx), "2"); + assert.throws(() => parseString(`@prefix a&b: abc`, $ctx), "3"); + assert.strictEqual( + parseString(`@prefix : abc`, $ctx).prefixes[""], + "abc" + ); + // prettier-ignore + assert.strictEqual(parseString(`@prefix _: abc`, $ctx).prefixes["_"], "abc"); + // prettier-ignore + assert.strictEqual(parseString(`@prefix $1a-b_C: abc`, $ctx).prefixes["$1a-b_C"], "abc"); + }, - qfn: () => { - const qfn = (id: string) => - qualifiedID({ "": "self/", thi: "thi.ng/" }, id); - assert.strictEqual(qfn(":a"), "self/a"); - assert.strictEqual(qfn("thi:a"), "thi.ng/a"); - assert.throws(() => qfn("foo:a")); - }, + qfn: () => { + const qfn = (id: string) => + qualifiedID({ "": "self/", thi: "thi.ng/" }, id); + assert.strictEqual(qfn(":a"), "self/a"); + assert.strictEqual(qfn("thi:a"), "thi.ng/a"); + assert.throws(() => qfn("foo:a")); + }, - "resolve w/ prefix": () => { - const { nodes, prefixes } = parseString( - ` + "resolve w/ prefix": () => { + const { nodes, prefixes } = parseString( + ` @prefix : self/ @prefix thi: thi.ng/ :a @@ -38,11 +38,11 @@ group("@prefix", { thi:b parentof -> :a `, - { opts: { prefixes: true, resolve: true } } - ); - assert.strictEqual(prefixes[""], "self/"); - assert.strictEqual(prefixes["thi"], "thi.ng/"); - assert.strictEqual(nodes["self/a"].partof.$id, "thi.ng/b"); - assert.strictEqual(nodes["thi.ng/b"].parentof.$id, "self/a"); - }, + { opts: { prefixes: true, resolve: true } } + ); + assert.strictEqual(prefixes[""], "self/"); + assert.strictEqual(prefixes["thi"], "thi.ng/"); + assert.strictEqual(nodes["self/a"].partof.$id, "thi.ng/b"); + assert.strictEqual(nodes["thi.ng/b"].parentof.$id, "self/a"); + }, }); diff --git a/packages/egf/test/ref.ts b/packages/egf/test/ref.ts index ec099dd923..a0687094b2 100644 --- a/packages/egf/test/ref.ts +++ b/packages/egf/test/ref.ts @@ -1,14 +1,14 @@ import { equiv } from "@thi.ng/equiv"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { parseString } from "../src/index.js" +import { parseString } from "../src/index.js"; const $ref = (id: string) => ({ $ref: id }); group("refs", { - "resolve w/ prefix": () => { - const db = parseString( - ` + "resolve w/ prefix": () => { + const db = parseString( + ` @prefix thi: thi.ng/ thi:a partof -> thi:b @@ -18,33 +18,33 @@ thi:a thi:c diff -> thi:a `, - { opts: { prefixes: true, resolve: true } } - ).nodes; - assert.deepStrictEqual(db["thi.ng/a"].partof, { $id: "thi.ng/b" }); - assert.strictEqual(db["thi.ng/a"].knows.$id, "alt.thi.ng/c"); - assert.strictEqual(db["alt.thi.ng/c"].diff.$id, "alt.thi.ng/a"); - }, + { opts: { prefixes: true, resolve: true } } + ).nodes; + assert.deepStrictEqual(db["thi.ng/a"].partof, { $id: "thi.ng/b" }); + assert.strictEqual(db["thi.ng/a"].knows.$id, "alt.thi.ng/c"); + assert.strictEqual(db["alt.thi.ng/c"].diff.$id, "alt.thi.ng/a"); + }, - "resolve circular": () => { - const db = parseString( - ` + "resolve circular": () => { + const db = parseString( + ` a knows -> b b knows -> a `, - { opts: { resolve: true } } - ).nodes; - assert.strictEqual(db.a.knows.$id, "b"); - assert.strictEqual(db.b.knows.$id, "a"); - }, + { opts: { resolve: true } } + ).nodes; + assert.strictEqual(db.a.knows.$id, "b"); + assert.strictEqual(db.b.knows.$id, "a"); + }, - "ref array item (unresolved)": () => { - assert.ok( - equiv( - parseString( - ` + "ref array item (unresolved)": () => { + assert.ok( + equiv( + parseString( + ` a knows -> b knows -> c @@ -59,22 +59,22 @@ c d name dd ` - ).nodes, - { - a: { $id: "a", knows: [$ref("b"), $ref("c"), $ref("d")] }, - b: { $id: "b", name: "bb" }, - c: { $id: "c", name: "cc" }, - d: { $id: "d", name: "dd" }, - } - ) - ); - }, + ).nodes, + { + a: { $id: "a", knows: [$ref("b"), $ref("c"), $ref("d")] }, + b: { $id: "b", name: "bb" }, + c: { $id: "c", name: "cc" }, + d: { $id: "d", name: "dd" }, + } + ) + ); + }, - "ref array item (resolved)": () => { - assert.ok( - equiv( - parseString( - ` + "ref array item (resolved)": () => { + assert.ok( + equiv( + parseString( + ` a knows -> b knows -> c @@ -89,22 +89,22 @@ c d name dd `, - { opts: { resolve: true } } - ).nodes, - { - a: { - $id: "a", - knows: [ - { $id: "b", name: "bb" }, - { $id: "c", name: "cc" }, - { $id: "d", name: "dd" }, - ], - }, - b: { $id: "b", name: "bb" }, - c: { $id: "c", name: "cc" }, - d: { $id: "d", name: "dd" }, - } - ) - ); - }, + { opts: { resolve: true } } + ).nodes, + { + a: { + $id: "a", + knows: [ + { $id: "b", name: "bb" }, + { $id: "c", name: "cc" }, + { $id: "d", name: "dd" }, + ], + }, + b: { $id: "b", name: "bb" }, + c: { $id: "c", name: "cc" }, + d: { $id: "d", name: "dd" }, + } + ) + ); + }, }); diff --git a/packages/egf/test/serialize.ts b/packages/egf/test/serialize.ts index 266393aa50..fb9d145c86 100644 --- a/packages/egf/test/serialize.ts +++ b/packages/egf/test/serialize.ts @@ -2,34 +2,34 @@ import { rdf, schema } from "@thi.ng/prefixes"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { writeFileSync } from "fs"; -import { toEGF } from "../src/index.js" +import { toEGF } from "../src/index.js"; group("serialize", { - basics: () => { - const res = toEGF( - [ - { - $id: "thi:egf", - "rdf:type": { $ref: "schema:SoftwareSourceCode" }, - "schema:isPartOf": { $id: "http://thi.ng/umbrella" }, - "schema:dateCreated": new Date("2020-02-16"), - }, - { - $id: "thi:umbrella", - "rdf:type": { $ref: "schema:SoftwareSourceCode" }, - "schema:programmingLanguage": "TypeScript", - }, - ], - { - thi: "http://thi.ng/", - schema, - rdf, - } - ); - writeFileSync("out.egf", res); - assert.strictEqual( - res, - `@prefix thi: http://thi.ng/ + basics: () => { + const res = toEGF( + [ + { + $id: "thi:egf", + "rdf:type": { $ref: "schema:SoftwareSourceCode" }, + "schema:isPartOf": { $id: "http://thi.ng/umbrella" }, + "schema:dateCreated": new Date("2020-02-16"), + }, + { + $id: "thi:umbrella", + "rdf:type": { $ref: "schema:SoftwareSourceCode" }, + "schema:programmingLanguage": "TypeScript", + }, + ], + { + thi: "http://thi.ng/", + schema, + rdf, + } + ); + writeFileSync("out.egf", res); + assert.strictEqual( + res, + `@prefix thi: http://thi.ng/ @prefix schema: http://schema.org/ @prefix rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns# @@ -42,6 +42,6 @@ thi:umbrella \trdf:type -> schema:SoftwareSourceCode \tschema:programmingLanguage TypeScript ` - ); - }, + ); + }, }); diff --git a/packages/egf/tsconfig.json b/packages/egf/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/egf/tsconfig.json +++ b/packages/egf/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/equiv/api-extractor.json b/packages/equiv/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/equiv/api-extractor.json +++ b/packages/equiv/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/equiv/package.json b/packages/equiv/package.json index ad58421573..f47568b13d 100644 --- a/packages/equiv/package.json +++ b/packages/equiv/package.json @@ -1,71 +1,71 @@ { - "name": "@thi.ng/equiv", - "version": "2.1.8", - "description": "Extensible deep value equivalence checking for any data types", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/equiv#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "array", - "deep", - "equality", - "interface", - "typescript", - "value-semantics" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - } + "name": "@thi.ng/equiv", + "version": "2.1.8", + "description": "Extensible deep value equivalence checking for any data types", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/equiv#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "array", + "deep", + "equality", + "interface", + "typescript", + "value-semantics" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + } } diff --git a/packages/equiv/src/index.ts b/packages/equiv/src/index.ts index 1689fc6824..447bc0b92c 100644 --- a/packages/equiv/src/index.ts +++ b/packages/equiv/src/index.ts @@ -3,83 +3,83 @@ const FN = "function"; const STR = "string"; export const equiv = (a: any, b: any): boolean => { - let proto; - if (a === b) { - return true; - } - if (a != null) { - if (typeof a.equiv === FN) { - return a.equiv(b); - } - } else { - return a == b; - } - if (b != null) { - if (typeof b.equiv === FN) { - return b.equiv(a); - } - } else { - return a == b; - } - if (typeof a === STR || typeof b === STR) { - return false; - } - if ( - ((proto = Object.getPrototypeOf(a)), proto == null || proto === OBJP) && - ((proto = Object.getPrototypeOf(b)), proto == null || proto === OBJP) - ) { - return equivObject(a, b); - } - if ( - typeof a !== FN && - a.length !== undefined && - typeof b !== FN && - b.length !== undefined - ) { - return equivArrayLike(a, b); - } - if (a instanceof Set && b instanceof Set) { - return equivSet(a, b); - } - if (a instanceof Map && b instanceof Map) { - return equivMap(a, b); - } - if (a instanceof Date && b instanceof Date) { - return a.getTime() === b.getTime(); - } - if (a instanceof RegExp && b instanceof RegExp) { - return a.toString() === b.toString(); - } - // NaN - return a !== a && b !== b; + let proto; + if (a === b) { + return true; + } + if (a != null) { + if (typeof a.equiv === FN) { + return a.equiv(b); + } + } else { + return a == b; + } + if (b != null) { + if (typeof b.equiv === FN) { + return b.equiv(a); + } + } else { + return a == b; + } + if (typeof a === STR || typeof b === STR) { + return false; + } + if ( + ((proto = Object.getPrototypeOf(a)), proto == null || proto === OBJP) && + ((proto = Object.getPrototypeOf(b)), proto == null || proto === OBJP) + ) { + return equivObject(a, b); + } + if ( + typeof a !== FN && + a.length !== undefined && + typeof b !== FN && + b.length !== undefined + ) { + return equivArrayLike(a, b); + } + if (a instanceof Set && b instanceof Set) { + return equivSet(a, b); + } + if (a instanceof Map && b instanceof Map) { + return equivMap(a, b); + } + if (a instanceof Date && b instanceof Date) { + return a.getTime() === b.getTime(); + } + if (a instanceof RegExp && b instanceof RegExp) { + return a.toString() === b.toString(); + } + // NaN + return a !== a && b !== b; }; export const equivArrayLike = ( - a: ArrayLike, - b: ArrayLike, - _equiv = equiv + a: ArrayLike, + b: ArrayLike, + _equiv = equiv ) => { - let l = a.length; - if (l === b.length) { - while (l-- > 0 && _equiv(a[l], b[l])); - } - return l < 0; + let l = a.length; + if (l === b.length) { + while (l-- > 0 && _equiv(a[l], b[l])); + } + return l < 0; }; export const equivSet = (a: Set, b: Set, _equiv = equiv) => - a.size === b.size && _equiv([...a.keys()].sort(), [...b.keys()].sort()); + a.size === b.size && _equiv([...a.keys()].sort(), [...b.keys()].sort()); export const equivMap = (a: Map, b: Map, _equiv = equiv) => - a.size === b.size && _equiv([...a].sort(), [...b].sort()); + a.size === b.size && _equiv([...a].sort(), [...b].sort()); export const equivObject = (a: any, b: any, _equiv = equiv) => { - if (Object.keys(a).length !== Object.keys(b).length) { - return false; - } - for (let k in a) { - if (!b.hasOwnProperty(k) || !_equiv(a[k], b[k])) { - return false; - } - } - return true; + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + for (let k in a) { + if (!b.hasOwnProperty(k) || !_equiv(a[k], b[k])) { + return false; + } + } + return true; }; diff --git a/packages/equiv/test/index.ts b/packages/equiv/test/index.ts index b00934bbe6..7520826c49 100644 --- a/packages/equiv/test/index.ts +++ b/packages/equiv/test/index.ts @@ -1,146 +1,146 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { equiv } from "../src/index.js" +import { equiv } from "../src/index.js"; group("equiv", { - null: () => { - assert.ok(equiv(null, null)); - assert.ok(equiv(null, undefined)); - assert.ok(equiv(undefined, null)); - }, + null: () => { + assert.ok(equiv(null, null)); + assert.ok(equiv(null, undefined)); + assert.ok(equiv(undefined, null)); + }, - boolean: () => { - assert.ok(!equiv(null, false)); - assert.ok(!equiv(false, null)); - assert.ok(!equiv(undefined, false)); - assert.ok(!equiv(false, undefined)); - }, + boolean: () => { + assert.ok(!equiv(null, false)); + assert.ok(!equiv(false, null)); + assert.ok(!equiv(undefined, false)); + assert.ok(!equiv(false, undefined)); + }, - number: () => { - assert.ok(!equiv(null, 0)); - assert.ok(!equiv(0, null)); - assert.ok(!equiv(0, undefined)); - assert.ok(!equiv(undefined, 0)); + number: () => { + assert.ok(!equiv(null, 0)); + assert.ok(!equiv(0, null)); + assert.ok(!equiv(0, undefined)); + assert.ok(!equiv(undefined, 0)); - assert.ok(equiv(0, 0)); - assert.ok(equiv(0, 0.0)); - assert.ok(!equiv(0, 1)); - assert.ok(!equiv(1, 0)); - assert.ok(!equiv(0, "0")); - assert.ok(!equiv("0", 0)); - assert.ok(!equiv(0, [0])); - assert.ok(!equiv([0], 0)); - }, + assert.ok(equiv(0, 0)); + assert.ok(equiv(0, 0.0)); + assert.ok(!equiv(0, 1)); + assert.ok(!equiv(1, 0)); + assert.ok(!equiv(0, "0")); + assert.ok(!equiv("0", 0)); + assert.ok(!equiv(0, [0])); + assert.ok(!equiv([0], 0)); + }, - string: () => { - assert.ok(!equiv(null, "")); - assert.ok(!equiv("", null)); - assert.ok(equiv("a", "a")); - assert.ok(!equiv("a", "ab")); - }, + string: () => { + assert.ok(!equiv(null, "")); + assert.ok(!equiv("", null)); + assert.ok(equiv("a", "a")); + assert.ok(!equiv("a", "ab")); + }, - array: () => { - assert.ok(equiv([], [])); - assert.ok(equiv([], [])); - assert.ok(equiv([], { length: 0 })); - assert.ok(equiv({ length: 0 }, [])); - assert.ok(equiv(["a"], ["a"])); - assert.ok(!equiv(["a"], ["b"])); - }, + array: () => { + assert.ok(equiv([], [])); + assert.ok(equiv([], [])); + assert.ok(equiv([], { length: 0 })); + assert.ok(equiv({ length: 0 }, [])); + assert.ok(equiv(["a"], ["a"])); + assert.ok(!equiv(["a"], ["b"])); + }, - object: () => { - assert.ok(!equiv(undefined, {})); - assert.ok(!equiv({}, undefined)); - assert.ok(!equiv(null, {})); - assert.ok(!equiv({}, null)); + object: () => { + assert.ok(!equiv(undefined, {})); + assert.ok(!equiv({}, undefined)); + assert.ok(!equiv(null, {})); + assert.ok(!equiv({}, null)); - assert.ok(equiv({}, {})); - assert.ok(!equiv({}, [])); - assert.ok(!equiv([], {})); - assert.ok(equiv({ a: 0 }, { a: 0 })); - assert.ok(equiv({ a: 0, b: { c: 1 } }, { a: 0, b: { c: 1 } })); - assert.ok(!equiv({ a: 0, b: { c: 1 } }, { a: 0, b: { c: 2 } })); - assert.ok(!equiv({ a: 0, b: { c: 1 } }, { a: 0, b: {} })); - }, + assert.ok(equiv({}, {})); + assert.ok(!equiv({}, [])); + assert.ok(!equiv([], {})); + assert.ok(equiv({ a: 0 }, { a: 0 })); + assert.ok(equiv({ a: 0, b: { c: 1 } }, { a: 0, b: { c: 1 } })); + assert.ok(!equiv({ a: 0, b: { c: 1 } }, { a: 0, b: { c: 2 } })); + assert.ok(!equiv({ a: 0, b: { c: 1 } }, { a: 0, b: {} })); + }, - "equiv impl": () => { - class A { - a: any; - constructor(a: any) { - this.a = a; - } + "equiv impl": () => { + class A { + a: any; + constructor(a: any) { + this.a = a; + } - equiv(b: any) { - return equiv(this.a, b); - } - } + equiv(b: any) { + return equiv(this.a, b); + } + } - assert.ok(!equiv(new A(1), null)); - assert.ok(!equiv(new A(1), undefined)); - assert.ok(!equiv(null, new A(1))); - assert.ok(!equiv(undefined, new A(1))); - assert.ok(equiv(new A(1), new A(1))); - assert.ok(equiv(new A(1), 1)); - assert.ok(equiv(1, new A(1))); - assert.ok( - equiv(1, { - equiv(x: number) { - return x === 1; - }, - }) - ); - assert.ok( - equiv( - { - equiv(x: number) { - return x === 1; - }, - }, - 1 - ) - ); - assert.ok(!equiv(new A(1), new A(2))); - assert.ok(!equiv(new A(1), 2)); - }, + assert.ok(!equiv(new A(1), null)); + assert.ok(!equiv(new A(1), undefined)); + assert.ok(!equiv(null, new A(1))); + assert.ok(!equiv(undefined, new A(1))); + assert.ok(equiv(new A(1), new A(1))); + assert.ok(equiv(new A(1), 1)); + assert.ok(equiv(1, new A(1))); + assert.ok( + equiv(1, { + equiv(x: number) { + return x === 1; + }, + }) + ); + assert.ok( + equiv( + { + equiv(x: number) { + return x === 1; + }, + }, + 1 + ) + ); + assert.ok(!equiv(new A(1), new A(2))); + assert.ok(!equiv(new A(1), 2)); + }, - set: () => { - const a = new Set([1, 2, 3]); - assert.ok(equiv(a, a)); - assert.ok(equiv(a, new Set([3, 2, 1]))); - assert.ok( - equiv( - new Set([{ a: 1 }, new Set([{ b: 2 }, [3]])]), - new Set([new Set([[3], { b: 2 }]), { a: 1 }]) - ) - ); - assert.ok(!equiv(a, new Set([3, 2, 0]))); - assert.ok(!equiv(a, [3, 2, 0])); - assert.ok( - !equiv( - a, - new Map([ - [3, 3], - [2, 2], - [1, 1], - ]) - ) - ); - assert.ok(!equiv(a, null)); - assert.ok(!equiv(null, a)); - }, + set: () => { + const a = new Set([1, 2, 3]); + assert.ok(equiv(a, a)); + assert.ok(equiv(a, new Set([3, 2, 1]))); + assert.ok( + equiv( + new Set([{ a: 1 }, new Set([{ b: 2 }, [3]])]), + new Set([new Set([[3], { b: 2 }]), { a: 1 }]) + ) + ); + assert.ok(!equiv(a, new Set([3, 2, 0]))); + assert.ok(!equiv(a, [3, 2, 0])); + assert.ok( + !equiv( + a, + new Map([ + [3, 3], + [2, 2], + [1, 1], + ]) + ) + ); + assert.ok(!equiv(a, null)); + assert.ok(!equiv(null, a)); + }, - date: () => { - const a = new Date(123456); - assert.ok(equiv(a, a)); - assert.ok(equiv(a, new Date(123456))); - assert.ok(!equiv(a, new Date(123))); - }, + date: () => { + const a = new Date(123456); + assert.ok(equiv(a, a)); + assert.ok(equiv(a, new Date(123456))); + assert.ok(!equiv(a, new Date(123))); + }, - regexp: () => { - const a = /(\w+)/g; - assert.ok(equiv(a, a)); - assert.ok(equiv(a, /(\w+)/g)); - assert.ok(!equiv(a, /(\w+)/)); - assert.ok(!equiv(a, /(\w*)/g)); - }, + regexp: () => { + const a = /(\w+)/g; + assert.ok(equiv(a, a)); + assert.ok(equiv(a, /(\w+)/g)); + assert.ok(!equiv(a, /(\w+)/)); + assert.ok(!equiv(a, /(\w*)/g)); + }, }); diff --git a/packages/equiv/tsconfig.json b/packages/equiv/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/equiv/tsconfig.json +++ b/packages/equiv/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/errors/api-extractor.json b/packages/errors/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/errors/api-extractor.json +++ b/packages/errors/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/errors/package.json b/packages/errors/package.json index 9f4cdd404c..462b14b459 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -1,93 +1,93 @@ { - "name": "@thi.ng/errors", - "version": "2.1.8", - "description": "Custom error types and error factory functions", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/errors#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "@types/node": "^17.0.41", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "assert", - "error", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./assert": { - "default": "./assert.js" - }, - "./deferror": { - "default": "./deferror.js" - }, - "./illegal-arguments": { - "default": "./illegal-arguments.js" - }, - "./illegal-arity": { - "default": "./illegal-arity.js" - }, - "./illegal-state": { - "default": "./illegal-state.js" - }, - "./out-of-bounds": { - "default": "./out-of-bounds.js" - }, - "./unsupported": { - "default": "./unsupported.js" - } - }, - "thi.ng": { - "year": 2018 - } + "name": "@thi.ng/errors", + "version": "2.1.8", + "description": "Custom error types and error factory functions", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/errors#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "@types/node": "^17.0.41", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "assert", + "error", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./assert": { + "default": "./assert.js" + }, + "./deferror": { + "default": "./deferror.js" + }, + "./illegal-arguments": { + "default": "./illegal-arguments.js" + }, + "./illegal-arity": { + "default": "./illegal-arity.js" + }, + "./illegal-state": { + "default": "./illegal-state.js" + }, + "./out-of-bounds": { + "default": "./out-of-bounds.js" + }, + "./unsupported": { + "default": "./unsupported.js" + } + }, + "thi.ng": { + "year": 2018 + } } diff --git a/packages/errors/src/assert.ts b/packages/errors/src/assert.ts index 964adb9cb1..e8bb11610d 100644 --- a/packages/errors/src/assert.ts +++ b/packages/errors/src/assert.ts @@ -17,17 +17,19 @@ export const AssertionError = defError(() => "Assertion failed"); * or if the `UMBRELLA_ASSERTS` env var is set to 1. */ export const assert = (() => - typeof process !== "undefined" && typeof process.env !== "undefined" - ? process.env.NODE_ENV !== "production" || - !!process.env.UMBRELLA_ASSERTS - : typeof __SNOWPACK_ENV__ !== "undefined" - ? __SNOWPACK_ENV__.MODE !== "production" || - !!__SNOWPACK_ENV__.UMBRELLA_ASSERTS || - !!__SNOWPACK_ENV__.SNOWPACK_PUBLIC_UMBRELLA_ASSERTS - : true)() - ? (test: boolean | (() => boolean), msg?: string | (() => string)) => { - if ((typeof test === "function" && !test()) || !test) { - throw new AssertionError(typeof msg === "function" ? msg() : msg); - } - } - : () => {}; + typeof process !== "undefined" && typeof process.env !== "undefined" + ? process.env.NODE_ENV !== "production" || + !!process.env.UMBRELLA_ASSERTS + : typeof __SNOWPACK_ENV__ !== "undefined" + ? __SNOWPACK_ENV__.MODE !== "production" || + !!__SNOWPACK_ENV__.UMBRELLA_ASSERTS || + !!__SNOWPACK_ENV__.SNOWPACK_PUBLIC_UMBRELLA_ASSERTS + : true)() + ? (test: boolean | (() => boolean), msg?: string | (() => string)) => { + if ((typeof test === "function" && !test()) || !test) { + throw new AssertionError( + typeof msg === "function" ? msg() : msg + ); + } + } + : () => {}; diff --git a/packages/errors/src/deferror.ts b/packages/errors/src/deferror.ts index d357390747..d5d93782e0 100644 --- a/packages/errors/src/deferror.ts +++ b/packages/errors/src/deferror.ts @@ -1,9 +1,9 @@ export const defError = ( - prefix: (msg?: T) => string, - suffix: (msg?: T) => string = (msg) => (msg !== undefined ? ": " + msg : "") + prefix: (msg?: T) => string, + suffix: (msg?: T) => string = (msg) => (msg !== undefined ? ": " + msg : "") ) => - class extends Error { - constructor(msg?: T) { - super(prefix(msg) + suffix(msg)); - } - }; + class extends Error { + constructor(msg?: T) { + super(prefix(msg) + suffix(msg)); + } + }; diff --git a/packages/errors/src/illegal-arguments.ts b/packages/errors/src/illegal-arguments.ts index 22f8588771..0691506ec8 100644 --- a/packages/errors/src/illegal-arguments.ts +++ b/packages/errors/src/illegal-arguments.ts @@ -3,5 +3,5 @@ import { defError } from "./deferror.js"; export const IllegalArgumentError = defError(() => "illegal argument(s)"); export const illegalArgs = (msg?: any): never => { - throw new IllegalArgumentError(msg); + throw new IllegalArgumentError(msg); }; diff --git a/packages/errors/src/illegal-arity.ts b/packages/errors/src/illegal-arity.ts index 45fcbb18e1..56b559275e 100644 --- a/packages/errors/src/illegal-arity.ts +++ b/packages/errors/src/illegal-arity.ts @@ -3,5 +3,5 @@ import { defError } from "./deferror.js"; export const IllegalArityError = defError(() => "illegal arity"); export const illegalArity = (n: number): never => { - throw new IllegalArityError(n); + throw new IllegalArityError(n); }; diff --git a/packages/errors/src/illegal-state.ts b/packages/errors/src/illegal-state.ts index da02463ac6..cb15fd9585 100644 --- a/packages/errors/src/illegal-state.ts +++ b/packages/errors/src/illegal-state.ts @@ -3,5 +3,5 @@ import { defError } from "./deferror.js"; export const IllegalStateError = defError(() => "illegal state"); export const illegalState = (msg?: any): never => { - throw new IllegalStateError(msg); + throw new IllegalStateError(msg); }; diff --git a/packages/errors/src/out-of-bounds.ts b/packages/errors/src/out-of-bounds.ts index 04f24942f8..570f374491 100644 --- a/packages/errors/src/out-of-bounds.ts +++ b/packages/errors/src/out-of-bounds.ts @@ -3,7 +3,7 @@ import { defError } from "./deferror.js"; export const OutOfBoundsError = defError(() => "index out of bounds"); export const outOfBounds = (index: any): never => { - throw new OutOfBoundsError(index); + throw new OutOfBoundsError(index); }; /** @@ -14,7 +14,7 @@ export const outOfBounds = (index: any): never => { * @param max - */ export const ensureIndex = (index: number, min: number, max: number) => - (index < min || index >= max) && outOfBounds(index); + (index < min || index >= max) && outOfBounds(index); /** * Throws an {@link OutOfBoundsError} if either `x` or `y` is outside their @@ -26,8 +26,8 @@ export const ensureIndex = (index: number, min: number, max: number) => * @param maxY - */ export const ensureIndex2 = ( - x: number, - y: number, - maxX: number, - maxY: number + x: number, + y: number, + maxX: number, + maxY: number ) => (x < 0 || x >= maxX || y < 0 || y >= maxY) && outOfBounds([x, y]); diff --git a/packages/errors/src/unsupported.ts b/packages/errors/src/unsupported.ts index 9a8cdd5ad0..b280337c59 100644 --- a/packages/errors/src/unsupported.ts +++ b/packages/errors/src/unsupported.ts @@ -1,9 +1,9 @@ import { defError } from "./deferror.js"; export const UnsupportedOperationError = defError( - () => "unsupported operation" + () => "unsupported operation" ); export const unsupported = (msg?: any): never => { - throw new UnsupportedOperationError(msg); + throw new UnsupportedOperationError(msg); }; diff --git a/packages/errors/tsconfig.json b/packages/errors/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/errors/tsconfig.json +++ b/packages/errors/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/expose/api-extractor.json b/packages/expose/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/expose/api-extractor.json +++ b/packages/expose/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/expose/package.json b/packages/expose/package.json index a6e12bd107..4bbad941fd 100644 --- a/packages/expose/package.json +++ b/packages/expose/package.json @@ -1,71 +1,71 @@ { - "name": "@thi.ng/expose", - "version": "1.1.8", - "description": "Conditional global variable exposition", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/expose#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "@types/node": "^17.0.41", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "global", - "scope", - "typescript", - "variable" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": {} + "name": "@thi.ng/expose", + "version": "1.1.8", + "description": "Conditional global variable exposition", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/expose#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "@types/node": "^17.0.41", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "global", + "scope", + "typescript", + "variable" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": {} } diff --git a/packages/expose/src/index.ts b/packages/expose/src/index.ts index 6bcc48fbc1..2fbe9628de 100644 --- a/packages/expose/src/index.ts +++ b/packages/expose/src/index.ts @@ -14,26 +14,26 @@ declare const __SNOWPACK_ENV__: any; * @param always - */ export const exposeGlobal = (id: string, value: any, always = false) => { - const glob: any = - typeof global !== "undefined" - ? global - : typeof window !== "undefined" - ? window - : undefined; - if ( - glob && - (always || - (() => - typeof process !== "undefined" && - typeof process.env !== "undefined" - ? process.env.NODE_ENV !== "production" || - !!process.env.UMBRELLA_GLOBALS - : typeof __SNOWPACK_ENV__ !== "undefined" - ? __SNOWPACK_ENV__.MODE !== "production" || - !!__SNOWPACK_ENV__.UMBRELLA_GLOBALS || - !!__SNOWPACK_ENV__.SNOWPACK_PUBLIC_UMBRELLA_GLOBALS - : true)()) - ) { - glob[id] = value; - } + const glob: any = + typeof global !== "undefined" + ? global + : typeof window !== "undefined" + ? window + : undefined; + if ( + glob && + (always || + (() => + typeof process !== "undefined" && + typeof process.env !== "undefined" + ? process.env.NODE_ENV !== "production" || + !!process.env.UMBRELLA_GLOBALS + : typeof __SNOWPACK_ENV__ !== "undefined" + ? __SNOWPACK_ENV__.MODE !== "production" || + !!__SNOWPACK_ENV__.UMBRELLA_GLOBALS || + !!__SNOWPACK_ENV__.SNOWPACK_PUBLIC_UMBRELLA_GLOBALS + : true)()) + ) { + glob[id] = value; + } }; diff --git a/packages/expose/test/index.ts b/packages/expose/test/index.ts index c642a69446..fa9e9c7ffd 100644 --- a/packages/expose/test/index.ts +++ b/packages/expose/test/index.ts @@ -2,6 +2,4 @@ import { group } from "@thi.ng/testament"; // import * as assert from "assert"; // import { } from "../src/index.js" -group("expose", { - -}); +group("expose", {}); diff --git a/packages/expose/tsconfig.json b/packages/expose/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/expose/tsconfig.json +++ b/packages/expose/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/file-io/api-extractor.json b/packages/file-io/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/file-io/api-extractor.json +++ b/packages/file-io/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/file-io/package.json b/packages/file-io/package.json index 5ccda123f6..a606eb9356 100644 --- a/packages/file-io/package.json +++ b/packages/file-io/package.json @@ -1,111 +1,111 @@ { - "name": "@thi.ng/file-io", - "version": "0.3.5", - "description": "Assorted file I/O utils (with logging support) for NodeJS", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/file-io#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/logger": "^1.1.8", - "@thi.ng/random": "^3.3.3" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "file", - "hash", - "json", - "logger", - "node-only", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=14" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./delete": { - "default": "./delete.js" - }, - "./dir": { - "default": "./dir.js" - }, - "./ext": { - "default": "./ext.js" - }, - "./files": { - "default": "./files.js" - }, - "./hash": { - "default": "./hash.js" - }, - "./json": { - "default": "./json.js" - }, - "./mask": { - "default": "./mask.js" - }, - "./temp": { - "default": "./temp.js" - }, - "./text": { - "default": "./text.js" - }, - "./write": { - "default": "./write.js" - } - }, - "thi.ng": { - "status": "stable", - "year": 2022 - } + "name": "@thi.ng/file-io", + "version": "0.3.5", + "description": "Assorted file I/O utils (with logging support) for NodeJS", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/file-io#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/logger": "^1.1.8", + "@thi.ng/random": "^3.3.3" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "file", + "hash", + "json", + "logger", + "node-only", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=14" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./delete": { + "default": "./delete.js" + }, + "./dir": { + "default": "./dir.js" + }, + "./ext": { + "default": "./ext.js" + }, + "./files": { + "default": "./files.js" + }, + "./hash": { + "default": "./hash.js" + }, + "./json": { + "default": "./json.js" + }, + "./mask": { + "default": "./mask.js" + }, + "./temp": { + "default": "./temp.js" + }, + "./text": { + "default": "./text.js" + }, + "./write": { + "default": "./write.js" + } + }, + "thi.ng": { + "status": "stable", + "year": 2022 + } } diff --git a/packages/file-io/src/delete.ts b/packages/file-io/src/delete.ts index ba9c8b082f..3e5f4488d4 100644 --- a/packages/file-io/src/delete.ts +++ b/packages/file-io/src/delete.ts @@ -11,7 +11,7 @@ import { unlinkSync } from "fs"; * @param dryRun */ export const deleteFile = (path: string, logger?: ILogger, dryRun = false) => { - logger && logger.info(`${dryRun ? "[dryrun] " : ""}deleting file: ${path}`); - if (dryRun) return; - unlinkSync(path); + logger && logger.info(`${dryRun ? "[dryrun] " : ""}deleting file: ${path}`); + if (dryRun) return; + unlinkSync(path); }; diff --git a/packages/file-io/src/dir.ts b/packages/file-io/src/dir.ts index bca1ddce85..1306260a47 100644 --- a/packages/file-io/src/dir.ts +++ b/packages/file-io/src/dir.ts @@ -12,10 +12,10 @@ import { sep } from "path"; * @param path */ export const ensureDirForFile = (path: string) => { - const dir = path.substring(0, path.lastIndexOf(sep)); - return dir.length > 0 && !existsSync(dir) - ? (mkdirSync(dir, { recursive: true }), true) - : false; + const dir = path.substring(0, path.lastIndexOf(sep)); + return dir.length > 0 && !existsSync(dir) + ? (mkdirSync(dir, { recursive: true }), true) + : false; }; /** diff --git a/packages/file-io/src/ext.ts b/packages/file-io/src/ext.ts index 183dccf452..31024587de 100644 --- a/packages/file-io/src/ext.ts +++ b/packages/file-io/src/ext.ts @@ -4,6 +4,6 @@ * @param path */ export const fileExt = (path: string) => { - const match = /\.(\w+)$/.exec(path); - return match ? match[1].toLowerCase() : ""; + const match = /\.(\w+)$/.exec(path); + return match ? match[1].toLowerCase() : ""; }; diff --git a/packages/file-io/src/files.ts b/packages/file-io/src/files.ts index f8cbbb24c3..6e31ff4f96 100644 --- a/packages/file-io/src/files.ts +++ b/packages/file-io/src/files.ts @@ -21,34 +21,34 @@ import { isDirectory } from "./dir.js"; * @param logger */ export const files = ( - dir: string, - match: string | RegExp = "", - maxDepth = Infinity, - logger?: ILogger + dir: string, + match: string | RegExp = "", + maxDepth = Infinity, + logger?: ILogger ) => __files(dir, match, logger, maxDepth, 0); function* __files( - dir: string, - match: string | RegExp = "", - logger?: ILogger, - maxDepth = Infinity, - depth = 0 + dir: string, + match: string | RegExp = "", + logger?: ILogger, + maxDepth = Infinity, + depth = 0 ): IterableIterator { - if (depth >= maxDepth) return; - const re = __ensureRegEx(match); - for (let f of readdirSync(dir)) { - const curr = dir + sep + f; - try { - if (isDirectory(curr)) { - yield* __files(curr, match, logger, maxDepth, depth + 1); - } else if (re.test(f)) { - yield curr; - } - } catch (e) { - logger && - logger.warn(`ignoring file: ${f} (${(e).message})`); - } - } + if (depth >= maxDepth) return; + const re = __ensureRegEx(match); + for (let f of readdirSync(dir)) { + const curr = dir + sep + f; + try { + if (isDirectory(curr)) { + yield* __files(curr, match, logger, maxDepth, depth + 1); + } else if (re.test(f)) { + yield curr; + } + } catch (e) { + logger && + logger.warn(`ignoring file: ${f} (${(e).message})`); + } + } } /** @@ -65,35 +65,35 @@ function* __files( * @param logger */ export const dirs = ( - dir: string, - match: string | RegExp = "", - maxDepth = Infinity, - logger?: ILogger + dir: string, + match: string | RegExp = "", + maxDepth = Infinity, + logger?: ILogger ) => __dirs(dir, match, logger, maxDepth, 0); function* __dirs( - dir: string, - match: string | RegExp = "", - logger?: ILogger, - maxDepth = Infinity, - depth = 0 + dir: string, + match: string | RegExp = "", + logger?: ILogger, + maxDepth = Infinity, + depth = 0 ): IterableIterator { - if (depth >= maxDepth) return; - const re = __ensureRegEx(match); - for (let f of readdirSync(dir)) { - const curr = dir + sep + f; - try { - if (statSync(curr).isDirectory()) { - if (re.test(curr)) yield curr; - yield* __dirs(curr, match, logger, maxDepth, depth + 1); - } - } catch (e) { - logger && - logger.warn(`ignoring file/dir: ${f} (${(e).message})`); - } - } + if (depth >= maxDepth) return; + const re = __ensureRegEx(match); + for (let f of readdirSync(dir)) { + const curr = dir + sep + f; + try { + if (statSync(curr).isDirectory()) { + if (re.test(curr)) yield curr; + yield* __dirs(curr, match, logger, maxDepth, depth + 1); + } + } catch (e) { + logger && + logger.warn(`ignoring file/dir: ${f} (${(e).message})`); + } + } } /** @internal */ const __ensureRegEx = (match: string | RegExp) => - isString(match) ? new RegExp(`${match.replace(/\./g, "\\.")}$`) : match; + isString(match) ? new RegExp(`${match.replace(/\./g, "\\.")}$`) : match; diff --git a/packages/file-io/src/hash.ts b/packages/file-io/src/hash.ts index 26df73b569..2b99453c87 100644 --- a/packages/file-io/src/hash.ts +++ b/packages/file-io/src/hash.ts @@ -3,40 +3,40 @@ import { createHash } from "crypto"; import { readFileSync } from "fs"; export type HashAlgo = - | "gost-mac" - | "md4" - | "md5" - | "md_gost94" - | "ripemd160" - | "sha1" - | "sha224" - | "sha256" - | "sha384" - | "sha512" - | "streebog256" - | "streebog512" - | "whirlpool"; + | "gost-mac" + | "md4" + | "md5" + | "md_gost94" + | "ripemd160" + | "sha1" + | "sha224" + | "sha256" + | "sha384" + | "sha512" + | "streebog256" + | "streebog512" + | "whirlpool"; export const fileHash = ( - path: string, - logger?: ILogger, - algo: HashAlgo = "sha256" + path: string, + logger?: ILogger, + algo: HashAlgo = "sha256" ) => { - const sum = createHash(algo); - sum.update(readFileSync(path)); - const hash = sum.digest("hex"); - logger && logger.info(`${algo} hash for ${path}: ${hash}`); - return hash; + const sum = createHash(algo); + sum.update(readFileSync(path)); + const hash = sum.digest("hex"); + logger && logger.info(`${algo} hash for ${path}: ${hash}`); + return hash; }; export const stringHash = ( - src: string, - logger?: ILogger, - algo: HashAlgo = "sha256" + src: string, + logger?: ILogger, + algo: HashAlgo = "sha256" ) => { - const sum = createHash(algo); - sum.update(src); - const hash = sum.digest("hex"); - logger && logger.info(`${algo} hash for string: ${hash}`); - return hash; + const sum = createHash(algo); + sum.update(src); + const hash = sum.digest("hex"); + logger && logger.info(`${algo} hash for string: ${hash}`); + return hash; }; diff --git a/packages/file-io/src/json.ts b/packages/file-io/src/json.ts index 7a5edd7379..4b40afe178 100644 --- a/packages/file-io/src/json.ts +++ b/packages/file-io/src/json.ts @@ -3,7 +3,7 @@ import type { ILogger } from "@thi.ng/logger"; import { readText, writeText } from "./text.js"; export const readJSON = (path: string, logger?: ILogger) => - JSON.parse(readText(path, logger)); + JSON.parse(readText(path, logger)); /** * Serializes `obj` to JSON and writes result to UTF-8 file `path`. See @@ -21,16 +21,16 @@ export const readJSON = (path: string, logger?: ILogger) => * @param dryRun */ export const writeJSON = ( - path: string, - obj: any, - replacer?: Fn3 | NumOrString[] | null | undefined, - space?: NumOrString | undefined, - logger?: ILogger, - dryRun = false + path: string, + obj: any, + replacer?: Fn3 | NumOrString[] | null | undefined, + space?: NumOrString | undefined, + logger?: ILogger, + dryRun = false ) => - writeText( - path, - JSON.stringify(obj, replacer, space) + "\n", - logger, - dryRun - ); + writeText( + path, + JSON.stringify(obj, replacer, space) + "\n", + logger, + dryRun + ); diff --git a/packages/file-io/src/mask.ts b/packages/file-io/src/mask.ts index 6a190d87a9..67cdcf44e2 100644 --- a/packages/file-io/src/mask.ts +++ b/packages/file-io/src/mask.ts @@ -6,7 +6,7 @@ * @param home */ export const maskHomeDir = ( - path: string, - home = process.env.HOME, - mask = "~" + path: string, + home = process.env.HOME, + mask = "~" ) => (home ? path.replace(home, mask) : path); diff --git a/packages/file-io/src/temp.ts b/packages/file-io/src/temp.ts index 8d9bab6b0a..f3cbc46e0a 100644 --- a/packages/file-io/src/temp.ts +++ b/packages/file-io/src/temp.ts @@ -8,16 +8,16 @@ import { sep } from "path"; import { ensureDirForFile } from "./dir.js"; export const createTempFile = ( - body: string | TypedArray, - logger?: ILogger, - name?: string + body: string | TypedArray, + logger?: ILogger, + name?: string ) => { - const path = tempFilePath(name); - logger && logger.debug("creating temp file:", path); - ensureDirForFile(path); - writeFileSync(path, body, isString(body) ? "utf-8" : undefined); - return path; + const path = tempFilePath(name); + logger && logger.debug("creating temp file:", path); + ensureDirForFile(path); + writeFileSync(path, body, isString(body) ? "utf-8" : undefined); + return path; }; export const tempFilePath = (name?: string) => - realpathSync(tmpdir()) + sep + (name || randomID(16, "tmp-")); + realpathSync(tmpdir()) + sep + (name || randomID(16, "tmp-")); diff --git a/packages/file-io/src/text.ts b/packages/file-io/src/text.ts index bd9a8aa4ed..d268cd97a1 100644 --- a/packages/file-io/src/text.ts +++ b/packages/file-io/src/text.ts @@ -4,8 +4,8 @@ import { readFileSync } from "fs"; import { writeFile } from "./write.js"; export const readText = (path: string, logger?: ILogger) => { - logger && logger.debug("reading file:", path); - return readFileSync(path, "utf-8"); + logger && logger.debug("reading file:", path); + return readFileSync(path, "utf-8"); }; /** @@ -19,15 +19,15 @@ export const readText = (path: string, logger?: ILogger) => { * @param dryRun */ export const writeText = ( - path: string, - body: string | string[], - logger?: ILogger, - dryRun = false + path: string, + body: string | string[], + logger?: ILogger, + dryRun = false ) => - writeFile( - path, - isArray(body) ? body.join("\n") : body, - "utf-8", - logger, - dryRun - ); + writeFile( + path, + isArray(body) ? body.join("\n") : body, + "utf-8", + logger, + dryRun + ); diff --git a/packages/file-io/src/write.ts b/packages/file-io/src/write.ts index fbce52b17c..6f8f970792 100644 --- a/packages/file-io/src/write.ts +++ b/packages/file-io/src/write.ts @@ -15,14 +15,14 @@ import { ensureDirForFile } from "./dir.js"; * @param dryRun */ export const writeFile = ( - path: string, - body: string | TypedArray, - opts?: WriteFileOptions, - logger?: ILogger, - dryRun = false + path: string, + body: string | TypedArray, + opts?: WriteFileOptions, + logger?: ILogger, + dryRun = false ) => { - logger && logger.info(`${dryRun ? "[dryrun] " : ""}writing file: ${path}`); - if (dryRun) return; - ensureDirForFile(path); - writeFileSync(path, body, !opts && isString(body) ? "utf-8" : opts); + logger && logger.info(`${dryRun ? "[dryrun] " : ""}writing file: ${path}`); + if (dryRun) return; + ensureDirForFile(path); + writeFileSync(path, body, !opts && isString(body) ? "utf-8" : opts); }; diff --git a/packages/file-io/test/index.ts b/packages/file-io/test/index.ts index 4fd649ba8d..c87c618653 100644 --- a/packages/file-io/test/index.ts +++ b/packages/file-io/test/index.ts @@ -2,6 +2,4 @@ import { group } from "@thi.ng/testament"; // import * as assert from "assert"; // import { } from "../src"; -group("file-io", { - -}); +group("file-io", {}); diff --git a/packages/file-io/tsconfig.json b/packages/file-io/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/file-io/tsconfig.json +++ b/packages/file-io/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/fsm/api-extractor.json b/packages/fsm/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/fsm/api-extractor.json +++ b/packages/fsm/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/fsm/package.json b/packages/fsm/package.json index d9329cec89..b28ca3a110 100644 --- a/packages/fsm/package.json +++ b/packages/fsm/package.json @@ -1,128 +1,128 @@ { - "name": "@thi.ng/fsm", - "version": "3.1.13", - "description": "Composable primitives for building declarative, transducer based Finite-State Machines & matchers for arbitrary data streams", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/fsm#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/arrays": "^2.3.1", - "@thi.ng/equiv": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/strings": "^3.3.6", - "@thi.ng/transducers": "^8.3.7" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "composition", - "declarative", - "fsm", - "functional", - "parser", - "regex", - "string", - "transducer", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./alts-lit": { - "default": "./alts-lit.js" - }, - "./alts": { - "default": "./alts.js" - }, - "./always": { - "default": "./always.js" - }, - "./api": { - "default": "./api.js" - }, - "./fsm": { - "default": "./fsm.js" - }, - "./lit": { - "default": "./lit.js" - }, - "./never": { - "default": "./never.js" - }, - "./not": { - "default": "./not.js" - }, - "./range": { - "default": "./range.js" - }, - "./repeat": { - "default": "./repeat.js" - }, - "./result": { - "default": "./result.js" - }, - "./seq": { - "default": "./seq.js" - }, - "./str": { - "default": "./str.js" - }, - "./until": { - "default": "./until.js" - } - }, - "thi.ng": { - "related": [ - "parse", - "transducers-fsm" - ], - "status": "deprecated", - "year": 2018 - } + "name": "@thi.ng/fsm", + "version": "3.1.13", + "description": "Composable primitives for building declarative, transducer based Finite-State Machines & matchers for arbitrary data streams", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/fsm#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/arrays": "^2.3.1", + "@thi.ng/equiv": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/strings": "^3.3.6", + "@thi.ng/transducers": "^8.3.7" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "composition", + "declarative", + "fsm", + "functional", + "parser", + "regex", + "string", + "transducer", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./alts-lit": { + "default": "./alts-lit.js" + }, + "./alts": { + "default": "./alts.js" + }, + "./always": { + "default": "./always.js" + }, + "./api": { + "default": "./api.js" + }, + "./fsm": { + "default": "./fsm.js" + }, + "./lit": { + "default": "./lit.js" + }, + "./never": { + "default": "./never.js" + }, + "./not": { + "default": "./not.js" + }, + "./range": { + "default": "./range.js" + }, + "./repeat": { + "default": "./repeat.js" + }, + "./result": { + "default": "./result.js" + }, + "./seq": { + "default": "./seq.js" + }, + "./str": { + "default": "./str.js" + }, + "./until": { + "default": "./until.js" + } + }, + "thi.ng": { + "related": [ + "parse", + "transducers-fsm" + ], + "status": "deprecated", + "year": 2018 + } } diff --git a/packages/fsm/src/alts-lit.ts b/packages/fsm/src/alts-lit.ts index 9b3a708264..56d615c3fe 100644 --- a/packages/fsm/src/alts-lit.ts +++ b/packages/fsm/src/alts-lit.ts @@ -3,25 +3,25 @@ import { LitCallback, Match, Matcher } from "./api.js"; import { result } from "./result.js"; export const altsLit = - ( - opts: Set, - success?: LitCallback, - fail?: LitCallback - ): Matcher => - () => - (ctx, x) => - opts.has(x) - ? result(success && success(ctx, x)) - : result(fail && fail(ctx, x), Match.FAIL); + ( + opts: Set, + success?: LitCallback, + fail?: LitCallback + ): Matcher => + () => + (ctx, x) => + opts.has(x) + ? result(success && success(ctx, x)) + : result(fail && fail(ctx, x), Match.FAIL); export const altsLitObj = - ( - opts: IObjectOf, - success?: LitCallback, - fail?: LitCallback - ): Matcher => - () => - (ctx, x) => - opts[x] - ? result(success && success(ctx, x)) - : result(fail && fail(ctx, x), Match.FAIL); + ( + opts: IObjectOf, + success?: LitCallback, + fail?: LitCallback + ): Matcher => + () => + (ctx, x) => + opts[x] + ? result(success && success(ctx, x)) + : result(fail && fail(ctx, x), Match.FAIL); diff --git a/packages/fsm/src/alts.ts b/packages/fsm/src/alts.ts index 646c63622f..64fa6ee172 100644 --- a/packages/fsm/src/alts.ts +++ b/packages/fsm/src/alts.ts @@ -1,11 +1,11 @@ import { - AltCallback, - AltFallback, - Match, - Matcher, - MatcherInst, - MatchResult, - RES_PARTIAL, + AltCallback, + AltFallback, + Match, + Matcher, + MatcherInst, + MatchResult, + RES_PARTIAL, } from "./api.js"; import { result } from "./result.js"; @@ -32,40 +32,40 @@ import { result } from "./result.js"; * @param fail - failure callback */ export const alts = - ( - opts: Matcher[], - fallback?: AltFallback, - success?: AltCallback, - fail?: AltFallback - ): Matcher => - () => { - const alts: (MatcherInst | null)[] = opts.map((o) => o()); - const buf: T[] = []; - let active = alts.length; - return (ctx, x) => { - for ( - let i = alts.length, - a: MatcherInst | null, - next: MatchResult; - i-- > 0; + ( + opts: Matcher[], + fallback?: AltFallback, + success?: AltCallback, + fail?: AltFallback + ): Matcher => + () => { + const alts: (MatcherInst | null)[] = opts.map((o) => o()); + const buf: T[] = []; + let active = alts.length; + return (ctx, x) => { + for ( + let i = alts.length, + a: MatcherInst | null, + next: MatchResult; + i-- > 0; - ) { - if (!(a = alts[i])) continue; - next = a(ctx, x); - if (next.type >= Match.FULL) { - return success - ? result(success(ctx, next.body, buf), next.type) - : next; - } else if (next.type === Match.FAIL) { - alts[i] = null; - active--; - } - } - (fallback || fail) && buf.push(x); - return active - ? RES_PARTIAL - : fallback - ? result(fallback(ctx, buf)) - : result(fail && fail(ctx, buf), Match.FAIL); - }; - }; + ) { + if (!(a = alts[i])) continue; + next = a(ctx, x); + if (next.type >= Match.FULL) { + return success + ? result(success(ctx, next.body, buf), next.type) + : next; + } else if (next.type === Match.FAIL) { + alts[i] = null; + active--; + } + } + (fallback || fail) && buf.push(x); + return active + ? RES_PARTIAL + : fallback + ? result(fallback(ctx, buf)) + : result(fail && fail(ctx, buf), Match.FAIL); + }; + }; diff --git a/packages/fsm/src/always.ts b/packages/fsm/src/always.ts index f41e79ec3b..ba96900be0 100644 --- a/packages/fsm/src/always.ts +++ b/packages/fsm/src/always.ts @@ -8,7 +8,7 @@ import { result } from "./result.js"; * @param callback - */ export const always = - (callback?: LitCallback): Matcher => - () => - (ctx, x) => - result(callback && callback(ctx, x)); + (callback?: LitCallback): Matcher => + () => + (ctx, x) => + result(callback && callback(ctx, x)); diff --git a/packages/fsm/src/api.ts b/packages/fsm/src/api.ts index 64b6ae57f9..f67510eeb3 100644 --- a/packages/fsm/src/api.ts +++ b/packages/fsm/src/api.ts @@ -1,27 +1,27 @@ export enum Match { - /** - * Partial match - */ - PARTIAL = 0, - /** - * Full match - */ - FULL = 1, - /** - * Full match (No Consume), i.e. didn't consume last input. The - * result will be treated like `FULL`, but the last input will be - * processed further. - */ - FULL_NC = 2, - /** - * Failed match. - */ - FAIL = -1, + /** + * Partial match + */ + PARTIAL = 0, + /** + * Full match + */ + FULL = 1, + /** + * Full match (No Consume), i.e. didn't consume last input. The + * result will be treated like `FULL`, but the last input will be + * processed further. + */ + FULL_NC = 2, + /** + * Failed match. + */ + FAIL = -1, } export interface MatchResult { - type: Match; - body?: ResultBody; + type: Match; + body?: ResultBody; } export type Matcher = () => MatcherInst; @@ -31,16 +31,16 @@ export type MatcherInst = (ctx: C, x: T) => MatchResult; export type ResultBody = [number | string, T[]?]; export type AltCallback = ( - ctx: C, - next: ResultBody | undefined, - x: T[] + ctx: C, + next: ResultBody | undefined, + x: T[] ) => ResultBody | undefined; export type LitCallback = (ctx: C, x: T) => ResultBody | undefined; export type SeqCallback = ( - ctx: C, - buf: T[] + ctx: C, + buf: T[] ) => ResultBody | undefined; export type AltFallback = SeqCallback; diff --git a/packages/fsm/src/fsm.ts b/packages/fsm/src/fsm.ts index 233618c245..38a0f32fcd 100644 --- a/packages/fsm/src/fsm.ts +++ b/packages/fsm/src/fsm.ts @@ -4,9 +4,9 @@ import { illegalState } from "@thi.ng/errors/illegal-state"; import type { Reducer, ReductionFn, Transducer } from "@thi.ng/transducers"; import { iterator } from "@thi.ng/transducers/iterator"; import { - ensureReduced, - isReduced, - unreduced, + ensureReduced, + isReduced, + unreduced, } from "@thi.ng/transducers/reduced"; import { Match, Matcher } from "./api.js"; @@ -44,79 +44,80 @@ import { Match, Matcher } from "./api.js"; * @param src - input */ export function fsm( - states: IObjectOf>, - ctx: C, - initial: string | number, - update?: Fn2 + states: IObjectOf>, + ctx: C, + initial: string | number, + update?: Fn2 ): Transducer; export function fsm( - states: IObjectOf>, - ctx: C, - initial: string | number, - update?: Fn2, - src?: Iterable + states: IObjectOf>, + ctx: C, + initial: string | number, + update?: Fn2, + src?: Iterable ): IterableIterator; export function fsm( - states: IObjectOf>, - ctx: C, - initial: string | number = "start", - update?: Fn2, - src?: Iterable + states: IObjectOf>, + ctx: C, + initial: string | number = "start", + update?: Fn2, + src?: Iterable ) { - return src - ? iterator(fsm(states, ctx, initial, update), src) - : ([init, complete, reduce]: Reducer) => { - let currID = initial; - let curr = states[initial] - ? states[initial]() - : illegalArgs(`invalid initial state: ${initial}`); - return >[ - init, - complete, - (acc, x) => { - update && update(ctx, x); - while (true) { - const { type, body } = curr(ctx, x); - const res = body && body[1]; - if (type >= Match.FULL) { - const next = body && states[body[0]]; - if (next) { - currID = body![0]; - curr = next(); - } else { - illegalState( - `unknown tx: ${currID} -> ${ - body && body[0] - }` - ); - } - if (res) { - acc = reduceResult(reduce, acc, res); - isReduced(res) && (acc = ensureReduced(acc)); - } - if (type === Match.FULL_NC && !isReduced(acc)) { - continue; - } - } else if (type === Match.FAIL) { - if (res) { - acc = reduceResult(reduce, acc, res); - } - return ensureReduced(acc); - } - break; - } - return acc; - }, - ]; - }; + return src + ? iterator(fsm(states, ctx, initial, update), src) + : ([init, complete, reduce]: Reducer) => { + let currID = initial; + let curr = states[initial] + ? states[initial]() + : illegalArgs(`invalid initial state: ${initial}`); + return >[ + init, + complete, + (acc, x) => { + update && update(ctx, x); + while (true) { + const { type, body } = curr(ctx, x); + const res = body && body[1]; + if (type >= Match.FULL) { + const next = body && states[body[0]]; + if (next) { + currID = body![0]; + curr = next(); + } else { + illegalState( + `unknown tx: ${currID} -> ${ + body && body[0] + }` + ); + } + if (res) { + acc = reduceResult(reduce, acc, res); + isReduced(res) && + (acc = ensureReduced(acc)); + } + if (type === Match.FULL_NC && !isReduced(acc)) { + continue; + } + } else if (type === Match.FAIL) { + if (res) { + acc = reduceResult(reduce, acc, res); + } + return ensureReduced(acc); + } + break; + } + return acc; + }, + ]; + }; } const reduceResult = (rfn: ReductionFn, acc: any, res: R[]) => { - for (let x of unreduced(res)) { - acc = rfn(acc, x); - if (isReduced(acc)) { - break; - } - } - return acc; + for (let x of unreduced(res)) { + acc = rfn(acc, x); + if (isReduced(acc)) { + break; + } + } + return acc; }; diff --git a/packages/fsm/src/lit.ts b/packages/fsm/src/lit.ts index 67515b171e..f3e1c7fc11 100644 --- a/packages/fsm/src/lit.ts +++ b/packages/fsm/src/lit.ts @@ -4,20 +4,20 @@ import { Match, Matcher, RES_PARTIAL, SeqCallback } from "./api.js"; import { result } from "./result.js"; export const lit = - ( - match: T[], - success?: SeqCallback, - fail?: SeqCallback, - equiv: Predicate2 = _equiv - ): Matcher => - () => { - const buf: T[] = []; - const n = match.length; - let i = 0; - return (ctx, x) => - equiv((buf.push(x), x), match[i++]) - ? i === n - ? result(success && success(ctx, buf)) - : RES_PARTIAL - : result(fail && fail(ctx, buf), Match.FAIL); - }; + ( + match: T[], + success?: SeqCallback, + fail?: SeqCallback, + equiv: Predicate2 = _equiv + ): Matcher => + () => { + const buf: T[] = []; + const n = match.length; + let i = 0; + return (ctx, x) => + equiv((buf.push(x), x), match[i++]) + ? i === n + ? result(success && success(ctx, buf)) + : RES_PARTIAL + : result(fail && fail(ctx, buf), Match.FAIL); + }; diff --git a/packages/fsm/src/never.ts b/packages/fsm/src/never.ts index 2b0d12bd3f..d2ccff45fd 100644 --- a/packages/fsm/src/never.ts +++ b/packages/fsm/src/never.ts @@ -6,7 +6,7 @@ import { result } from "./result.js"; * for any given input. Use {@link always} for the opposite effect. */ export const never = - (callback?: LitCallback): Matcher => - () => - (ctx, x) => - result(callback && callback(ctx, x), Match.FAIL); + (callback?: LitCallback): Matcher => + () => + (ctx, x) => + result(callback && callback(ctx, x), Match.FAIL); diff --git a/packages/fsm/src/not.ts b/packages/fsm/src/not.ts index 164820b342..5fd895aad8 100644 --- a/packages/fsm/src/not.ts +++ b/packages/fsm/src/not.ts @@ -12,22 +12,22 @@ import { result } from "./result.js"; * @param fail - failure callback */ export const not = - ( - match: Matcher, - success?: SeqCallback, - fail?: SeqCallback - ): Matcher => - () => { - let m = match(); - const buf: T[] = []; - return (ctx, x) => { - buf.push(x); - const { type } = m(ctx, x); - return type === Match.FAIL - ? result(success && success(ctx, buf)) - : type !== Match.PARTIAL - ? // TODO Match.FULL_NC handling? - result(fail && fail(ctx, buf), Match.FAIL) - : RES_PARTIAL; - }; - }; + ( + match: Matcher, + success?: SeqCallback, + fail?: SeqCallback + ): Matcher => + () => { + let m = match(); + const buf: T[] = []; + return (ctx, x) => { + buf.push(x); + const { type } = m(ctx, x); + return type === Match.FAIL + ? result(success && success(ctx, buf)) + : type !== Match.PARTIAL + ? // TODO Match.FULL_NC handling? + result(fail && fail(ctx, buf), Match.FAIL) + : RES_PARTIAL; + }; + }; diff --git a/packages/fsm/src/range.ts b/packages/fsm/src/range.ts index a509d63a0f..3716809c9c 100644 --- a/packages/fsm/src/range.ts +++ b/packages/fsm/src/range.ts @@ -13,17 +13,17 @@ import { result } from "./result.js"; * @param fail - failure callback */ export const range = - ( - min: T, - max: T, - success?: LitCallback, - fail?: LitCallback - ): Matcher => - () => - (ctx, x) => - x >= min && x <= max - ? result(success && success(ctx, x)) - : result(fail && fail(ctx, x)); + ( + min: T, + max: T, + success?: LitCallback, + fail?: LitCallback + ): Matcher => + () => + (ctx, x) => + x >= min && x <= max + ? result(success && success(ctx, x)) + : result(fail && fail(ctx, x)); /** * Matcher for single digit characters (0-9). @@ -32,8 +32,8 @@ export const range = * @param fail - failure callback */ export const digit = ( - success?: LitCallback, - fail?: LitCallback + success?: LitCallback, + fail?: LitCallback ): Matcher => altsLitObj(DIGITS, success, fail); /** @@ -43,8 +43,8 @@ export const digit = ( * @param fail - failure callback */ export const alpha = ( - success?: LitCallback, - fail?: LitCallback + success?: LitCallback, + fail?: LitCallback ): Matcher => altsLitObj(ALPHA, success, fail); /** @@ -54,8 +54,8 @@ export const alpha = ( * @param fail - failure callback */ export const alphaNum = ( - success?: LitCallback, - fail?: LitCallback + success?: LitCallback, + fail?: LitCallback ): Matcher => altsLitObj({ ...ALPHA, ...DIGITS }, success, fail); /** @@ -65,6 +65,6 @@ export const alphaNum = ( * @param fail - failure callback */ export const whitespace = ( - success?: LitCallback, - fail?: LitCallback + success?: LitCallback, + fail?: LitCallback ): Matcher => altsLitObj(WS, success, fail); diff --git a/packages/fsm/src/repeat.ts b/packages/fsm/src/repeat.ts index 68b73c5852..a84c10d3ce 100644 --- a/packages/fsm/src/repeat.ts +++ b/packages/fsm/src/repeat.ts @@ -13,35 +13,35 @@ import { result } from "./result.js"; * @param fail - failure callback */ export const repeat = - ( - match: Matcher, - min: number, - max: number, - success?: SeqCallback, - fail?: SeqCallback - ): Matcher => - () => { - let m = match(); - let i = 0; - const buf: T[] = []; - return (ctx, x) => { - buf.push(x); - const r = m(ctx, x); - if (r.type === Match.FULL) { - i++; - if (i === max) { - return result(success && success(ctx, buf)); - } - m = match(); - return RES_PARTIAL; - } else if (r.type === Match.FAIL) { - if (i >= min) { - buf.pop(); - return result(success && success(ctx, buf), Match.FULL_NC); - } else { - return result(fail && fail(ctx, buf), Match.FAIL); - } - } - return r; - }; - }; + ( + match: Matcher, + min: number, + max: number, + success?: SeqCallback, + fail?: SeqCallback + ): Matcher => + () => { + let m = match(); + let i = 0; + const buf: T[] = []; + return (ctx, x) => { + buf.push(x); + const r = m(ctx, x); + if (r.type === Match.FULL) { + i++; + if (i === max) { + return result(success && success(ctx, buf)); + } + m = match(); + return RES_PARTIAL; + } else if (r.type === Match.FAIL) { + if (i >= min) { + buf.pop(); + return result(success && success(ctx, buf), Match.FULL_NC); + } else { + return result(fail && fail(ctx, buf), Match.FAIL); + } + } + return r; + }; + }; diff --git a/packages/fsm/src/result.ts b/packages/fsm/src/result.ts index d0e850f75f..58085309f3 100644 --- a/packages/fsm/src/result.ts +++ b/packages/fsm/src/result.ts @@ -1,6 +1,6 @@ import { Match, MatchResult, ResultBody } from "./api.js"; export const result = ( - body?: ResultBody, - type = Match.FULL + body?: ResultBody, + type = Match.FULL ): MatchResult => ({ type, body }); diff --git a/packages/fsm/src/seq.ts b/packages/fsm/src/seq.ts index 84cc6a7e44..edc2980796 100644 --- a/packages/fsm/src/seq.ts +++ b/packages/fsm/src/seq.ts @@ -11,34 +11,34 @@ import { result } from "./result.js"; * @param fail - failure callback */ export const seq = - ( - matches: Matcher[], - success?: SeqCallback, - fail?: SeqCallback - ): Matcher => - () => { - let i = 0; - let m = matches[i](); - const n = matches.length - 1; - const buf: T[] = []; - return (ctx, x) => { - if (i > n) return result(fail && fail(ctx, buf), Match.FAIL); - success && buf.push(x); - while (i <= n) { - const { type } = m(ctx, x); - if (type >= Match.FULL) { - if (i === n) { - return result(success && success(ctx, buf)); - } - m = matches[++i](); - if (type === Match.FULL_NC) { - continue; - } - } - return type === Match.FAIL - ? result(fail && fail(ctx, buf), Match.FAIL) - : RES_PARTIAL; - } - return result(fail && fail(ctx, buf), Match.FAIL); - }; - }; + ( + matches: Matcher[], + success?: SeqCallback, + fail?: SeqCallback + ): Matcher => + () => { + let i = 0; + let m = matches[i](); + const n = matches.length - 1; + const buf: T[] = []; + return (ctx, x) => { + if (i > n) return result(fail && fail(ctx, buf), Match.FAIL); + success && buf.push(x); + while (i <= n) { + const { type } = m(ctx, x); + if (type >= Match.FULL) { + if (i === n) { + return result(success && success(ctx, buf)); + } + m = matches[++i](); + if (type === Match.FULL_NC) { + continue; + } + } + return type === Match.FAIL + ? result(fail && fail(ctx, buf), Match.FAIL) + : RES_PARTIAL; + } + return result(fail && fail(ctx, buf), Match.FAIL); + }; + }; diff --git a/packages/fsm/src/str.ts b/packages/fsm/src/str.ts index 9532e621bb..80f0e91591 100644 --- a/packages/fsm/src/str.ts +++ b/packages/fsm/src/str.ts @@ -17,30 +17,30 @@ import { result } from "./result.js"; * @param collect - true, if input is to be collected/buffered */ export const str = ( - str: string, - success?: LitCallback, - fail?: LitCallback, - collect = false + str: string, + success?: LitCallback, + fail?: LitCallback, + collect = false ): Matcher => - collect - ? () => { - let buf = ""; - return (ctx, x) => - (buf += x) === str - ? result(success && success(ctx, buf)) - : str.indexOf(buf) === 0 - ? RES_PARTIAL - : result(fail && fail(ctx, buf), Match.FAIL); - } - : () => { - let matched = 0; - let i = 0; - return (ctx, x) => { - str.charAt(i++) === x && matched++; - return matched === str.length - ? result(success && success(ctx, str)) - : matched === i - ? RES_PARTIAL - : result(fail && fail(ctx, ""), Match.FAIL); - }; - }; + collect + ? () => { + let buf = ""; + return (ctx, x) => + (buf += x) === str + ? result(success && success(ctx, buf)) + : str.indexOf(buf) === 0 + ? RES_PARTIAL + : result(fail && fail(ctx, buf), Match.FAIL); + } + : () => { + let matched = 0; + let i = 0; + return (ctx, x) => { + str.charAt(i++) === x && matched++; + return matched === str.length + ? result(success && success(ctx, str)) + : matched === i + ? RES_PARTIAL + : result(fail && fail(ctx, ""), Match.FAIL); + }; + }; diff --git a/packages/fsm/src/until.ts b/packages/fsm/src/until.ts index 34ddf34eec..787be63380 100644 --- a/packages/fsm/src/until.ts +++ b/packages/fsm/src/until.ts @@ -15,25 +15,25 @@ import { result } from "./result.js"; * @param callback - result callback */ export const untilStr = - ( - str: string, - callback?: LitCallback - ): Matcher => - () => { - let buf = ""; - return (ctx, x) => { - buf += x; - return buf.endsWith(str) - ? result( - callback && - callback( - ctx, - buf.substring(0, buf.length - str.length) - ) - ) - : RES_PARTIAL; - }; - }; + ( + str: string, + callback?: LitCallback + ): Matcher => + () => { + let buf = ""; + return (ctx, x) => { + buf += x; + return buf.endsWith(str) + ? result( + callback && + callback( + ctx, + buf.substring(0, buf.length - str.length) + ) + ) + : RES_PARTIAL; + }; + }; /** * Generic array version of {@link untilStr}. @@ -42,16 +42,16 @@ export const untilStr = * @param callback - result callback */ export const until = - (str: T[], callback?: LitCallback): Matcher => - () => { - let buf: T[] = []; - return (ctx, x) => { - buf.push(x); - return endsWith(buf, str) - ? result( - callback && - callback(ctx, buf.slice(0, buf.length - str.length)) - ) - : RES_PARTIAL; - }; - }; + (str: T[], callback?: LitCallback): Matcher => + () => { + let buf: T[] = []; + return (ctx, x) => { + buf.push(x); + return endsWith(buf, str) + ? result( + callback && + callback(ctx, buf.slice(0, buf.length - str.length)) + ) + : RES_PARTIAL; + }; + }; diff --git a/packages/fsm/tsconfig.json b/packages/fsm/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/fsm/tsconfig.json +++ b/packages/fsm/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/fuzzy-viz/api-extractor.json b/packages/fuzzy-viz/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/fuzzy-viz/api-extractor.json +++ b/packages/fuzzy-viz/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/fuzzy-viz/package.json b/packages/fuzzy-viz/package.json index 1e720e7b57..bbc16db6b4 100644 --- a/packages/fuzzy-viz/package.json +++ b/packages/fuzzy-viz/package.json @@ -1,94 +1,94 @@ { - "name": "@thi.ng/fuzzy-viz", - "version": "2.1.19", - "description": "Visualization, instrumentation & introspection utils for @thi.ng/fuzzy", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/fuzzy-viz#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/fuzzy": "^2.1.8", - "@thi.ng/hiccup": "^4.2.10", - "@thi.ng/hiccup-svg": "^4.3.3", - "@thi.ng/math": "^5.3.4", - "@thi.ng/strings": "^3.3.6", - "@thi.ng/text-canvas": "^2.4.1" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "area plot", - "ascii", - "debug", - "fuzzy", - "hiccup", - "inspect", - "instrumentation", - "logic", - "svg", - "typescript", - "visualization" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./strategy": { - "default": "./strategy.js" - }, - "./var": { - "default": "./var.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/fuzzy", - "year": 2020 - } + "name": "@thi.ng/fuzzy-viz", + "version": "2.1.19", + "description": "Visualization, instrumentation & introspection utils for @thi.ng/fuzzy", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/fuzzy-viz#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/fuzzy": "^2.1.8", + "@thi.ng/hiccup": "^4.2.10", + "@thi.ng/hiccup-svg": "^4.3.3", + "@thi.ng/math": "^5.3.4", + "@thi.ng/strings": "^3.3.6", + "@thi.ng/text-canvas": "^2.4.1" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "area plot", + "ascii", + "debug", + "fuzzy", + "hiccup", + "inspect", + "instrumentation", + "logic", + "svg", + "typescript", + "visualization" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./strategy": { + "default": "./strategy.js" + }, + "./var": { + "default": "./var.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/fuzzy", + "year": 2020 + } } diff --git a/packages/fuzzy-viz/src/api.ts b/packages/fuzzy-viz/src/api.ts index 400b71da5b..c24e9ab785 100644 --- a/packages/fuzzy-viz/src/api.ts +++ b/packages/fuzzy-viz/src/api.ts @@ -4,59 +4,59 @@ import type { FuzzyFn, LVarDomain } from "@thi.ng/fuzzy"; export type InstrumentFn = Fn3; export interface AsciiVizOpts { - /** - * Width in characters - * - * @defaultValue 100 - */ - width: number; - /** - * Height in characters - * - * @defaultValue 16 - */ - height: number; - /** - * Char to use for empty space - * - * @defaultValue "." - */ - empty: string; + /** + * Width in characters + * + * @defaultValue 100 + */ + width: number; + /** + * Height in characters + * + * @defaultValue 16 + */ + height: number; + /** + * Char to use for empty space + * + * @defaultValue "." + */ + empty: string; } export interface VizualizeVarOpts { - /** - * Number of samples to evaluate for each fuzzy set. - * - * @defaultValue 200 - */ - samples: number; - /** - * Visualization width - * - * @defaultValue 600 - */ - width: number; - /** - * Visualization height - * - * @defaultValue 100 - */ - height: number; - /** - * If true, includes a legend of color coded labels of the fuzzy sets. - * - * @defaultValue true - */ - labels: boolean; - /** - * Color factory function. Converts number in [0..1) interval into an CSS - * color string. - */ - stroke: Fn; - /** - * Color factory function. Converts number in [0..1) interval into an CSS - * color string. - */ - fill: Fn; + /** + * Number of samples to evaluate for each fuzzy set. + * + * @defaultValue 200 + */ + samples: number; + /** + * Visualization width + * + * @defaultValue 600 + */ + width: number; + /** + * Visualization height + * + * @defaultValue 100 + */ + height: number; + /** + * If true, includes a legend of color coded labels of the fuzzy sets. + * + * @defaultValue true + */ + labels: boolean; + /** + * Color factory function. Converts number in [0..1) interval into an CSS + * color string. + */ + stroke: Fn; + /** + * Color factory function. Converts number in [0..1) interval into an CSS + * color string. + */ + fill: Fn; } diff --git a/packages/fuzzy-viz/src/strategy.ts b/packages/fuzzy-viz/src/strategy.ts index 55b4d16b2c..5d99966296 100644 --- a/packages/fuzzy-viz/src/strategy.ts +++ b/packages/fuzzy-viz/src/strategy.ts @@ -58,73 +58,73 @@ import { varToHiccup } from "./var.js"; * @param instrument - */ export const instrumentStrategy = ( - strategy: DefuzzStrategy, - instrument: Fn3 + strategy: DefuzzStrategy, + instrument: Fn3 ) => { - const acc: T[] = []; - const impl: DefuzzStrategy & IClear & IDeref = (fn, domain) => { - const res = strategy(fn, domain); - acc.push(instrument(fn, domain, res)); - return res; - }; - impl.clear = () => (acc.length = 0); - impl.deref = () => acc; - return impl; + const acc: T[] = []; + const impl: DefuzzStrategy & IClear & IDeref = (fn, domain) => { + const res = strategy(fn, domain); + acc.push(instrument(fn, domain, res)); + return res; + }; + impl.clear = () => (acc.length = 0); + impl.deref = () => acc; + return impl; }; export const fuzzySetToHiccup = - (opts?: Partial): InstrumentFn => - (fn, domain, res) => { - const tree = varToHiccup(variable(domain, { main: fn }), { - labels: false, - stroke: () => "#333", - fill: () => "#999", - ...opts, - }); - const { width, height } = tree[1]; - const x = fit(res, domain[0], domain[1], 0, width); - tree.push([ - "g", - { translate: [x, 0] }, - ["line", { stroke: "red" }, [0, 0], [0, height - 12]], - [ - "text", - { align: "center", fill: "red" }, - [0, height - 2], - res.toFixed(2), - ], - ]); - return tree; - }; + (opts?: Partial): InstrumentFn => + (fn, domain, res) => { + const tree = varToHiccup(variable(domain, { main: fn }), { + labels: false, + stroke: () => "#333", + fill: () => "#999", + ...opts, + }); + const { width, height } = tree[1]; + const x = fit(res, domain[0], domain[1], 0, width); + tree.push([ + "g", + { translate: [x, 0] }, + ["line", { stroke: "red" }, [0, 0], [0, height - 12]], + [ + "text", + { align: "center", fill: "red" }, + [0, height - 2], + res.toFixed(2), + ], + ]); + return tree; + }; export const fuzzySetToSvg = - (opts?: Partial): InstrumentFn => - (fn, domain, res) => - serialize(convertTree(fuzzySetToHiccup(opts)(fn, domain, res))); + (opts?: Partial): InstrumentFn => + (fn, domain, res) => + serialize(convertTree(fuzzySetToHiccup(opts)(fn, domain, res))); export const fuzzySetToAscii = - (opts?: Partial): InstrumentFn => - (fn, domain, res) => { - const { width, height, empty } = { - width: 100, - height: 16, - empty: ".", - ...opts, - }; - const [min, max] = domain; - const delta = (max - min) / width; - const vals: number[] = []; - for (let i = min; i <= max; i += delta) { - vals.push(fn(i)); - } - const index = Math.round(fit(res, min, max, 0, vals.length)); - let chart = barChartHLines(height, vals, 0, 1) - .map( - (line) => - line.substring(0, index) + "|" + line.substring(index + 1) - ) - .join("\n") - .replace(/ /g, empty); - const legend = repeat(" ", index) + "^ " + res.toFixed(2); - return chart + "\n" + legend; - }; + (opts?: Partial): InstrumentFn => + (fn, domain, res) => { + const { width, height, empty } = { + width: 100, + height: 16, + empty: ".", + ...opts, + }; + const [min, max] = domain; + const delta = (max - min) / width; + const vals: number[] = []; + for (let i = min; i <= max; i += delta) { + vals.push(fn(i)); + } + const index = Math.round(fit(res, min, max, 0, vals.length)); + let chart = barChartHLines(height, vals, 0, 1) + .map( + (line) => + line.substring(0, index) + "|" + line.substring(index + 1) + ) + .join("\n") + .replace(/ /g, empty); + const legend = repeat(" ", index) + "^ " + res.toFixed(2); + return chart + "\n" + legend; + }; diff --git a/packages/fuzzy-viz/src/var.ts b/packages/fuzzy-viz/src/var.ts index 26cf2158f3..4c04e44801 100644 --- a/packages/fuzzy-viz/src/var.ts +++ b/packages/fuzzy-viz/src/var.ts @@ -12,107 +12,107 @@ import type { VizualizeVarOpts } from "./api.js"; * polygons. Returns a {@link @thi.ng/hiccup-canvas#} compatible shape component * tree. * - * @param var - - * @param opts - + * @param var - + * @param opts - */ export const varToHiccup = ( - { domain: [min, max], terms }: LVar, - opts: Partial = {} + { domain: [min, max], terms }: LVar, + opts: Partial = {} ) => { - const { - samples, - width, - height, - labels, - stroke: strokeFn, - fill: fillFn, - } = { - samples: 200, - width: 600, - height: 100, - labels: true, - stroke: (x: number) => `hsl(${(x * 360) | 0},100%,40%)`, - fill: (x: number) => `hsla(${(x * 360) | 0},100%,50%,20%)`, - ...opts, - }; - const keys = Object.keys(terms); - const dt = (max - min) / samples; - const ds = width / samples; - const dn = 1 / keys.length; - const curves: any[] = []; - const legend: any[] = []; - for (let i = 0; i < keys.length; i++) { - const id = keys[i]; - const f = terms[id]; - const y = (i + 1) * 12; - const stroke = strokeFn(i * dn); - const curr: number[][] = []; - for (let i = 0; i <= samples; i++) { - curr.push([i * ds, (1 - f(min + i * dt)) * height]); - } - curr.push([width, height], [0, height]); - curves.push([ - "polygon", - { - stroke, - fill: fillFn(i * dn), - }, - curr, - ]); - if (labels) { - legend.push( - ["line", { stroke }, [0, y], [20, y]], - [ - "text", - { - baseline: "middle", - fill: "black", - }, - [30, y], - id, - ] - ); - } - } - const zero = fit(0, min, max, 0, width); - return svg( - { - width, - height: height + 12, - fill: "none", - "font-family": "sans-serif", - "font-size": 10, - }, - ...curves, - ...legend, - inRange(zero, width * 0.05, width * 0.95) - ? [ - "g", - {}, - [ - "line", - { - stroke: "black", - dash: [1, 1], - }, - [zero, 0], - [zero, height], - ], - [ - "text", - { align: "center", fill: "black" }, - [zero, height + 10], - "0.00", - ], - ] - : null, - [ - "g", - { fill: "black" }, - ["text", {}, [0, height + 10], min.toFixed(2)], - ["text", { align: "end" }, [width, height + 10], max.toFixed(2)], - ] - ); + const { + samples, + width, + height, + labels, + stroke: strokeFn, + fill: fillFn, + } = { + samples: 200, + width: 600, + height: 100, + labels: true, + stroke: (x: number) => `hsl(${(x * 360) | 0},100%,40%)`, + fill: (x: number) => `hsla(${(x * 360) | 0},100%,50%,20%)`, + ...opts, + }; + const keys = Object.keys(terms); + const dt = (max - min) / samples; + const ds = width / samples; + const dn = 1 / keys.length; + const curves: any[] = []; + const legend: any[] = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const f = terms[id]; + const y = (i + 1) * 12; + const stroke = strokeFn(i * dn); + const curr: number[][] = []; + for (let i = 0; i <= samples; i++) { + curr.push([i * ds, (1 - f(min + i * dt)) * height]); + } + curr.push([width, height], [0, height]); + curves.push([ + "polygon", + { + stroke, + fill: fillFn(i * dn), + }, + curr, + ]); + if (labels) { + legend.push( + ["line", { stroke }, [0, y], [20, y]], + [ + "text", + { + baseline: "middle", + fill: "black", + }, + [30, y], + id, + ] + ); + } + } + const zero = fit(0, min, max, 0, width); + return svg( + { + width, + height: height + 12, + fill: "none", + "font-family": "sans-serif", + "font-size": 10, + }, + ...curves, + ...legend, + inRange(zero, width * 0.05, width * 0.95) + ? [ + "g", + {}, + [ + "line", + { + stroke: "black", + dash: [1, 1], + }, + [zero, 0], + [zero, height], + ], + [ + "text", + { align: "center", fill: "black" }, + [zero, height + 10], + "0.00", + ], + ] + : null, + [ + "g", + { fill: "black" }, + ["text", {}, [0, height + 10], min.toFixed(2)], + ["text", { align: "end" }, [width, height + 10], max.toFixed(2)], + ] + ); }; /** @@ -120,7 +120,7 @@ export const varToHiccup = ( * actual SVG string. * * @param $var - * @param opts - + * @param opts - */ export const varToSvg = ($var: LVar, opts: Partial) => - serialize(convertTree(varToHiccup($var, opts))); + serialize(convertTree(varToHiccup($var, opts))); diff --git a/packages/fuzzy-viz/test/index.ts b/packages/fuzzy-viz/test/index.ts index 1f4f8b6898..f061115951 100644 --- a/packages/fuzzy-viz/test/index.ts +++ b/packages/fuzzy-viz/test/index.ts @@ -2,17 +2,17 @@ import { centroidStrategy, gaussian } from "@thi.ng/fuzzy"; import { eqDelta } from "@thi.ng/math"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { fuzzySetToAscii, instrumentStrategy } from "../src/index.js" +import { fuzzySetToAscii, instrumentStrategy } from "../src/index.js"; group("fuzzy-viz", { - "strategy (ascii)": () => { - const strategy = instrumentStrategy( - centroidStrategy({ samples: 1000 }), - fuzzySetToAscii({ width: 40, height: 8 }) - ); - assert.ok(eqDelta(strategy(gaussian(5, 2), [0, 10]), 5)); - assert.deepStrictEqual(strategy.deref(), [ - `.................▄▆█|█▆▄................. + "strategy (ascii)": () => { + const strategy = instrumentStrategy( + centroidStrategy({ samples: 1000 }), + fuzzySetToAscii({ width: 40, height: 8 }) + ); + assert.ok(eqDelta(strategy(gaussian(5, 2), [0, 10]), 5)); + assert.deepStrictEqual(strategy.deref(), [ + `.................▄▆█|█▆▄................. ...............▅████|████▅............... .............▄██████|██████▄............. ...........▂▇███████|███████▇▂........... @@ -21,6 +21,6 @@ group("fuzzy-viz", { .....▃▆█████████████|█████████████▆▃..... ▃▄▅▇████████████████|████████████████▇▅▄▃ ^ 5.00`, - ]); - }, + ]); + }, }); diff --git a/packages/fuzzy-viz/tsconfig.json b/packages/fuzzy-viz/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/fuzzy-viz/tsconfig.json +++ b/packages/fuzzy-viz/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/fuzzy/api-extractor.json b/packages/fuzzy/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/fuzzy/api-extractor.json +++ b/packages/fuzzy/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/fuzzy/package.json b/packages/fuzzy/package.json index 599cacfe49..801ca54caf 100644 --- a/packages/fuzzy/package.json +++ b/packages/fuzzy/package.json @@ -1,108 +1,108 @@ { - "name": "@thi.ng/fuzzy", - "version": "2.1.8", - "description": "Fuzzy logic operators & configurable rule inferencing engine", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/fuzzy#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc strategies", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/math": "^5.3.4" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "agent", - "functional", - "math", - "fuzzy", - "inference", - "logic", - "rule", - "t-norm", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "strategies" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./defuzz": { - "default": "./defuzz.js" - }, - "./rules": { - "default": "./rules.js" - }, - "./shapes": { - "default": "./shapes.js" - }, - "./strategies/bisector": { - "default": "./strategies/bisector.js" - }, - "./strategies/centroid": { - "default": "./strategies/centroid.js" - }, - "./strategies/maxima": { - "default": "./strategies/maxima.js" - }, - "./strategies/opts": { - "default": "./strategies/opts.js" - }, - "./tnorms": { - "default": "./tnorms.js" - }, - "./var": { - "default": "./var.js" - } - }, - "thi.ng": { - "year": 2020 - } + "name": "@thi.ng/fuzzy", + "version": "2.1.8", + "description": "Fuzzy logic operators & configurable rule inferencing engine", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/fuzzy#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc strategies", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/math": "^5.3.4" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "agent", + "functional", + "math", + "fuzzy", + "inference", + "logic", + "rule", + "t-norm", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "strategies" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./defuzz": { + "default": "./defuzz.js" + }, + "./rules": { + "default": "./rules.js" + }, + "./shapes": { + "default": "./shapes.js" + }, + "./strategies/bisector": { + "default": "./strategies/bisector.js" + }, + "./strategies/centroid": { + "default": "./strategies/centroid.js" + }, + "./strategies/maxima": { + "default": "./strategies/maxima.js" + }, + "./strategies/opts": { + "default": "./strategies/opts.js" + }, + "./tnorms": { + "default": "./tnorms.js" + }, + "./var": { + "default": "./var.js" + } + }, + "thi.ng": { + "year": 2020 + } } diff --git a/packages/fuzzy/src/api.ts b/packages/fuzzy/src/api.ts index 5386c5d0a4..812a76c641 100644 --- a/packages/fuzzy/src/api.ts +++ b/packages/fuzzy/src/api.ts @@ -14,52 +14,50 @@ export type LVarSet = Record>; export type LVarKeys> = keyof T["terms"]; -export type LVarKeySet, K extends keyof I> = Partial< - { - [k in K]: LVarKeys; - } ->; +export type LVarKeySet, K extends keyof I> = Partial<{ + [k in K]: LVarKeys; +}>; /** * Linguistic Variable, defining several (possibly overlapping) fuzzy sets in an * overall global domain. */ export interface LVar { - /** - * Value domain/interval used to evaluate (and integrate) all terms during - * {@link defuzz}. Interval is semi-open, i.e. `[min, max)` - * - * @remarks - * The domain can be smaller or larger than the actual union bounds of the - * defined sets. However, for precision and performance reasons, it's - * recommended to keep this interval as compact as possible. - */ - domain: LVarDomain; - /** - * Object of named fuzzy sets. - */ - terms: LVarTerms; + /** + * Value domain/interval used to evaluate (and integrate) all terms during + * {@link defuzz}. Interval is semi-open, i.e. `[min, max)` + * + * @remarks + * The domain can be smaller or larger than the actual union bounds of the + * defined sets. However, for precision and performance reasons, it's + * recommended to keep this interval as compact as possible. + */ + domain: LVarDomain; + /** + * Object of named fuzzy sets. + */ + terms: LVarTerms; } export interface Rule, O extends LVarSet> { - op: FnN2; - if: LVarKeySet; - then: LVarKeySet; - weight: number; + op: FnN2; + if: LVarKeySet; + then: LVarKeySet; + weight: number; } export interface DefuzzStrategyOpts { - /** - * Number of samples/steps to use for integration of the fuzzy set. - * - * @defaultValue 100 - */ - samples: number; - /** - * Tolerance value (only used by some strategies, e.g. - * {@link meanOfMaximaStrategy}). - * - * @defaultValue 1e-6 - */ - eps: number; + /** + * Number of samples/steps to use for integration of the fuzzy set. + * + * @defaultValue 100 + */ + samples: number; + /** + * Tolerance value (only used by some strategies, e.g. + * {@link meanOfMaximaStrategy}). + * + * @defaultValue 1e-6 + */ + eps: number; } diff --git a/packages/fuzzy/src/defuzz.ts b/packages/fuzzy/src/defuzz.ts index a5e3bc595d..084b507d58 100644 --- a/packages/fuzzy/src/defuzz.ts +++ b/packages/fuzzy/src/defuzz.ts @@ -22,54 +22,54 @@ import { snormMax, tnormMin } from "./tnorms.js"; * relevant output sets of all rules for integration/analysis by the given * defuzz `strategy` actually producing the crisp result. * - * @param ins - - * @param outs - - * @param rules - - * @param vals - - * @param strategy - - * @param imply - - * @param combine - + * @param ins - + * @param outs - + * @param rules - + * @param vals - + * @param strategy - + * @param imply - + * @param combine - */ export const defuzz = , O extends LVarSet>( - ins: I, - outs: O, - rules: Rule[], - vals: Partial>, - strategy = centroidStrategy(), - imply = tnormMin, - combine = snormMax + ins: I, + outs: O, + rules: Rule[], + vals: Partial>, + strategy = centroidStrategy(), + imply = tnormMin, + combine = snormMax ) => { - const ruleTerms = rules.map((r) => { - let alpha: number | null = null; - for (let id in vals) { - if (r.if[id]) { - const v = ins[id].terms[r.if[id]](vals[id]!); - alpha = alpha !== null ? r.op(alpha, v) : v; - } - } - const terms: IObjectOf = {}; - if (alpha) { - const aterm = constant(alpha); - for (let id in r.then) { - if (outs[id]) { - const oterm = outs[id].terms[r.then[id]]; - terms[id] = intersect( - imply, - r.weight == 1 ? oterm : weighted(oterm, r.weight), - aterm - ); - } - } - } - return terms; - }); + const ruleTerms = rules.map((r) => { + let alpha: number | null = null; + for (let id in vals) { + if (r.if[id]) { + const v = ins[id].terms[r.if[id]](vals[id]!); + alpha = alpha !== null ? r.op(alpha, v) : v; + } + } + const terms: IObjectOf = {}; + if (alpha) { + const aterm = constant(alpha); + for (let id in r.then) { + if (outs[id]) { + const oterm = outs[id].terms[r.then[id]]; + terms[id] = intersect( + imply, + r.weight == 1 ? oterm : weighted(oterm, r.weight), + aterm + ); + } + } + } + return terms; + }); - const res: Partial> = {}; - for (let id in outs) { - res[id] = strategy( - union(combine, ...ruleTerms.map((r) => r[id]).filter((f) => !!f)), - outs[id].domain - ); - } - return res; + const res: Partial> = {}; + for (let id in outs) { + res[id] = strategy( + union(combine, ...ruleTerms.map((r) => r[id]).filter((f) => !!f)), + outs[id].domain + ); + } + return res; }; diff --git a/packages/fuzzy/src/rules.ts b/packages/fuzzy/src/rules.ts index 60eec0879c..7f100767be 100644 --- a/packages/fuzzy/src/rules.ts +++ b/packages/fuzzy/src/rules.ts @@ -31,37 +31,37 @@ import { snormMax, tnormMin, tnormProduct } from "./tnorms.js"; * ) * ``` * - * @param op - + * @param op - * @param $if - * @param then - - * @param weight - + * @param then - + * @param weight - */ export const rule = , O extends LVarSet>( - op: FnN2, - $if: LVarKeySet, - then: LVarKeySet, - weight = 1 + op: FnN2, + $if: LVarKeySet, + then: LVarKeySet, + weight = 1 ): Rule => ({ - if: $if, - then, - op, - weight, + if: $if, + then, + op, + weight, }); export const and = , O extends LVarSet>( - $if: LVarKeySet, - then: LVarKeySet, - weight?: number + $if: LVarKeySet, + then: LVarKeySet, + weight?: number ) => rule(tnormMin, $if, then, weight); export const strongAnd = , O extends LVarSet>( - $if: LVarKeySet, - then: LVarKeySet, - weight?: number + $if: LVarKeySet, + then: LVarKeySet, + weight?: number ) => rule(tnormProduct, $if, then, weight); export const or = , O extends LVarSet>( - $if: LVarKeySet, - then: LVarKeySet, - weight?: number + $if: LVarKeySet, + then: LVarKeySet, + weight?: number ) => rule(snormMax, $if, then, weight); diff --git a/packages/fuzzy/src/shapes.ts b/packages/fuzzy/src/shapes.ts index 82e8fc1bd0..8d4342364e 100644 --- a/packages/fuzzy/src/shapes.ts +++ b/packages/fuzzy/src/shapes.ts @@ -9,150 +9,150 @@ import type { FuzzyFn } from "./api.js"; * HOF {@link FuzzyFn} always yielding given `x` (should be in [0,1] * interval). * - * @param x - + * @param x - */ export const constant = - (x: number): FuzzyFn => - () => - x; + (x: number): FuzzyFn => + () => + x; /** * HOF {@link FuzzyFn} which takes a value `p` and tolerance `eps`, then yields * a discrete window function: `|p - x| <= eps ? 1 : 0` * - * @param p - - * @param eps - + * @param p - + * @param eps - */ export const point = - (p: number, eps = EPS): FuzzyFn => - (x) => - eqDelta(x, p, eps) ? 1 : 0; + (p: number, eps = EPS): FuzzyFn => + (x) => + eqDelta(x, p, eps) ? 1 : 0; /** * HOF {@link FuzzyFn} yielding a rising ramp in [a,b] interval, clamped to * [0,1] outputs. Returns 0.0 for inputs <= `a` and 1.0 for inputs >= `b`. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const ramp: FnU2 = (a, b) => (x) => - fitClamped(x, a, b, 0, 1); + fitClamped(x, a, b, 0, 1); /** * HOF {@link FuzzyFn} yielding a triangle in the input range `[a..b..c]` with * `b` defining the position of the peak value (1.0). Returns 0.0 for inputs < * `a` or > `c`. * - * @param a - - * @param b - - * @param c - + * @param a - + * @param b - + * @param c - */ export const triangle: FnU3 = (a, b, c) => (x) => - x < a || x > c ? 0 : x <= b ? fit(x, a, b, 0, 1) : fit(x, b, c, 1, 0); + x < a || x > c ? 0 : x <= b ? fit(x, a, b, 0, 1) : fit(x, b, c, 1, 0); /** * Similar to {@link triangle}, but yielding a trapezoid for the input range * `[a..b..c..d]` with `b` and `c` defining the peak value range (with 1.0 * outputs). Returns 0.0 for inputs < `a` or > `d`. * - * @param a - - * @param b - - * @param c - - * @param d - + * @param a - + * @param b - + * @param c - + * @param d - */ export const trapezoid: FnU4 = (a, b, c, d) => (x) => - x < a || x > d - ? 0 - : x > b && x < c - ? 1 - : x <= b - ? fit(x, a, b, 0, 1) - : fit(x, c, d, 1, 0); + x < a || x > d + ? 0 + : x > b && x < c + ? 1 + : x <= b + ? fit(x, a, b, 0, 1) + : fit(x, c, d, 1, 0); /** * HOF {@link FuzzyFn}, yielding sigmoid curve with configurable `steep` and * positioned such that `f(bias) = 0.5`. * - * @param bias - - * @param steep - + * @param bias - + * @param steep - */ export const sigmoid: FnU2 = (bias, steep) => (x) => - $sigmoid(bias, steep, x); + $sigmoid(bias, steep, x); /** * HOF {@link FuzzyFn}, yielding gaussian bell curve with its peak at `bias` and * width defined by `sigma`. * - * @param bias - - * @param sigma - + * @param bias - + * @param sigma - */ export const gaussian: FnU2 = (bias, sigma) => (x) => - $gaussian(bias, sigma, x); + $gaussian(bias, sigma, x); /** * Higher-order function: Takes an existing {@link FuzzyFn} `fn` and returns * a new one producing its negated outcome aka `1 - fn(x)`. * - * @param fn - + * @param fn - */ export const negate: FnU = (fn) => (x) => 1 - fn(x); /** * Inverse of {@link ramp}, i.e. a falling slope from `a` -> `b`. * - * @param a - - * @param b - + * @param a - + * @param b - */ export const invRamp: FnU2 = (a, b) => negate(ramp(a, b)); /** * Inverse of {@link sigmoid}. * - * @param bias - - * @param steep - + * @param bias - + * @param steep - */ export const invSigmoid: FnU2 = (bias, steep) => - negate(sigmoid(bias, steep)); + negate(sigmoid(bias, steep)); /** * Higher-order function: Takes an existing {@link FuzzyFn} `fn` and `weight` * factor. Returns new function which computes: `weight * fn(x)`. * - * @param fn - - * @param weight - + * @param fn - + * @param weight - */ export const weighted = - (fn: FuzzyFn, weight: number): FuzzyFn => - (x) => - weight * fn(x); + (fn: FuzzyFn, weight: number): FuzzyFn => + (x) => + weight * fn(x); /** * Higher order function. Returns new function which selects subset of given * fuzzy set where `fn(x) > alpha`, or else returns 0. * - * @param fn - - * @param alpha - + * @param fn - + * @param alpha - */ export const alphaCut = - (fn: FuzzyFn, alpha = 0.5): FuzzyFn => - (x) => { - const y = fn(x); - return y > alpha ? y : 0; - }; + (fn: FuzzyFn, alpha = 0.5): FuzzyFn => + (x) => { + const y = fn(x); + return y > alpha ? y : 0; + }; /** * Higher order function. Returns new function which selects subset of given * fuzzy set where `fn(x) < alpha`, or else returns 0. * - * @param fn - - * @param alpha - + * @param fn - + * @param alpha - */ export const invAlphaCut = - (fn: FuzzyFn, alpha = 0.5): FuzzyFn => - (x) => { - const y = fn(x); - return y < alpha ? y : 0; - }; + (fn: FuzzyFn, alpha = 0.5): FuzzyFn => + (x) => { + const y = fn(x); + return y < alpha ? y : 0; + }; /** * Higher order function, complex shape generator. Takes a T-norm (or S-norm) as @@ -196,43 +196,43 @@ export const invAlphaCut = * M(5) // 1 * ``` * - * @param op - - * @param initial - - * @param fns - + * @param op - + * @param initial - + * @param fns - */ export const compose = ( - op: FnN2, - initial: number, - ...fns: FuzzyFn[] + op: FnN2, + initial: number, + ...fns: FuzzyFn[] ): FuzzyFn => { - const [a, b] = fns; - switch (fns.length) { - case 0: - throw new Error("no fuzzy sets given"); - case 1: - return a; - case 2: - return (x) => op(a(x), b(x)); - default: - return (x) => fns.reduce((acc, f) => op(acc, f(x)), initial); - } + const [a, b] = fns; + switch (fns.length) { + case 0: + throw new Error("no fuzzy sets given"); + case 1: + return a; + case 2: + return (x) => op(a(x), b(x)); + default: + return (x) => fns.reduce((acc, f) => op(acc, f(x)), initial); + } }; /** * Syntax sugar for {@link compose} with an initial value of 1.0. The `op` is * supposed to be a T-norm. * - * @param op - - * @param fns - + * @param op - + * @param fns - */ export const intersect = (op: FnN2, ...fns: FuzzyFn[]) => - compose(op, 1, ...fns); + compose(op, 1, ...fns); /** * Syntax sugar for {@link compose} with an initial value of 0.0. The `op` is * supposed to be a S-norm. * - * @param op - - * @param fns - + * @param op - + * @param fns - */ export const union = (op: FnN2, ...fns: FuzzyFn[]) => compose(op, 0, ...fns); diff --git a/packages/fuzzy/src/strategies/bisector.ts b/packages/fuzzy/src/strategies/bisector.ts index 50b236a548..8ef819e7c8 100644 --- a/packages/fuzzy/src/strategies/bisector.ts +++ b/packages/fuzzy/src/strategies/bisector.ts @@ -30,32 +30,32 @@ import { defaultOpts } from "./opts.js"; * // ^ 2.97 * ``` * - * @param opts - + * @param opts - */ export const bisectorStrategy = ( - opts?: Partial + opts?: Partial ): DefuzzStrategy => { - let { samples } = defaultOpts(opts); - return (fn, [min, max]) => { - const delta = (max - min) / samples; - let sum: number[] = []; - for (let i = 0, acc = 0; i <= samples; i++) { - acc += fn(min + i * delta); - sum.push(acc); - } - if (!sum.length) return min; - const mean = sum[samples] * 0.5; - for (let i = 1; i <= samples; i++) { - if (sum[i] >= mean) { - return fit( - mean, - sum[i - 1], - sum[i], - min + (i - 1) * delta, - min + i * delta - ); - } - } - return min; - }; + let { samples } = defaultOpts(opts); + return (fn, [min, max]) => { + const delta = (max - min) / samples; + let sum: number[] = []; + for (let i = 0, acc = 0; i <= samples; i++) { + acc += fn(min + i * delta); + sum.push(acc); + } + if (!sum.length) return min; + const mean = sum[samples] * 0.5; + for (let i = 1; i <= samples; i++) { + if (sum[i] >= mean) { + return fit( + mean, + sum[i - 1], + sum[i], + min + (i - 1) * delta, + min + i * delta + ); + } + } + return min; + }; }; diff --git a/packages/fuzzy/src/strategies/centroid.ts b/packages/fuzzy/src/strategies/centroid.ts index 6adfb747ff..7db4cc84ac 100644 --- a/packages/fuzzy/src/strategies/centroid.ts +++ b/packages/fuzzy/src/strategies/centroid.ts @@ -29,22 +29,22 @@ import { defaultOpts } from "./opts.js"; * // ^ 3.00 * ``` * - * @param opts - + * @param opts - */ export const centroidStrategy = ( - opts?: Partial + opts?: Partial ): DefuzzStrategy => { - let { samples } = defaultOpts(opts); - return (fn, [min, max]) => { - const delta = (max - min) / samples; - let num = 0; - let denom = 0; - for (let i = 0; i <= samples; i++) { - const x = min + i * delta; - const y = fn(x); - num += x * y; - denom += y; - } - return num / denom; - }; + let { samples } = defaultOpts(opts); + return (fn, [min, max]) => { + const delta = (max - min) / samples; + let num = 0; + let denom = 0; + for (let i = 0; i <= samples; i++) { + const x = min + i * delta; + const y = fn(x); + num += x * y; + denom += y; + } + return num / denom; + }; }; diff --git a/packages/fuzzy/src/strategies/maxima.ts b/packages/fuzzy/src/strategies/maxima.ts index fe8e063316..30b10faac3 100644 --- a/packages/fuzzy/src/strategies/maxima.ts +++ b/packages/fuzzy/src/strategies/maxima.ts @@ -27,31 +27,31 @@ import { defaultOpts } from "./opts.js"; * // ^ 3.00 * ``` * - * @param opts - + * @param opts - */ export const meanOfMaximaStrategy = ( - opts?: Partial + opts?: Partial ): DefuzzStrategy => { - const { samples, eps } = defaultOpts(opts); - return (fn, [min, max]) => { - const delta = (max - min) / samples; - let peak = -Infinity; - let peakPos = min; - let n = 1; - for (let i = 0; i <= samples; i++) { - const t = min + i * delta; - const x = fn(t); - if (eqDelta(x, peak, eps)) { - peakPos += t; - n++; - } else if (x > peak) { - peak = x; - peakPos = t; - n = 1; - } - } - return peakPos / n; - }; + const { samples, eps } = defaultOpts(opts); + return (fn, [min, max]) => { + const delta = (max - min) / samples; + let peak = -Infinity; + let peakPos = min; + let n = 1; + for (let i = 0; i <= samples; i++) { + const t = min + i * delta; + const x = fn(t); + if (eqDelta(x, peak, eps)) { + peakPos += t; + n++; + } else if (x > peak) { + peak = x; + peakPos = t; + n = 1; + } + } + return peakPos / n; + }; }; /** @@ -80,26 +80,26 @@ export const meanOfMaximaStrategy = ( * // ^ 1.02 * ``` * - * @param opts - + * @param opts - */ export const firstOfMaximaStrategy = ( - opts?: Partial + opts?: Partial ): DefuzzStrategy => { - const { samples } = defaultOpts(opts); - return (fn, [min, max]) => { - const delta = (max - min) / samples; - let peak = -Infinity; - let peakPos = min; - for (let i = 0; i <= samples; i++) { - const t = min + i * delta; - const x = fn(t); - if (x > peak) { - peak = x; - peakPos = t; - } - } - return peakPos; - }; + const { samples } = defaultOpts(opts); + return (fn, [min, max]) => { + const delta = (max - min) / samples; + let peak = -Infinity; + let peakPos = min; + for (let i = 0; i <= samples; i++) { + const t = min + i * delta; + const x = fn(t); + if (x > peak) { + peak = x; + peakPos = t; + } + } + return peakPos; + }; }; /** @@ -128,11 +128,11 @@ export const firstOfMaximaStrategy = ( * // ^ 4.98 * ``` * - * @param opts - + * @param opts - */ export const lastOfMaximaStrategy = ( - opts?: Partial + opts?: Partial ): DefuzzStrategy => { - const impl = firstOfMaximaStrategy(opts); - return (fn, [min, max]) => impl(fn, [max, min]); + const impl = firstOfMaximaStrategy(opts); + return (fn, [min, max]) => impl(fn, [max, min]); }; diff --git a/packages/fuzzy/src/strategies/opts.ts b/packages/fuzzy/src/strategies/opts.ts index 5a36045260..6b2a973461 100644 --- a/packages/fuzzy/src/strategies/opts.ts +++ b/packages/fuzzy/src/strategies/opts.ts @@ -1,9 +1,9 @@ import type { DefuzzStrategyOpts } from "../api.js"; export const defaultOpts = ( - opts?: Partial + opts?: Partial ): DefuzzStrategyOpts => ({ - samples: 100, - eps: 1e-6, - ...opts, + samples: 100, + eps: 1e-6, + ...opts, }); diff --git a/packages/fuzzy/src/tnorms.ts b/packages/fuzzy/src/tnorms.ts index 4b8112b737..022f51ba32 100644 --- a/packages/fuzzy/src/tnorms.ts +++ b/packages/fuzzy/src/tnorms.ts @@ -23,9 +23,9 @@ export const tnormNilpotent: FnN2 = (x, y) => (x + y > 1 ? Math.min(x, y) : 0); * @param p - curve param [0..∞], default: 2 */ export const tnormHamacher = - (p = 2): FnN2 => - (x, y) => - x === 0 && y === 0 ? 0 : (x * y) / (p + (1 - p) * (x + y - x * y)); + (p = 2): FnN2 => + (x, y) => + x === 0 && y === 0 ? 0 : (x * y) / (p + (1 - p) * (x + y - x * y)); /** * HOF T-norm. Parametric Yager with `p` controlling curvature. @@ -36,9 +36,9 @@ export const tnormHamacher = * @param p - curve param [0..∞], default: 2 */ export const tnormYager = (p = 2): FnN2 => - p === 0 - ? () => 0 - : (x, y) => clamp0(1 - ((1 - x) ** p + (1 - y) ** p) ** (1 / p)); + p === 0 + ? () => 0 + : (x, y) => clamp0(1 - ((1 - x) ** p + (1 - y) ** p) ** (1 / p)); /** * HOF T-norm. Parametric Dombi with `p` controlling curvature. @@ -49,13 +49,15 @@ export const tnormYager = (p = 2): FnN2 => * @param p - curve param [0..∞], default: 2 */ export const tnormDombi = (p = 2): FnN2 => - p === 0 - ? () => 0 - : (x, y) => - x === 0 || y === 0 - ? 0 - : 1 / - (1 + (((1 - x) / x) ** p + ((1 - y) / y) ** p) ** (1 / p)); + p === 0 + ? () => 0 + : (x, y) => + x === 0 || y === 0 + ? 0 + : 1 / + (1 + + (((1 - x) / x) ** p + ((1 - y) / y) ** p) ** + (1 / p)); /** * HOF T-norm. Parametric Aczél–Alsina with `p` controlling curvature. @@ -66,19 +68,19 @@ export const tnormDombi = (p = 2): FnN2 => * @param p - curve param [0..∞], default: 2 */ export const tnormAczelAlsina = - (p = 2): FnN2 => - (x, y) => - Math.exp( - -( - (Math.abs(Math.log(x)) ** p + Math.abs(Math.log(y)) ** p) ** - (1 / p) - ) - ); + (p = 2): FnN2 => + (x, y) => + Math.exp( + -( + (Math.abs(Math.log(x)) ** p + Math.abs(Math.log(y)) ** p) ** + (1 / p) + ) + ); /** * S-norm (T-conorm), dual of {@link tnormMin}. * - * @param x - - * @param y - + * @param x - + * @param y - */ export const snormMax: FnN2 = (x, y) => Math.max(x, y); @@ -86,16 +88,16 @@ export const snormMax: FnN2 = (x, y) => Math.max(x, y); * S-norm (T-conorm), dual of {@link tnormProduct}, probabilistic sum: * `a + b - a * b` * - * @param x - - * @param y - + * @param x - + * @param y - */ export const snormProbabilistic: FnN2 = (x, y) => x + y - x * y; /** * S-norm (T-conorm), dual of {@link tnormLukasiewicz}. * - * @param x - - * @param y - + * @param x - + * @param y - */ export const snormBoundedSum: FnN2 = (x, y) => Math.min(x + y, 1); @@ -107,8 +109,8 @@ export const snormDrastic: FnN2 = (x, y) => (x === 0 ? y : y === 0 ? x : 1); /** * S-norm (T-conorm), dual of {@link tnormNilpotent}. * - * @param x - - * @param y - + * @param x - + * @param y - */ export const snormNilpotent: FnN2 = (x, y) => (x + y < 1 ? Math.max(x, y) : 1); @@ -116,8 +118,8 @@ export const snormNilpotent: FnN2 = (x, y) => (x + y < 1 ? Math.max(x, y) : 1); * S-norm (T-conorm), dual of {@link tnormHamacher}, iff that T-norm's curve * param is `p=2`. * - * @param x - - * @param y - + * @param x - + * @param y - */ export const snormEinstein: FnN2 = (x, y) => (x + y) / (1 + x * y); @@ -128,16 +130,16 @@ export const snormEinstein: FnN2 = (x, y) => (x + y) / (1 + x * y); * @remarks * Reference: https://en.wikipedia.org/wiki/Construction_of_t-norms#Ordinal_sums * - * @param specs - + * @param specs - */ export const ordinalSum = - (specs: { domain: Range; tnorm: FnN2 }[]): FnN2 => - (x, y) => { - for (let s of specs) { - const [a, b] = s.domain; - if (x >= a && x <= b && y >= a && y <= b) { - return a + (b - a) * s.tnorm(norm(x, a, b), norm(y, a, b)); - } - } - return Math.min(x, y); - }; + (specs: { domain: Range; tnorm: FnN2 }[]): FnN2 => + (x, y) => { + for (let s of specs) { + const [a, b] = s.domain; + if (x >= a && x <= b && y >= a && y <= b) { + return a + (b - a) * s.tnorm(norm(x, a, b), norm(y, a, b)); + } + } + return Math.min(x, y); + }; diff --git a/packages/fuzzy/src/var.ts b/packages/fuzzy/src/var.ts index 9a47054155..e2aeec3023 100644 --- a/packages/fuzzy/src/var.ts +++ b/packages/fuzzy/src/var.ts @@ -16,15 +16,15 @@ import type { LVar, LVarDomain } from "./api.js"; * }); * ``` * - * @param domain - - * @param terms - + * @param domain - + * @param terms - */ export const variable = ( - domain: LVarDomain, - terms: LVar["terms"] + domain: LVarDomain, + terms: LVar["terms"] ): LVar => ({ - domain, - terms, + domain, + terms, }); /** @@ -47,25 +47,25 @@ export const variable = ( * // "warm" * ``` * - * @param var - - * @param x - - * @param threshold - + * @param var - + * @param x - + * @param threshold - */ export const classify = ( - { terms }: LVar, - x: number, - threshold = 0.5 + { terms }: LVar, + x: number, + threshold = 0.5 ) => { - let max = threshold; - let maxID: K | undefined; - for (let id in terms) { - const t = terms[id](x); - if (t >= max) { - max = t; - maxID = id; - } - } - return maxID; + let max = threshold; + let maxID: K | undefined; + for (let id in terms) { + const t = terms[id](x); + if (t >= max) { + max = t; + maxID = id; + } + } + return maxID; }; /** @@ -86,13 +86,13 @@ export const classify = ( * // { freezing: 0, cold: 0, warm: 0.4, hot: 0.01798620996209156 } * ``` * - * @param var - - * @param x - + * @param var - + * @param x - */ export const evaluate = ({ terms }: LVar, x: number) => { - const res = >{}; - for (let id in terms) { - res[id] = terms[id](x); - } - return res; + const res = >{}; + for (let id in terms) { + res[id] = terms[id](x); + } + return res; }; diff --git a/packages/fuzzy/test/defuzz.ts b/packages/fuzzy/test/defuzz.ts index 14af3bdc97..47fba59f75 100644 --- a/packages/fuzzy/test/defuzz.ts +++ b/packages/fuzzy/test/defuzz.ts @@ -2,89 +2,89 @@ import { eqDelta, roundTo } from "@thi.ng/math"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { - bisectorStrategy, - centroidStrategy, - defuzz, - DefuzzStrategy, - firstOfMaximaStrategy, - gaussian, - invRamp, - lastOfMaximaStrategy, - meanOfMaximaStrategy, - or, - ramp, - triangle, - variable, -} from "../src/index.js" + bisectorStrategy, + centroidStrategy, + defuzz, + DefuzzStrategy, + firstOfMaximaStrategy, + gaussian, + invRamp, + lastOfMaximaStrategy, + meanOfMaximaStrategy, + or, + ramp, + triangle, + variable, +} from "../src/index.js"; group("defuzz", { - strategies: () => { - // https://www.researchgate.net/publication/267041266_Introduction_to_fuzzy_logic - const inputs = { - food: variable([0, 10], { - awful: invRamp(1, 3), - delicious: ramp(7, 9), - }), - service: variable([0, 10], { - poor: gaussian(0, 1.5), - good: gaussian(5, 1.5), - excellent: gaussian(10, 1.5), - }), - }; + strategies: () => { + // https://www.researchgate.net/publication/267041266_Introduction_to_fuzzy_logic + const inputs = { + food: variable([0, 10], { + awful: invRamp(1, 3), + delicious: ramp(7, 9), + }), + service: variable([0, 10], { + poor: gaussian(0, 1.5), + good: gaussian(5, 1.5), + excellent: gaussian(10, 1.5), + }), + }; - const outputs = { - tip: variable([0, 30], { - low: triangle(0, 5, 10), - medium: triangle(10, 15, 20), - high: triangle(20, 25, 30), - }), - }; + const outputs = { + tip: variable([0, 30], { + low: triangle(0, 5, 10), + medium: triangle(10, 15, 20), + high: triangle(20, 25, 30), + }), + }; - type I = typeof inputs; - type O = typeof outputs; + type I = typeof inputs; + type O = typeof outputs; - // if service is poor OR food is awful -> tip is low - // if service is normal -> tip is medium - // if service is excellent OR food is delicious -> tip is high - const rules = [ - or({ food: "awful", service: "poor" }, { tip: "low" }), - or({ service: "good" }, { tip: "medium" }), - or( - { food: "delicious", service: "excellent" }, - { tip: "high" } - ), - ]; + // if service is poor OR food is awful -> tip is low + // if service is normal -> tip is medium + // if service is excellent OR food is delicious -> tip is high + const rules = [ + or({ food: "awful", service: "poor" }, { tip: "low" }), + or({ service: "good" }, { tip: "medium" }), + or( + { food: "delicious", service: "excellent" }, + { tip: "high" } + ), + ]; - const testStrategy = ( - id: string, - strategy: DefuzzStrategy, - expected: number[] - ) => { - // const all = []; - for (let i = 0, k = 0; i <= 10; i++) { - for (let j = 0; j <= 10; j++, k++) { - let res = defuzz( - inputs, - outputs, - rules, - { food: i, service: j }, - strategy - ); - assert.ok( - eqDelta(roundTo(res.tip!, 0.01), expected[k]), - `${id}(${i},${j}): expected: ${expected[k]}, got: ${res.tip}` - ); - // all.push(res.tip!.toFixed(2)); - } - } - // for (let i = 0; i <= 10; i++) { - // console.log(all.slice(i * 11, (i + 1) * 11).join(", ")); - // } - // console.log("--"); - }; + const testStrategy = ( + id: string, + strategy: DefuzzStrategy, + expected: number[] + ) => { + // const all = []; + for (let i = 0, k = 0; i <= 10; i++) { + for (let j = 0; j <= 10; j++, k++) { + let res = defuzz( + inputs, + outputs, + rules, + { food: i, service: j }, + strategy + ); + assert.ok( + eqDelta(roundTo(res.tip!, 0.01), expected[k]), + `${id}(${i},${j}): expected: ${expected[k]}, got: ${res.tip}` + ); + // all.push(res.tip!.toFixed(2)); + } + } + // for (let i = 0; i <= 10; i++) { + // console.log(all.slice(i * 11, (i + 1) * 11).join(", ")); + // } + // console.log("--"); + }; - // prettier-ignore - const centroidResults = [ + // prettier-ignore + const centroidResults = [ 5.08, 5.54, 7.02, 8.95, 9.91, 10.06, 10.32, 11.08, 13.18, 14.80, 15.00, 5.08, 5.54, 7.02, 8.95, 9.91, 10.06, 10.32, 11.08, 13.18, 14.80, 15.00, 5.08, 5.56, 7.52, 9.66, 10.62, 10.78, 11.07, 11.99, 14.41, 16.19, 16.42, @@ -97,10 +97,10 @@ group("defuzz", { 15.00, 15.20, 16.82, 18.92, 19.68, 19.94, 20.09, 21.05, 22.98, 24.46, 24.92, 15.00, 15.20, 16.82, 18.92, 19.68, 19.94, 20.09, 21.05, 22.98, 24.46, 24.92, ]; - testStrategy("centroid", centroidStrategy(), centroidResults); + testStrategy("centroid", centroidStrategy(), centroidResults); - // prettier-ignore - const bisectResults = [ + // prettier-ignore + const bisectResults = [ 4.87, 5.00, 5.53, 6.91, 8.88, 10.30, 10.50, 8.32, 8.32, 11.41, 14.85, 4.87, 5.00, 5.53, 6.91, 8.88, 10.30, 10.50, 8.32, 8.32, 11.41, 14.85, 4.87, 5.03, 6.12, 8.30, 12.14, 12.39, 12.43, 11.82, 13.06, 21.81, 22.31, @@ -114,10 +114,10 @@ group("defuzz", { 14.85, 18.29, 21.38, 21.38, 19.20, 19.40, 20.82, 22.79, 24.17, 24.70, 24.83, ]; - testStrategy("bisect", bisectorStrategy(), bisectResults); + testStrategy("bisect", bisectorStrategy(), bisectResults); - // prettier-ignore - const foMaResults = [ + // prettier-ignore + const foMaResults = [ 5.10, 5.10, 5.10, 5.10, 5.10, 15.00, 5.10, 5.10, 5.10, 5.10, 5.10, 5.10, 5.10, 5.10, 5.10, 5.10, 15.00, 5.10, 5.10, 5.10, 5.10, 5.10, 5.10, 4.20, 2.70, 2.70, 14.10, 15.00, 14.10, 2.70, 2.70, 24.30, 24.90, @@ -130,10 +130,10 @@ group("defuzz", { 5.10, 24.90, 24.90, 24.90, 24.90, 15.00, 24.90, 24.90, 24.90, 24.90, 24.90, 5.10, 24.90, 24.90, 24.90, 24.90, 15.00, 24.90, 24.90, 24.90, 24.90, 24.90, ]; - testStrategy("first", firstOfMaximaStrategy(), foMaResults); + testStrategy("first", firstOfMaximaStrategy(), foMaResults); - // prettier-ignore - const loMaResults = [ + // prettier-ignore + const loMaResults = [ 5.10, 5.10, 5.10, 5.10, 5.10, 15.00, 5.10, 5.10, 5.10, 5.10, 24.90, 5.10, 5.10, 5.10, 5.10, 5.10, 15.00, 5.10, 5.10, 5.10, 5.10, 24.90, 5.10, 5.70, 7.50, 7.50, 15.90, 15.00, 15.90, 7.50, 7.50, 25.80, 24.90, @@ -146,10 +146,10 @@ group("defuzz", { 24.90, 24.90, 24.90, 24.90, 24.90, 15.00, 24.90, 24.90, 24.90, 24.90, 24.90, 24.90, 24.90, 24.90, 24.90, 24.90, 15.00, 24.90, 24.90, 24.90, 24.90, 24.90, ]; - testStrategy("last", lastOfMaximaStrategy(), loMaResults); + testStrategy("last", lastOfMaximaStrategy(), loMaResults); - // prettier-ignore - const meoMaResults = [ + // prettier-ignore + const meoMaResults = [ 5.10, 5.10, 5.10, 5.10, 5.10, 15.00, 5.10, 5.10, 5.10, 5.10, 15.00, 5.10, 5.10, 5.10, 5.10, 5.10, 15.00, 5.10, 5.10, 5.10, 5.10, 15.00, 5.10, 4.95, 5.10, 5.10, 15.00, 15.00, 15.00, 5.10, 5.10, 25.05, 24.90, @@ -162,17 +162,17 @@ group("defuzz", { 15.00, 24.90, 24.90, 24.90, 24.90, 15.00, 24.90, 24.90, 24.90, 24.90, 24.90, 15.00, 24.90, 24.90, 24.90, 24.90, 15.00, 24.90, 24.90, 24.90, 24.90, 24.90, ]; - testStrategy("mean", meanOfMaximaStrategy(), meoMaResults); + testStrategy("mean", meanOfMaximaStrategy(), meoMaResults); - // const strat = instrumentStrategy(centroidStrategy(), fuzzySetToAscii()); - // defuzz( - // inputs, - // outputs, - // rules, - // { food: 7.32, service: 7.83 }, - // strat, - // tnormAczelAlsina(2) - // ); - // console.log(strat.deref()[0]); - }, + // const strat = instrumentStrategy(centroidStrategy(), fuzzySetToAscii()); + // defuzz( + // inputs, + // outputs, + // rules, + // { food: 7.32, service: 7.83 }, + // strat, + // tnormAczelAlsina(2) + // ); + // console.log(strat.deref()[0]); + }, }); diff --git a/packages/fuzzy/test/lvar.ts b/packages/fuzzy/test/lvar.ts index 1683f653c5..d0cf50688e 100644 --- a/packages/fuzzy/test/lvar.ts +++ b/packages/fuzzy/test/lvar.ts @@ -3,53 +3,53 @@ import { roundTo } from "@thi.ng/math"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; import { - classify, - evaluate, - invSigmoid, - sigmoid, - trapezoid, - variable, -} from "../src/index.js" + classify, + evaluate, + invSigmoid, + sigmoid, + trapezoid, + variable, +} from "../src/index.js"; const roundVals = (obj: IObjectOf) => { - for (let k in obj) obj[k] = roundTo(obj[k], 1e-3); - return obj; + for (let k in obj) obj[k] = roundTo(obj[k], 1e-3); + return obj; }; const temp = variable([-20, 40], { - freezing: invSigmoid(0.01, 2), - cold: trapezoid(0, 4, 16, 20), - warm: trapezoid(15, 20, 25, 30), - hot: sigmoid(29.99, 2), + freezing: invSigmoid(0.01, 2), + cold: trapezoid(0, 4, 16, 20), + warm: trapezoid(15, 20, 25, 30), + hot: sigmoid(29.99, 2), }); group("lvar", { - eval: () => { - assert.deepStrictEqual( - roundVals(evaluate(temp, 18)), - roundVals({ - freezing: 0, - cold: 0.5, - warm: 0.6, - hot: 0, - }) - ); - assert.deepStrictEqual( - roundVals(evaluate(temp, 28)), - roundVals({ - freezing: 0, - cold: 0, - warm: 0.4, - hot: 0.018, - }) - ); - }, + eval: () => { + assert.deepStrictEqual( + roundVals(evaluate(temp, 18)), + roundVals({ + freezing: 0, + cold: 0.5, + warm: 0.6, + hot: 0, + }) + ); + assert.deepStrictEqual( + roundVals(evaluate(temp, 28)), + roundVals({ + freezing: 0, + cold: 0, + warm: 0.4, + hot: 0.018, + }) + ); + }, - classify: () => { - assert.strictEqual(classify(temp, -1), "freezing"); - assert.strictEqual(classify(temp, 0), "freezing"); - assert.strictEqual(classify(temp, 10), "cold"); - assert.strictEqual(classify(temp, 20), "warm"); - assert.strictEqual(classify(temp, 30), "hot"); - }, + classify: () => { + assert.strictEqual(classify(temp, -1), "freezing"); + assert.strictEqual(classify(temp, 0), "freezing"); + assert.strictEqual(classify(temp, 10), "cold"); + assert.strictEqual(classify(temp, 20), "warm"); + assert.strictEqual(classify(temp, 30), "hot"); + }, }); diff --git a/packages/fuzzy/tsconfig.json b/packages/fuzzy/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/fuzzy/tsconfig.json +++ b/packages/fuzzy/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-accel/api-extractor.json b/packages/geom-accel/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-accel/api-extractor.json +++ b/packages/geom-accel/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-accel/package.json b/packages/geom-accel/package.json index 6af676a0fa..2d79b13a96 100644 --- a/packages/geom-accel/package.json +++ b/packages/geom-accel/package.json @@ -1,116 +1,116 @@ { - "name": "@thi.ng/geom-accel", - "version": "3.2.10", - "description": "n-D spatial indexing data structures with a shared ES6 Map/Set-like API", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-accel#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "bench": "tools:node-esm bench/index.ts", - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc internal", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/arrays": "^2.3.1", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/equiv": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/geom-api": "^3.3.2", - "@thi.ng/geom-isec": "^2.1.19", - "@thi.ng/heaps": "^2.1.8", - "@thi.ng/math": "^5.3.4", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "3d", - "acceleration", - "datastructure", - "graphics", - "grid", - "kd-tree", - "map", - "nd", - "octtree", - "points", - "quadtree", - "query", - "set", - "spatial", - "tree", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./aspatial-grid": { - "default": "./aspatial-grid.js" - }, - "./kd-tree-map": { - "default": "./kd-tree-map.js" - }, - "./kd-tree-set": { - "default": "./kd-tree-set.js" - }, - "./nd-quadtree-map": { - "default": "./nd-quadtree-map.js" - }, - "./nd-quadtree-set": { - "default": "./nd-quadtree-set.js" - }, - "./spatial-grid2": { - "default": "./spatial-grid2.js" - }, - "./spatial-grid3": { - "default": "./spatial-grid3.js" - } - }, - "thi.ng": { - "year": 2013 - } + "name": "@thi.ng/geom-accel", + "version": "3.2.10", + "description": "n-D spatial indexing data structures with a shared ES6 Map/Set-like API", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-accel#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "bench": "tools:node-esm bench/index.ts", + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc internal", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/arrays": "^2.3.1", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/equiv": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/geom-api": "^3.3.2", + "@thi.ng/geom-isec": "^2.1.19", + "@thi.ng/heaps": "^2.1.8", + "@thi.ng/math": "^5.3.4", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "3d", + "acceleration", + "datastructure", + "graphics", + "grid", + "kd-tree", + "map", + "nd", + "octtree", + "points", + "quadtree", + "query", + "set", + "spatial", + "tree", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./aspatial-grid": { + "default": "./aspatial-grid.js" + }, + "./kd-tree-map": { + "default": "./kd-tree-map.js" + }, + "./kd-tree-set": { + "default": "./kd-tree-set.js" + }, + "./nd-quadtree-map": { + "default": "./nd-quadtree-map.js" + }, + "./nd-quadtree-set": { + "default": "./nd-quadtree-set.js" + }, + "./spatial-grid2": { + "default": "./spatial-grid2.js" + }, + "./spatial-grid3": { + "default": "./spatial-grid3.js" + } + }, + "thi.ng": { + "year": 2013 + } } diff --git a/packages/geom-accel/src/aspatial-grid.ts b/packages/geom-accel/src/aspatial-grid.ts index 4573bd9f8f..c65f7b9ce4 100644 --- a/packages/geom-accel/src/aspatial-grid.ts +++ b/packages/geom-accel/src/aspatial-grid.ts @@ -16,155 +16,155 @@ import { into } from "./utils.js"; * @internal */ export abstract class ASpatialGrid - implements IRegionQuery, ISpatialMap + implements IRegionQuery, ISpatialMap { - protected _cells!: Nullable[]>[]; - protected _num: number; - protected _invSize: ReadonlyVec; - protected _res1: ReadonlyVec; - - constructor( - protected _min: ReadonlyVec, - protected _size: ReadonlyVec, - protected _res: ReadonlyVec - ) { - floor(null, this._res); - this._res1 = subN([], this._res, 1); - this._invSize = div([], this._res, _size); - this._num = 0; - } - - get size() { - return this._num; - } - - *[Symbol.iterator](): IterableIterator> { - const cells = this._cells; - for (let i = cells.length; i-- > 0; ) { - if (cells[i]) yield* cells[i]!; - } - } - - keys(): IterableIterator { - return map((p) => p[0], this); - } - - values(): IterableIterator { - return map((p) => p[1], this); - } - - copy() { - const copy = this.empty(); - copy._num = this._num; - const src = this._cells; - const dest = copy._cells; - for (let i = src.length; i-- > 0; ) { - dest[i] = src[i] ? src[i]!.slice() : null; - } - return copy; - } - - abstract empty(): ASpatialGrid; - - clear() { - this._cells.fill(null); - this._num = 0; - } - - has(k: K, eps = EPS) { - return !!this.find(k, eps); - } - - get(k: K, eps = EPS) { - const pair = this.find(k, eps); - return pair ? pair[1] : undefined; - } - - set(k: K, v: V, eps = EPS) { - if (eps >= 0 && this.query(k, eps, 1).length) return false; - const id = this.findIndex(k); - const cell = this._cells[id]; - if (!cell) { - this._cells[id] = [[k, v]]; - } else { - cell.push([k, v]); - } - this._num++; - return true; - } - - into(pairs: Iterable>, eps = EPS) { - return into(this, pairs, eps); - } - - remove(k: K) { - const id = this.findIndex(k); - const cell = this._cells[id]; - if (!cell) return false; - for (let i = cell.length; i-- > 0; ) { - if (equals(cell[i][0], k)) { - cell.splice(i, 1); - this._num--; - return true; - } - } - return false; - } - - query( - q: K, - radius: number, - limit?: number, - acc?: Pair[] - ): Pair[] { - return this.doQuery((p) => p, q, radius, limit, acc); - } - - queryKeys(q: K, radius: number, limit?: number, acc?: K[]): K[] { - return this.doQuery((p) => p[0], q, radius, limit, acc); - } - - queryValues(q: K, radius: number, limit?: number, acc?: V[]): V[] { - return this.doQuery((p) => p[1], q, radius, limit, acc); - } - - protected abstract doQuery( - fn: Fn, T>, - k: K, - r: number, - limit?: number, - acc?: T[] - ): T[]; - - protected queryCell( - dist: VecOpRoVV, - heap: Heap<[number, Nullable>?]>, - c: Pair[], - k: K, - limit: number - ) { - for (let i = c.length; i-- > 0; ) { - const d = dist(c[i][0], k); - if (d <= heap.values[0][0]) { - heap.length >= limit - ? heap.pushPop([d, c[i]]) - : heap.push([d, c[i]]); - } - } - } - - protected find(k: K, eps: number) { - if (eps > 0) { - const res = this.query(k, EPS, 1); - return res.length ? res[0] : undefined; - } - const cell = this._cells[this.findIndex(k)]; - if (cell) { - for (let i = cell.length; i-- > 0; ) { - if (equals(cell[i][0], k)) return cell[i]; - } - } - } - - protected abstract findIndex(k: ReadonlyVec): number; + protected _cells!: Nullable[]>[]; + protected _num: number; + protected _invSize: ReadonlyVec; + protected _res1: ReadonlyVec; + + constructor( + protected _min: ReadonlyVec, + protected _size: ReadonlyVec, + protected _res: ReadonlyVec + ) { + floor(null, this._res); + this._res1 = subN([], this._res, 1); + this._invSize = div([], this._res, _size); + this._num = 0; + } + + get size() { + return this._num; + } + + *[Symbol.iterator](): IterableIterator> { + const cells = this._cells; + for (let i = cells.length; i-- > 0; ) { + if (cells[i]) yield* cells[i]!; + } + } + + keys(): IterableIterator { + return map((p) => p[0], this); + } + + values(): IterableIterator { + return map((p) => p[1], this); + } + + copy() { + const copy = this.empty(); + copy._num = this._num; + const src = this._cells; + const dest = copy._cells; + for (let i = src.length; i-- > 0; ) { + dest[i] = src[i] ? src[i]!.slice() : null; + } + return copy; + } + + abstract empty(): ASpatialGrid; + + clear() { + this._cells.fill(null); + this._num = 0; + } + + has(k: K, eps = EPS) { + return !!this.find(k, eps); + } + + get(k: K, eps = EPS) { + const pair = this.find(k, eps); + return pair ? pair[1] : undefined; + } + + set(k: K, v: V, eps = EPS) { + if (eps >= 0 && this.query(k, eps, 1).length) return false; + const id = this.findIndex(k); + const cell = this._cells[id]; + if (!cell) { + this._cells[id] = [[k, v]]; + } else { + cell.push([k, v]); + } + this._num++; + return true; + } + + into(pairs: Iterable>, eps = EPS) { + return into(this, pairs, eps); + } + + remove(k: K) { + const id = this.findIndex(k); + const cell = this._cells[id]; + if (!cell) return false; + for (let i = cell.length; i-- > 0; ) { + if (equals(cell[i][0], k)) { + cell.splice(i, 1); + this._num--; + return true; + } + } + return false; + } + + query( + q: K, + radius: number, + limit?: number, + acc?: Pair[] + ): Pair[] { + return this.doQuery((p) => p, q, radius, limit, acc); + } + + queryKeys(q: K, radius: number, limit?: number, acc?: K[]): K[] { + return this.doQuery((p) => p[0], q, radius, limit, acc); + } + + queryValues(q: K, radius: number, limit?: number, acc?: V[]): V[] { + return this.doQuery((p) => p[1], q, radius, limit, acc); + } + + protected abstract doQuery( + fn: Fn, T>, + k: K, + r: number, + limit?: number, + acc?: T[] + ): T[]; + + protected queryCell( + dist: VecOpRoVV, + heap: Heap<[number, Nullable>?]>, + c: Pair[], + k: K, + limit: number + ) { + for (let i = c.length; i-- > 0; ) { + const d = dist(c[i][0], k); + if (d <= heap.values[0][0]) { + heap.length >= limit + ? heap.pushPop([d, c[i]]) + : heap.push([d, c[i]]); + } + } + } + + protected find(k: K, eps: number) { + if (eps > 0) { + const res = this.query(k, EPS, 1); + return res.length ? res[0] : undefined; + } + const cell = this._cells[this.findIndex(k)]; + if (cell) { + for (let i = cell.length; i-- > 0; ) { + if (equals(cell[i][0], k)) return cell[i]; + } + } + } + + protected abstract findIndex(k: ReadonlyVec): number; } diff --git a/packages/geom-accel/src/kd-tree-map.ts b/packages/geom-accel/src/kd-tree-map.ts index 1de0986e27..96fc2f5eeb 100644 --- a/packages/geom-accel/src/kd-tree-map.ts +++ b/packages/geom-accel/src/kd-tree-map.ts @@ -11,25 +11,25 @@ import { addResults, CMP, into } from "./utils.js"; type MaybeKdNode = KdNode | undefined; export class KdNode { - d: number; - parent?: KdNode; - l?: KdNode; - r?: KdNode; - k: K; - v: V; - - constructor(parent: MaybeKdNode, dim: number, key: K, val: V) { - this.parent = parent; - this.d = dim; - this.k = key; - this.v = val; - } - - get height(): number { - return ( - 1 + Math.max(this.l ? this.l.height : 0, this.r ? this.r.height : 0) - ); - } + d: number; + parent?: KdNode; + l?: KdNode; + r?: KdNode; + k: K; + v: V; + + constructor(parent: MaybeKdNode, dim: number, key: K, val: V) { + this.parent = parent; + this.d = dim; + this.k = key; + this.v = val; + } + + get height(): number { + return ( + 1 + Math.max(this.l ? this.l.height : 0, this.r ? this.r.height : 0) + ); + } } /** @@ -40,225 +40,225 @@ export class KdNode { * */ export class KdTreeMap - implements - ICopy>, - IEmpty>, - IRegionQuery, - ISpatialMap + implements + ICopy>, + IEmpty>, + IRegionQuery, + ISpatialMap { - readonly dim: number; - - protected root: MaybeKdNode; - protected _size: number; - - constructor( - dim: number, - pairs?: Iterable>, - public readonly distanceFn: DistanceFn = distSq - ) { - this.dim = dim; - this._size = 0; - this.root = pairs ? this.buildTree(ensureArray(pairs), 0) : undefined; - } - - *[Symbol.iterator]() { - let queue: MaybeKdNode[] = this.root ? [this.root] : []; - while (queue.length) { - const n = queue.pop(); - if (n) { - yield >[n.k, n.v]; - queue.push(n.r, n.l); - } - } - } - - *keys() { - let queue: MaybeKdNode[] = this.root ? [this.root] : []; - while (queue.length) { - const n = queue.pop(); - if (n) { - yield n.k; - queue.push(n.r, n.l); - } - } - } - - values() { - return map((p) => p[1], this); - } - - get size() { - return this._size; - } - - get height() { - return this.root ? this.root.height : 0; - } - - get ratio() { - return this._size ? this.height / Math.log2(this._size) : 0; - } - - copy() { - return new KdTreeMap(this.dim, this, this.distanceFn); - } - - clear() { - delete this.root; - this._size = 0; - } - - empty() { - return new KdTreeMap(this.dim, undefined, this.distanceFn); - } - - set(key: K, val: V, eps = EPS) { - eps = Math.max(0, eps); - eps *= eps; - const search = ( - node: MaybeKdNode, - parent: MaybeKdNode - ): MaybeKdNode => - node - ? search(key[node.d] < node.k[node.d] ? node.l : node.r, node) - : parent; - let parent: MaybeKdNode; - if (this.root) { - parent = nearest1( - key, - [eps, undefined], - this.dim, - this.root, - this.distanceFn - )[1]; - if (parent) { - parent.v = val; - return false; - } - parent = search(this.root, undefined)!; - const dim = parent.d; - parent[key[dim] < parent.k[dim] ? "l" : "r"] = new KdNode( - parent, - (dim + 1) % this.dim, - key, - val - ); - } else { - this.root = new KdNode(undefined, 0, key, val); - } - this._size++; - return true; - } - - into(pairs: Iterable>, eps = EPS) { - return into(this, pairs, eps); - } - - remove(key: K) { - const node = find(key, this.root, 0); - if (node) { - remove(node) && (this.root = undefined); - this._size--; - return true; - } - return false; - } - - has(key: K, eps = EPS) { - return ( - !!this.root && - !!nearest1( - key, - [eps * eps, undefined], - this.dim, - this.root, - this.distanceFn - )[1] - ); - } - - get(key: K, eps = EPS) { - if (this.root) { - const node = nearest1( - key, - [eps * eps, undefined], - this.dim, - this.root, - this.distanceFn - )[1]; - return node ? node.v : undefined; - } - } - - query( - q: K, - maxDist: number, - limit?: number, - acc?: Pair[] - ): Pair[] { - return this.doSelect(q, (x) => [x.k, x.v], maxDist, limit, acc); - } - - queryKeys(q: K, maxDist: number, limit?: number, acc?: K[]): K[] { - return this.doSelect(q, (x) => x.k, maxDist, limit, acc); - } - - queryValues(q: K, maxDist: number, limit?: number, acc?: V[]): V[] { - return this.doSelect(q, (x) => x.v, maxDist, limit, acc); - } - - protected doSelect( - q: K, - f: Fn, T>, - maxDist: number, - maxNum = 1, - acc: T[] = [] - ): T[] { - if (!this.root) return []; - maxDist *= maxDist; - if (maxNum === 1) { - const sel = nearest1( - q, - [maxDist, undefined], - this.dim, - this.root, - this.distanceFn - )[1]; - sel && acc.push(f(sel)); - } else { - const nodes = new Heap<[number, MaybeKdNode]>( - [[maxDist, undefined]], - { - compare: CMP, - } - ); - nearest(q, nodes, this.dim, maxNum, this.root!, this.distanceFn); - return addResults(f, nodes.values, acc); - } - return acc; - } - - protected buildTree( - points: Pair[], - depth: number, - parent?: KdNode - ) { - const n = points.length; - if (n === 0) { - return; - } - this._size++; - let dim = depth % this.dim; - if (n === 1) { - return new KdNode(parent, dim, ...points[0]); - } - points.sort((a, b) => a[0][dim] - b[0][dim]); - const med = n >>> 1; - const node = new KdNode(parent, dim, ...points[med]); - node.l = this.buildTree(points.slice(0, med), depth + 1, node); - node.r = this.buildTree(points.slice(med + 1), depth + 1, node); - return node; - } + readonly dim: number; + + protected root: MaybeKdNode; + protected _size: number; + + constructor( + dim: number, + pairs?: Iterable>, + public readonly distanceFn: DistanceFn = distSq + ) { + this.dim = dim; + this._size = 0; + this.root = pairs ? this.buildTree(ensureArray(pairs), 0) : undefined; + } + + *[Symbol.iterator]() { + let queue: MaybeKdNode[] = this.root ? [this.root] : []; + while (queue.length) { + const n = queue.pop(); + if (n) { + yield >[n.k, n.v]; + queue.push(n.r, n.l); + } + } + } + + *keys() { + let queue: MaybeKdNode[] = this.root ? [this.root] : []; + while (queue.length) { + const n = queue.pop(); + if (n) { + yield n.k; + queue.push(n.r, n.l); + } + } + } + + values() { + return map((p) => p[1], this); + } + + get size() { + return this._size; + } + + get height() { + return this.root ? this.root.height : 0; + } + + get ratio() { + return this._size ? this.height / Math.log2(this._size) : 0; + } + + copy() { + return new KdTreeMap(this.dim, this, this.distanceFn); + } + + clear() { + delete this.root; + this._size = 0; + } + + empty() { + return new KdTreeMap(this.dim, undefined, this.distanceFn); + } + + set(key: K, val: V, eps = EPS) { + eps = Math.max(0, eps); + eps *= eps; + const search = ( + node: MaybeKdNode, + parent: MaybeKdNode + ): MaybeKdNode => + node + ? search(key[node.d] < node.k[node.d] ? node.l : node.r, node) + : parent; + let parent: MaybeKdNode; + if (this.root) { + parent = nearest1( + key, + [eps, undefined], + this.dim, + this.root, + this.distanceFn + )[1]; + if (parent) { + parent.v = val; + return false; + } + parent = search(this.root, undefined)!; + const dim = parent.d; + parent[key[dim] < parent.k[dim] ? "l" : "r"] = new KdNode( + parent, + (dim + 1) % this.dim, + key, + val + ); + } else { + this.root = new KdNode(undefined, 0, key, val); + } + this._size++; + return true; + } + + into(pairs: Iterable>, eps = EPS) { + return into(this, pairs, eps); + } + + remove(key: K) { + const node = find(key, this.root, 0); + if (node) { + remove(node) && (this.root = undefined); + this._size--; + return true; + } + return false; + } + + has(key: K, eps = EPS) { + return ( + !!this.root && + !!nearest1( + key, + [eps * eps, undefined], + this.dim, + this.root, + this.distanceFn + )[1] + ); + } + + get(key: K, eps = EPS) { + if (this.root) { + const node = nearest1( + key, + [eps * eps, undefined], + this.dim, + this.root, + this.distanceFn + )[1]; + return node ? node.v : undefined; + } + } + + query( + q: K, + maxDist: number, + limit?: number, + acc?: Pair[] + ): Pair[] { + return this.doSelect(q, (x) => [x.k, x.v], maxDist, limit, acc); + } + + queryKeys(q: K, maxDist: number, limit?: number, acc?: K[]): K[] { + return this.doSelect(q, (x) => x.k, maxDist, limit, acc); + } + + queryValues(q: K, maxDist: number, limit?: number, acc?: V[]): V[] { + return this.doSelect(q, (x) => x.v, maxDist, limit, acc); + } + + protected doSelect( + q: K, + f: Fn, T>, + maxDist: number, + maxNum = 1, + acc: T[] = [] + ): T[] { + if (!this.root) return []; + maxDist *= maxDist; + if (maxNum === 1) { + const sel = nearest1( + q, + [maxDist, undefined], + this.dim, + this.root, + this.distanceFn + )[1]; + sel && acc.push(f(sel)); + } else { + const nodes = new Heap<[number, MaybeKdNode]>( + [[maxDist, undefined]], + { + compare: CMP, + } + ); + nearest(q, nodes, this.dim, maxNum, this.root!, this.distanceFn); + return addResults(f, nodes.values, acc); + } + return acc; + } + + protected buildTree( + points: Pair[], + depth: number, + parent?: KdNode + ) { + const n = points.length; + if (n === 0) { + return; + } + this._size++; + let dim = depth % this.dim; + if (n === 1) { + return new KdNode(parent, dim, ...points[0]); + } + points.sort((a, b) => a[0][dim] - b[0][dim]); + const med = n >>> 1; + const node = new KdNode(parent, dim, ...points[med]); + node.l = this.buildTree(points.slice(0, med), depth + 1, node); + node.r = this.buildTree(points.slice(med + 1), depth + 1, node); + return node; + } } /** @@ -269,35 +269,35 @@ export class KdTreeMap * @param epsSq - squared epsilon / tolerance */ const find = ( - p: K, - node: MaybeKdNode, - epsSq: number + p: K, + node: MaybeKdNode, + epsSq: number ): KdNode | undefined => { - if (!node) return; - return distSq(p, node.k) <= epsSq - ? node - : find(p, p[node.d] < node.k[node.d] ? node.l : node.r, epsSq); + if (!node) return; + return distSq(p, node.k) <= epsSq + ? node + : find(p, p[node.d] < node.k[node.d] ? node.l : node.r, epsSq); }; const findMin = ( - node: MaybeKdNode, - dim: number + node: MaybeKdNode, + dim: number ): MaybeKdNode => { - if (!node) return; - if (node.d === dim) { - return node.l ? findMin(node.l, dim) : node; - } - const q = node.k[dim]; - const l = findMin(node.l, dim); - const r = findMin(node.r, dim); - let min = node; - if (l && l.k[dim] < q) { - min = l; - } - if (r && r.k[dim] < min.k[dim]) { - min = r; - } - return min; + if (!node) return; + if (node.d === dim) { + return node.l ? findMin(node.l, dim) : node; + } + const q = node.k[dim]; + const l = findMin(node.l, dim); + const r = findMin(node.r, dim); + let min = node; + if (l && l.k[dim] < q) { + min = l; + } + if (r && r.k[dim] < min.k[dim]) { + min = r; + } + return min; }; /** @@ -306,54 +306,54 @@ const findMin = ( * @param node - tree node */ const remove = (node: KdNode) => { - if (!node.l && !node.r) { - if (!node.parent) { - return true; - } - const parent = node.parent; - const pdim = parent.d; - parent[node.k[pdim] < parent.k[pdim] ? "l" : "r"] = undefined; - return; - } - let next: MaybeKdNode; - let nextP: K; - if (node.r) { - next = findMin(node.r, node.d)!; - nextP = next.k; - remove(next); - node.k = nextP; - } else { - next = findMin(node.l, node.d)!; - nextP = next.k; - remove(next); - node.r = node.l; - node.l = undefined; - node.k = nextP; - } + if (!node.l && !node.r) { + if (!node.parent) { + return true; + } + const parent = node.parent; + const pdim = parent.d; + parent[node.k[pdim] < parent.k[pdim] ? "l" : "r"] = undefined; + return; + } + let next: MaybeKdNode; + let nextP: K; + if (node.r) { + next = findMin(node.r, node.d)!; + nextP = next.k; + remove(next); + node.k = nextP; + } else { + next = findMin(node.l, node.d)!; + nextP = next.k; + remove(next); + node.r = node.l; + node.l = undefined; + node.k = nextP; + } }; const nearest = ( - q: K, - acc: Heap<[number, MaybeKdNode]>, - dims: number, - maxNum: number, - node: KdNode, - distFn: DistanceFn + q: K, + acc: Heap<[number, MaybeKdNode]>, + dims: number, + maxNum: number, + node: KdNode, + distFn: DistanceFn ) => { - const p = node.k; - const ndist = distSq(p, q); - if (!node.l && !node.r) { - collect(acc, maxNum, node, ndist); - return; - } - const tdist = nodeDist(node, dims, q, p, distFn); - let best = bestChild(node, q); - nearest(q, acc, dims, maxNum, best!, distFn); - collect(acc, maxNum, node, ndist); - if (tdist < acc.values[0][0]) { - best = best === node.l ? node.r : node.l; - best && nearest(q, acc, dims, maxNum, best, distFn); - } + const p = node.k; + const ndist = distSq(p, q); + if (!node.l && !node.r) { + collect(acc, maxNum, node, ndist); + return; + } + const tdist = nodeDist(node, dims, q, p, distFn); + let best = bestChild(node, q); + nearest(q, acc, dims, maxNum, best!, distFn); + collect(acc, maxNum, node, ndist); + if (tdist < acc.values[0][0]) { + best = best === node.l ? node.r : node.l; + best && nearest(q, acc, dims, maxNum, best, distFn); + } }; /** @@ -365,68 +365,68 @@ const nearest = ( * @param node - tree node */ const nearest1 = ( - q: K, - acc: [number, MaybeKdNode], - dims: number, - node: KdNode, - distFn: DistanceFn + q: K, + acc: [number, MaybeKdNode], + dims: number, + node: KdNode, + distFn: DistanceFn ): [number, MaybeKdNode] => { - const p = node.k; - const ndist = distFn(p, q); - if (!node.l && !node.r) { - collect1(acc, node, ndist); - return acc; - } - const tdist = nodeDist(node, dims, q, p, distFn); - let best = bestChild(node, q); - nearest1(q, acc, dims, best!, distFn); - collect1(acc, node, ndist); - if (tdist < acc[0]) { - best = best === node.l ? node.r : node.l; - best && nearest1(q, acc, dims, best, distFn); - } - return acc; + const p = node.k; + const ndist = distFn(p, q); + if (!node.l && !node.r) { + collect1(acc, node, ndist); + return acc; + } + const tdist = nodeDist(node, dims, q, p, distFn); + let best = bestChild(node, q); + nearest1(q, acc, dims, best!, distFn); + collect1(acc, node, ndist); + if (tdist < acc[0]) { + best = best === node.l ? node.r : node.l; + best && nearest1(q, acc, dims, best, distFn); + } + return acc; }; const bestChild = (node: KdNode, q: K) => { - const d = node.d; - return !node.r - ? node.l - : !node.l - ? node.r - : q[d] < node.k[d] - ? node.l - : node.r; + const d = node.d; + return !node.r + ? node.l + : !node.l + ? node.r + : q[d] < node.k[d] + ? node.l + : node.r; }; const collect = ( - acc: Heap<[number, MaybeKdNode]>, - maxNum: number, - node: KdNode, - ndist: number + acc: Heap<[number, MaybeKdNode]>, + maxNum: number, + node: KdNode, + ndist: number ) => - (!acc.length || ndist < acc.peek()![0]) && - (acc.length >= maxNum - ? acc.pushPop([ndist, node]) - : acc.push([ndist, node])); + (!acc.length || ndist < acc.peek()![0]) && + (acc.length >= maxNum + ? acc.pushPop([ndist, node]) + : acc.push([ndist, node])); const collect1 = ( - acc: [number, MaybeKdNode], - node: KdNode, - ndist: number + acc: [number, MaybeKdNode], + node: KdNode, + ndist: number ) => ndist < acc[0] && ((acc[0] = ndist), (acc[1] = node)); const TMP: Vec = []; const nodeDist = ( - node: KdNode, - dims: number, - q: K, - p: K, - distFn: DistanceFn + node: KdNode, + dims: number, + q: K, + p: K, + distFn: DistanceFn ) => { - for (let i = dims, d = node.d; i-- > 0; ) { - TMP[i] = i === d ? q[i] : p[i]; - } - return distFn(TMP, p); + for (let i = dims, d = node.d; i-- > 0; ) { + TMP[i] = i === d ? q[i] : p[i]; + } + return distFn(TMP, p); }; diff --git a/packages/geom-accel/src/kd-tree-set.ts b/packages/geom-accel/src/kd-tree-set.ts index 6b958fa15d..0cac332bb9 100644 --- a/packages/geom-accel/src/kd-tree-set.ts +++ b/packages/geom-accel/src/kd-tree-set.ts @@ -4,88 +4,88 @@ import type { DistanceFn, ReadonlyVec } from "@thi.ng/vectors"; import { KdTreeMap } from "./kd-tree-map.js"; export class KdTreeSet - implements - ICopy>, - IEmpty>, - IRegionQuery, - ISpatialSet + implements + ICopy>, + IEmpty>, + IRegionQuery, + ISpatialSet { - protected tree: KdTreeMap; - - constructor(dim: number, keys?: Iterable, distanceFn?: DistanceFn) { - this.tree = new KdTreeMap(dim, undefined, distanceFn); - keys && this.into(keys); - } - - [Symbol.iterator]() { - return this.tree.keys(); - } - - keys() { - return this.tree.keys(); - } - - values() { - return this.tree.keys(); - } - - get size() { - return this.tree.size; - } - - get height() { - return this.tree.height; - } - - get ratio() { - return this.tree.ratio; - } - - copy() { - return new KdTreeSet(this.tree.dim, this, this.tree.distanceFn); - } - - clear() { - this.tree.clear(); - } - - empty() { - return new KdTreeSet(this.tree.dim, undefined, this.tree.distanceFn); - } - - add(key: K, eps?: number) { - return this.tree.set(key, key, eps); - } - - into(ks: Iterable, eps?: number) { - let ok = true; - for (let k of ks) { - ok = this.tree.set(k, k, eps) && ok; - } - return ok; - } - - remove(key: K) { - return this.tree.remove(key); - } - - has(key: K, eps?: number) { - return this.tree.has(key, eps); - } - - get(key: K, eps?: number) { - return this.tree.get(key, eps); - } - - query(q: K, maxDist: number, limit?: number, acc?: Pair[]) { - return this.tree.query(q, maxDist, limit, acc); - } - - queryKeys(q: K, maxDist: number, limit?: number, acc?: K[]) { - return this.tree.queryKeys(q, maxDist, limit, acc); - } - - queryValues(q: K, maxDist: number, limit?: number, acc?: K[]) { - return this.tree.queryKeys(q, maxDist, limit, acc); - } + protected tree: KdTreeMap; + + constructor(dim: number, keys?: Iterable, distanceFn?: DistanceFn) { + this.tree = new KdTreeMap(dim, undefined, distanceFn); + keys && this.into(keys); + } + + [Symbol.iterator]() { + return this.tree.keys(); + } + + keys() { + return this.tree.keys(); + } + + values() { + return this.tree.keys(); + } + + get size() { + return this.tree.size; + } + + get height() { + return this.tree.height; + } + + get ratio() { + return this.tree.ratio; + } + + copy() { + return new KdTreeSet(this.tree.dim, this, this.tree.distanceFn); + } + + clear() { + this.tree.clear(); + } + + empty() { + return new KdTreeSet(this.tree.dim, undefined, this.tree.distanceFn); + } + + add(key: K, eps?: number) { + return this.tree.set(key, key, eps); + } + + into(ks: Iterable, eps?: number) { + let ok = true; + for (let k of ks) { + ok = this.tree.set(k, k, eps) && ok; + } + return ok; + } + + remove(key: K) { + return this.tree.remove(key); + } + + has(key: K, eps?: number) { + return this.tree.has(key, eps); + } + + get(key: K, eps?: number) { + return this.tree.get(key, eps); + } + + query(q: K, maxDist: number, limit?: number, acc?: Pair[]) { + return this.tree.query(q, maxDist, limit, acc); + } + + queryKeys(q: K, maxDist: number, limit?: number, acc?: K[]) { + return this.tree.queryKeys(q, maxDist, limit, acc); + } + + queryValues(q: K, maxDist: number, limit?: number, acc?: K[]) { + return this.tree.queryKeys(q, maxDist, limit, acc); + } } diff --git a/packages/geom-accel/src/nd-quadtree-map.ts b/packages/geom-accel/src/nd-quadtree-map.ts index 560295e203..5505b38780 100644 --- a/packages/geom-accel/src/nd-quadtree-map.ts +++ b/packages/geom-accel/src/nd-quadtree-map.ts @@ -21,158 +21,158 @@ import { vop } from "@thi.ng/vectors/vop"; import { addResults, CMP, into } from "./utils.js"; export class NdQtNode { - pos: ReadonlyVec; - ext: ReadonlyVec; - parent?: NdQtNode; - children?: NdQtNode[]; - numC: number; - k?: K; - v?: V; - - constructor( - parent: NdQtNode | undefined, - pos: ReadonlyVec, - ext: ReadonlyVec - ) { - this.parent = parent; - this.pos = pos; - this.ext = ext; - this.numC = 0; - } - - clear() { - delete this.children; - delete this.k; - delete this.v; - this.numC = 0; - } - - set(p: K, val: V, eps: number, distFn: DistanceFn): boolean { - return ( - (eps <= 0 || !this.queryKeys(p, eps, 1, [], distFn).length) && - this.containsPoint(p) && - this.setUnsafe(p, val) - ); - } - - setUnsafe(p: K, val: V): boolean { - if (this.k) { - if (equivArrayLike(this.k, p)) { - this.v = val; - return false; - } - this.ensureChild(childID(this.k, this.pos)).setUnsafe( - this.k, - this.v! - ); - delete this.k; - delete this.v; - } - if (this.children) { - return this.ensureChild(childID(p, this.pos)).setUnsafe(p, val); - } else { - this.k = p; - this.v = val; - } - return true; - } - - query( - fn: Fn, T>, - p: K, - r: number, - max: number, - acc: T[], - distFn: DistanceFn - ) { - return addResults( - fn, - this.doQuery( - p, - r, - max, - new Heap<[number, NdQtNode?]>([[r * r]], { - compare: CMP, - }), - distFn - ).values, - acc - ); - } - - queryKeys(p: K, r: number, max: number, acc: K[], distFn: DistanceFn): K[] { - return this.query((n) => n.k, p, r, max, acc, distFn); - } - - queryValues( - p: K, - r: number, - max: number, - acc: V[], - distFn: DistanceFn - ): V[] { - return this.query((n) => n.v, p, r, max, acc, distFn); - } - - containsPoint(p: K) { - return pointInCenteredBox(p, this.pos, this.ext); - } - - nodeForPoint(p: K): NdQtNode | undefined { - if (this.k && equivArrayLike(this.k, p)) { - return this; - } - if (this.children) { - const child = this.children[childID(p, this.pos)]; - return child ? child.nodeForPoint(p) : undefined; - } - } - - protected doQuery( - p: K, - r: number, - max: number, - acc: Heap<[number, NdQtNode?]>, - distFn: DistanceFn - ): Heap<[number, NdQtNode?]> { - if (testCenteredBoxSphere(this.pos, this.ext, p, r)) { - if (this.k) { - const d = distFn(this.k, p); - if (d <= acc.values[0][0]) { - acc.length >= max - ? acc.pushPop([d, this]) - : acc.push([d, this]); - } - } else if (this.children) { - for ( - let i = MAX_CHILDREN[this.pos.length], j = this.numC; - i-- > 0 && j > 0; - - ) { - if (this.children[i]) { - this.children[i].doQuery(p, r, max, acc, distFn); - j--; - } - } - } - } - return acc; - } - - protected ensureChild(id: number) { - !this.children && (this.children = []); - let c = this.children[id]; - if (!c) { - const csize = mulN([], this.ext, 0.5); - this.children[id] = c = new NdQtNode( - this, - madd([], csize, CHILD_OFFSETS[csize.length][id], this.pos), - csize - ); - this.numC++; - } - return c; - } + pos: ReadonlyVec; + ext: ReadonlyVec; + parent?: NdQtNode; + children?: NdQtNode[]; + numC: number; + k?: K; + v?: V; + + constructor( + parent: NdQtNode | undefined, + pos: ReadonlyVec, + ext: ReadonlyVec + ) { + this.parent = parent; + this.pos = pos; + this.ext = ext; + this.numC = 0; + } + + clear() { + delete this.children; + delete this.k; + delete this.v; + this.numC = 0; + } + + set(p: K, val: V, eps: number, distFn: DistanceFn): boolean { + return ( + (eps <= 0 || !this.queryKeys(p, eps, 1, [], distFn).length) && + this.containsPoint(p) && + this.setUnsafe(p, val) + ); + } + + setUnsafe(p: K, val: V): boolean { + if (this.k) { + if (equivArrayLike(this.k, p)) { + this.v = val; + return false; + } + this.ensureChild(childID(this.k, this.pos)).setUnsafe( + this.k, + this.v! + ); + delete this.k; + delete this.v; + } + if (this.children) { + return this.ensureChild(childID(p, this.pos)).setUnsafe(p, val); + } else { + this.k = p; + this.v = val; + } + return true; + } + + query( + fn: Fn, T>, + p: K, + r: number, + max: number, + acc: T[], + distFn: DistanceFn + ) { + return addResults( + fn, + this.doQuery( + p, + r, + max, + new Heap<[number, NdQtNode?]>([[r * r]], { + compare: CMP, + }), + distFn + ).values, + acc + ); + } + + queryKeys(p: K, r: number, max: number, acc: K[], distFn: DistanceFn): K[] { + return this.query((n) => n.k, p, r, max, acc, distFn); + } + + queryValues( + p: K, + r: number, + max: number, + acc: V[], + distFn: DistanceFn + ): V[] { + return this.query((n) => n.v, p, r, max, acc, distFn); + } + + containsPoint(p: K) { + return pointInCenteredBox(p, this.pos, this.ext); + } + + nodeForPoint(p: K): NdQtNode | undefined { + if (this.k && equivArrayLike(this.k, p)) { + return this; + } + if (this.children) { + const child = this.children[childID(p, this.pos)]; + return child ? child.nodeForPoint(p) : undefined; + } + } + + protected doQuery( + p: K, + r: number, + max: number, + acc: Heap<[number, NdQtNode?]>, + distFn: DistanceFn + ): Heap<[number, NdQtNode?]> { + if (testCenteredBoxSphere(this.pos, this.ext, p, r)) { + if (this.k) { + const d = distFn(this.k, p); + if (d <= acc.values[0][0]) { + acc.length >= max + ? acc.pushPop([d, this]) + : acc.push([d, this]); + } + } else if (this.children) { + for ( + let i = MAX_CHILDREN[this.pos.length], j = this.numC; + i-- > 0 && j > 0; + + ) { + if (this.children[i]) { + this.children[i].doQuery(p, r, max, acc, distFn); + j--; + } + } + } + } + return acc; + } + + protected ensureChild(id: number) { + !this.children && (this.children = []); + let c = this.children[id]; + if (!c) { + const csize = mulN([], this.ext, 0.5); + this.children[id] = c = new NdQtNode( + this, + madd([], csize, CHILD_OFFSETS[csize.length][id], this.pos), + csize + ); + this.numC++; + } + return c; + } } /** @@ -184,210 +184,210 @@ export class NdQtNode { * Partially ported from Clojure version of {@link http://thi.ng/geom}. */ export class NdQuadtreeMap - implements - ICopy>, - IEmpty>, - IRegionQuery, - ISpatialMap + implements + ICopy>, + IEmpty>, + IRegionQuery, + ISpatialMap { - static readonly MAX_DIM = 16; - - /** - * Returns a new point-based `NdQuadtreeMap` for nD keys in given - * region defined by `min` / `max` coordinates. The dimensionality - * of the tree is implicitly defined by the provided coordinates. - * Only points within that region can be indexed. - * - * @remarks - * Due to exponentially growing lookup tables, currently only - * supports up to 16 dimensions. - */ - static fromMinMax( - min: ReadonlyVec, - max: ReadonlyVec - ) { - return new NdQuadtreeMap( - addmN([], min, max, 0.5), - submN([], max, min, 0.5) - ); - } - - root: NdQtNode; - protected _size: number; - - constructor( - pos: ReadonlyVec, - ext: ReadonlyVec, - pairs?: Iterable>, - public readonly distanceFn: DistanceFn = distSq - ) { - const dim = pos.length; - assert( - dim > 0 && dim <= NdQuadtreeMap.MAX_DIM, - `illegal dimension: ${dim}` - ); - assert(ext.length === dim, `pos/ext dimensions must be equal`); - initChildOffsets(dim); - this.root = new NdQtNode(undefined, pos, ext); - this._size = 0; - pairs && this.into(pairs, -1); - } - - get size() { - return this._size; - } - - [Symbol.iterator](): IterableIterator> { - return map((n) => [n.k!, n.v!], this.nodes()); - } - - keys() { - return map((n) => n.k, this.nodes()); - } - - values() { - return map((n) => n.v, this.nodes()); - } - - *nodes(all = false) { - let queue: NdQtNode[] = [this.root]; - while (queue.length) { - const n = queue.pop(); - if (n) { - if (all || n.k) yield n; - if (n.children) queue = queue.concat(n.children); - } - } - } - - copy(): NdQuadtreeMap { - const tree = new NdQuadtreeMap( - this.root.pos, - this.root.ext, - this, - this.distanceFn - ); - return tree; - } - - clear() { - this.root.clear(); - this._size = 0; - } - - empty() { - return new NdQuadtreeMap( - this.root.pos, - this.root.ext, - undefined, - this.distanceFn - ); - } - - set(key: K, val: V, eps = EPS) { - if (this.root.set(key, val, eps, this.distanceFn)) { - this._size++; - return true; - } - return false; - } - - into(pairs: Iterable>, eps = EPS) { - return into(this, pairs, eps); - } - - remove(p: K) { - let node = this.root.nodeForPoint(p); - if (!node) return false; - this._size--; - delete node.k; - delete node.v; - let doPrune = true; - while (node.parent) { - node = node!.parent; - delete node.children![childID(p, node.pos)]; - doPrune = --node.numC === 0; - if (doPrune) delete node.children; - else break; - } - return true; - } - - has(p: K, eps = EPS) { - return !!(eps <= 0 - ? this.root.nodeForPoint(p) - : this.root.queryKeys(p, eps, 1, [], this.distanceFn).length); - } - - get(p: K, eps = EPS) { - if (eps <= 0) { - const node = this.root.nodeForPoint(p); - return node ? node.v : undefined; - } - return this.root.queryValues(p, eps, 1, [], this.distanceFn)[0]; - } - - query(p: K, r: number, max = 1, acc: Pair[] = []) { - return this.root.query( - (n) => >[n.k, n.v], - p, - r, - max, - acc, - this.distanceFn - ); - } - - queryKeys(p: K, r: number, max = 1, acc: K[] = []) { - return this.root.queryKeys(p, r, max, acc, this.distanceFn); - } - - queryValues(p: K, r: number, max = 1, acc: V[] = []) { - return this.root.queryValues(p, r, max, acc, this.distanceFn); - } - - containsPoint(p: K) { - return this.root.containsPoint(p); - } - - nodeForPoint(p: K): NdQtNode | undefined { - return this.root.nodeForPoint(p); - } + static readonly MAX_DIM = 16; + + /** + * Returns a new point-based `NdQuadtreeMap` for nD keys in given + * region defined by `min` / `max` coordinates. The dimensionality + * of the tree is implicitly defined by the provided coordinates. + * Only points within that region can be indexed. + * + * @remarks + * Due to exponentially growing lookup tables, currently only + * supports up to 16 dimensions. + */ + static fromMinMax( + min: ReadonlyVec, + max: ReadonlyVec + ) { + return new NdQuadtreeMap( + addmN([], min, max, 0.5), + submN([], max, min, 0.5) + ); + } + + root: NdQtNode; + protected _size: number; + + constructor( + pos: ReadonlyVec, + ext: ReadonlyVec, + pairs?: Iterable>, + public readonly distanceFn: DistanceFn = distSq + ) { + const dim = pos.length; + assert( + dim > 0 && dim <= NdQuadtreeMap.MAX_DIM, + `illegal dimension: ${dim}` + ); + assert(ext.length === dim, `pos/ext dimensions must be equal`); + initChildOffsets(dim); + this.root = new NdQtNode(undefined, pos, ext); + this._size = 0; + pairs && this.into(pairs, -1); + } + + get size() { + return this._size; + } + + [Symbol.iterator](): IterableIterator> { + return map((n) => [n.k!, n.v!], this.nodes()); + } + + keys() { + return map((n) => n.k, this.nodes()); + } + + values() { + return map((n) => n.v, this.nodes()); + } + + *nodes(all = false) { + let queue: NdQtNode[] = [this.root]; + while (queue.length) { + const n = queue.pop(); + if (n) { + if (all || n.k) yield n; + if (n.children) queue = queue.concat(n.children); + } + } + } + + copy(): NdQuadtreeMap { + const tree = new NdQuadtreeMap( + this.root.pos, + this.root.ext, + this, + this.distanceFn + ); + return tree; + } + + clear() { + this.root.clear(); + this._size = 0; + } + + empty() { + return new NdQuadtreeMap( + this.root.pos, + this.root.ext, + undefined, + this.distanceFn + ); + } + + set(key: K, val: V, eps = EPS) { + if (this.root.set(key, val, eps, this.distanceFn)) { + this._size++; + return true; + } + return false; + } + + into(pairs: Iterable>, eps = EPS) { + return into(this, pairs, eps); + } + + remove(p: K) { + let node = this.root.nodeForPoint(p); + if (!node) return false; + this._size--; + delete node.k; + delete node.v; + let doPrune = true; + while (node.parent) { + node = node!.parent; + delete node.children![childID(p, node.pos)]; + doPrune = --node.numC === 0; + if (doPrune) delete node.children; + else break; + } + return true; + } + + has(p: K, eps = EPS) { + return !!(eps <= 0 + ? this.root.nodeForPoint(p) + : this.root.queryKeys(p, eps, 1, [], this.distanceFn).length); + } + + get(p: K, eps = EPS) { + if (eps <= 0) { + const node = this.root.nodeForPoint(p); + return node ? node.v : undefined; + } + return this.root.queryValues(p, eps, 1, [], this.distanceFn)[0]; + } + + query(p: K, r: number, max = 1, acc: Pair[] = []) { + return this.root.query( + (n) => >[n.k, n.v], + p, + r, + max, + acc, + this.distanceFn + ); + } + + queryKeys(p: K, r: number, max = 1, acc: K[] = []) { + return this.root.queryKeys(p, r, max, acc, this.distanceFn); + } + + queryValues(p: K, r: number, max = 1, acc: V[] = []) { + return this.root.queryValues(p, r, max, acc, this.distanceFn); + } + + containsPoint(p: K) { + return this.root.containsPoint(p); + } + + nodeForPoint(p: K): NdQtNode | undefined { + return this.root.nodeForPoint(p); + } } const MAX_CHILDREN = [ - ...take( - NdQuadtreeMap.MAX_DIM + 1, - iterate((x) => x * 2, 1) - ), + ...take( + NdQuadtreeMap.MAX_DIM + 1, + iterate((x) => x * 2, 1) + ), ]; const CHILD_OFFSETS: ReadonlyVec[][] = []; const initChildOffsets = (dim: number) => - CHILD_OFFSETS[dim] || - (CHILD_OFFSETS[dim] = [...permutations(...repeat([-1, 1], dim))]); + CHILD_OFFSETS[dim] || + (CHILD_OFFSETS[dim] = [...permutations(...repeat([-1, 1], dim))]); const childID: MultiVecOpRoVV = vop(0); childID.add(1, (p, q) => (p[0] >= q[0] ? 1 : 0)); childID.add(2, (p, q) => (p[0] >= q[0] ? 2 : 0) | (p[1] >= q[1] ? 1 : 0)); childID.add( - 3, - (p, q) => - (p[0] >= q[0] ? 4 : 0) | (p[1] >= q[1] ? 2 : 0) | (p[2] >= q[2] ? 1 : 0) + 3, + (p, q) => + (p[0] >= q[0] ? 4 : 0) | (p[1] >= q[1] ? 2 : 0) | (p[2] >= q[2] ? 1 : 0) ); childID.add( - 4, - (p, q) => - (p[0] >= q[0] ? 8 : 0) | - (p[1] >= q[1] ? 4 : 0) | - (p[2] >= q[2] ? 2 : 0) | - (p[3] >= q[3] ? 1 : 0) + 4, + (p, q) => + (p[0] >= q[0] ? 8 : 0) | + (p[1] >= q[1] ? 4 : 0) | + (p[2] >= q[2] ? 2 : 0) | + (p[3] >= q[3] ? 1 : 0) ); childID.default((p, q) => { - let id = 0; - for (let i = 0, n = p.length - 1, bit = 1 << n; i <= n; i++, bit >>>= 1) { - p[i] >= q[i] && (id += bit); - } - return id; + let id = 0; + for (let i = 0, n = p.length - 1, bit = 1 << n; i <= n; i++, bit >>>= 1) { + p[i] >= q[i] && (id += bit); + } + return id; }); diff --git a/packages/geom-accel/src/nd-quadtree-set.ts b/packages/geom-accel/src/nd-quadtree-set.ts index 250764e798..c3083d7088 100644 --- a/packages/geom-accel/src/nd-quadtree-set.ts +++ b/packages/geom-accel/src/nd-quadtree-set.ts @@ -7,118 +7,118 @@ import { submN } from "@thi.ng/vectors/submn"; import { NdQuadtreeMap } from "./nd-quadtree-map.js"; export class NdQuadtreeSet - implements - ICopy>, - IEmpty>, - IRegionQuery, - ISpatialSet + implements + ICopy>, + IEmpty>, + IRegionQuery, + ISpatialSet { - /** - * Returns a new point-based `NdQuadtreeSet` for nD keys in given - * region defined by `min` / `max` coordinates. The dimensionality - * of the tree is implicitly defined by the provided coordinates. - * Only points within that region can be indexed. - * - * @remarks - * Due to exponentially growing lookup tables, currently only - * supports up to 16 dimensions. - */ - static fromMinMax( - min: ReadonlyVec, - max: ReadonlyVec - ) { - return new NdQuadtreeSet( - addmN([], min, max, 0.5), - submN([], max, min, 0.5) - ); - } - - protected tree: NdQuadtreeMap; - protected _size: number; - - constructor( - pos: ReadonlyVec, - ext: ReadonlyVec, - keys?: Iterable, - distanceFn?: DistanceFn - ) { - this.tree = new NdQuadtreeMap(pos, ext, undefined, distanceFn); - this._size = 0; - keys && this.into(keys); - } - - [Symbol.iterator]() { - return this.tree.keys(); - } - - keys() { - return this.tree.keys(); - } - - values() { - return this.tree.values(); - } - - get size() { - return this._size; - } - - copy(): NdQuadtreeSet { - return new NdQuadtreeSet( - this.tree.root.pos, - this.tree.root.ext, - this, - this.tree.distanceFn - ); - } - - clear() { - this.tree.clear(); - } - - empty() { - return new NdQuadtreeSet( - this.tree.root.pos, - this.tree.root.ext, - undefined, - this.tree.distanceFn - ); - } - - add(key: K, eps = EPS) { - return this.tree.set(key, key, eps); - } - - into(keys: Iterable, eps = EPS) { - let ok = true; - const tree = this.tree; - for (let k of keys) { - ok = tree.set(k, k, eps) && ok; - } - return ok; - } - - remove(key: K) { - return this.tree.remove(key); - } - - has(key: K, eps = EPS) { - return this.tree.has(key, eps); - } - - get(key: K, eps?: number) { - return this.tree.get(key, eps); - } - - query(q: K, maxDist: number, limit?: number, acc?: Pair[]) { - return this.tree.query(q, maxDist, limit, acc); - } - - queryKeys(q: K, maxDist: number, limit: number, acc?: K[]): K[] { - return this.tree.queryKeys(q, maxDist, limit, acc); - } - - queryValues(q: K, maxDist: number, limit: number, acc?: K[]): K[] { - return this.tree.queryKeys(q, maxDist, limit, acc); - } + /** + * Returns a new point-based `NdQuadtreeSet` for nD keys in given + * region defined by `min` / `max` coordinates. The dimensionality + * of the tree is implicitly defined by the provided coordinates. + * Only points within that region can be indexed. + * + * @remarks + * Due to exponentially growing lookup tables, currently only + * supports up to 16 dimensions. + */ + static fromMinMax( + min: ReadonlyVec, + max: ReadonlyVec + ) { + return new NdQuadtreeSet( + addmN([], min, max, 0.5), + submN([], max, min, 0.5) + ); + } + + protected tree: NdQuadtreeMap; + protected _size: number; + + constructor( + pos: ReadonlyVec, + ext: ReadonlyVec, + keys?: Iterable, + distanceFn?: DistanceFn + ) { + this.tree = new NdQuadtreeMap(pos, ext, undefined, distanceFn); + this._size = 0; + keys && this.into(keys); + } + + [Symbol.iterator]() { + return this.tree.keys(); + } + + keys() { + return this.tree.keys(); + } + + values() { + return this.tree.values(); + } + + get size() { + return this._size; + } + + copy(): NdQuadtreeSet { + return new NdQuadtreeSet( + this.tree.root.pos, + this.tree.root.ext, + this, + this.tree.distanceFn + ); + } + + clear() { + this.tree.clear(); + } + + empty() { + return new NdQuadtreeSet( + this.tree.root.pos, + this.tree.root.ext, + undefined, + this.tree.distanceFn + ); + } + + add(key: K, eps = EPS) { + return this.tree.set(key, key, eps); + } + + into(keys: Iterable, eps = EPS) { + let ok = true; + const tree = this.tree; + for (let k of keys) { + ok = tree.set(k, k, eps) && ok; + } + return ok; + } + + remove(key: K) { + return this.tree.remove(key); + } + + has(key: K, eps = EPS) { + return this.tree.has(key, eps); + } + + get(key: K, eps?: number) { + return this.tree.get(key, eps); + } + + query(q: K, maxDist: number, limit?: number, acc?: Pair[]) { + return this.tree.query(q, maxDist, limit, acc); + } + + queryKeys(q: K, maxDist: number, limit: number, acc?: K[]): K[] { + return this.tree.queryKeys(q, maxDist, limit, acc); + } + + queryValues(q: K, maxDist: number, limit: number, acc?: K[]): K[] { + return this.tree.queryKeys(q, maxDist, limit, acc); + } } diff --git a/packages/geom-accel/src/spatial-grid2.ts b/packages/geom-accel/src/spatial-grid2.ts index 139ceafb67..5105f99ab5 100644 --- a/packages/geom-accel/src/spatial-grid2.ts +++ b/packages/geom-accel/src/spatial-grid2.ts @@ -12,58 +12,58 @@ import { addResults, CMP } from "./utils.js"; const TMP: Vec = []; export class SpatialGrid2 extends ASpatialGrid { - constructor( - min: ReadonlyVec, - size: ReadonlyVec, - res: ReadonlyVec | number - ) { - super(min, size, isNumber(res) ? [res, res] : res); - this._cells = new Array(this._res[0] * this._res[1]); - } + constructor( + min: ReadonlyVec, + size: ReadonlyVec, + res: ReadonlyVec | number + ) { + super(min, size, isNumber(res) ? [res, res] : res); + this._cells = new Array(this._res[0] * this._res[1]); + } - copy(): SpatialGrid2 { - return >super.copy(); - } + copy(): SpatialGrid2 { + return >super.copy(); + } - empty() { - return new SpatialGrid2(this._min, this._size, this._res); - } + empty() { + return new SpatialGrid2(this._min, this._size, this._res); + } - protected doQuery( - fn: Fn, T>, - k: K, - r: number, - limit = Infinity, - acc: T[] = [] - ) { - const id1 = this.findIndex(subN2(TMP, k, r)); - const id2 = this.findIndex(addN2(TMP, k, r)); - const stride = this._res[0]; - const x1 = id1 % stride; - const x2 = id2 % stride; - const y1 = ((id1 / stride) | 0) * stride; - const y2 = ((id2 / stride) | 0) * stride; - const cells = this._cells; - let c: Nullable[]>; - let x: number, y: number; - r *= r; - const heap = new Heap<[number, Nullable>?]>([[r]], { - compare: CMP, - }); - const sel = heap.values; - for (y = y1; y <= y2; y += stride) { - for (x = x1; x <= x2; x++) { - c = cells[y + x]; - c && c.length && this.queryCell(distSq2, heap, c, k, limit); - } - } - return addResults(fn, sel, acc); - } + protected doQuery( + fn: Fn, T>, + k: K, + r: number, + limit = Infinity, + acc: T[] = [] + ) { + const id1 = this.findIndex(subN2(TMP, k, r)); + const id2 = this.findIndex(addN2(TMP, k, r)); + const stride = this._res[0]; + const x1 = id1 % stride; + const x2 = id2 % stride; + const y1 = ((id1 / stride) | 0) * stride; + const y2 = ((id2 / stride) | 0) * stride; + const cells = this._cells; + let c: Nullable[]>; + let x: number, y: number; + r *= r; + const heap = new Heap<[number, Nullable>?]>([[r]], { + compare: CMP, + }); + const sel = heap.values; + for (y = y1; y <= y2; y += stride) { + for (x = x1; x <= x2; x++) { + c = cells[y + x]; + c && c.length && this.queryCell(distSq2, heap, c, k, limit); + } + } + return addResults(fn, sel, acc); + } - protected findIndex(k: ReadonlyVec) { - const { _min: min, _res1: res1, _invSize: invSize } = this; - const kx = clamp((k[0] - min[0]) * invSize[0], 0, res1[0]); - const ky = clamp((k[1] - min[1]) * invSize[1], 0, res1[1]); - return (kx | 0) + (ky | 0) * this._res[0]; - } + protected findIndex(k: ReadonlyVec) { + const { _min: min, _res1: res1, _invSize: invSize } = this; + const kx = clamp((k[0] - min[0]) * invSize[0], 0, res1[0]); + const ky = clamp((k[1] - min[1]) * invSize[1], 0, res1[1]); + return (kx | 0) + (ky | 0) * this._res[0]; + } } diff --git a/packages/geom-accel/src/spatial-grid3.ts b/packages/geom-accel/src/spatial-grid3.ts index 6f5f2f6306..c2c53750df 100644 --- a/packages/geom-accel/src/spatial-grid3.ts +++ b/packages/geom-accel/src/spatial-grid3.ts @@ -12,68 +12,68 @@ import { addResults, CMP } from "./utils.js"; const TMP: Vec = []; export class SpatialGrid3 extends ASpatialGrid { - protected _stride: ReadonlyVec; + protected _stride: ReadonlyVec; - constructor( - min: ReadonlyVec, - size: ReadonlyVec, - res: ReadonlyVec | number - ) { - super(min, size, isNumber(res) ? [res, res, res] : res); - this._cells = new Array(this._res[0] * this._res[1] * this._res[2]); - this._stride = [this._res[0], this._res[0] * this._res[1]]; - } + constructor( + min: ReadonlyVec, + size: ReadonlyVec, + res: ReadonlyVec | number + ) { + super(min, size, isNumber(res) ? [res, res, res] : res); + this._cells = new Array(this._res[0] * this._res[1] * this._res[2]); + this._stride = [this._res[0], this._res[0] * this._res[1]]; + } - copy(): SpatialGrid3 { - return >super.copy(); - } + copy(): SpatialGrid3 { + return >super.copy(); + } - empty() { - return new SpatialGrid3(this._min, this._size, this._res); - } + empty() { + return new SpatialGrid3(this._min, this._size, this._res); + } - protected doQuery( - fn: Fn, T>, - k: K, - r: number, - limit = Infinity, - acc: T[] = [] - ) { - const id1 = this.findIndex(subN3(TMP, k, r)); - const id2 = this.findIndex(addN3(TMP, k, r)); - const [width, slice] = this._stride; - const x1 = id1 % width; - const x2 = id2 % width; - const y1 = ((id1 / width) | 0) * width; - const y2 = ((id2 / width) | 0) * width; - const z1 = ((id1 / slice) | 0) * slice; - const z2 = ((id2 / slice) | 0) * slice; - const cells = this._cells; - let c: Nullable[]>; - let x: number, y: number, z: number; - r *= r; - const heap = new Heap<[number, Nullable>?]>([[r]], { - compare: CMP, - }); - const sel = heap.values; - for (z = z1; z <= z2; z += slice) { - for (y = y1; y <= y2; y += width) { - for (x = x1; x <= x2; x++) { - c = cells[z + y + x]; - c && c.length && this.queryCell(distSq3, heap, c, k, limit); - } - } - } - return addResults(fn, sel, acc); - } + protected doQuery( + fn: Fn, T>, + k: K, + r: number, + limit = Infinity, + acc: T[] = [] + ) { + const id1 = this.findIndex(subN3(TMP, k, r)); + const id2 = this.findIndex(addN3(TMP, k, r)); + const [width, slice] = this._stride; + const x1 = id1 % width; + const x2 = id2 % width; + const y1 = ((id1 / width) | 0) * width; + const y2 = ((id2 / width) | 0) * width; + const z1 = ((id1 / slice) | 0) * slice; + const z2 = ((id2 / slice) | 0) * slice; + const cells = this._cells; + let c: Nullable[]>; + let x: number, y: number, z: number; + r *= r; + const heap = new Heap<[number, Nullable>?]>([[r]], { + compare: CMP, + }); + const sel = heap.values; + for (z = z1; z <= z2; z += slice) { + for (y = y1; y <= y2; y += width) { + for (x = x1; x <= x2; x++) { + c = cells[z + y + x]; + c && c.length && this.queryCell(distSq3, heap, c, k, limit); + } + } + } + return addResults(fn, sel, acc); + } - protected findIndex(k: ReadonlyVec) { - const { _min: min, _res1: res1, _invSize: invSize } = this; - const kx = clamp((k[0] - min[0]) * invSize[0], 0, res1[0]); - const ky = clamp((k[1] - min[1]) * invSize[1], 0, res1[1]); - const kz = clamp((k[2] - min[2]) * invSize[2], 0, res1[2]); - return ( - (kx | 0) + (ky | 0) * this._stride[0] + (kz | 0) * this._stride[1] - ); - } + protected findIndex(k: ReadonlyVec) { + const { _min: min, _res1: res1, _invSize: invSize } = this; + const kx = clamp((k[0] - min[0]) * invSize[0], 0, res1[0]); + const ky = clamp((k[1] - min[1]) * invSize[1], 0, res1[1]); + const kz = clamp((k[2] - min[2]) * invSize[2], 0, res1[2]); + return ( + (kx | 0) + (ky | 0) * this._stride[0] + (kz | 0) * this._stride[1] + ); + } } diff --git a/packages/geom-accel/src/utils.ts b/packages/geom-accel/src/utils.ts index 69deec061e..7ebc04d9b1 100644 --- a/packages/geom-accel/src/utils.ts +++ b/packages/geom-accel/src/utils.ts @@ -6,34 +6,34 @@ export const CMP = (a: [number, any?], b: [number, any?]) => b[0] - a[0]; /** @internal */ export const addResults = ( - fn: Fn, - sel: [number, Nullable?][], - acc: B[] + fn: Fn, + sel: [number, Nullable?][], + acc: B[] ) => { - for (let n = sel.sort(CMP).length; n-- > 0; ) { - const s = sel[n][1]; - s && acc.push(fn(s)); - } - return acc; + for (let n = sel.sort(CMP).length; n-- > 0; ) { + const s = sel[n][1]; + s && acc.push(fn(s)); + } + return acc; }; /** * Shared `into()` impl for spatial map types. * - * @param map - - * @param pairs - - * @param eps - + * @param map - + * @param pairs - + * @param eps - * * @internal */ export const into = ( - map: { set: Fn3 }, - pairs: Iterable>, - eps: number + map: { set: Fn3 }, + pairs: Iterable>, + eps: number ) => { - let ok = true; - for (let p of pairs) { - ok = map.set(p[0], p[1], eps) && ok; - } - return ok; + let ok = true; + for (let p of pairs) { + ok = map.set(p[0], p[1], eps) && ok; + } + return ok; }; diff --git a/packages/geom-accel/test/kdtree.ts b/packages/geom-accel/test/kdtree.ts index 70333e2054..f033fb5c93 100644 --- a/packages/geom-accel/test/kdtree.ts +++ b/packages/geom-accel/test/kdtree.ts @@ -5,80 +5,98 @@ import * as assert from "assert"; import { KdTreeMap, KdTreeSet } from "../src/index.js"; const pts3D = new Set([ - [10, 20, 30], - [60, 70, 80], - [44, 55, 66], + [10, 20, 30], + [60, 70, 80], + [44, 55, 66], ]); -const pairs3D = new Set(mapIndexed((i, p) => <[ReadonlyVec, number]>[p, i], pts3D)); +const pairs3D = new Set( + mapIndexed((i, p) => <[ReadonlyVec, number]>[p, i], pts3D) +); const pts2D = new Set([ - [0, 0], - [85, 0], - [70, 180], - [-90, 45], + [0, 0], + [85, 0], + [70, 180], + [-90, 45], ]); -const pairs2D = new Set(mapIndexed((i, p) => <[ReadonlyVec, number]>[p, i], pts2D)); +const pairs2D = new Set( + mapIndexed((i, p) => <[ReadonlyVec, number]>[p, i], pts2D) +); let treeMap: KdTreeMap; let treeSet: KdTreeSet; group( - "KdTreeMap - 3D", - { - ctor: () => { - assert.deepEqual(treeMap.dim, 3); - assert.deepEqual(treeMap.height, 2); - }, - }, - { - beforeEach: () => { - treeMap = new KdTreeMap(3, pairs3D); - } - } + "KdTreeMap - 3D", + { + ctor: () => { + assert.deepEqual(treeMap.dim, 3); + assert.deepEqual(treeMap.height, 2); + }, + }, + { + beforeEach: () => { + treeMap = new KdTreeMap(3, pairs3D); + }, + } ); group( - "KdTreeMap - 2D", - { - ctor: () => { - assert.deepEqual(treeMap.dim, 2); - assert.deepEqual(treeMap.height, 3); - assert.deepEqual(treeMap.size, 4); - }, - query: () => { - assert.deepEqual(treeMap.query([85, 180], Infinity, 1), [[[70, 180], 2]]); - }, - "haversine distance / query": () => { - treeMap = new KdTreeMap(2, pairs2D, distHaversineLatLon); - assert.deepEqual(treeMap.query([85, 180], Infinity, 1), [[[85, 0], 1]]); - } - }, - { - beforeEach: () => { - treeMap = new KdTreeMap(2, pairs2D); - } - } + "KdTreeMap - 2D", + { + ctor: () => { + assert.deepEqual(treeMap.dim, 2); + assert.deepEqual(treeMap.height, 3); + assert.deepEqual(treeMap.size, 4); + }, + query: () => { + assert.deepEqual(treeMap.query([85, 180], Infinity, 1), [ + [[70, 180], 2], + ]); + }, + "haversine distance / query": () => { + treeMap = new KdTreeMap(2, pairs2D, distHaversineLatLon); + assert.deepEqual(treeMap.query([85, 180], Infinity, 1), [ + [[85, 0], 1], + ]); + }, + }, + { + beforeEach: () => { + treeMap = new KdTreeMap(2, pairs2D); + }, + } ); group( - "KdTreeSet - 2D", - { - ctor: () => { - assert.deepEqual(treeSet.size, 4); - }, - query: () => { - assert.deepEqual(treeSet.query([85, 180], Infinity, 1), [[[70, 180], [70, 180]]]); - }, - "haversine distance / query": () => { - treeSet = new KdTreeSet(2, pts2D, distHaversineLatLon); - assert.deepEqual(treeSet.query([85, 180], Infinity, 1), [[[85, 0], [85, 0]]]); - } - }, - { - beforeEach: () => { - treeSet = new KdTreeSet(2, pts2D); - } - } -) \ No newline at end of file + "KdTreeSet - 2D", + { + ctor: () => { + assert.deepEqual(treeSet.size, 4); + }, + query: () => { + assert.deepEqual(treeSet.query([85, 180], Infinity, 1), [ + [ + [70, 180], + [70, 180], + ], + ]); + }, + "haversine distance / query": () => { + treeSet = new KdTreeSet(2, pts2D, distHaversineLatLon); + assert.deepEqual(treeSet.query([85, 180], Infinity, 1), [ + [ + [85, 0], + [85, 0], + ], + ]); + }, + }, + { + beforeEach: () => { + treeSet = new KdTreeSet(2, pts2D); + }, + } +); diff --git a/packages/geom-accel/test/quadtree.ts b/packages/geom-accel/test/quadtree.ts index 8e844fb387..398510f139 100644 --- a/packages/geom-accel/test/quadtree.ts +++ b/packages/geom-accel/test/quadtree.ts @@ -2,12 +2,12 @@ import { group } from "@thi.ng/testament"; import { mapIndexed } from "@thi.ng/transducers"; import type { ReadonlyVec } from "@thi.ng/vectors"; import * as assert from "assert"; -import { NdQuadtreeMap } from "../src/index.js" +import { NdQuadtreeMap } from "../src/index.js"; const pts = new Set([ - [10, 20, 30], - [60, 70, 80], - [44, 55, 66], + [10, 20, 30], + [60, 70, 80], + [44, 55, 66], ]); const pairs = new Set(mapIndexed((i, p) => <[ReadonlyVec, number]>[p, i], pts)); @@ -15,59 +15,59 @@ const pairs = new Set(mapIndexed((i, p) => <[ReadonlyVec, number]>[p, i], pts)); let tree: NdQuadtreeMap; group( - "NdTree", - { - ctor: () => { - assert.deepStrictEqual(tree.root.pos, [50, 50, 50]); - assert.deepStrictEqual(tree.root.ext, [50, 50, 50]); - }, + "NdTree", + { + ctor: () => { + assert.deepStrictEqual(tree.root.pos, [50, 50, 50]); + assert.deepStrictEqual(tree.root.ext, [50, 50, 50]); + }, - "into / get / has": () => { - assert.ok(tree.into(pairs)); - for (let p of pairs) { - assert.ok(tree.has(p[0]), `has: ${p}`); - assert.strictEqual(tree.get(p[0]), p[1], `get ${p}`); - } - }, + "into / get / has": () => { + assert.ok(tree.into(pairs)); + for (let p of pairs) { + assert.ok(tree.has(p[0]), `has: ${p}`); + assert.strictEqual(tree.get(p[0]), p[1], `get ${p}`); + } + }, - "add duplicate": () => { - tree.into(pairs); - assert.ok(!tree.set([10, 20, 30], 10)); - assert.ok(!tree.set([10.01, 20, 30], 100, 0.1)); - // TODO check new value - }, + "add duplicate": () => { + tree.into(pairs); + assert.ok(!tree.set([10, 20, 30], 10)); + assert.ok(!tree.set([10.01, 20, 30], 100, 0.1)); + // TODO check new value + }, - iterators: () => { - tree.into(pairs); - assert.deepStrictEqual(new Set(tree), pairs); - assert.deepStrictEqual(new Set(tree.keys()), pts); - }, + iterators: () => { + tree.into(pairs); + assert.deepStrictEqual(new Set(tree), pairs); + assert.deepStrictEqual(new Set(tree.keys()), pts); + }, - selectKeys: () => { - tree.into(pairs); - assert.deepStrictEqual( - new Set(tree.queryKeys([50, 50, 50], 100, Infinity)), - pts, - "r=100" - ); - assert.deepStrictEqual( - new Set(tree.queryKeys([50, 50, 50], 50, Infinity)), - new Set([ - [44, 55, 66], - [60, 70, 80], - ]), - "r=50" - ); - assert.deepStrictEqual( - new Set(tree.queryKeys([20, 20, 20], 15, Infinity)), - new Set([[10, 20, 30]]), - "r=25" - ); - }, - }, - { - beforeEach: () => { - tree = NdQuadtreeMap.fromMinMax([0, 0, 0], [100, 100, 100]); - }, - } + selectKeys: () => { + tree.into(pairs); + assert.deepStrictEqual( + new Set(tree.queryKeys([50, 50, 50], 100, Infinity)), + pts, + "r=100" + ); + assert.deepStrictEqual( + new Set(tree.queryKeys([50, 50, 50], 50, Infinity)), + new Set([ + [44, 55, 66], + [60, 70, 80], + ]), + "r=50" + ); + assert.deepStrictEqual( + new Set(tree.queryKeys([20, 20, 20], 15, Infinity)), + new Set([[10, 20, 30]]), + "r=25" + ); + }, + }, + { + beforeEach: () => { + tree = NdQuadtreeMap.fromMinMax([0, 0, 0], [100, 100, 100]); + }, + } ); diff --git a/packages/geom-accel/tsconfig.json b/packages/geom-accel/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-accel/tsconfig.json +++ b/packages/geom-accel/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-api/api-extractor.json b/packages/geom-api/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-api/api-extractor.json +++ b/packages/geom-api/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-api/package.json b/packages/geom-api/package.json index d547ae5e08..cbe3d31438 100644 --- a/packages/geom-api/package.json +++ b/packages/geom-api/package.json @@ -1,102 +1,102 @@ { - "name": "@thi.ng/geom-api", - "version": "3.3.2", - "description": "Shared type & interface declarations for @thi.ng/geom packages", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-api#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "geometry", - "interface", - "type", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./accel": { - "default": "./accel.js" - }, - "./clip": { - "default": "./clip.js" - }, - "./convex": { - "default": "./convex.js" - }, - "./cubic": { - "default": "./cubic.js" - }, - "./isec": { - "default": "./isec.js" - }, - "./path": { - "default": "./path.js" - }, - "./sample": { - "default": "./sample.js" - }, - "./shape": { - "default": "./shape.js" - }, - "./subdiv": { - "default": "./subdiv.js" - }, - "./tessel": { - "default": "./tessel.js" - } - }, - "thi.ng": { - "year": 2013 - } + "name": "@thi.ng/geom-api", + "version": "3.3.2", + "description": "Shared type & interface declarations for @thi.ng/geom packages", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-api#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "geometry", + "interface", + "type", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./accel": { + "default": "./accel.js" + }, + "./clip": { + "default": "./clip.js" + }, + "./convex": { + "default": "./convex.js" + }, + "./cubic": { + "default": "./cubic.js" + }, + "./isec": { + "default": "./isec.js" + }, + "./path": { + "default": "./path.js" + }, + "./sample": { + "default": "./sample.js" + }, + "./shape": { + "default": "./shape.js" + }, + "./subdiv": { + "default": "./subdiv.js" + }, + "./tessel": { + "default": "./tessel.js" + } + }, + "thi.ng": { + "year": 2013 + } } diff --git a/packages/geom-api/src/accel.ts b/packages/geom-api/src/accel.ts index 64e8dd29c3..c428d46480 100644 --- a/packages/geom-api/src/accel.ts +++ b/packages/geom-api/src/accel.ts @@ -1,41 +1,41 @@ import type { IClear, ICopy, IEmpty, Pair } from "@thi.ng/api"; export interface ISpatialMap - extends Iterable>, - IClear, - ICopy>, - IEmpty> { - readonly size: number; + extends Iterable>, + IClear, + ICopy>, + IEmpty> { + readonly size: number; - keys(): IterableIterator; - values(): IterableIterator; + keys(): IterableIterator; + values(): IterableIterator; - set(key: K, v: V, eps?: number): boolean; - into(pairs: Iterable>, eps?: number): boolean; - remove(key: K): boolean; - has(key: K, eps?: number): boolean; - get(key: K, eps?: number): V | undefined; + set(key: K, v: V, eps?: number): boolean; + into(pairs: Iterable>, eps?: number): boolean; + remove(key: K): boolean; + has(key: K, eps?: number): boolean; + get(key: K, eps?: number): V | undefined; } export interface ISpatialSet - extends Iterable, - IClear, - ICopy>, - IEmpty> { - readonly size: number; + extends Iterable, + IClear, + ICopy>, + IEmpty> { + readonly size: number; - keys(): IterableIterator; - values(): IterableIterator; + keys(): IterableIterator; + values(): IterableIterator; - add(key: K, eps?: number): boolean; - into(keys: Iterable, eps?: number): boolean; - remove(key: K): boolean; - has(key: K, eps?: number): boolean; - get(key: K, eps?: number): K | undefined; + add(key: K, eps?: number): boolean; + into(keys: Iterable, eps?: number): boolean; + remove(key: K): boolean; + has(key: K, eps?: number): boolean; + get(key: K, eps?: number): K | undefined; } export interface IRegionQuery { - query(q: K, region: R, limit: number, acc?: Pair[]): Pair[]; - queryKeys(q: K, region: number, limit: number, acc?: K[]): K[]; - queryValues(q: K, region: number, limit: number, acc?: V[]): V[]; + query(q: K, region: R, limit: number, acc?: Pair[]): Pair[]; + queryKeys(q: K, region: number, limit: number, acc?: K[]): K[]; + queryValues(q: K, region: number, limit: number, acc?: V[]): V[]; } diff --git a/packages/geom-api/src/convex.ts b/packages/geom-api/src/convex.ts index 8002d41a38..bedd7c3345 100644 --- a/packages/geom-api/src/convex.ts +++ b/packages/geom-api/src/convex.ts @@ -2,8 +2,8 @@ * Polygon convexity classifier. */ export enum Convexity { - ILLEGAL = -1, - COLINEAR = 0, - CONVEX, - CONCAVE, + ILLEGAL = -1, + COLINEAR = 0, + CONVEX, + CONCAVE, } diff --git a/packages/geom-api/src/cubic.ts b/packages/geom-api/src/cubic.ts index 59585a7ddc..b57bcc5a33 100644 --- a/packages/geom-api/src/cubic.ts +++ b/packages/geom-api/src/cubic.ts @@ -1,18 +1,18 @@ export interface CubicOpts { - /** - * Set to true (default false) to interpret original vertices as - * breakpoints - */ - breakPoints: boolean; - /** - * True, to enable uniform tangent scaling. If false (default), each - * tangent will be also scaled by the length of its related parent - * edge in the source shape. - */ - uniform: boolean; - /** - * Tangent scale factor. Actual length in uniform scaling mode, else - * should be a value between [0..1.33333] - */ - scale: number; + /** + * Set to true (default false) to interpret original vertices as + * breakpoints + */ + breakPoints: boolean; + /** + * True, to enable uniform tangent scaling. If false (default), each + * tangent will be also scaled by the length of its related parent + * edge in the source shape. + */ + uniform: boolean; + /** + * Tangent scale factor. Actual length in uniform scaling mode, else + * should be a value between [0..1.33333] + */ + scale: number; } diff --git a/packages/geom-api/src/isec.ts b/packages/geom-api/src/isec.ts index fac84560d7..980c906150 100644 --- a/packages/geom-api/src/isec.ts +++ b/packages/geom-api/src/isec.ts @@ -1,19 +1,19 @@ import type { Vec } from "@thi.ng/vectors"; export enum IntersectionType { - NONE, - PARALLEL, - COINCIDENT, - COINCIDENT_NO_INTERSECT, - INTERSECT, - INTERSECT_OUTSIDE, + NONE, + PARALLEL, + COINCIDENT, + COINCIDENT_NO_INTERSECT, + INTERSECT, + INTERSECT_OUTSIDE, } export interface IntersectionResult { - type: IntersectionType; - isec?: Vec | Vec[]; - det?: number; - alpha?: number; - beta?: number; - inside?: boolean; + type: IntersectionType; + isec?: Vec | Vec[]; + det?: number; + alpha?: number; + beta?: number; + inside?: boolean; } diff --git a/packages/geom-api/src/path.ts b/packages/geom-api/src/path.ts index 58132f3fcb..e14a28043d 100644 --- a/packages/geom-api/src/path.ts +++ b/packages/geom-api/src/path.ts @@ -2,20 +2,20 @@ import type { Vec } from "@thi.ng/vectors"; import type { IShape } from "./shape.js"; export type SegmentType = - | "m" // move - | "l" // line - | "p" // polyline - | "a" // arc - | "c" // cubic - | "q" // quadratic - | "z"; // close + | "m" // move + | "l" // line + | "p" // polyline + | "a" // arc + | "c" // cubic + | "q" // quadratic + | "z"; // close export interface PathSegment { - type: SegmentType; - point?: Vec; - geo?: IShape & IHiccupPathSegment; + type: SegmentType; + point?: Vec; + geo?: IShape & IHiccupPathSegment; } export interface IHiccupPathSegment { - toHiccupPathSegments(): any[]; + toHiccupPathSegments(): any[]; } diff --git a/packages/geom-api/src/sample.ts b/packages/geom-api/src/sample.ts index 89102f0ab4..e4f9dd644c 100644 --- a/packages/geom-api/src/sample.ts +++ b/packages/geom-api/src/sample.ts @@ -8,44 +8,44 @@ export let DEFAULT_SAMPLES = 20; export const setDefaultSamples = (n: number) => (DEFAULT_SAMPLES = n); export interface SamplingOpts { - /** - * Number of points to sample & return. Defaults to the implementing - * type's default samples if neither this nor `theta` option is - * given. - */ - num: number; - /** - * Defines the target angle between sampled points. If greater than - * the actual range of the arc, only the two end points will be - * returned at most. This option is used to derive a `num` value and - * takes priority if `num` is given as well. - * - * This option is useful to adapt the sampling based on angular - * resolution, rather than a fixed number of samples. - */ - theta: number; - /** - * Approximate desired distance between sampled result points. If - * given, takes priority over the `num` option, but the latter MIGHT - * be used as part of the sampling process (implementation - * specific). - */ - dist: number; - /** - * If `true`, the shape's end point will be included in the result - * array. The default setting for open geometries is `true`, for - * closed ones `false`. This option has no influence on any internal - * resolution calculation. - * - * For open geometry this option is useful to when re-sampling paths - * of consecutive segments, where the end points of each segment - * coincide with the start points of the next segment. For all but - * the last segment, this option should be `false` and so can be - * used to avoid duplicate vertices in the concatenated result. - * - * When sampling closed shapes, enabling this option will include an - * extra point (start), i.e. if the `num` option was given, results - * in `num+1` points. - */ - last: boolean; + /** + * Number of points to sample & return. Defaults to the implementing + * type's default samples if neither this nor `theta` option is + * given. + */ + num: number; + /** + * Defines the target angle between sampled points. If greater than + * the actual range of the arc, only the two end points will be + * returned at most. This option is used to derive a `num` value and + * takes priority if `num` is given as well. + * + * This option is useful to adapt the sampling based on angular + * resolution, rather than a fixed number of samples. + */ + theta: number; + /** + * Approximate desired distance between sampled result points. If + * given, takes priority over the `num` option, but the latter MIGHT + * be used as part of the sampling process (implementation + * specific). + */ + dist: number; + /** + * If `true`, the shape's end point will be included in the result + * array. The default setting for open geometries is `true`, for + * closed ones `false`. This option has no influence on any internal + * resolution calculation. + * + * For open geometry this option is useful to when re-sampling paths + * of consecutive segments, where the end points of each segment + * coincide with the start points of the next segment. For all but + * the last segment, this option should be `false` and so can be + * used to avoid duplicate vertices in the concatenated result. + * + * When sampling closed shapes, enabling this option will include an + * extra point (start), i.e. if the `num` option was given, results + * in `num+1` points. + */ + last: boolean; } diff --git a/packages/geom-api/src/shape.ts b/packages/geom-api/src/shape.ts index 234c55fc50..98f6e31115 100644 --- a/packages/geom-api/src/shape.ts +++ b/packages/geom-api/src/shape.ts @@ -4,36 +4,36 @@ import type { Vec } from "@thi.ng/vectors"; export type Attribs = IObjectOf; export interface IAttributed { - attribs?: Attribs; + attribs?: Attribs; - withAttribs(attribs: Attribs): T; + withAttribs(attribs: Attribs): T; } export interface IShape> - extends IAttributed, - ICopy { - readonly type: number | string; + extends IAttributed, + ICopy { + readonly type: number | string; } export interface AABBLike extends IShape { - pos: Vec; - size: Vec; + pos: Vec; + size: Vec; - max(): Vec; - offset(x: number): this; + max(): Vec; + offset(x: number): this; } export interface SphereLike extends IShape { - pos: Vec; - r: number; + pos: Vec; + r: number; } export interface PCLike extends IShape { - points: Vec[]; + points: Vec[]; } export interface PCLikeConstructor { - new (pts: Vec[], attribs: Attribs): PCLike; + new (pts: Vec[], attribs: Attribs): PCLike; } export interface IHiccupShape extends IShape, IToHiccup {} diff --git a/packages/geom-api/src/subdiv.ts b/packages/geom-api/src/subdiv.ts index b4927c3730..9c8fbf73b0 100644 --- a/packages/geom-api/src/subdiv.ts +++ b/packages/geom-api/src/subdiv.ts @@ -1,26 +1,26 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; export interface SubdivKernel { - /** - * Subdivision function. Called with an array of `size` consecutive - * points of the original curve and can produce any number of result - * points. Additionally is passed the index `i` of the processed - * point and `nump`, the total number of point in the - * curve/polyline. The latter 2 args are useful to implement custom - * behaviors for the start / end points of the curve. - */ - fn: (pts: ReadonlyVec[], i: number, nump: number) => Vec[]; - /** - * Optional function to pre-process the original curve points prior - * to each subdivision iteration and MUST yield an iterable of - * points (e.g. for closed curves / polygons to prepend the last - * point before the first). If omitted, the curve points are - * processed as is. - */ - pre?: (pts: ReadonlyVec[]) => Iterable; - /** - * Kernal size. The subdivision function `fn` always receives `size` - * number consecutive points. - */ - size: number; + /** + * Subdivision function. Called with an array of `size` consecutive + * points of the original curve and can produce any number of result + * points. Additionally is passed the index `i` of the processed + * point and `nump`, the total number of point in the + * curve/polyline. The latter 2 args are useful to implement custom + * behaviors for the start / end points of the curve. + */ + fn: (pts: ReadonlyVec[], i: number, nump: number) => Vec[]; + /** + * Optional function to pre-process the original curve points prior + * to each subdivision iteration and MUST yield an iterable of + * points (e.g. for closed curves / polygons to prepend the last + * point before the first). If omitted, the curve points are + * processed as is. + */ + pre?: (pts: ReadonlyVec[]) => Iterable; + /** + * Kernal size. The subdivision function `fn` always receives `size` + * number consecutive points. + */ + size: number; } diff --git a/packages/geom-api/tsconfig.json b/packages/geom-api/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-api/tsconfig.json +++ b/packages/geom-api/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-arc/api-extractor.json b/packages/geom-arc/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-arc/api-extractor.json +++ b/packages/geom-arc/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-arc/package.json b/packages/geom-arc/package.json index 247705368c..26e2be9a56 100644 --- a/packages/geom-arc/package.json +++ b/packages/geom-arc/package.json @@ -1,100 +1,100 @@ { - "name": "@thi.ng/geom-arc", - "version": "2.1.19", - "description": "2D circular / elliptic arc operations", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-arc#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/checks": "^3.2.2", - "@thi.ng/geom-api": "^3.3.2", - "@thi.ng/geom-resample": "^2.1.19", - "@thi.ng/math": "^5.3.4", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "arc", - "bbox", - "circle", - "circumcenter", - "clipping", - "elliptic", - "geometry", - "interpolation", - "sample", - "shape", - "svg", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./bounds": { - "default": "./bounds.js" - }, - "./closest-point": { - "default": "./closest-point.js" - }, - "./from-endpoints": { - "default": "./from-endpoints.js" - }, - "./point-at": { - "default": "./point-at.js" - }, - "./sample": { - "default": "./sample.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "year": 2018 - } + "name": "@thi.ng/geom-arc", + "version": "2.1.19", + "description": "2D circular / elliptic arc operations", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-arc#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/checks": "^3.2.2", + "@thi.ng/geom-api": "^3.3.2", + "@thi.ng/geom-resample": "^2.1.19", + "@thi.ng/math": "^5.3.4", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "arc", + "bbox", + "circle", + "circumcenter", + "clipping", + "elliptic", + "geometry", + "interpolation", + "sample", + "shape", + "svg", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./bounds": { + "default": "./bounds.js" + }, + "./closest-point": { + "default": "./closest-point.js" + }, + "./from-endpoints": { + "default": "./from-endpoints.js" + }, + "./point-at": { + "default": "./point-at.js" + }, + "./sample": { + "default": "./sample.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "year": 2018 + } } diff --git a/packages/geom-arc/src/bounds.ts b/packages/geom-arc/src/bounds.ts index 35a0bb0f8f..dbcab8e374 100644 --- a/packages/geom-arc/src/bounds.ts +++ b/packages/geom-arc/src/bounds.ts @@ -8,34 +8,34 @@ import { set2 } from "@thi.ng/vectors/set"; import { pointAtTheta } from "./point-at.js"; export const bounds = ( - pos: ReadonlyVec, - r: ReadonlyVec, - axis: number, - start: number, - end: number + pos: ReadonlyVec, + r: ReadonlyVec, + axis: number, + start: number, + end: number ): VecPair => { - const min = set2([], MAX2); - const max = set2([], MIN2); - const p: Vec = []; - const update = (theta: number) => { - pointAtTheta(pos, r, axis, theta, p); - min2(null, min, p); - max2(null, max, p); - }; - update(start); - update(end); - if (start > end) { - const t = start; - start = end; - end = t; - } - // include multiples of π/2 within [start,end] interval - for ( - let i = roundTo(start, HALF_PI), j = roundTo(end, HALF_PI); - i < j; - i += HALF_PI - ) { - inRange(i, start, end) && update(i); - } - return [min, max]; + const min = set2([], MAX2); + const max = set2([], MIN2); + const p: Vec = []; + const update = (theta: number) => { + pointAtTheta(pos, r, axis, theta, p); + min2(null, min, p); + max2(null, max, p); + }; + update(start); + update(end); + if (start > end) { + const t = start; + start = end; + end = t; + } + // include multiples of π/2 within [start,end] interval + for ( + let i = roundTo(start, HALF_PI), j = roundTo(end, HALF_PI); + i < j; + i += HALF_PI + ) { + inRange(i, start, end) && update(i); + } + return [min, max]; }; diff --git a/packages/geom-arc/src/closest-point.ts b/packages/geom-arc/src/closest-point.ts index 19aad02c2d..b39c3a78a3 100644 --- a/packages/geom-arc/src/closest-point.ts +++ b/packages/geom-arc/src/closest-point.ts @@ -5,17 +5,17 @@ import { distSq2 } from "@thi.ng/vectors/distsq"; import { pointAtTheta } from "./point-at.js"; export const closestPoint = ( - p: ReadonlyVec, - o: ReadonlyVec, - r: ReadonlyVec, - axis: number, - start: number, - end: number, - out: Vec = [], - res?: number, - iter?: number + p: ReadonlyVec, + o: ReadonlyVec, + r: ReadonlyVec, + axis: number, + start: number, + end: number, + out: Vec = [], + res?: number, + iter?: number ) => { - const fn = (t: number) => - pointAtTheta(o, r, axis, fit01(t, start, end), out); - return fn(minError(fn, distSq2, p, res, iter)); + const fn = (t: number) => + pointAtTheta(o, r, axis, fit01(t, start, end), out); + return fn(minError(fn, distSq2, p, res, iter)); }; diff --git a/packages/geom-arc/src/from-endpoints.ts b/packages/geom-arc/src/from-endpoints.ts index 359fe696cb..07c9940bdc 100644 --- a/packages/geom-arc/src/from-endpoints.ts +++ b/packages/geom-arc/src/from-endpoints.ts @@ -26,61 +26,61 @@ import { submN2 } from "@thi.ng/vectors/submn"; * @param cw - clockwise flag */ export const fromEndPoints = ( - a: ReadonlyVec, - b: ReadonlyVec, - radii: ReadonlyVec, - axis = 0, - xl = false, - cw = false + a: ReadonlyVec, + b: ReadonlyVec, + radii: ReadonlyVec, + axis = 0, + xl = false, + cw = false ) => { - const r = abs2([], radii); - if (eqDelta2(a, b) || r[0] < EPS || r[1] < EPS) { - return; - } - axis %= TAU; - const d = submN2([], a, b, 0.5); - const c = Math.cos(axis); - const s = Math.sin(axis); - // transformed point - const tp = [c * d[0] + s * d[1], -s * d[0] + c * d[1]]; - const [tx2, ty2] = powN2([], tp, 2); - // ensure radii - const rc = tx2 / (r[0] * r[0]) + ty2 / (r[1] * r[1]); - rc > 1 && mulN2(r, r, Math.sqrt(rc)); - const [rx, ry] = r; - const rx2 = rx * rx; - const ry2 = ry * ry; - // transformed center - const radicant = Math.max( - 0, - (rx2 * ry2 - rx2 * ty2 - ry2 * tx2) / (rx2 * ty2 + ry2 * tx2) - ); - const coeff = (xl !== cw ? 1 : -1) * Math.sqrt(radicant); - const tc = [coeff * ((rx * tp[1]) / ry), coeff * (-(ry * tp[0]) / rx)]; - // actual center - const center: Vec = [ - c * tc[0] - s * tc[1] + (a[0] + b[0]) / 2, - s * tc[0] + c * tc[1] + (a[1] + b[1]) / 2, - ]; - // transformed end points & angles - const ta = div2(null, sub2([], tp, tc), r); - const tb = div2(null, sub2(null, neg([], tp), tc), r); - const start = angleBetween2(X2, ta); - let sweep = angleBetween2(ta, tb); - if (!cw && sweep > 0) { - sweep -= TAU; - } else if (cw && sweep < 0) { - sweep += TAU; - } - sweep %= TAU; + const r = abs2([], radii); + if (eqDelta2(a, b) || r[0] < EPS || r[1] < EPS) { + return; + } + axis %= TAU; + const d = submN2([], a, b, 0.5); + const c = Math.cos(axis); + const s = Math.sin(axis); + // transformed point + const tp = [c * d[0] + s * d[1], -s * d[0] + c * d[1]]; + const [tx2, ty2] = powN2([], tp, 2); + // ensure radii + const rc = tx2 / (r[0] * r[0]) + ty2 / (r[1] * r[1]); + rc > 1 && mulN2(r, r, Math.sqrt(rc)); + const [rx, ry] = r; + const rx2 = rx * rx; + const ry2 = ry * ry; + // transformed center + const radicant = Math.max( + 0, + (rx2 * ry2 - rx2 * ty2 - ry2 * tx2) / (rx2 * ty2 + ry2 * tx2) + ); + const coeff = (xl !== cw ? 1 : -1) * Math.sqrt(radicant); + const tc = [coeff * ((rx * tp[1]) / ry), coeff * (-(ry * tp[0]) / rx)]; + // actual center + const center: Vec = [ + c * tc[0] - s * tc[1] + (a[0] + b[0]) / 2, + s * tc[0] + c * tc[1] + (a[1] + b[1]) / 2, + ]; + // transformed end points & angles + const ta = div2(null, sub2([], tp, tc), r); + const tb = div2(null, sub2(null, neg([], tp), tc), r); + const start = angleBetween2(X2, ta); + let sweep = angleBetween2(ta, tb); + if (!cw && sweep > 0) { + sweep -= TAU; + } else if (cw && sweep < 0) { + sweep += TAU; + } + sweep %= TAU; - return { - center, - r, - axis, - start, - end: start + sweep, - xl, - cw, - }; + return { + center, + r, + axis, + start, + end: start + sweep, + xl, + cw, + }; }; diff --git a/packages/geom-arc/src/point-at.ts b/packages/geom-arc/src/point-at.ts index 8fadab2603..0aac35f779 100644 --- a/packages/geom-arc/src/point-at.ts +++ b/packages/geom-arc/src/point-at.ts @@ -6,19 +6,19 @@ import { mul2 } from "@thi.ng/vectors/mul"; import { rotateZ } from "@thi.ng/vectors/rotate"; export const pointAt = ( - pos: ReadonlyVec, - r: ReadonlyVec, - axis: number, - start: number, - end: number, - t: number, - out: Vec = [] + pos: ReadonlyVec, + r: ReadonlyVec, + axis: number, + start: number, + end: number, + t: number, + out: Vec = [] ) => pointAtTheta(pos, r, axis, fit01(t, start, end), out); export const pointAtTheta = ( - pos: ReadonlyVec, - r: ReadonlyVec, - axis: number, - theta: number, - out: Vec = [] + pos: ReadonlyVec, + r: ReadonlyVec, + axis: number, + theta: number, + out: Vec = [] ) => add2(null, rotateZ(null, mul2(out, cossin(theta), r), axis), pos); diff --git a/packages/geom-arc/src/sample.ts b/packages/geom-arc/src/sample.ts index 10cd3f5dc7..9e91bb5799 100644 --- a/packages/geom-arc/src/sample.ts +++ b/packages/geom-arc/src/sample.ts @@ -8,37 +8,37 @@ import { cartesian2 } from "@thi.ng/vectors/cartesian"; import { pointAtTheta } from "./point-at.js"; export const sample = ( - pos: ReadonlyVec, - r: ReadonlyVec, - axis: number, - start: number, - end: number, - opts?: number | Partial + pos: ReadonlyVec, + r: ReadonlyVec, + axis: number, + start: number, + end: number, + opts?: number | Partial ): Vec[] => { - if (isPlainObject(opts) && (opts).dist !== undefined) { - return new Sampler( - sample( - pos, - r, - axis, - start, - end, - (opts).num || DEFAULT_SAMPLES - ) - ).sampleUniform((opts).dist, (opts).last !== false); - } - opts = isNumber(opts) - ? { num: opts, last: true } - : { num: DEFAULT_SAMPLES, ...opts }; - let delta = end - start; - let num = opts.theta ? Math.round(delta / opts.theta) : opts.num!; - delta /= num; - opts.last !== false && num++; - const pts: Vec[] = new Array(num); - for (let i = 0; i < num; i++) { - pts[i] = pointAtTheta(pos, r, axis, start + i * delta); - } - return pts; + if (isPlainObject(opts) && (opts).dist !== undefined) { + return new Sampler( + sample( + pos, + r, + axis, + start, + end, + (opts).num || DEFAULT_SAMPLES + ) + ).sampleUniform((opts).dist, (opts).last !== false); + } + opts = isNumber(opts) + ? { num: opts, last: true } + : { num: DEFAULT_SAMPLES, ...opts }; + let delta = end - start; + let num = opts.theta ? Math.round(delta / opts.theta) : opts.num!; + delta /= num; + opts.last !== false && num++; + const pts: Vec[] = new Array(num); + for (let i = 0; i < num; i++) { + pts[i] = pointAtTheta(pos, r, axis, start + i * delta); + } + return pts; }; /** @@ -47,35 +47,35 @@ export const sample = ( * optional `out` (or a new array). If `addLast` is falst (default: * true), point `b` will NOT be added. * - * @param origin - - * @param r - - * @param a - - * @param b - - * @param out - - * @param steps - - * @param outwards - - * @param addLast - + * @param origin - + * @param r - + * @param a - + * @param b - + * @param out - + * @param steps - + * @param outwards - + * @param addLast - */ export const sampleCircular = ( - origin: Vec, - r: number, - a: Vec, - b: Vec, - out: Vec[] = [], - steps = 8, - outwards = true, - addLast = true + origin: Vec, + r: number, + a: Vec, + b: Vec, + out: Vec[] = [], + steps = 8, + outwards = true, + addLast = true ) => { - let ta = Math.atan2(a[1] - origin[1], a[0] - origin[0]); - let tb = Math.atan2(b[1] - origin[1], b[0] - origin[0]); - ta < 0 && (ta += TAU); - tb < 0 && (tb += TAU); - const theta = ta > tb ? ta - tb : ta + TAU - tb; - const delta = (outwards ? -theta : TAU - theta) / steps; - out.push(a); - for (let i = 1; i < steps; i++) { - out.push(cartesian2(null, [r, ta + delta * i], origin)); - } - addLast && out.push(b); - return out; + let ta = Math.atan2(a[1] - origin[1], a[0] - origin[0]); + let tb = Math.atan2(b[1] - origin[1], b[0] - origin[0]); + ta < 0 && (ta += TAU); + tb < 0 && (tb += TAU); + const theta = ta > tb ? ta - tb : ta + TAU - tb; + const delta = (outwards ? -theta : TAU - theta) / steps; + out.push(a); + for (let i = 1; i < steps; i++) { + out.push(cartesian2(null, [r, ta + delta * i], origin)); + } + addLast && out.push(b); + return out; }; diff --git a/packages/geom-arc/tsconfig.json b/packages/geom-arc/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-arc/tsconfig.json +++ b/packages/geom-arc/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-clip-line/api-extractor.json b/packages/geom-clip-line/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-clip-line/api-extractor.json +++ b/packages/geom-clip-line/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-clip-line/package.json b/packages/geom-clip-line/package.json index db424e385a..776362d3e9 100644 --- a/packages/geom-clip-line/package.json +++ b/packages/geom-clip-line/package.json @@ -1,87 +1,87 @@ { - "name": "@thi.ng/geom-clip-line", - "version": "2.1.19", - "description": "2D line clipping (Liang-Barsky)", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-clip-line#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/geom-isec": "^2.1.19", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "bbox", - "clipping", - "geometry", - "graphics", - "liang-barsky", - "line", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./clip-poly": { - "default": "./clip-poly.js" - }, - "./liang-barsky": { - "default": "./liang-barsky.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "related": [ - "geom-clip-poly" - ], - "year": 2013 - } + "name": "@thi.ng/geom-clip-line", + "version": "2.1.19", + "description": "2D line clipping (Liang-Barsky)", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-clip-line#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/geom-isec": "^2.1.19", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "bbox", + "clipping", + "geometry", + "graphics", + "liang-barsky", + "line", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./clip-poly": { + "default": "./clip-poly.js" + }, + "./liang-barsky": { + "default": "./liang-barsky.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "related": [ + "geom-clip-poly" + ], + "year": 2013 + } } diff --git a/packages/geom-clip-line/src/clip-poly.ts b/packages/geom-clip-line/src/clip-poly.ts index 3300ce724d..e828a18dab 100644 --- a/packages/geom-clip-line/src/clip-poly.ts +++ b/packages/geom-clip-line/src/clip-poly.ts @@ -9,44 +9,44 @@ import { direction } from "@thi.ng/vectors/direction"; * `b` with the given polygon. Returns an array of segments where the * line is inside the polygon. * - * @param a - - * @param b - - * @param pts - + * @param a - + * @param b - + * @param pts - */ export const clipLinePoly = ( - a: ReadonlyVec, - b: ReadonlyVec, - pts: ReadonlyVec[] + a: ReadonlyVec, + b: ReadonlyVec, + pts: ReadonlyVec[] ) => { - const isecs = intersectRayPolylineAll( - a, - direction([], a, b), - pts, - true - ).isec; - return isecs ? collectSegments(isecs) : undefined; + const isecs = intersectRayPolylineAll( + a, + direction([], a, b), + pts, + true + ).isec; + return isecs ? collectSegments(isecs) : undefined; }; export const clipLineSegmentPoly = ( - a: ReadonlyVec, - b: ReadonlyVec, - pts: ReadonlyVec[] + a: ReadonlyVec, + b: ReadonlyVec, + pts: ReadonlyVec[] ) => { - const isecs = intersectLinePolylineAll(a, b, pts, true).isec; - const isAInside = pointInPolygon2(a, pts); - const isBInside = pointInPolygon2(b, pts); - if (!isecs) { - return isAInside && isBInside ? [[a, b]] : undefined; - } - isAInside && (isecs).unshift(a); - isBInside && (isecs).push(b); - return collectSegments(isecs); + const isecs = intersectLinePolylineAll(a, b, pts, true).isec; + const isAInside = pointInPolygon2(a, pts); + const isBInside = pointInPolygon2(b, pts); + if (!isecs) { + return isAInside && isBInside ? [[a, b]] : undefined; + } + isAInside && (isecs).unshift(a); + isBInside && (isecs).push(b); + return collectSegments(isecs); }; const collectSegments = (isecs: Vec[]) => { - const segments: Vec[][] = []; - for (let i = 0, n = isecs.length - 1; i < n; i += 2) { - segments.push([isecs[i], isecs[i + 1]]); - } - return segments; + const segments: Vec[][] = []; + for (let i = 0, n = isecs.length - 1; i < n; i += 2) { + segments.push([isecs[i], isecs[i + 1]]); + } + return segments; }; diff --git a/packages/geom-clip-line/src/liang-barsky.ts b/packages/geom-clip-line/src/liang-barsky.ts index 70f08a7089..75fbbe9135 100644 --- a/packages/geom-clip-line/src/liang-barsky.ts +++ b/packages/geom-clip-line/src/liang-barsky.ts @@ -22,94 +22,94 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; * @param cb - result B */ export const liangBarsky2 = ( - a: ReadonlyVec, - b: ReadonlyVec, - min: ReadonlyVec, - max: ReadonlyVec, - ca: Vec = [], - cb: Vec = [] + a: ReadonlyVec, + b: ReadonlyVec, + min: ReadonlyVec, + max: ReadonlyVec, + ca: Vec = [], + cb: Vec = [] ): [Vec, Vec, number, number] | undefined => { - const res = liangBarsky2Raw( - a[0], - a[1], - b[0], - b[1], - min[0], - min[1], - max[0], - max[1] - ); - if (!res) return; + const res = liangBarsky2Raw( + a[0], + a[1], + b[0], + b[1], + min[0], + min[1], + max[0], + max[1] + ); + if (!res) return; - ca[0] = res[0]; - ca[1] = res[1]; - cb[0] = res[2]; - cb[1] = res[3]; + ca[0] = res[0]; + ca[1] = res[1]; + cb[0] = res[2]; + cb[1] = res[3]; - return [ca, cb, res[4], res[5]]; + return [ca, cb, res[4], res[5]]; }; /** * Same as {@link liangBarsky2} but for non-vector arguments. * - * @param ax - - * @param ay - - * @param bx - - * @param by - - * @param minx - - * @param miny - - * @param maxx - - * @param maxy - + * @param ax - + * @param ay - + * @param bx - + * @param by - + * @param minx - + * @param miny - + * @param maxx - + * @param maxy - */ export const liangBarsky2Raw: FnU8 | undefined> = ( - ax, - ay, - bx, - by, - minx, - miny, - maxx, - maxy + ax, + ay, + bx, + by, + minx, + miny, + maxx, + maxy ) => { - const dx = bx - ax; - const dy = by - ay; - let alpha = 0; - let beta = 1; + const dx = bx - ax; + const dy = by - ay; + let alpha = 0; + let beta = 1; - const clip: FnU2 = (p, q) => { - if (p < 0) { - const r = q / p; - if (r > beta) { - return false; - } - if (r > alpha) { - alpha = r; - } - } else if (p > 0) { - const r = q / p; - if (r < alpha) { - return false; - } - if (r < beta) { - beta = r; - } - } else if (q < 0) { - return false; - } - return true; - }; + const clip: FnU2 = (p, q) => { + if (p < 0) { + const r = q / p; + if (r > beta) { + return false; + } + if (r > alpha) { + alpha = r; + } + } else if (p > 0) { + const r = q / p; + if (r < alpha) { + return false; + } + if (r < beta) { + beta = r; + } + } else if (q < 0) { + return false; + } + return true; + }; - return clip(-dx, ax - minx) && - clip(dx, maxx - ax) && - clip(-dy, ay - miny) && - clip(dy, maxy - ay) - ? [ - alpha * dx + ax, - alpha * dy + ay, - beta * dx + ax, - beta * dy + ay, - alpha, - beta, - ] - : undefined; + return clip(-dx, ax - minx) && + clip(dx, maxx - ax) && + clip(-dy, ay - miny) && + clip(dy, maxy - ay) + ? [ + alpha * dx + ax, + alpha * dy + ay, + beta * dx + ax, + beta * dy + ay, + alpha, + beta, + ] + : undefined; }; diff --git a/packages/geom-clip-line/tsconfig.json b/packages/geom-clip-line/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-clip-line/tsconfig.json +++ b/packages/geom-clip-line/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-clip-poly/api-extractor.json b/packages/geom-clip-poly/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-clip-poly/api-extractor.json +++ b/packages/geom-clip-poly/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-clip-poly/package.json b/packages/geom-clip-poly/package.json index b6b0300f3a..168d908985 100644 --- a/packages/geom-clip-poly/package.json +++ b/packages/geom-clip-poly/package.json @@ -1,86 +1,86 @@ { - "name": "@thi.ng/geom-clip-poly", - "version": "2.1.19", - "description": "2D polygon clipping / offsetting (Sutherland-Hodgeman, Grainer-Hormann)", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-clip-poly#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/geom-isec": "^2.1.19", - "@thi.ng/geom-poly-utils": "^2.3.3", - "@thi.ng/math": "^5.3.4", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "bbox", - "clipping", - "convex", - "geometry", - "grainer-hormann", - "graphics", - "offset", - "polygon", - "rect", - "sutherland-hodgeman", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "related": [ - "geom-clip-line" - ], - "year": 2013 - } + "name": "@thi.ng/geom-clip-poly", + "version": "2.1.19", + "description": "2D polygon clipping / offsetting (Sutherland-Hodgeman, Grainer-Hormann)", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-clip-poly#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/geom-isec": "^2.1.19", + "@thi.ng/geom-poly-utils": "^2.3.3", + "@thi.ng/math": "^5.3.4", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "bbox", + "clipping", + "convex", + "geometry", + "grainer-hormann", + "graphics", + "offset", + "polygon", + "rect", + "sutherland-hodgeman", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "related": [ + "geom-clip-line" + ], + "year": 2013 + } } diff --git a/packages/geom-clip-poly/src/index.ts b/packages/geom-clip-poly/src/index.ts index 8d45770ea9..90c5eece4b 100644 --- a/packages/geom-clip-poly/src/index.ts +++ b/packages/geom-clip-poly/src/index.ts @@ -17,35 +17,35 @@ import { corner2 } from "@thi.ng/vectors/clockwise"; * @param eps - edge classification tolerance */ export const sutherlandHodgeman = ( - pts: ReadonlyVec[], - bounds: ReadonlyVec[], - bc?: ReadonlyVec, - eps = EPS + pts: ReadonlyVec[], + bounds: ReadonlyVec[], + bc?: ReadonlyVec, + eps = EPS ) => { - bc = bc || centroid(bounds); - for (let ne = bounds.length, j = ne - 1, i = 0; i < ne; j = i, i++) { - const clipped: Vec[] = []; - const ca = bounds[j]; - const cb = bounds[i]; - const sign = corner2(ca, cb, bc, eps); - for (let np = pts.length, k = np - 1, l = 0; l < np; k = l, l++) { - const p = pts[k]; - const q = pts[l]; - const cqsign = corner2(ca, cb, q, eps); - if (corner2(ca, cb, p, eps) === sign) { - clipped.push( - cqsign !== sign - ? intersectLineLine(ca, cb, p, q).isec - : q - ); - } else if (cqsign === sign) { - clipped.push(intersectLineLine(ca, cb, p, q).isec, q); - } - } - if (clipped.length < 2) { - return []; - } - pts = clipped; - } - return pts; + bc = bc || centroid(bounds); + for (let ne = bounds.length, j = ne - 1, i = 0; i < ne; j = i, i++) { + const clipped: Vec[] = []; + const ca = bounds[j]; + const cb = bounds[i]; + const sign = corner2(ca, cb, bc, eps); + for (let np = pts.length, k = np - 1, l = 0; l < np; k = l, l++) { + const p = pts[k]; + const q = pts[l]; + const cqsign = corner2(ca, cb, q, eps); + if (corner2(ca, cb, p, eps) === sign) { + clipped.push( + cqsign !== sign + ? intersectLineLine(ca, cb, p, q).isec + : q + ); + } else if (cqsign === sign) { + clipped.push(intersectLineLine(ca, cb, p, q).isec, q); + } + } + if (clipped.length < 2) { + return []; + } + pts = clipped; + } + return pts; }; diff --git a/packages/geom-clip-poly/tsconfig.json b/packages/geom-clip-poly/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-clip-poly/tsconfig.json +++ b/packages/geom-clip-poly/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-closest-point/api-extractor.json b/packages/geom-closest-point/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-closest-point/api-extractor.json +++ b/packages/geom-closest-point/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-closest-point/package.json b/packages/geom-closest-point/package.json index bf2f2fef30..dd0982b521 100644 --- a/packages/geom-closest-point/package.json +++ b/packages/geom-closest-point/package.json @@ -1,106 +1,106 @@ { - "name": "@thi.ng/geom-closest-point", - "version": "2.1.16", - "description": "2D / 3D closest point / proximity helpers", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-closest-point#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/math": "^5.3.4", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "3d", - "bbox", - "circle", - "distance", - "ellipse", - "geometry", - "line", - "points", - "polygon", - "polyline", - "proximity", - "rect", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./box": { - "default": "./box.js" - }, - "./circle": { - "default": "./circle.js" - }, - "./ellipse": { - "default": "./ellipse.js" - }, - "./line": { - "default": "./line.js" - }, - "./plane": { - "default": "./plane.js" - }, - "./points": { - "default": "./points.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "related": [ - "geom-isec", - "geom-resample" - ], - "year": 2018 - } + "name": "@thi.ng/geom-closest-point", + "version": "2.1.16", + "description": "2D / 3D closest point / proximity helpers", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-closest-point#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/math": "^5.3.4", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "3d", + "bbox", + "circle", + "distance", + "ellipse", + "geometry", + "line", + "points", + "polygon", + "polyline", + "proximity", + "rect", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./box": { + "default": "./box.js" + }, + "./circle": { + "default": "./circle.js" + }, + "./ellipse": { + "default": "./ellipse.js" + }, + "./line": { + "default": "./line.js" + }, + "./plane": { + "default": "./plane.js" + }, + "./points": { + "default": "./points.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "related": [ + "geom-isec", + "geom-resample" + ], + "year": 2018 + } } diff --git a/packages/geom-closest-point/src/box.ts b/packages/geom-closest-point/src/box.ts index 114a3bfb93..7e5e8c71f5 100644 --- a/packages/geom-closest-point/src/box.ts +++ b/packages/geom-closest-point/src/box.ts @@ -3,64 +3,64 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; import { setC2, setC3 } from "@thi.ng/vectors/setc"; export const closestPointRect = ( - p: ReadonlyVec, - bmin: ReadonlyVec, - bmax: ReadonlyVec, - out: Vec = [] + p: ReadonlyVec, + bmin: ReadonlyVec, + bmax: ReadonlyVec, + out: Vec = [] ) => { - const [minID, minW] = closestBoxEdge(p, bmin, bmax, 4); - return minID! === 0 - ? setC2(out, minW!, clamp(p[1], bmin[1], bmax[1])) - : setC2(out, clamp(p[0], bmin[0], bmax[0]), minW!); + const [minID, minW] = closestBoxEdge(p, bmin, bmax, 4); + return minID! === 0 + ? setC2(out, minW!, clamp(p[1], bmin[1], bmax[1])) + : setC2(out, clamp(p[0], bmin[0], bmax[0]), minW!); }; export const closestPointAABB = ( - p: ReadonlyVec, - bmin: ReadonlyVec, - bmax: ReadonlyVec, - out: Vec = [] + p: ReadonlyVec, + bmin: ReadonlyVec, + bmax: ReadonlyVec, + out: Vec = [] ) => { - const [minID, minW] = closestBoxEdge(p, bmin, bmax, 6); - return minID! === 0 - ? setC3( - out, - minW, - clamp(p[1], bmin[1], bmax[1]), - clamp(p[2], bmin[2], bmax[2]) - ) - : minID! === 1 - ? setC3( - out, - clamp(p[0], bmin[0], bmax[0]), - minW, - clamp(p[2], bmin[2], bmax[2]) - ) - : setC3( - out, - clamp(p[0], bmin[0], bmax[0]), - clamp(p[1], bmin[1], bmax[1]), - minW - ); + const [minID, minW] = closestBoxEdge(p, bmin, bmax, 6); + return minID! === 0 + ? setC3( + out, + minW, + clamp(p[1], bmin[1], bmax[1]), + clamp(p[2], bmin[2], bmax[2]) + ) + : minID! === 1 + ? setC3( + out, + clamp(p[0], bmin[0], bmax[0]), + minW, + clamp(p[2], bmin[2], bmax[2]) + ) + : setC3( + out, + clamp(p[0], bmin[0], bmax[0]), + clamp(p[1], bmin[1], bmax[1]), + minW + ); }; const closestBoxEdge = ( - p: ReadonlyVec, - bmin: ReadonlyVec, - bmax: ReadonlyVec, - n: number + p: ReadonlyVec, + bmin: ReadonlyVec, + bmax: ReadonlyVec, + n: number ) => { - let minD = Infinity; - let minID: number; - let minW: number; - for (let i = 0; i < n; i++) { - const j = i >> 1; - const w = (i & 1 ? bmax : bmin)[j]; - const d = Math.abs(p[j] - w); - if (d < minD) { - minD = d; - minID = j; - minW = w; - } - } - return [minID!, minW!]; + let minD = Infinity; + let minID: number; + let minW: number; + for (let i = 0; i < n; i++) { + const j = i >> 1; + const w = (i & 1 ? bmax : bmin)[j]; + const d = Math.abs(p[j] - w); + if (d < minD) { + minD = d; + minID = j; + minW = w; + } + } + return [minID!, minW!]; }; diff --git a/packages/geom-closest-point/src/circle.ts b/packages/geom-closest-point/src/circle.ts index 94bb373000..925d3a1348 100644 --- a/packages/geom-closest-point/src/circle.ts +++ b/packages/geom-closest-point/src/circle.ts @@ -5,16 +5,16 @@ import { direction } from "@thi.ng/vectors/direction"; /** * Returns closest point to `p` on circle defined by origin `c` and radius `r`. * - * @param p - - * @param c - - * @param r - - * @param out - + * @param p - + * @param c - + * @param r - + * @param out - */ export const closestPointCircle = ( - p: ReadonlyVec, - c: ReadonlyVec, - r: number, - out: Vec = [] + p: ReadonlyVec, + c: ReadonlyVec, + r: number, + out: Vec = [] ) => add(out, c, direction(out, c, p, r)); /** diff --git a/packages/geom-closest-point/src/ellipse.ts b/packages/geom-closest-point/src/ellipse.ts index c04a96fc09..d95923cacc 100644 --- a/packages/geom-closest-point/src/ellipse.ts +++ b/packages/geom-closest-point/src/ellipse.ts @@ -18,28 +18,28 @@ import type { ReadonlyVec } from "@thi.ng/vectors"; * @param n - number of iterations */ export const closestPointEllipse = ( - [px, py]: ReadonlyVec, - [ex, ey]: ReadonlyVec, - [rx, ry]: ReadonlyVec, - n = 3 + [px, py]: ReadonlyVec, + [ex, ey]: ReadonlyVec, + [rx, ry]: ReadonlyVec, + n = 3 ) => { - const apx = Math.abs(px - ex); - const apy = Math.abs(py - ey); - const ab = (rx * rx - ry * ry) / rx; - const ba = (ry * ry - rx * rx) / ry; - let tx = SQRT2_2; - let ty = tx; - for (; n-- > 0; ) { - const _ex = ab * tx * tx * tx; - const _ey = ba * ty * ty * ty; - const qx = apx - _ex; - const qy = apy - _ey; - const q = Math.hypot(rx * tx - _ex, ry * ty - _ey) / Math.hypot(qx, qy); - tx = clamp01((qx * q + _ex) / rx); - ty = clamp01((qy * q + _ey) / ry); - const t = Math.hypot(tx, ty); - tx /= t; - ty /= t; - } - return [rx * (px < ex ? -tx : tx) + ex, ry * (py < ey ? -ty : ty) + ey]; + const apx = Math.abs(px - ex); + const apy = Math.abs(py - ey); + const ab = (rx * rx - ry * ry) / rx; + const ba = (ry * ry - rx * rx) / ry; + let tx = SQRT2_2; + let ty = tx; + for (; n-- > 0; ) { + const _ex = ab * tx * tx * tx; + const _ey = ba * ty * ty * ty; + const qx = apx - _ex; + const qy = apy - _ey; + const q = Math.hypot(rx * tx - _ex, ry * ty - _ey) / Math.hypot(qx, qy); + tx = clamp01((qx * q + _ex) / rx); + ty = clamp01((qy * q + _ey) / ry); + const t = Math.hypot(tx, ty); + tx /= t; + ty /= t; + } + return [rx * (px < ex ? -tx : tx) + ex, ry * (py < ey ? -ty : ty) + ey]; }; diff --git a/packages/geom-closest-point/src/line.ts b/packages/geom-closest-point/src/line.ts index 85be589056..9e29d2accf 100644 --- a/packages/geom-closest-point/src/line.ts +++ b/packages/geom-closest-point/src/line.ts @@ -31,9 +31,9 @@ import { sub } from "@thi.ng/vectors/sub"; * @param b - line point B */ export const closestT: FnU3 = (p, a, b) => { - const d = sub([], b, a); - const l = magSq(d); - return l > 1e-6 ? dot(sub([], p, a), d) / l : undefined; + const d = sub([], b, a); + const l = magSq(d); + return l > 1e-6 ? dot(sub([], p, a), d) / l : undefined; }; /** @@ -48,7 +48,7 @@ export const closestT: FnU3 = (p, a, b) => { * @param b - line point B */ export const closestPointLine: FnU3 = (p, a, b) => - mixN([], a, b, closestT(p, a, b) || 0); + mixN([], a, b, closestT(p, a, b) || 0); /** * Returns distance from `p` to closest point to infinite line `a` -> @@ -62,7 +62,7 @@ export const closestPointLine: FnU3 = (p, a, b) => * @param b - line point B */ export const distToLine: FnU3 = (p, a, b) => - dist(p, closestPointLine(p, a, b) || a); + dist(p, closestPointLine(p, a, b) || a); /** * Returns closest point to `p` on line segment `a` -> `b`. By default, @@ -88,18 +88,18 @@ export const distToLine: FnU3 = (p, a, b) => * @param eps - epsilon value */ export const closestPointSegment = ( - p: ReadonlyVec, - a: ReadonlyVec, - b: ReadonlyVec, - out?: Vec, - insideOnly = false, - eps = 0 + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + out?: Vec, + insideOnly = false, + eps = 0 ) => { - const t = closestT(p, a, b); - if (t !== undefined && (!insideOnly || (t >= eps && t <= 1 - eps))) { - out = out || empty(p); - return t <= 0 ? set(out, a) : t >= 1 ? set(out, b) : mixN(out, a, b, t); - } + const t = closestT(p, a, b); + if (t !== undefined && (!insideOnly || (t >= eps && t <= 1 - eps))) { + out = out || empty(p); + return t <= 0 ? set(out, a) : t >= 1 ? set(out, b) : mixN(out, a, b, t); + } }; /** @@ -111,37 +111,37 @@ export const closestPointSegment = ( * @param b - line point B */ export const distToSegment: FnU3 = (p, a, b) => - dist(p, closestPointSegment(p, a, b) || a); + dist(p, closestPointSegment(p, a, b) || a); export const closestPointPolyline = ( - p: ReadonlyVec, - pts: ReadonlyArray, - closed = false, - out: Vec = [] + p: ReadonlyVec, + pts: ReadonlyArray, + closed = false, + out: Vec = [] ) => { - if (!pts.length) return; - const tmp: Vec = []; - const n = pts.length - 1; - let minD = Infinity, - i, - j; - if (closed) { - i = n; - j = 0; - } else { - i = 0; - j = 1; - } - for (; j <= n; i = j, j++) { - if (closestPointSegment(p, pts[i], pts[j], tmp)) { - const d = distSq(p, tmp); - if (d < minD) { - minD = d; - set(out, tmp); - } - } - } - return minD < Infinity ? out : undefined; + if (!pts.length) return; + const tmp: Vec = []; + const n = pts.length - 1; + let minD = Infinity, + i, + j; + if (closed) { + i = n; + j = 0; + } else { + i = 0; + j = 1; + } + for (; j <= n; i = j, j++) { + if (closestPointSegment(p, pts[i], pts[j], tmp)) { + const d = distSq(p, tmp); + if (d < minD) { + minD = d; + set(out, tmp); + } + } + } + return minD < Infinity ? out : undefined; }; /** @@ -157,22 +157,22 @@ export const closestPointPolyline = ( * @param to - end search index */ export const farthestPointSegment = ( - a: ReadonlyVec, - b: ReadonlyVec, - points: ReadonlyVec[], - from = 0, - to = points.length + a: ReadonlyVec, + b: ReadonlyVec, + points: ReadonlyVec[], + from = 0, + to = points.length ) => { - let maxD = -1; - let maxIdx: number = -1; - const tmp = empty(a); - for (let i = from; i < to; i++) { - const p = points[i]; - const d = distSq(p, closestPointSegment(p, a, b, tmp) || a); - if (d > maxD) { - maxD = d; - maxIdx = i; - } - } - return [maxIdx, Math.sqrt(maxD)]; + let maxD = -1; + let maxIdx: number = -1; + const tmp = empty(a); + for (let i = from; i < to; i++) { + const p = points[i]; + const d = distSq(p, closestPointSegment(p, a, b, tmp) || a); + if (d > maxD) { + maxD = d; + maxIdx = i; + } + } + return [maxIdx, Math.sqrt(maxD)]; }; diff --git a/packages/geom-closest-point/src/plane.ts b/packages/geom-closest-point/src/plane.ts index 6c6cfb9b2f..d9dee034c1 100644 --- a/packages/geom-closest-point/src/plane.ts +++ b/packages/geom-closest-point/src/plane.ts @@ -11,25 +11,25 @@ import { sub } from "@thi.ng/vectors/sub"; * If result is > 0 the point lies "above" the plane, if < 0 below the plane or * if zero on the plane. * - * @param p - - * @param n - - * @param w - + * @param p - + * @param n - + * @param w - */ export const distToPlane = (p: ReadonlyVec, n: ReadonlyVec, w: number) => - dot(n, p) - w; + dot(n, p) - w; /** * Returns closest point to `p` on the plane defined by normal `n` and `w`. In * 2D this also works for lines. * - * @param p - - * @param normal - - * @param w - - * @param out - + * @param p - + * @param normal - + * @param w - + * @param out - */ export const closestPointPlane = ( - p: ReadonlyVec, - normal: ReadonlyVec, - w: number, - out: Vec = [] + p: ReadonlyVec, + normal: ReadonlyVec, + w: number, + out: Vec = [] ) => sub(out, p, normalize(out, normal, distToPlane(p, normal, w))); diff --git a/packages/geom-closest-point/src/points.ts b/packages/geom-closest-point/src/points.ts index f1d6b40aad..533f7de8d3 100644 --- a/packages/geom-closest-point/src/points.ts +++ b/packages/geom-closest-point/src/points.ts @@ -7,25 +7,25 @@ import { set } from "@thi.ng/vectors/set"; * Returns closest point to `p` in given point array, optionally using custom * distance function `dist` (default: {@link @thi.ng/vectors#distSq}). * - * @param p - - * @param pts - - * @param out - - * @param dist - + * @param p - + * @param pts - + * @param out - + * @param dist - */ export const closestPointArray = ( - p: ReadonlyVec, - pts: Vec[], - out: Vec = [], - dist: FnU2 = distSq + p: ReadonlyVec, + pts: Vec[], + out: Vec = [], + dist: FnU2 = distSq ) => { - let minD = Infinity; - let closest: Vec | undefined; - for (let i = pts.length; i-- > 0; ) { - const d = dist(pts[i], p); - if (d < minD) { - minD = d; - closest = pts[i]; - } - } - return closest ? set(out, closest) : undefined; + let minD = Infinity; + let closest: Vec | undefined; + for (let i = pts.length; i-- > 0; ) { + const d = dist(pts[i], p); + if (d < minD) { + minD = d; + closest = pts[i]; + } + } + return closest ? set(out, closest) : undefined; }; diff --git a/packages/geom-closest-point/tsconfig.json b/packages/geom-closest-point/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-closest-point/tsconfig.json +++ b/packages/geom-closest-point/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-fuzz/api-extractor.json b/packages/geom-fuzz/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-fuzz/api-extractor.json +++ b/packages/geom-fuzz/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-fuzz/package.json b/packages/geom-fuzz/package.json index 6ee849d1e6..bfad18072f 100644 --- a/packages/geom-fuzz/package.json +++ b/packages/geom-fuzz/package.json @@ -1,123 +1,123 @@ { - "name": "@thi.ng/geom-fuzz", - "version": "2.1.19", - "description": "Highly configurable, fuzzy line & polygon creation with presets and composable fill & stroke styles. Canvas & SVG support", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/geom-fuzz#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/associative": "^6.2.0", - "@thi.ng/color": "^5.1.4", - "@thi.ng/geom": "^3.4.2", - "@thi.ng/geom-api": "^3.3.2", - "@thi.ng/geom-clip-line": "^2.1.19", - "@thi.ng/geom-resample": "^2.1.19", - "@thi.ng/grid-iterators": "^2.3.6", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "animation", - "canvas", - "datastructure", - "dots", - "drawing", - "fuzzy", - "geometry", - "graphics", - "hatching", - "hiccup", - "noise", - "npr", - "polygon", - "random", - "shape", - "svg", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./comp": { - "default": "./comp.js" - }, - "./dots": { - "default": "./dots.js" - }, - "./hatch": { - "default": "./hatch.js" - }, - "./line": { - "default": "./line.js" - }, - "./points": { - "default": "./points.js" - }, - "./polygon": { - "default": "./polygon.js" - }, - "./presets": { - "default": "./presets.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "related": [ - "hiccup-canvas", - "hiccup-svg" - ], - "year": 2020 - } + "name": "@thi.ng/geom-fuzz", + "version": "2.1.19", + "description": "Highly configurable, fuzzy line & polygon creation with presets and composable fill & stroke styles. Canvas & SVG support", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/geom-fuzz#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/associative": "^6.2.0", + "@thi.ng/color": "^5.1.4", + "@thi.ng/geom": "^3.4.2", + "@thi.ng/geom-api": "^3.3.2", + "@thi.ng/geom-clip-line": "^2.1.19", + "@thi.ng/geom-resample": "^2.1.19", + "@thi.ng/grid-iterators": "^2.3.6", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "animation", + "canvas", + "datastructure", + "dots", + "drawing", + "fuzzy", + "geometry", + "graphics", + "hatching", + "hiccup", + "noise", + "npr", + "polygon", + "random", + "shape", + "svg", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./comp": { + "default": "./comp.js" + }, + "./dots": { + "default": "./dots.js" + }, + "./hatch": { + "default": "./hatch.js" + }, + "./line": { + "default": "./line.js" + }, + "./points": { + "default": "./points.js" + }, + "./polygon": { + "default": "./polygon.js" + }, + "./presets": { + "default": "./presets.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "related": [ + "hiccup-canvas", + "hiccup-svg" + ], + "year": 2020 + } } diff --git a/packages/geom-fuzz/src/api.ts b/packages/geom-fuzz/src/api.ts index c51f60005b..c72b282add 100644 --- a/packages/geom-fuzz/src/api.ts +++ b/packages/geom-fuzz/src/api.ts @@ -10,40 +10,40 @@ export type FillFn = Fn; export type HatchDir = "d" | "h" | "v"; export interface FuzzyPolygonOpts { - num: number; - jitter: number; - curveBreakPoints: boolean; - curveScale: number; - fill: FillFn; + num: number; + jitter: number; + curveBreakPoints: boolean; + curveScale: number; + fill: FillFn; } export interface FuzzyLineOpts { - jitter: number; - resample: number; - attribs: any; + jitter: number; + resample: number; + attribs: any; } export interface HatchOpts { - dir: HatchDir; - space: number; - line: Partial; + dir: HatchDir; + space: number; + line: Partial; } export interface DotFillOpts { - space: number; - jitter: number; - attribs: Partial<{ - shape: "circle" | "square"; - [id: string]: any; - }>; + space: number; + jitter: number; + attribs: Partial<{ + shape: "circle" | "square"; + [id: string]: any; + }>; } export const DEFAULT_LINE = { - resample: 0, - jitter: 2, - attribs: { - lineCap: "butt", - lineJoin: "round", - stroke: "black", - }, + resample: 0, + jitter: 2, + attribs: { + lineCap: "butt", + lineJoin: "round", + stroke: "black", + }, }; diff --git a/packages/geom-fuzz/src/comp.ts b/packages/geom-fuzz/src/comp.ts index c6d75bfd7e..af5fec2ebd 100644 --- a/packages/geom-fuzz/src/comp.ts +++ b/packages/geom-fuzz/src/comp.ts @@ -2,6 +2,6 @@ import { group } from "@thi.ng/geom/group"; import type { FillFn } from "./api.js"; export const compFill = - (a: FillFn, b: FillFn): FillFn => - (poly) => - group({}, [a(poly), b(poly)]); + (a: FillFn, b: FillFn): FillFn => + (poly) => + group({}, [a(poly), b(poly)]); diff --git a/packages/geom-fuzz/src/dots.ts b/packages/geom-fuzz/src/dots.ts index 8f29c715b7..96755caa8f 100644 --- a/packages/geom-fuzz/src/dots.ts +++ b/packages/geom-fuzz/src/dots.ts @@ -10,33 +10,33 @@ import { jitter } from "@thi.ng/vectors/jitter"; import type { DotFillOpts, FillFn } from "./api.js"; export const defDots = (opts: Partial = {}): FillFn => { - opts = mergeDeepObj( - { - space: 5, - jitter: 0.5, - attribs: { - shape: "circle", - stroke: "black", - fill: "none", - }, - }, - opts - ); - return (shape) => { - const box = bounds(shape)!; - const [w, h] = box.size; - const cols = ~~(w / opts.space!); - const rows = ~~(h / opts.space!); - const maxg = [cols - 1, rows - 1]; - const acc: Vec[] = []; - for (let p of range2d(cols, rows)) { - if (p[1] & 1) p[0] += 0.5; - unmapPoint(box, div2(null, p, maxg), p); - jitter(p, p, opts.jitter); - if (pointInside(shape, p)) { - acc.push(p); - } - } - return points(acc, opts.attribs); - }; + opts = mergeDeepObj( + { + space: 5, + jitter: 0.5, + attribs: { + shape: "circle", + stroke: "black", + fill: "none", + }, + }, + opts + ); + return (shape) => { + const box = bounds(shape)!; + const [w, h] = box.size; + const cols = ~~(w / opts.space!); + const rows = ~~(h / opts.space!); + const maxg = [cols - 1, rows - 1]; + const acc: Vec[] = []; + for (let p of range2d(cols, rows)) { + if (p[1] & 1) p[0] += 0.5; + unmapPoint(box, div2(null, p, maxg), p); + jitter(p, p, opts.jitter); + if (pointInside(shape, p)) { + acc.push(p); + } + } + return points(acc, opts.attribs); + }; }; diff --git a/packages/geom-fuzz/src/hatch.ts b/packages/geom-fuzz/src/hatch.ts index 8752c31cd6..88fe7bcdab 100644 --- a/packages/geom-fuzz/src/hatch.ts +++ b/packages/geom-fuzz/src/hatch.ts @@ -14,38 +14,38 @@ import { DEFAULT_LINE, FillFn, HatchOpts } from "./api.js"; import { defLine } from "./line.js"; const HATCH_DIRS = { - d: diagonalEnds2d, - h: rowEnds2d, - v: columnEnds2d, + d: diagonalEnds2d, + h: rowEnds2d, + v: columnEnds2d, }; export const defHatch = (opts: Partial = {}): FillFn => { - opts = mergeDeepObj( - { - dir: "d", - space: 5, - line: DEFAULT_LINE, - }, - opts - ); - const line = defLine(opts.line); - return (shape) => { - const box = offset(bounds(shape)!, 1); - const [w, h] = box.size; - const cols = ~~(w / opts.space!); - const rows = ~~(h / opts.space!); - const maxg = [cols - 1, rows - 1]; - const acc = group(opts.line ? opts.line.attribs : null); - for (let [a, b] of partition(2, HATCH_DIRS[opts.dir!](cols, rows))) { - unmapPoint(box, div2(null, a, maxg), a); - unmapPoint(box, div2(null, b, maxg), b); - const segments = clipLinePoly(a, b, shape.points); - if (segments) { - for (let s of segments) { - acc.children.push(line(s[0], s[1], false)); - } - } - } - return acc; - }; + opts = mergeDeepObj( + { + dir: "d", + space: 5, + line: DEFAULT_LINE, + }, + opts + ); + const line = defLine(opts.line); + return (shape) => { + const box = offset(bounds(shape)!, 1); + const [w, h] = box.size; + const cols = ~~(w / opts.space!); + const rows = ~~(h / opts.space!); + const maxg = [cols - 1, rows - 1]; + const acc = group(opts.line ? opts.line.attribs : null); + for (let [a, b] of partition(2, HATCH_DIRS[opts.dir!](cols, rows))) { + unmapPoint(box, div2(null, a, maxg), a); + unmapPoint(box, div2(null, b, maxg), b); + const segments = clipLinePoly(a, b, shape.points); + if (segments) { + for (let s of segments) { + acc.children.push(line(s[0], s[1], false)); + } + } + } + return acc; + }; }; diff --git a/packages/geom-fuzz/src/line.ts b/packages/geom-fuzz/src/line.ts index d3596d8fe1..78ad359461 100644 --- a/packages/geom-fuzz/src/line.ts +++ b/packages/geom-fuzz/src/line.ts @@ -10,22 +10,22 @@ import { DEFAULT_LINE, FuzzyLineOpts } from "./api.js"; import { jitterPoints } from "./points.js"; export const defLine = ( - opts: Partial = {} + opts: Partial = {} ): Fn3 => { - opts = mergeDeepObj(DEFAULT_LINE, opts); - return opts.resample! > 1 - ? (a, b, useAttr = true) => - polyline( - jitterPoints( - resample([a, b], { num: opts.resample, last: true }), - opts.jitter - ), - useAttr ? opts.attribs : undefined - ) - : (a, b, useAttr = true) => - line( - jitter(null, a, opts.jitter), - jitter(null, b, opts.jitter), - useAttr ? opts.attribs : undefined - ); + opts = mergeDeepObj(DEFAULT_LINE, opts); + return opts.resample! > 1 + ? (a, b, useAttr = true) => + polyline( + jitterPoints( + resample([a, b], { num: opts.resample, last: true }), + opts.jitter + ), + useAttr ? opts.attribs : undefined + ) + : (a, b, useAttr = true) => + line( + jitter(null, a, opts.jitter), + jitter(null, b, opts.jitter), + useAttr ? opts.attribs : undefined + ); }; diff --git a/packages/geom-fuzz/src/points.ts b/packages/geom-fuzz/src/points.ts index c47a948132..13fd02d1c7 100644 --- a/packages/geom-fuzz/src/points.ts +++ b/packages/geom-fuzz/src/points.ts @@ -2,4 +2,4 @@ import type { Vec } from "@thi.ng/vectors"; import { jitter } from "@thi.ng/vectors/jitter"; export const jitterPoints = (pts: Vec[], scl = 5) => - pts.map((p) => jitter([], p, scl)); + pts.map((p) => jitter([], p, scl)); diff --git a/packages/geom-fuzz/src/polygon.ts b/packages/geom-fuzz/src/polygon.ts index c47d34ab43..0ad32ab2a7 100644 --- a/packages/geom-fuzz/src/polygon.ts +++ b/packages/geom-fuzz/src/polygon.ts @@ -7,31 +7,31 @@ import type { FuzzyPolygonOpts } from "./api.js"; import { jitterPoints } from "./points.js"; export const fuzzyPoly = ( - pts: ReadonlyVec[], - attribs = {}, - opts: Partial = {} + pts: ReadonlyVec[], + attribs = {}, + opts: Partial = {} ) => { - opts = { - num: 2, - jitter: 2, - curveBreakPoints: true, - curveScale: 0.1, - ...opts, - }; - const acc = group(attribs, []); - for (; --opts.num! >= 0; ) { - const poly = polygon(jitterPoints(pts, opts.jitter)); - acc.children.push( - pathFromCubics( - asCubic(poly, { - breakPoints: opts.curveBreakPoints, - scale: opts.curveScale, - }) - ) - ); - if (!opts.num && opts.fill) { - acc.children.push(opts.fill(poly)); - } - } - return acc; + opts = { + num: 2, + jitter: 2, + curveBreakPoints: true, + curveScale: 0.1, + ...opts, + }; + const acc = group(attribs, []); + for (; --opts.num! >= 0; ) { + const poly = polygon(jitterPoints(pts, opts.jitter)); + acc.children.push( + pathFromCubics( + asCubic(poly, { + breakPoints: opts.curveBreakPoints, + scale: opts.curveScale, + }) + ) + ); + if (!opts.num && opts.fill) { + acc.children.push(opts.fill(poly)); + } + } + return acc; }; diff --git a/packages/geom-fuzz/src/presets.ts b/packages/geom-fuzz/src/presets.ts index ec1e0fb116..b0a70aaaba 100644 --- a/packages/geom-fuzz/src/presets.ts +++ b/packages/geom-fuzz/src/presets.ts @@ -2,21 +2,21 @@ import type { Color, HatchDir } from "./api.js"; import { defHatch } from "./hatch.js"; export const defHatchPen = ( - color: Color, - dir: HatchDir = "d", - thick = 8, - space = 1, - steps = 4 + color: Color, + dir: HatchDir = "d", + thick = 8, + space = 1, + steps = 4 ) => - defHatch({ - dir, - space: thick * space, - line: { - attribs: { - stroke: color, - weight: thick, - }, - jitter: thick * space * 0.25, - resample: steps, - }, - }); + defHatch({ + dir, + space: thick * space, + line: { + attribs: { + stroke: color, + weight: thick, + }, + jitter: thick * space * 0.25, + resample: steps, + }, + }); diff --git a/packages/geom-fuzz/tsconfig.json b/packages/geom-fuzz/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-fuzz/tsconfig.json +++ b/packages/geom-fuzz/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-hull/api-extractor.json b/packages/geom-hull/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-hull/api-extractor.json +++ b/packages/geom-hull/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-hull/package.json b/packages/geom-hull/package.json index 47b6b8c77d..f1eb925b3d 100644 --- a/packages/geom-hull/package.json +++ b/packages/geom-hull/package.json @@ -1,79 +1,79 @@ { - "name": "@thi.ng/geom-hull", - "version": "2.1.16", - "description": "Fast 2D convex hull (Graham Scan)", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-hull#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/math": "^5.3.4", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "convex", - "geometry", - "graphics", - "hull", - "points", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./graham-scan": { - "default": "./graham-scan.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "year": 2013 - } + "name": "@thi.ng/geom-hull", + "version": "2.1.16", + "description": "Fast 2D convex hull (Graham Scan)", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-hull#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/math": "^5.3.4", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "convex", + "geometry", + "graphics", + "hull", + "points", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./graham-scan": { + "default": "./graham-scan.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "year": 2013 + } } diff --git a/packages/geom-hull/src/graham-scan.ts b/packages/geom-hull/src/graham-scan.ts index 3f5c9dcc63..41daa1699b 100644 --- a/packages/geom-hull/src/graham-scan.ts +++ b/packages/geom-hull/src/graham-scan.ts @@ -14,44 +14,44 @@ const atan2 = Math.atan2; * @param eps - tolerance for colinear neighbor detection */ export const grahamScan2 = (pts: ReadonlyVec[], eps = EPS) => { - const num = pts.length; - if (num <= 3) return pts.slice(); - let h = 1; - let i; - let p; - let q; - let r; - let rx; - let ry; - // find min YX index - const min = findMin(pts); - [rx, ry] = pts[min]; - const sorted: { p: ReadonlyVec; t: number }[] = []; - // compute & sort by polar ordering relative to min - for (i = 0; i < num; i++) { - p = pts[i]; - sorted[i] = { p, t: atan2(p[1] - ry, p[0] - rx) }; - } - sorted.sort((a, b) => (a.t !== b.t ? a.t - b.t : a.p[0] - b.p[0])); - const hull: Vec[] = [sorted[0].p]; - for (i = 1; i < num; i++) { - p = hull[h - 2]; - q = hull[h - 1]; - r = sorted[i].p; - rx = r[0]; - ry = r[1]; - while ( - (h > 1 && notCCW(p[0], p[1], q[0], q[1], rx, ry, eps)) || - (h === 1 && q[0] === rx && q[1] === ry) - ) { - h--; - q = p; - p = hull[h - 2]; - } - hull[h++] = r; - } - hull.length = h; - return hull; + const num = pts.length; + if (num <= 3) return pts.slice(); + let h = 1; + let i; + let p; + let q; + let r; + let rx; + let ry; + // find min YX index + const min = findMin(pts); + [rx, ry] = pts[min]; + const sorted: { p: ReadonlyVec; t: number }[] = []; + // compute & sort by polar ordering relative to min + for (i = 0; i < num; i++) { + p = pts[i]; + sorted[i] = { p, t: atan2(p[1] - ry, p[0] - rx) }; + } + sorted.sort((a, b) => (a.t !== b.t ? a.t - b.t : a.p[0] - b.p[0])); + const hull: Vec[] = [sorted[0].p]; + for (i = 1; i < num; i++) { + p = hull[h - 2]; + q = hull[h - 1]; + r = sorted[i].p; + rx = r[0]; + ry = r[1]; + while ( + (h > 1 && notCCW(p[0], p[1], q[0], q[1], rx, ry, eps)) || + (h === 1 && q[0] === rx && q[1] === ry) + ) { + h--; + q = p; + p = hull[h - 2]; + } + hull[h++] = r; + } + hull.length = h; + return hull; }; /** @@ -68,13 +68,13 @@ export const grahamScan2 = (pts: ReadonlyVec[], eps = EPS) => { * @param cy - */ const notCCW = ( - ax: number, - ay: number, - bx: number, - by: number, - cx: number, - cy: number, - eps: number + ax: number, + ay: number, + bx: number, + by: number, + cx: number, + cy: number, + eps: number ) => (by - ay) * (cx - ax) >= (bx - ax) * (cy - ay) - eps; /** @@ -83,18 +83,18 @@ const notCCW = ( * @param pts - */ const findMin = (pts: ReadonlyVec[]) => { - let n = pts.length - 1; - let minID = n; - let [minX, minY] = pts[n]; - let p, y; - for (; n-- > 0; ) { - p = pts[n]; - y = p[1]; - if (y < minY || (y === minY && p[0] < minX)) { - minX = p[0]; - minY = y; - minID = n; - } - } - return minID; + let n = pts.length - 1; + let minID = n; + let [minX, minY] = pts[n]; + let p, y; + for (; n-- > 0; ) { + p = pts[n]; + y = p[1]; + if (y < minY || (y === minY && p[0] < minX)) { + minX = p[0]; + minY = y; + minID = n; + } + } + return minID; }; diff --git a/packages/geom-hull/tsconfig.json b/packages/geom-hull/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-hull/tsconfig.json +++ b/packages/geom-hull/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-io-obj/api-extractor.json b/packages/geom-io-obj/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-io-obj/api-extractor.json +++ b/packages/geom-io-obj/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-io-obj/package.json b/packages/geom-io-obj/package.json index 3618739357..40291c9770 100644 --- a/packages/geom-io-obj/package.json +++ b/packages/geom-io-obj/package.json @@ -1,87 +1,87 @@ { - "name": "@thi.ng/geom-io-obj", - "version": "0.3.16", - "description": "Wavefront OBJ parser (& exporter soon)", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-io-obj#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "3d", - "datastructure", - "fileformat", - "geometry", - "mesh", - "parser", - "points", - "polygon", - "polyline", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./parser": { - "default": "./parser.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "status": "alpha", - "year": 2016 - } + "name": "@thi.ng/geom-io-obj", + "version": "0.3.16", + "description": "Wavefront OBJ parser (& exporter soon)", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-io-obj#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "3d", + "datastructure", + "fileformat", + "geometry", + "mesh", + "parser", + "points", + "polygon", + "polyline", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./parser": { + "default": "./parser.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "status": "alpha", + "year": 2016 + } } diff --git a/packages/geom-io-obj/src/api.ts b/packages/geom-io-obj/src/api.ts index 33e0acd22a..77eca1466c 100644 --- a/packages/geom-io-obj/src/api.ts +++ b/packages/geom-io-obj/src/api.ts @@ -2,85 +2,85 @@ import type { Fn } from "@thi.ng/api"; import type { Vec } from "@thi.ng/vectors"; export interface ParseOpts { - /** - * If true, creates declared objects in OBJ source. If false, all - * faces/lines will be merged in default object. - * - * @defaultValue true - */ - objects: boolean; - /** - * If true, creates declared (optionally named) groups in OBJ - * source. If false, all faces/lines will be merged in default - * group of current object. - * - * @defaultValue true - */ - groups: boolean; - /** - * If true, n-gon faces (quads or higher) will be split into - * triangles. - * - * @defaultValue false - */ - tessellate: boolean; - /** - * If true, retains all comment lines (e.g. for metadata) - * - * @defaultValue false - */ - comments: boolean; - /** - * If false, skips parsing of normals and ignores their references - * in faces. - * - * @defaultValue true - */ - normals: boolean; - /** - * If false, skips parsing of UVs / tex coords and ignores their - * references in faces. - * - * @defaultValue true - */ - uvs: boolean; - /** - * Transform function applied to all read vertices and normals. - */ - xform?: Fn; - /** - * Transform function applied to all read UVs. - */ - xformUV?: Fn; + /** + * If true, creates declared objects in OBJ source. If false, all + * faces/lines will be merged in default object. + * + * @defaultValue true + */ + objects: boolean; + /** + * If true, creates declared (optionally named) groups in OBJ + * source. If false, all faces/lines will be merged in default + * group of current object. + * + * @defaultValue true + */ + groups: boolean; + /** + * If true, n-gon faces (quads or higher) will be split into + * triangles. + * + * @defaultValue false + */ + tessellate: boolean; + /** + * If true, retains all comment lines (e.g. for metadata) + * + * @defaultValue false + */ + comments: boolean; + /** + * If false, skips parsing of normals and ignores their references + * in faces. + * + * @defaultValue true + */ + normals: boolean; + /** + * If false, skips parsing of UVs / tex coords and ignores their + * references in faces. + * + * @defaultValue true + */ + uvs: boolean; + /** + * Transform function applied to all read vertices and normals. + */ + xform?: Fn; + /** + * Transform function applied to all read UVs. + */ + xformUV?: Fn; } /** * Result data structure returned by {@link parseOBJ}. */ export interface OBJModel { - vertices: Vec[]; - normals: Vec[]; - uvs: Vec[]; - objects: OBJObject[]; - mtlLibs: string[]; - comments: string[]; + vertices: Vec[]; + normals: Vec[]; + uvs: Vec[]; + objects: OBJObject[]; + mtlLibs: string[]; + comments: string[]; } export interface OBJObject { - id: string; - groups: OBJGroup[]; + id: string; + groups: OBJGroup[]; } export interface OBJGroup { - id: string; - smooth: boolean; - mtl?: string; - faces: OBJFace[]; - lines: number[][]; + id: string; + smooth: boolean; + mtl?: string; + faces: OBJFace[]; + lines: number[][]; } export interface OBJFace { - v: number[]; - n?: number[]; - uv?: number[]; + v: number[]; + n?: number[]; + uv?: number[]; } diff --git a/packages/geom-io-obj/src/parser.ts b/packages/geom-io-obj/src/parser.ts index 047832adef..7cd75c001f 100644 --- a/packages/geom-io-obj/src/parser.ts +++ b/packages/geom-io-obj/src/parser.ts @@ -3,190 +3,190 @@ import type { Vec } from "@thi.ng/vectors"; import type { OBJFace, OBJGroup, OBJModel, ParseOpts } from "./api.js"; export const parseOBJ = (src: string, opts?: Partial) => { - opts = { - normals: true, - uvs: true, - objects: true, - groups: true, - comments: false, - tessellate: false, - ...opts, - }; + opts = { + normals: true, + uvs: true, + objects: true, + groups: true, + comments: false, + tessellate: false, + ...opts, + }; - const vertices: Vec[] = []; - const normals: Vec[] = []; - const uvs: Vec[] = []; - const result = { - vertices, - normals, - uvs, - objects: [], - mtlLibs: [], - comments: [], - }; - let faces: OBJFace[]; - let currGroup!: OBJGroup; - let nextID = 0; + const vertices: Vec[] = []; + const normals: Vec[] = []; + const uvs: Vec[] = []; + const result = { + vertices, + normals, + uvs, + objects: [], + mtlLibs: [], + comments: [], + }; + let faces: OBJFace[]; + let currGroup!: OBJGroup; + let nextID = 0; - const newGroup = (id: string, force = false) => { - id = id || `group-${nextID++}`; - (force || opts!.groups) && - result.objects[result.objects.length - 1].groups.push( - (currGroup = { - id, - smooth: false, - faces: (faces = []), - lines: [], - mtl: currGroup ? currGroup.mtl : undefined, - }) - ); - }; + const newGroup = (id: string, force = false) => { + id = id || `group-${nextID++}`; + (force || opts!.groups) && + result.objects[result.objects.length - 1].groups.push( + (currGroup = { + id, + smooth: false, + faces: (faces = []), + lines: [], + mtl: currGroup ? currGroup.mtl : undefined, + }) + ); + }; - const newObject = (id: string, force = false) => { - (force || opts!.objects) && - result.objects.push({ id: id || `object-${nextID++}`, groups: [] }); - newGroup("default", force); - }; + const newObject = (id: string, force = false) => { + (force || opts!.objects) && + result.objects.push({ id: id || `object-${nextID++}`, groups: [] }); + newGroup("default", force); + }; - const readFace = (line: string[]) => { - const face = { v: [] }; - const n = line.length; - const nv = vertices.length; - const nuv = uvs.length; - const nn = normals.length; - const items = line[1].split("/"); - switch (items.length) { - case 1: - for (let i = 1; i < n; i++) { - addID(face.v, line[i], nv); - } - break; - case 2: - opts!.uvs && (face.uv = []); - for (let i = 1; i < n; i++) { - const f = line[i].split("/"); - addID(face.v, f[0], nv); - face.uv && addID(face.uv!, f[1], nuv); - } - break; - case 3: - opts!.uvs && items[1].length && (face.uv = []); - opts!.normals && items[2].length && (face.n = []); - for (let i = 1; i < n; i++) { - const f = line[i].split("/"); - addID(face.v, f[0], nv); - face.uv && addID(face.uv!, f[1], nuv); - face.n && addID(face.n!, f[2], nn); - } - break; - default: - } - return face; - }; + const readFace = (line: string[]) => { + const face = { v: [] }; + const n = line.length; + const nv = vertices.length; + const nuv = uvs.length; + const nn = normals.length; + const items = line[1].split("/"); + switch (items.length) { + case 1: + for (let i = 1; i < n; i++) { + addID(face.v, line[i], nv); + } + break; + case 2: + opts!.uvs && (face.uv = []); + for (let i = 1; i < n; i++) { + const f = line[i].split("/"); + addID(face.v, f[0], nv); + face.uv && addID(face.uv!, f[1], nuv); + } + break; + case 3: + opts!.uvs && items[1].length && (face.uv = []); + opts!.normals && items[2].length && (face.n = []); + for (let i = 1; i < n; i++) { + const f = line[i].split("/"); + addID(face.v, f[0], nv); + face.uv && addID(face.uv!, f[1], nuv); + face.n && addID(face.n!, f[2], nn); + } + break; + default: + } + return face; + }; - const readPolyLine = (items: string[]) => { - const nv = vertices.length; - const verts: number[] = []; - for (let i = 1, n = items.length; i < n; i++) { - addID(verts, items[i], nv); - } - return verts; - }; + const readPolyLine = (items: string[]) => { + const nv = vertices.length; + const verts: number[] = []; + for (let i = 1, n = items.length; i < n; i++) { + addID(verts, items[i], nv); + } + return verts; + }; - newObject("default", true); + newObject("default", true); - const { xform, xformUV, tessellate, comments } = opts; - const lines = src.split(/[\n\r]+/g); + const { xform, xformUV, tessellate, comments } = opts; + const lines = src.split(/[\n\r]+/g); - for (let i = 0, n = lines.length; i < n; i++) { - const l = lines[i]; - if (!l.length) continue; - if (l[0] === "#") { - comments && result.comments.push(l.substring(1).trim()); - continue; - } - const items = l.trim().split(/\s+/g); - const len = items.length; - switch (items[0]) { - case "v": { - assert(len > 3, `invalid vertex @ line ${i}`); - const v = readVec3(items); - vertices.push(xform ? xform(v) : v); - break; - } - case "vn": { - assert(len > 3, `invalid normal @ line ${i}`); - const v = readVec3(items); - normals.push(xform ? xform(v) : v); - break; - } - case "vt": { - assert(len > 2, `invalid uv @ line ${i}`); - const v = readVec2(items); - uvs.push(xformUV ? xformUV(v) : v); - break; - } - case "f": { - assert(len > 3, `invalid face @ line ${i}`); - const f = readFace(items); - tessellate && f.v.length > 3 - ? faces!.push(...tessellateFace(f)) - : faces!.push(f); - break; - } - case "l": - assert(len > 2, `invalid polyline @ line ${i}`); - currGroup.lines.push(readPolyLine(items)); - break; - case "o": - opts.objects && newObject(items[1]); - break; - case "g": - opts.groups && newGroup(items[1]); - break; - case "s": - currGroup.smooth = items[1] !== "0" && items[1] !== "off"; - break; - case "mtllib": - result.mtlLibs.push(items[1]); - break; - case "usemtl": - currGroup.mtl = items[1]; - break; - default: - console.log(`ignoring token: ${items[0]} @ line ${i}`); - } - } - return result; + for (let i = 0, n = lines.length; i < n; i++) { + const l = lines[i]; + if (!l.length) continue; + if (l[0] === "#") { + comments && result.comments.push(l.substring(1).trim()); + continue; + } + const items = l.trim().split(/\s+/g); + const len = items.length; + switch (items[0]) { + case "v": { + assert(len > 3, `invalid vertex @ line ${i}`); + const v = readVec3(items); + vertices.push(xform ? xform(v) : v); + break; + } + case "vn": { + assert(len > 3, `invalid normal @ line ${i}`); + const v = readVec3(items); + normals.push(xform ? xform(v) : v); + break; + } + case "vt": { + assert(len > 2, `invalid uv @ line ${i}`); + const v = readVec2(items); + uvs.push(xformUV ? xformUV(v) : v); + break; + } + case "f": { + assert(len > 3, `invalid face @ line ${i}`); + const f = readFace(items); + tessellate && f.v.length > 3 + ? faces!.push(...tessellateFace(f)) + : faces!.push(f); + break; + } + case "l": + assert(len > 2, `invalid polyline @ line ${i}`); + currGroup.lines.push(readPolyLine(items)); + break; + case "o": + opts.objects && newObject(items[1]); + break; + case "g": + opts.groups && newGroup(items[1]); + break; + case "s": + currGroup.smooth = items[1] !== "0" && items[1] !== "off"; + break; + case "mtllib": + result.mtlLibs.push(items[1]); + break; + case "usemtl": + currGroup.mtl = items[1]; + break; + default: + console.log(`ignoring token: ${items[0]} @ line ${i}`); + } + } + return result; }; const addID = (acc: number[], x: string, num: number) => { - const v = parseInt(x); - acc.push(v < 0 ? v + num : v - 1); + const v = parseInt(x); + acc.push(v < 0 ? v + num : v - 1); }; const readVec2 = (items: string[]) => [ - parseFloat(items[1]), - parseFloat(items[2]), + parseFloat(items[1]), + parseFloat(items[2]), ]; const readVec3 = (items: string[]) => [ - parseFloat(items[1]), - parseFloat(items[2]), - parseFloat(items[3]), + parseFloat(items[1]), + parseFloat(items[2]), + parseFloat(items[3]), ]; const tessellateFace = (face: OBJFace) => { - const { v, uv, n } = face; - const v0 = v[0]; - const uv0 = uv && uv[0]; - const n0 = n && n[0]; - const acc: OBJFace[] = []; - for (let i = 1, num = v.length - 1; i < num; i++) { - const tri: OBJFace = { v: [v0, v[i], v[i + 1]] }; - uv && (tri.uv = [uv0!, uv[i], uv[i + 1]]); - n && (tri.n = [n0!, n[i], n[i + 1]]); - acc.push(tri); - } - return acc; + const { v, uv, n } = face; + const v0 = v[0]; + const uv0 = uv && uv[0]; + const n0 = n && n[0]; + const acc: OBJFace[] = []; + for (let i = 1, num = v.length - 1; i < num; i++) { + const tri: OBJFace = { v: [v0, v[i], v[i + 1]] }; + uv && (tri.uv = [uv0!, uv[i], uv[i + 1]]); + n && (tri.n = [n0!, n[i], n[i + 1]]); + acc.push(tri); + } + return acc; }; diff --git a/packages/geom-io-obj/test/index.ts b/packages/geom-io-obj/test/index.ts index 6fef7d7604..507645589e 100644 --- a/packages/geom-io-obj/test/index.ts +++ b/packages/geom-io-obj/test/index.ts @@ -1,6 +1,6 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { parseOBJ } from "../src/index.js" +import { parseOBJ } from "../src/index.js"; const src = ` # test cube @@ -30,98 +30,98 @@ f 3 8 5 2 `; const cubeVerts = [ - [1, 1, 1], - [1, 1, 0], - [1, 0, 0], - [1, 0, 1], - [0, 1, 0], - [0, 1, 1], - [0, 0, 1], - [0, 0, 0], + [1, 1, 1], + [1, 1, 0], + [1, 0, 0], + [1, 0, 1], + [0, 1, 0], + [0, 1, 1], + [0, 0, 1], + [0, 0, 0], ]; const cubeFaces = [ - { v: [3, 2, 1, 0] }, - { v: [7, 6, 5, 4] }, - { v: [5, 0, 1, 4] }, - { v: [7, 2, 3, 6] }, - { v: [6, 3, 0, 5] }, - { v: [2, 7, 4, 1] }, + { v: [3, 2, 1, 0] }, + { v: [7, 6, 5, 4] }, + { v: [5, 0, 1, 4] }, + { v: [7, 2, 3, 6] }, + { v: [6, 3, 0, 5] }, + { v: [2, 7, 4, 1] }, ]; group("geom-io-obj", { - "cube (default)": () => { - const model = parseOBJ(src); - assert.deepStrictEqual(model.vertices, cubeVerts); - assert.strictEqual(model.objects.length, 2); - assert.strictEqual(model.objects[1].id, "cube"); - assert.deepStrictEqual(model.objects[1].groups, [ - { - id: "default", - smooth: false, - mtl: "noise", - lines: [], - faces: cubeFaces.slice(0, 3), - }, - { - id: "other", - smooth: true, - mtl: "noise", - lines: [], - faces: cubeFaces.slice(3), - }, - ]); - assert.deepStrictEqual(model.mtlLibs, ["cube.mtl"]); - }, + "cube (default)": () => { + const model = parseOBJ(src); + assert.deepStrictEqual(model.vertices, cubeVerts); + assert.strictEqual(model.objects.length, 2); + assert.strictEqual(model.objects[1].id, "cube"); + assert.deepStrictEqual(model.objects[1].groups, [ + { + id: "default", + smooth: false, + mtl: "noise", + lines: [], + faces: cubeFaces.slice(0, 3), + }, + { + id: "other", + smooth: true, + mtl: "noise", + lines: [], + faces: cubeFaces.slice(3), + }, + ]); + assert.deepStrictEqual(model.mtlLibs, ["cube.mtl"]); + }, - "cube (no obj, no groups)": () => { - const model = parseOBJ(src, { objects: false, groups: false }); - assert.deepStrictEqual(model.vertices, cubeVerts); - assert.strictEqual(model.objects.length, 1); - assert.strictEqual(model.objects[0].id, "default"); - assert.deepStrictEqual(model.objects[0].groups, [ - { - id: "default", - smooth: true, - mtl: "noise", - lines: [], - faces: cubeFaces, - }, - ]); - }, + "cube (no obj, no groups)": () => { + const model = parseOBJ(src, { objects: false, groups: false }); + assert.deepStrictEqual(model.vertices, cubeVerts); + assert.strictEqual(model.objects.length, 1); + assert.strictEqual(model.objects[0].id, "default"); + assert.deepStrictEqual(model.objects[0].groups, [ + { + id: "default", + smooth: true, + mtl: "noise", + lines: [], + faces: cubeFaces, + }, + ]); + }, - "cube (tessel)": () => { - const model = parseOBJ(src, { - objects: false, - groups: false, - tessellate: true, - }); - assert.deepStrictEqual(model.objects[0].groups, [ - { - id: "default", - smooth: true, - mtl: "noise", - lines: [], - faces: [ - { v: [3, 2, 1] }, - { v: [3, 1, 0] }, - { v: [7, 6, 5] }, - { v: [7, 5, 4] }, - { v: [5, 0, 1] }, - { v: [5, 1, 4] }, - { v: [7, 2, 3] }, - { v: [7, 3, 6] }, - { v: [6, 3, 0] }, - { v: [6, 0, 5] }, - { v: [2, 7, 4] }, - { v: [2, 4, 1] }, - ], - }, - ]); - }, + "cube (tessel)": () => { + const model = parseOBJ(src, { + objects: false, + groups: false, + tessellate: true, + }); + assert.deepStrictEqual(model.objects[0].groups, [ + { + id: "default", + smooth: true, + mtl: "noise", + lines: [], + faces: [ + { v: [3, 2, 1] }, + { v: [3, 1, 0] }, + { v: [7, 6, 5] }, + { v: [7, 5, 4] }, + { v: [5, 0, 1] }, + { v: [5, 1, 4] }, + { v: [7, 2, 3] }, + { v: [7, 3, 6] }, + { v: [6, 3, 0] }, + { v: [6, 0, 5] }, + { v: [2, 7, 4] }, + { v: [2, 4, 1] }, + ], + }, + ]); + }, - comments: () => { - const model = parseOBJ(src, { comments: true }); - assert.deepStrictEqual(model.comments, ["test cube", "quad faces"]); - }, + comments: () => { + const model = parseOBJ(src, { comments: true }); + assert.deepStrictEqual(model.comments, ["test cube", "quad faces"]); + }, }); diff --git a/packages/geom-io-obj/tsconfig.json b/packages/geom-io-obj/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-io-obj/tsconfig.json +++ b/packages/geom-io-obj/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-isec/api-extractor.json b/packages/geom-isec/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-isec/api-extractor.json +++ b/packages/geom-isec/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-isec/package.json b/packages/geom-isec/package.json index bd90f9107f..3f002dd25d 100644 --- a/packages/geom-isec/package.json +++ b/packages/geom-isec/package.json @@ -1,122 +1,122 @@ { - "name": "@thi.ng/geom-isec", - "version": "2.1.19", - "description": "2D/3D shape intersection checks", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-isec#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/geom-api": "^3.3.2", - "@thi.ng/geom-closest-point": "^2.1.16", - "@thi.ng/math": "^5.3.4", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "3d", - "bbox", - "circle", - "geometry", - "intersection", - "line", - "ray", - "rect", - "sphere", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./circle-circle": { - "default": "./circle-circle.js" - }, - "./line-line": { - "default": "./line-line.js" - }, - "./line-poly": { - "default": "./line-poly.js" - }, - "./plane-plane": { - "default": "./plane-plane.js" - }, - "./point": { - "default": "./point.js" - }, - "./ray-circle": { - "default": "./ray-circle.js" - }, - "./ray-line": { - "default": "./ray-line.js" - }, - "./ray-plane": { - "default": "./ray-plane.js" - }, - "./ray-poly": { - "default": "./ray-poly.js" - }, - "./ray-rect": { - "default": "./ray-rect.js" - }, - "./rect-circle": { - "default": "./rect-circle.js" - }, - "./rect-rect": { - "default": "./rect-rect.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "year": 2013 - } + "name": "@thi.ng/geom-isec", + "version": "2.1.19", + "description": "2D/3D shape intersection checks", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-isec#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/geom-api": "^3.3.2", + "@thi.ng/geom-closest-point": "^2.1.16", + "@thi.ng/math": "^5.3.4", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "3d", + "bbox", + "circle", + "geometry", + "intersection", + "line", + "ray", + "rect", + "sphere", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./circle-circle": { + "default": "./circle-circle.js" + }, + "./line-line": { + "default": "./line-line.js" + }, + "./line-poly": { + "default": "./line-poly.js" + }, + "./plane-plane": { + "default": "./plane-plane.js" + }, + "./point": { + "default": "./point.js" + }, + "./ray-circle": { + "default": "./ray-circle.js" + }, + "./ray-line": { + "default": "./ray-line.js" + }, + "./ray-plane": { + "default": "./ray-plane.js" + }, + "./ray-poly": { + "default": "./ray-poly.js" + }, + "./ray-rect": { + "default": "./ray-rect.js" + }, + "./rect-circle": { + "default": "./rect-circle.js" + }, + "./rect-rect": { + "default": "./rect-rect.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "year": 2013 + } } diff --git a/packages/geom-isec/src/api.ts b/packages/geom-isec/src/api.ts index 299d3681c5..65dd3d24b2 100644 --- a/packages/geom-isec/src/api.ts +++ b/packages/geom-isec/src/api.ts @@ -1,5 +1,5 @@ import { IntersectionResult, IntersectionType } from "@thi.ng/geom-api/isec"; export const NONE: IntersectionResult = Object.freeze({ - type: IntersectionType.NONE, + type: IntersectionType.NONE, }); diff --git a/packages/geom-isec/src/circle-circle.ts b/packages/geom-isec/src/circle-circle.ts index 8f81f15d9e..34c5483ee9 100644 --- a/packages/geom-isec/src/circle-circle.ts +++ b/packages/geom-isec/src/circle-circle.ts @@ -11,33 +11,33 @@ import { sub } from "@thi.ng/vectors/sub"; import { NONE } from "./api.js"; export const intersectCircleCircle = ( - a: ReadonlyVec, - b: ReadonlyVec, - ar: number, - br: number + a: ReadonlyVec, + b: ReadonlyVec, + ar: number, + br: number ): IntersectionResult => { - const delta = sub([], b, a); - const d = mag(delta); - if (eqDelta(d, 0)) { - return { type: IntersectionType.COINCIDENT }; - } - if (d <= ar + br && d >= Math.abs(ar - br)) { - ar *= ar; - const alpha = (ar - br * br + d * d) / (2 * d); - const h = Math.sqrt(ar - alpha * alpha); - const p = maddN([], delta, alpha / d, a); - const t = mulN(null, perpendicularCCW(null, delta), h / d); - return { - type: IntersectionType.INTERSECT, - isec: [add([], p, t), sub([], p, t)], - }; - } - return NONE; + const delta = sub([], b, a); + const d = mag(delta); + if (eqDelta(d, 0)) { + return { type: IntersectionType.COINCIDENT }; + } + if (d <= ar + br && d >= Math.abs(ar - br)) { + ar *= ar; + const alpha = (ar - br * br + d * d) / (2 * d); + const h = Math.sqrt(ar - alpha * alpha); + const p = maddN([], delta, alpha / d, a); + const t = mulN(null, perpendicularCCW(null, delta), h / d); + return { + type: IntersectionType.INTERSECT, + isec: [add([], p, t), sub([], p, t)], + }; + } + return NONE; }; export const testCircleCircle = ( - a: ReadonlyVec, - b: ReadonlyVec, - ar: number, - br: number + a: ReadonlyVec, + b: ReadonlyVec, + ar: number, + br: number ) => distSq(a, b) <= Math.pow(ar + br, 2); diff --git a/packages/geom-isec/src/line-line.ts b/packages/geom-isec/src/line-line.ts index 8ee16f0c98..37f0343f36 100644 --- a/packages/geom-isec/src/line-line.ts +++ b/packages/geom-isec/src/line-line.ts @@ -7,48 +7,48 @@ import type { ReadonlyVec } from "@thi.ng/vectors"; import { mixN2 } from "@thi.ng/vectors/mixn"; export const intersectLineLine = ( - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - d: ReadonlyVec, - eps = EPS + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + d: ReadonlyVec, + eps = EPS ): IntersectionResult => { - const bax = b[0] - a[0]; - const bay = b[1] - a[1]; - const dcx = d[0] - c[0]; - const dcy = d[1] - c[1]; - const acx = a[0] - c[0]; - const acy = a[1] - c[1]; - const det = dcy * bax - dcx * bay; - let alpha = dcx * acy - dcy * acx; - let beta = bax * acy - bay * acx; - if (eqDelta(det, 0, eps)) { - if (eqDelta(alpha, 0, eps) && eqDelta(beta, 0, eps)) { - let isec = - closestPointSegment(c, a, b, undefined, true) || - closestPointSegment(d, a, b, undefined, true); - return { - type: isec - ? IntersectionType.COINCIDENT - : IntersectionType.COINCIDENT_NO_INTERSECT, - isec, - }; - } - return { type: IntersectionType.PARALLEL }; - } - alpha /= det; - beta /= det; - const ieps = 1 - eps; - return { - type: - eps < alpha && alpha < ieps && eps < beta && beta < ieps - ? IntersectionType.INTERSECT - : IntersectionType.INTERSECT_OUTSIDE, - isec: mixN2([], a, b, alpha), - alpha, - beta, - det, - }; + const bax = b[0] - a[0]; + const bay = b[1] - a[1]; + const dcx = d[0] - c[0]; + const dcy = d[1] - c[1]; + const acx = a[0] - c[0]; + const acy = a[1] - c[1]; + const det = dcy * bax - dcx * bay; + let alpha = dcx * acy - dcy * acx; + let beta = bax * acy - bay * acx; + if (eqDelta(det, 0, eps)) { + if (eqDelta(alpha, 0, eps) && eqDelta(beta, 0, eps)) { + let isec = + closestPointSegment(c, a, b, undefined, true) || + closestPointSegment(d, a, b, undefined, true); + return { + type: isec + ? IntersectionType.COINCIDENT + : IntersectionType.COINCIDENT_NO_INTERSECT, + isec, + }; + } + return { type: IntersectionType.PARALLEL }; + } + alpha /= det; + beta /= det; + const ieps = 1 - eps; + return { + type: + eps < alpha && alpha < ieps && eps < beta && beta < ieps + ? IntersectionType.INTERSECT + : IntersectionType.INTERSECT_OUTSIDE, + isec: mixN2([], a, b, alpha), + alpha, + beta, + det, + }; }; /** @@ -61,4 +61,4 @@ export const intersectLineLine = ( * @param d - line 2 end point */ export const isParallelLine: FnU4 = (a, b, c, d) => - eqDelta((d[1] - c[1]) * (b[0] - a[0]) - (d[0] - c[0]) * (b[1] - a[1]), 0); + eqDelta((d[1] - c[1]) * (b[0] - a[0]) - (d[0] - c[0]) * (b[1] - a[1]), 0); diff --git a/packages/geom-isec/src/line-poly.ts b/packages/geom-isec/src/line-poly.ts index cf695f7e88..6c6696cc6d 100644 --- a/packages/geom-isec/src/line-poly.ts +++ b/packages/geom-isec/src/line-poly.ts @@ -6,19 +6,19 @@ import { sub } from "@thi.ng/vectors/sub"; import { intersectRayPolylineAll } from "./ray-poly.js"; export const intersectLinePolylineAll = ( - a: ReadonlyVec, - b: ReadonlyVec, - pts: ReadonlyVec[], - closed = false + a: ReadonlyVec, + b: ReadonlyVec, + pts: ReadonlyVec[], + closed = false ): IntersectionResult => { - const dir = sub([], b, a); - const maxD = mag(dir); - return intersectRayPolylineAll( - a, - normalize(null, dir), - pts, - closed, - 0, - maxD - ); + const dir = sub([], b, a); + const maxD = mag(dir); + return intersectRayPolylineAll( + a, + normalize(null, dir), + pts, + closed, + 0, + maxD + ); }; diff --git a/packages/geom-isec/src/plane-plane.ts b/packages/geom-isec/src/plane-plane.ts index ef1cf606ce..e9921718d9 100644 --- a/packages/geom-isec/src/plane-plane.ts +++ b/packages/geom-isec/src/plane-plane.ts @@ -8,23 +8,23 @@ import { mulN3 } from "@thi.ng/vectors/muln"; import { NONE } from "./api.js"; export const intersectPlanePlane = ( - na: ReadonlyVec, - wa: number, - nb: ReadonlyVec, - wb: number + na: ReadonlyVec, + wa: number, + nb: ReadonlyVec, + wb: number ) => { - const dn = dot3(na, nb); - if (eqDelta(dn, 1)) { - return eqDelta(wa, wb) ? { type: IntersectionType.COINCIDENT } : NONE; - } - const det = 1 / (1 - dn * dn); - const da = (wa - wb * dn) * det; - const db = (wb - wa * dn) * det; - return { - type: IntersectionType.INTERSECT, - isec: [ - add3(null, mulN3([], na, da), mulN3([], nb, db)), - cross3([], na, nb), - ], - }; + const dn = dot3(na, nb); + if (eqDelta(dn, 1)) { + return eqDelta(wa, wb) ? { type: IntersectionType.COINCIDENT } : NONE; + } + const det = 1 / (1 - dn * dn); + const da = (wa - wb * dn) * det; + const db = (wb - wa * dn) * det; + return { + type: IntersectionType.INTERSECT, + isec: [ + add3(null, mulN3([], na, da), mulN3([], nb, db)), + cross3([], na, nb), + ], + }; }; diff --git a/packages/geom-isec/src/point.ts b/packages/geom-isec/src/point.ts index 08f62264b8..629e08a372 100644 --- a/packages/geom-isec/src/point.ts +++ b/packages/geom-isec/src/point.ts @@ -12,27 +12,27 @@ import { signedArea2 } from "@thi.ng/vectors/signed-area"; import { vop } from "@thi.ng/vectors/vop"; export const pointInSegment = ( - p: ReadonlyVec, - a: ReadonlyVec, - b: ReadonlyVec, - eps = EPS + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + eps = EPS ) => { - const t = closestT(p, a, b); - return t !== undefined - ? distSq(p, mixN([], a, b, clamp01(t))) < eps * eps - : false; + const t = closestT(p, a, b); + return t !== undefined + ? distSq(p, mixN([], a, b, clamp01(t))) < eps * eps + : false; }; export const pointInCircle = (p: ReadonlyVec, pos: ReadonlyVec, r: number) => - distSq(pos, p) <= r * r; + distSq(pos, p) <= r * r; export const pointInSphere = pointInCircle; export const classifyPointInCircle = ( - p: ReadonlyVec, - pos: ReadonlyVec, - r: number, - eps = EPS + p: ReadonlyVec, + pos: ReadonlyVec, + r: number, + eps = EPS ) => sign(r * r - distSq(pos, p), eps); /** @@ -54,26 +54,26 @@ export const classifyPointInCircle = ( * @param c - */ export const pointIn3Circle: FnU4 = ( - [px, py], - a, - b, - c + [px, py], + a, + b, + c ) => { - const apx = a[0] - px; - const apy = a[1] - py; - const bpx = b[0] - px; - const bpy = b[1] - py; - const cpx = c[0] - px; - const cpy = c[1] - py; - - const abdet = apx * bpy - bpx * apy; - const bcdet = bpx * cpy - cpx * bpy; - const cadet = cpx * apy - apx * cpy; - const alift = apx * apx + apy * apy; - const blift = bpx * bpx + bpy * bpy; - const clift = cpx * cpx + cpy * cpy; - - return alift * bcdet + blift * cadet + clift * abdet; + const apx = a[0] - px; + const apy = a[1] - py; + const bpx = b[0] - px; + const bpy = b[1] - py; + const cpx = c[0] - px; + const cpy = c[1] - py; + + const abdet = apx * bpy - bpx * apy; + const bcdet = bpx * cpy - cpx * bpy; + const cadet = cpx * apy - apx * cpy; + const alift = apx * apx + apy * apy; + const blift = bpx * bpx + bpy * bpy; + const clift = cpx * cpx + cpy * cpy; + + return alift * bcdet + blift * cadet + clift * abdet; }; /** @@ -94,161 +94,161 @@ export const pointIn3Circle: FnU4 = ( * @param d - */ export const pointIn4Sphere: FnU5 = ( - [px, py, pz], - a, - b, - c, - d + [px, py, pz], + a, + b, + c, + d ) => { - const apx = a[0] - px; - const bpx = b[0] - px; - const cpx = c[0] - px; - const dpx = d[0] - px; - const apy = a[1] - py; - const bpy = b[1] - py; - const cpy = c[1] - py; - const dpy = d[1] - py; - const apz = a[2] - pz; - const bpz = b[2] - pz; - const cpz = c[2] - pz; - const dpz = d[2] - pz; - - const ab = apx * bpy - bpx * apy; - const bc = bpx * cpy - cpx * bpy; - const cd = cpx * dpy - dpx * cpy; - const da = dpx * apy - apx * dpy; - - const ac = apx * cpy - cpx * apy; - const bd = bpx * dpy - dpx * bpy; - - const abc = apz * bc - bpz * ac + cpz * ab; - const bcd = bpz * cd - cpz * bd + dpz * bc; - const cda = cpz * da + dpz * ac + apz * cd; - const dab = dpz * ab + apz * bd + bpz * da; - - const alift = apx * apx + apy * apy + apz * apz; - const blift = bpx * bpx + bpy * bpy + bpz * bpz; - const clift = cpx * cpx + cpy * cpy + cpz * cpz; - const dlift = dpx * dpx + dpy * dpy + dpz * dpz; - - return dlift * abc - clift * dab + (blift * cda - alift * bcd); + const apx = a[0] - px; + const bpx = b[0] - px; + const cpx = c[0] - px; + const dpx = d[0] - px; + const apy = a[1] - py; + const bpy = b[1] - py; + const cpy = c[1] - py; + const dpy = d[1] - py; + const apz = a[2] - pz; + const bpz = b[2] - pz; + const cpz = c[2] - pz; + const dpz = d[2] - pz; + + const ab = apx * bpy - bpx * apy; + const bc = bpx * cpy - cpx * bpy; + const cd = cpx * dpy - dpx * cpy; + const da = dpx * apy - apx * dpy; + + const ac = apx * cpy - cpx * apy; + const bd = bpx * dpy - dpx * bpy; + + const abc = apz * bc - bpz * ac + cpz * ab; + const bcd = bpz * cd - cpz * bd + dpz * bc; + const cda = cpz * da + dpz * ac + apz * cd; + const dab = dpz * ab + apz * bd + bpz * da; + + const alift = apx * apx + apy * apy + apz * apz; + const blift = bpx * bpx + bpy * bpy + bpz * bpz; + const clift = cpx * cpx + cpy * cpy + cpz * cpz; + const dlift = dpx * dpx + dpy * dpy + dpz * dpz; + + return dlift * abc - clift * dab + (blift * cda - alift * bcd); }; export const pointInCircumCircle: FnU4 = (a, b, c, d) => - magSq(a) * signedArea2(b, c, d) - - magSq(b) * signedArea2(a, c, d) + - magSq(c) * signedArea2(a, b, d) - - magSq(d) * signedArea2(a, b, c) > - 0; + magSq(a) * signedArea2(b, c, d) - + magSq(b) * signedArea2(a, c, d) + + magSq(c) * signedArea2(a, b, d) - + magSq(d) * signedArea2(a, b, c) > + 0; export const pointInTriangle2: FnU4 = (p, a, b, c) => { - const s = clockwise2(a, b, c) ? 1 : -1; - return ( - s * signedArea2(a, c, p) >= 0 && - s * signedArea2(b, a, p) >= 0 && - s * signedArea2(c, b, p) >= 0 - ); + const s = clockwise2(a, b, c) ? 1 : -1; + return ( + s * signedArea2(a, c, p) >= 0 && + s * signedArea2(b, a, p) >= 0 && + s * signedArea2(c, b, p) >= 0 + ); }; export const classifyPointInTriangle2 = ( - p: ReadonlyVec, - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - eps = EPS + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + eps = EPS ) => { - const s = clockwise2(a, b, c) ? 1 : -1; - return sign( - Math.min( - s * signedArea2(a, c, p), - s * signedArea2(b, a, p), - s * signedArea2(c, b, p) - ), - eps - ); + const s = clockwise2(a, b, c) ? 1 : -1; + return sign( + Math.min( + s * signedArea2(a, c, p), + s * signedArea2(b, a, p), + s * signedArea2(c, b, p) + ), + eps + ); }; export const pointInPolygon2 = (p: ReadonlyVec, pts: ReadonlyVec[]) => { - const n = pts.length - 1; - const px = p[0]; - const py = p[1]; - let a = pts[n]; - let b = pts[0]; - let inside = 0; - for (let i = 0; i <= n; a = b, b = pts[++i]) { - inside = classifyPointPolyPair(px, py, a[0], a[1], b[0], b[1], inside); - } - return inside; + const n = pts.length - 1; + const px = p[0]; + const py = p[1]; + let a = pts[n]; + let b = pts[0]; + let inside = 0; + for (let i = 0; i <= n; a = b, b = pts[++i]) { + inside = classifyPointPolyPair(px, py, a[0], a[1], b[0], b[1], inside); + } + return inside; }; export const classifyPointPolyPair: FnN7 = (px, py, ax, ay, bx, by, inside) => - ((ay < py && by >= py) || (by < py && ay >= py)) && (ax <= px || bx <= px) - ? inside ^ ~~(ax + ((py - ay) / (by - ay)) * (bx - ax) < px) - : inside; + ((ay < py && by >= py) || (by < py && ay >= py)) && (ax <= px || bx <= px) + ? inside ^ ~~(ax + ((py - ay) / (by - ay)) * (bx - ax) < px) + : inside; export const pointInBox: MultiVecOpImpl< - Fn3 + Fn3 > = vop(0); export const pointInRect = pointInBox.add( - 2, - ([x, y]: ReadonlyVec, pos: ReadonlyVec, size: ReadonlyVec) => - x >= pos[0] && - x <= pos[0] + size[0] && - y >= pos[1] && - y <= pos[1] + size[1] + 2, + ([x, y]: ReadonlyVec, pos: ReadonlyVec, size: ReadonlyVec) => + x >= pos[0] && + x <= pos[0] + size[0] && + y >= pos[1] && + y <= pos[1] + size[1] ); export const pointInAABB = pointInBox.add( - 3, - ([x, y, z]: ReadonlyVec, pos: ReadonlyVec, size: ReadonlyVec) => - x >= pos[0] && - x <= pos[0] + size[0] && - y >= pos[1] && - y <= pos[1] + size[1] && - z >= pos[2] && - z <= pos[2] + size[2] + 3, + ([x, y, z]: ReadonlyVec, pos: ReadonlyVec, size: ReadonlyVec) => + x >= pos[0] && + x <= pos[0] + size[0] && + y >= pos[1] && + y <= pos[1] + size[1] && + z >= pos[2] && + z <= pos[2] + size[2] ); pointInBox.default((p, boxMin, boxSize) => { - for (let i = p.length; i-- > 0; ) { - const x = p[i]; - const y = boxMin[i]; - if (x < y || x > y + boxSize[i]) return false; - } - return true; + for (let i = p.length; i-- > 0; ) { + const x = p[i]; + const y = boxMin[i]; + if (x < y || x > y + boxSize[i]) return false; + } + return true; }); export const pointInCenteredBox: MultiVecOpImpl< - Fn3 + Fn3 > = vop(0); export const pointInCenteredRect = pointInCenteredBox.add( - 2, - ([x, y], pos, size) => - x >= pos[0] - size[0] && - x <= pos[0] + size[0] && - y >= pos[1] - size[1] && - y <= pos[1] + size[1] + 2, + ([x, y], pos, size) => + x >= pos[0] - size[0] && + x <= pos[0] + size[0] && + y >= pos[1] - size[1] && + y <= pos[1] + size[1] ); export const pointInCenteredAABB = pointInCenteredBox.add( - 3, - ([x, y, z]: ReadonlyVec, pos: ReadonlyVec, size: ReadonlyVec) => - x >= pos[0] - size[0] && - x <= pos[0] + size[0] && - y >= pos[1] - size[1] && - y <= pos[1] + size[1] && - z >= pos[2] - size[2] && - z <= pos[2] + size[2] + 3, + ([x, y, z]: ReadonlyVec, pos: ReadonlyVec, size: ReadonlyVec) => + x >= pos[0] - size[0] && + x <= pos[0] + size[0] && + y >= pos[1] - size[1] && + y <= pos[1] + size[1] && + z >= pos[2] - size[2] && + z <= pos[2] + size[2] ); pointInCenteredBox.default((p, boxCenter, boxExtent) => { - for (let i = p.length; i-- > 0; ) { - const x = p[i]; - const y = boxCenter[i]; - const z = boxExtent[i]; - if (x < y - z || x > y + z) return false; - } - return true; + for (let i = p.length; i-- > 0; ) { + const x = p[i]; + const y = boxCenter[i]; + const z = boxExtent[i]; + if (x < y - z || x > y + z) return false; + } + return true; }); diff --git a/packages/geom-isec/src/ray-circle.ts b/packages/geom-isec/src/ray-circle.ts index f648d03df0..8bbc65515f 100644 --- a/packages/geom-isec/src/ray-circle.ts +++ b/packages/geom-isec/src/ray-circle.ts @@ -7,27 +7,27 @@ import { sub } from "@thi.ng/vectors/sub"; import { NONE } from "./api.js"; export const intersectRayCircle = ( - rpos: ReadonlyVec, - dir: ReadonlyVec, - spos: ReadonlyVec, - r: number + rpos: ReadonlyVec, + dir: ReadonlyVec, + spos: ReadonlyVec, + r: number ): IntersectionResult => { - const delta = sub([], spos, rpos); - const w = dot(delta, dir); - let d = r * r + w * w - magSq(delta); - if (d < 0) return NONE; - d = Math.sqrt(d); - const a = w + d; - const b = w - d; - const isec = - a >= 0 - ? b >= 0 - ? a > b - ? [maddN(delta, dir, b, rpos), maddN([], dir, a, rpos)] - : [maddN(delta, dir, a, rpos), maddN([], dir, b, rpos)] - : [maddN(delta, dir, a, rpos)] - : b >= 0 - ? [maddN(delta, dir, b, rpos)] - : undefined; - return isec ? { type: IntersectionType.INTERSECT, isec } : NONE; + const delta = sub([], spos, rpos); + const w = dot(delta, dir); + let d = r * r + w * w - magSq(delta); + if (d < 0) return NONE; + d = Math.sqrt(d); + const a = w + d; + const b = w - d; + const isec = + a >= 0 + ? b >= 0 + ? a > b + ? [maddN(delta, dir, b, rpos), maddN([], dir, a, rpos)] + : [maddN(delta, dir, a, rpos), maddN([], dir, b, rpos)] + : [maddN(delta, dir, a, rpos)] + : b >= 0 + ? [maddN(delta, dir, b, rpos)] + : undefined; + return isec ? { type: IntersectionType.INTERSECT, isec } : NONE; }; diff --git a/packages/geom-isec/src/ray-line.ts b/packages/geom-isec/src/ray-line.ts index 397458beb7..d2366988b7 100644 --- a/packages/geom-isec/src/ray-line.ts +++ b/packages/geom-isec/src/ray-line.ts @@ -5,28 +5,28 @@ import { maddN } from "@thi.ng/vectors/maddn"; import { NONE } from "./api.js"; export const intersectRayLine = ( - rpos: ReadonlyVec, - dir: ReadonlyVec, - a: ReadonlyVec, - b: ReadonlyVec, - minD = 0, - maxD = Infinity + rpos: ReadonlyVec, + dir: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + minD = 0, + maxD = Infinity ) => { - const bax = b[0] - a[0]; - const bay = b[1] - a[1]; - const d = dir[0] * bay - dir[1] * bax; - if (eqDelta(d, 0)) { - return NONE; - } - const arx = a[0] - rpos[0]; - const ary = a[1] - rpos[1]; - const t = (bay * arx - bax * ary) / d; - const s = (dir[1] * arx - dir[0] * ary) / d; - return t >= minD && t <= maxD && s >= 0 && s <= 1 - ? { - type: IntersectionType.INTERSECT, - isec: maddN([], dir, t, rpos), - alpha: t, - } - : NONE; + const bax = b[0] - a[0]; + const bay = b[1] - a[1]; + const d = dir[0] * bay - dir[1] * bax; + if (eqDelta(d, 0)) { + return NONE; + } + const arx = a[0] - rpos[0]; + const ary = a[1] - rpos[1]; + const t = (bay * arx - bax * ary) / d; + const s = (dir[1] * arx - dir[0] * ary) / d; + return t >= minD && t <= maxD && s >= 0 && s <= 1 + ? { + type: IntersectionType.INTERSECT, + isec: maddN([], dir, t, rpos), + alpha: t, + } + : NONE; }; diff --git a/packages/geom-isec/src/ray-plane.ts b/packages/geom-isec/src/ray-plane.ts index 9cd197d685..3de496a787 100644 --- a/packages/geom-isec/src/ray-plane.ts +++ b/packages/geom-isec/src/ray-plane.ts @@ -10,27 +10,27 @@ import { sub } from "@thi.ng/vectors/sub"; import { NONE } from "./api.js"; export const intersectRayPlane = ( - rpos: ReadonlyVec, - dir: ReadonlyVec, - normal: ReadonlyVec, - w: number, - eps = EPS + rpos: ReadonlyVec, + dir: ReadonlyVec, + normal: ReadonlyVec, + w: number, + eps = EPS ) => { - const d = dot(normal, dir); - const cp = sign(dot(normal, rpos) - w, eps); - if ((d > eps && cp < 0) || (d < -eps && cp > 0)) { - const isec = sub(null, mulN([], normal, w), rpos); - const alpha = dot(normal, isec) / d; - return { - type: IntersectionType.INTERSECT, - isec: maddN(isec, dir, alpha, rpos), - alpha, - }; - } - return cp === 0 - ? { - type: IntersectionType.COINCIDENT, - isec: copy(rpos), - } - : NONE; + const d = dot(normal, dir); + const cp = sign(dot(normal, rpos) - w, eps); + if ((d > eps && cp < 0) || (d < -eps && cp > 0)) { + const isec = sub(null, mulN([], normal, w), rpos); + const alpha = dot(normal, isec) / d; + return { + type: IntersectionType.INTERSECT, + isec: maddN(isec, dir, alpha, rpos), + alpha, + }; + } + return cp === 0 + ? { + type: IntersectionType.COINCIDENT, + isec: copy(rpos), + } + : NONE; }; diff --git a/packages/geom-isec/src/ray-poly.ts b/packages/geom-isec/src/ray-poly.ts index bb86e7f22d..025ee882b5 100644 --- a/packages/geom-isec/src/ray-poly.ts +++ b/packages/geom-isec/src/ray-poly.ts @@ -5,58 +5,58 @@ import { NONE } from "./api.js"; import { intersectRayLine } from "./ray-line.js"; const startPoints = (pts: ReadonlyVec[], closed: boolean) => - closed ? [pts[pts.length - 1], pts[0]] : [pts[0], pts[1]]; + closed ? [pts[pts.length - 1], pts[0]] : [pts[0], pts[1]]; export const intersectRayPolyline = ( - rpos: ReadonlyVec, - dir: ReadonlyVec, - pts: ReadonlyVec[], - closed = false, - minD = 0, - maxD = Infinity + rpos: ReadonlyVec, + dir: ReadonlyVec, + pts: ReadonlyVec[], + closed = false, + minD = 0, + maxD = Infinity ): IntersectionResult => { - const n = pts.length - 1; - let alpha = maxD; - let cross = 0; - let [i, j] = startPoints(pts, closed); - for (let k = 0; k <= n; i = j, j = pts[++k]) { - const d = intersectRayLine(rpos, dir, i, j, minD, maxD).alpha; - if (d !== undefined) { - cross++; - if (d < alpha) alpha = d; - } - } - return cross > 0 - ? { - type: IntersectionType.INTERSECT, - isec: maddN2([], dir, alpha, rpos), - inside: !(cross & 1), - alpha, - } - : NONE; + const n = pts.length - 1; + let alpha = maxD; + let cross = 0; + let [i, j] = startPoints(pts, closed); + for (let k = 0; k <= n; i = j, j = pts[++k]) { + const d = intersectRayLine(rpos, dir, i, j, minD, maxD).alpha; + if (d !== undefined) { + cross++; + if (d < alpha) alpha = d; + } + } + return cross > 0 + ? { + type: IntersectionType.INTERSECT, + isec: maddN2([], dir, alpha, rpos), + inside: !(cross & 1), + alpha, + } + : NONE; }; export const intersectRayPolylineAll = ( - rpos: ReadonlyVec, - dir: ReadonlyVec, - pts: ReadonlyVec[], - closed = false, - minD = 0, - maxD = Infinity + rpos: ReadonlyVec, + dir: ReadonlyVec, + pts: ReadonlyVec[], + closed = false, + minD = 0, + maxD = Infinity ): IntersectionResult => { - const n = pts.length - 1; - let [i, j] = startPoints(pts, closed); - const res: [number, Vec][] = []; - for (let k = 0; k <= n; i = j, j = pts[++k]) { - const d = intersectRayLine(rpos, dir, i, j, minD, maxD).alpha; - if (d !== undefined) { - res.push([d, maddN2([], dir, d, rpos)]); - } - } - return res.length - ? { - type: IntersectionType.INTERSECT, - isec: res.sort((a, b) => a[0] - b[0]).map((x) => x[1]), - } - : NONE; + const n = pts.length - 1; + let [i, j] = startPoints(pts, closed); + const res: [number, Vec][] = []; + for (let k = 0; k <= n; i = j, j = pts[++k]) { + const d = intersectRayLine(rpos, dir, i, j, minD, maxD).alpha; + if (d !== undefined) { + res.push([d, maddN2([], dir, d, rpos)]); + } + } + return res.length + ? { + type: IntersectionType.INTERSECT, + isec: res.sort((a, b) => a[0] - b[0]).map((x) => x[1]), + } + : NONE; }; diff --git a/packages/geom-isec/src/ray-rect.ts b/packages/geom-isec/src/ray-rect.ts index be0d3ed8ac..caf809aeaf 100644 --- a/packages/geom-isec/src/ray-rect.ts +++ b/packages/geom-isec/src/ray-rect.ts @@ -17,17 +17,17 @@ const max = Math.max; * @param bmax - rect max */ const rayRect: FnU4 = (rpos, dir, bmin, bmax) => { - let p = rpos[0]; - let d = 1 / dir[0]; - let t1 = (bmin[0] - p) * d; - let t2 = (bmax[0] - p) * d; - let tmin = min(t1, t2); - let tmax = max(t1, t2); - p = rpos[1]; - d = 1 / dir[1]; - t1 = (bmin[1] - p) * d; - t2 = (bmax[1] - p) * d; - return [max(tmin, min(t1, t2)), min(tmax, max(t1, t2))]; + let p = rpos[0]; + let d = 1 / dir[0]; + let t1 = (bmin[0] - p) * d; + let t2 = (bmax[0] - p) * d; + let tmin = min(t1, t2); + let tmax = max(t1, t2); + p = rpos[1]; + d = 1 / dir[1]; + t1 = (bmin[1] - p) * d; + t2 = (bmax[1] - p) * d; + return [max(tmin, min(t1, t2)), min(tmax, max(t1, t2))]; }; /** @@ -39,72 +39,72 @@ const rayRect: FnU4 = (rpos, dir, bmin, bmax) => { * @param bmax - box max */ const rayBox: FnU4 = (rpos, dir, bmin, bmax) => { - let p = rpos[0]; - let d = 1 / dir[0]; - let t1 = (bmin[0] - p) * d; - let t2 = (bmax[0] - p) * d; - let tmin = min(t1, t2); - let tmax = max(t1, t2); - p = rpos[1]; - d = 1 / dir[1]; - t1 = (bmin[1] - p) * d; - t2 = (bmax[1] - p) * d; - tmin = max(tmin, min(t1, t2)); - tmax = min(tmax, max(t1, t2)); - p = rpos[2]; - d = 1 / dir[2]; - t1 = (bmin[2] - p) * d; - t2 = (bmax[2] - p) * d; - return [max(tmin, min(t1, t2)), min(tmax, max(t1, t2))]; + let p = rpos[0]; + let d = 1 / dir[0]; + let t1 = (bmin[0] - p) * d; + let t2 = (bmax[0] - p) * d; + let tmin = min(t1, t2); + let tmax = max(t1, t2); + p = rpos[1]; + d = 1 / dir[1]; + t1 = (bmin[1] - p) * d; + t2 = (bmax[1] - p) * d; + tmin = max(tmin, min(t1, t2)); + tmax = min(tmax, max(t1, t2)); + p = rpos[2]; + d = 1 / dir[2]; + t1 = (bmin[2] - p) * d; + t2 = (bmax[2] - p) * d; + return [max(tmin, min(t1, t2)), min(tmax, max(t1, t2))]; }; const intersectWith = - (fn: FnU4): FnU4 => - (rpos, dir, bmin, bmax) => { - const t = fn(rpos, dir, bmin, bmax); - const tmin = t[0]; - const tmax = t[1]; - const inside = tmin < 0; - return tmax > max(tmin, 0) - ? inside - ? { - type: IntersectionType.INTERSECT, - isec: [maddN([], dir, tmax, rpos)], - alpha: tmax, - inside, - } - : { - type: IntersectionType.INTERSECT, - isec: [ - maddN([], dir, tmin, rpos), - maddN([], dir, tmax, rpos), - ], - alpha: tmin, - beta: tmax, - } - : NONE; - }; + (fn: FnU4): FnU4 => + (rpos, dir, bmin, bmax) => { + const t = fn(rpos, dir, bmin, bmax); + const tmin = t[0]; + const tmax = t[1]; + const inside = tmin < 0; + return tmax > max(tmin, 0) + ? inside + ? { + type: IntersectionType.INTERSECT, + isec: [maddN([], dir, tmax, rpos)], + alpha: tmax, + inside, + } + : { + type: IntersectionType.INTERSECT, + isec: [ + maddN([], dir, tmin, rpos), + maddN([], dir, tmax, rpos), + ], + alpha: tmin, + beta: tmax, + } + : NONE; + }; export const intersectRayRect = intersectWith(rayRect); export const intersectRayAABB = intersectWith(rayBox); export const testRayRect: FnU4 = ( - rpos, - dir, - bmin, - bmax + rpos, + dir, + bmin, + bmax ) => { - const t = rayRect(rpos, dir, bmin, bmax); - return t[1] > max(t[0], 0); + const t = rayRect(rpos, dir, bmin, bmax); + return t[1] > max(t[0], 0); }; export const testRayAABB: FnU4 = ( - rpos, - dir, - bmin, - bmax + rpos, + dir, + bmin, + bmax ) => { - const t = rayBox(rpos, dir, bmin, bmax); - return t[1] > max(t[0], 0); + const t = rayBox(rpos, dir, bmin, bmax); + return t[1] > max(t[0], 0); }; diff --git a/packages/geom-isec/src/rect-circle.ts b/packages/geom-isec/src/rect-circle.ts index f13cded4bc..040fb7e0f4 100644 --- a/packages/geom-isec/src/rect-circle.ts +++ b/packages/geom-isec/src/rect-circle.ts @@ -3,106 +3,106 @@ import type { MultiVecOpImpl, ReadonlyVec } from "@thi.ng/vectors"; import { vop } from "@thi.ng/vectors/vop"; export const testBoxSphere: MultiVecOpImpl< - Fn4 + Fn4 > = vop(0); /** * Returns true if given 2D rect defined by `boxMinPos` and `boxSize` * intersects circle. * - * @param boxMinPos - - * @param boxSize - - * @param circlePos - - * @param r - + * @param boxMinPos - + * @param boxSize - + * @param circlePos - + * @param r - */ export const testRectCircle = testBoxSphere.add( - 2, - (boxMinPos, boxSize, circlePos, r) => - axis(circlePos[0], boxMinPos[0], boxSize[0]) + - axis(circlePos[1], boxMinPos[1], boxSize[1]) <= - r * r + 2, + (boxMinPos, boxSize, circlePos, r) => + axis(circlePos[0], boxMinPos[0], boxSize[0]) + + axis(circlePos[1], boxMinPos[1], boxSize[1]) <= + r * r ); /** * Same as {@link testRectCircle}, but for 3D AABB and sphere. * - * @param boxMinPos - - * @param boxSize - - * @param spherePos - - * @param r - + * @param boxMinPos - + * @param boxSize - + * @param spherePos - + * @param r - */ export const testAABBSphere = testBoxSphere.add( - 3, - (boxMinPos, boxSize, spherePos, r) => - axis(spherePos[0], boxMinPos[0], boxSize[0]) + - axis(spherePos[1], boxMinPos[1], boxSize[1]) + - axis(spherePos[2], boxMinPos[2], boxSize[2]) <= - r * r + 3, + (boxMinPos, boxSize, spherePos, r) => + axis(spherePos[0], boxMinPos[0], boxSize[0]) + + axis(spherePos[1], boxMinPos[1], boxSize[1]) + + axis(spherePos[2], boxMinPos[2], boxSize[2]) <= + r * r ); testBoxSphere.default((boxPos, boxSize, spherePos, r) => { - let sum = 0; - for (let i = boxPos.length; i-- > 0; ) { - sum += axis(spherePos[i], boxPos[i], boxSize[i]); - } - return sum <= r * r; + let sum = 0; + for (let i = boxPos.length; i-- > 0; ) { + sum += axis(spherePos[i], boxPos[i], boxSize[i]); + } + return sum <= r * r; }); /** * Like {@link testCenteredAABBSphere}, but for arbitrary dimensions w/ * optimized execution for 2D & 3D cases. * - * @param boxCenter - - * @param boxExtent - - * @param spherePos - - * @param r - + * @param boxCenter - + * @param boxExtent - + * @param spherePos - + * @param r - */ export const testCenteredBoxSphere: MultiVecOpImpl< - Fn4 + Fn4 > = vop(0); /** * Similar to {@link testRectCircle}, but for rects defined by centroid * and radius-like extent. * - * @param rectPos - - * @param extent - - * @param circlePos - - * @param r - + * @param rectPos - + * @param extent - + * @param circlePos - + * @param r - */ export const testCenteredRectCircle = testCenteredBoxSphere.add( - 2, - (boxPos, { 0: w, 1: h }, circlePos, r) => - axis(circlePos[0], boxPos[0] - w, w * 2) + - axis(circlePos[1], boxPos[1] - h, h * 2) <= - r * r + 2, + (boxPos, { 0: w, 1: h }, circlePos, r) => + axis(circlePos[0], boxPos[0] - w, w * 2) + + axis(circlePos[1], boxPos[1] - h, h * 2) <= + r * r ); /** * Similar to {@link testAABBSphere}, but for AABBs defined by centroid * and radius-like extent. * - * @param boxCenter - - * @param boxExtent - - * @param spherePos - - * @param r - + * @param boxCenter - + * @param boxExtent - + * @param spherePos - + * @param r - */ export const testCenteredAABBSphere = testCenteredBoxSphere.add( - 3, - (boxPos, { 0: w, 1: h, 2: d }, spherePos, r) => - axis(spherePos[0], boxPos[0] - w, w * 2) + - axis(spherePos[1], boxPos[1] - h, h * 2) + - axis(spherePos[2], boxPos[2] - d, d * 2) <= - r * r + 3, + (boxPos, { 0: w, 1: h, 2: d }, spherePos, r) => + axis(spherePos[0], boxPos[0] - w, w * 2) + + axis(spherePos[1], boxPos[1] - h, h * 2) + + axis(spherePos[2], boxPos[2] - d, d * 2) <= + r * r ); testCenteredBoxSphere.default((boxPos, boxExtent, spherePos, r) => { - let sum = 0; - for (let i = boxPos.length; i-- > 0; ) { - sum += axis(spherePos[i], boxPos[i] - boxExtent[i], boxExtent[i] * 2); - } - return sum <= r * r; + let sum = 0; + for (let i = boxPos.length; i-- > 0; ) { + sum += axis(spherePos[i], boxPos[i] - boxExtent[i], boxExtent[i] * 2); + } + return sum <= r * r; }); const axis: FnN3 = (a, b, c) => - (a < b ? a - b : a > b + c ? a - b - c : 0) ** 2; + (a < b ? a - b : a > b + c ? a - b - c : 0) ** 2; diff --git a/packages/geom-isec/src/rect-rect.ts b/packages/geom-isec/src/rect-rect.ts index 8de528c1e7..3f5b575045 100644 --- a/packages/geom-isec/src/rect-rect.ts +++ b/packages/geom-isec/src/rect-rect.ts @@ -2,23 +2,23 @@ import type { FnU4 } from "@thi.ng/api"; import type { ReadonlyVec } from "@thi.ng/vectors"; export const testRectRect: FnU4 = ( - [ax, ay], - [aw, ah], - [bx, by], - [bw, bh] + [ax, ay], + [aw, ah], + [bx, by], + [bw, bh] ) => !(ax > bx + bw || bx > ax + aw || ay > by + bh || by > ay + ah); export const testAabbAabb: FnU4 = ( - [ax, ay, az], - [aw, ah, ad], - [bx, by, bz], - [bw, bh, bd] + [ax, ay, az], + [aw, ah, ad], + [bx, by, bz], + [bw, bh, bd] ) => - !( - ax > bx + bw || - bx > ax + aw || - ay > by + bh || - by > ay + ah || - az > bz + bd || - bz > az + ad - ); + !( + ax > bx + bw || + bx > ax + aw || + ay > by + bh || + by > ay + ah || + az > bz + bd || + bz > az + ad + ); diff --git a/packages/geom-isec/test/point-segment.ts b/packages/geom-isec/test/point-segment.ts index 8bb368420f..691b4f0719 100644 --- a/packages/geom-isec/test/point-segment.ts +++ b/packages/geom-isec/test/point-segment.ts @@ -1,37 +1,37 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { pointInSegment } from "../src/index.js" +import { pointInSegment } from "../src/index.js"; group("pointInSegment", { - "2d": () => { - assert.ok(pointInSegment([0, 0], [-10, -10], [10, 10]), "1"); - assert.ok(pointInSegment([-5, -5], [-10, -10], [10, 10]), "2"); - assert.ok(pointInSegment([5, 5], [-10, -10], [10, 10]), "3"); - assert.ok(!pointInSegment([5, 5.01], [-10, -10], [10, 10]), "4"); - assert.ok(pointInSegment([5, 5.01], [-10, -10], [10, 10], 0.01), "4.1"); - assert.ok(pointInSegment([5, 4.99], [-10, -10], [10, 10], 0.01), "4.2"); - assert.ok( - !pointInSegment([5, 5.02], [-10, -10], [10, 10], 0.01), - "4.3" - ); - assert.ok( - !pointInSegment([5, 4.98], [-10, -10], [10, 10], 0.01), - "4.4" - ); - }, + "2d": () => { + assert.ok(pointInSegment([0, 0], [-10, -10], [10, 10]), "1"); + assert.ok(pointInSegment([-5, -5], [-10, -10], [10, 10]), "2"); + assert.ok(pointInSegment([5, 5], [-10, -10], [10, 10]), "3"); + assert.ok(!pointInSegment([5, 5.01], [-10, -10], [10, 10]), "4"); + assert.ok(pointInSegment([5, 5.01], [-10, -10], [10, 10], 0.01), "4.1"); + assert.ok(pointInSegment([5, 4.99], [-10, -10], [10, 10], 0.01), "4.2"); + assert.ok( + !pointInSegment([5, 5.02], [-10, -10], [10, 10], 0.01), + "4.3" + ); + assert.ok( + !pointInSegment([5, 4.98], [-10, -10], [10, 10], 0.01), + "4.4" + ); + }, - "2d axis aligned": () => { - assert.ok(pointInSegment([9, 10], [5, 10], [10, 10]), "1"); - assert.ok(pointInSegment([9, 10], [10, 10], [5, 10]), "1.1"); - assert.ok(pointInSegment([10, 9], [10, 5], [10, 10]), "2"); - assert.ok(pointInSegment([10, 9], [10, 10], [10, 5]), "2.1"); - assert.ok(pointInSegment([4.9, 10], [5, 10], [10, 10], 0.1), "3"); - assert.ok(!pointInSegment([4.89, 10], [5, 10], [10, 10], 0.1), "3.1"); - assert.ok(pointInSegment([10.1, 10], [5, 10], [10, 10], 0.1), "3.2"); - assert.ok(!pointInSegment([10.11, 10], [5, 10], [10, 10], 0.1), "3.3"); - assert.ok(pointInSegment([9, 10.1], [5, 10], [10, 10], 0.1), "4"); - assert.ok(!pointInSegment([9, 10.11], [5, 10], [10, 10], 0.1), "4.1"); - assert.ok(pointInSegment([9, 9.9], [5, 10], [10, 10], 0.1), "4.2"); - assert.ok(!pointInSegment([9, 9.89], [5, 10], [10, 10], 0.1), "4.3"); - }, + "2d axis aligned": () => { + assert.ok(pointInSegment([9, 10], [5, 10], [10, 10]), "1"); + assert.ok(pointInSegment([9, 10], [10, 10], [5, 10]), "1.1"); + assert.ok(pointInSegment([10, 9], [10, 5], [10, 10]), "2"); + assert.ok(pointInSegment([10, 9], [10, 10], [10, 5]), "2.1"); + assert.ok(pointInSegment([4.9, 10], [5, 10], [10, 10], 0.1), "3"); + assert.ok(!pointInSegment([4.89, 10], [5, 10], [10, 10], 0.1), "3.1"); + assert.ok(pointInSegment([10.1, 10], [5, 10], [10, 10], 0.1), "3.2"); + assert.ok(!pointInSegment([10.11, 10], [5, 10], [10, 10], 0.1), "3.3"); + assert.ok(pointInSegment([9, 10.1], [5, 10], [10, 10], 0.1), "4"); + assert.ok(!pointInSegment([9, 10.11], [5, 10], [10, 10], 0.1), "4.1"); + assert.ok(pointInSegment([9, 9.9], [5, 10], [10, 10], 0.1), "4.2"); + assert.ok(!pointInSegment([9, 9.89], [5, 10], [10, 10], 0.1), "4.3"); + }, }); diff --git a/packages/geom-isec/test/polyline.ts b/packages/geom-isec/test/polyline.ts index c954028b7c..9a7a3ef68b 100644 --- a/packages/geom-isec/test/polyline.ts +++ b/packages/geom-isec/test/polyline.ts @@ -1,142 +1,145 @@ import { IntersectionType } from "@thi.ng/geom-api"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { intersectLinePolylineAll, intersectRayPolylineAll } from "../src/index.js" +import { + intersectLinePolylineAll, + intersectRayPolylineAll, +} from "../src/index.js"; const pts = [ - [0, 0], - [100, 0], - [100, 50], - [0, 100], + [0, 0], + [100, 0], + [100, 50], + [0, 100], ]; group("polyline", { - "ray (x)": () => { - assert.deepStrictEqual( - intersectRayPolylineAll([-50, 25], [1, 0], pts, false), - { - type: IntersectionType.INTERSECT, - isec: [[100, 25]], - } - ); - assert.deepStrictEqual( - intersectRayPolylineAll([-50, 25], [1, 0], pts, true), - { - type: IntersectionType.INTERSECT, - isec: [ - [0, 25], - [100, 25], - ], - } - ); - }, + "ray (x)": () => { + assert.deepStrictEqual( + intersectRayPolylineAll([-50, 25], [1, 0], pts, false), + { + type: IntersectionType.INTERSECT, + isec: [[100, 25]], + } + ); + assert.deepStrictEqual( + intersectRayPolylineAll([-50, 25], [1, 0], pts, true), + { + type: IntersectionType.INTERSECT, + isec: [ + [0, 25], + [100, 25], + ], + } + ); + }, - "ray (y)": () => { - assert.deepStrictEqual( - intersectRayPolylineAll([50, -50], [0, 1], pts, false), - { - type: IntersectionType.INTERSECT, - isec: [ - [50, 0], - [50, 75], - ], - } - ); - assert.deepStrictEqual( - intersectRayPolylineAll([50, -50], [0, 1], pts, true), - { - type: IntersectionType.INTERSECT, - isec: [ - [50, 0], - [50, 75], - ], - } - ); - }, + "ray (y)": () => { + assert.deepStrictEqual( + intersectRayPolylineAll([50, -50], [0, 1], pts, false), + { + type: IntersectionType.INTERSECT, + isec: [ + [50, 0], + [50, 75], + ], + } + ); + assert.deepStrictEqual( + intersectRayPolylineAll([50, -50], [0, 1], pts, true), + { + type: IntersectionType.INTERSECT, + isec: [ + [50, 0], + [50, 75], + ], + } + ); + }, - "line (x)": () => { - assert.deepStrictEqual( - intersectLinePolylineAll([-50, 25], [50, 25], pts, false), - { - type: IntersectionType.NONE, - } - ); - assert.deepStrictEqual( - intersectLinePolylineAll([-50, 25], [110, 25], pts, false), - { - type: IntersectionType.INTERSECT, - isec: [[100, 25]], - } - ); - assert.deepStrictEqual( - intersectLinePolylineAll([-50, 25], [50, 25], pts, true), - { - type: IntersectionType.INTERSECT, - isec: [[0, 25]], - } - ); - assert.deepStrictEqual( - intersectLinePolylineAll([-50, 25], [110, 25], pts, true), - { - type: IntersectionType.INTERSECT, - isec: [ - [0, 25], - [100, 25], - ], - } - ); - }, + "line (x)": () => { + assert.deepStrictEqual( + intersectLinePolylineAll([-50, 25], [50, 25], pts, false), + { + type: IntersectionType.NONE, + } + ); + assert.deepStrictEqual( + intersectLinePolylineAll([-50, 25], [110, 25], pts, false), + { + type: IntersectionType.INTERSECT, + isec: [[100, 25]], + } + ); + assert.deepStrictEqual( + intersectLinePolylineAll([-50, 25], [50, 25], pts, true), + { + type: IntersectionType.INTERSECT, + isec: [[0, 25]], + } + ); + assert.deepStrictEqual( + intersectLinePolylineAll([-50, 25], [110, 25], pts, true), + { + type: IntersectionType.INTERSECT, + isec: [ + [0, 25], + [100, 25], + ], + } + ); + }, - "line (y)": () => { - assert.deepStrictEqual( - intersectLinePolylineAll([50, -25], [50, -20], pts, false), - { - type: IntersectionType.NONE, - } - ); - assert.deepStrictEqual( - intersectLinePolylineAll([50, -25], [50, 50], pts, false), - { - type: IntersectionType.INTERSECT, - isec: [[50, 0]], - } - ); - assert.deepStrictEqual( - intersectLinePolylineAll([50, -25], [50, 100], pts, false), - { - type: IntersectionType.INTERSECT, - isec: [ - [50, 0], - [50, 75], - ], - } - ); - }, + "line (y)": () => { + assert.deepStrictEqual( + intersectLinePolylineAll([50, -25], [50, -20], pts, false), + { + type: IntersectionType.NONE, + } + ); + assert.deepStrictEqual( + intersectLinePolylineAll([50, -25], [50, 50], pts, false), + { + type: IntersectionType.INTERSECT, + isec: [[50, 0]], + } + ); + assert.deepStrictEqual( + intersectLinePolylineAll([50, -25], [50, 100], pts, false), + { + type: IntersectionType.INTERSECT, + isec: [ + [50, 0], + [50, 75], + ], + } + ); + }, - "ray minD/maxD": () => { - const I = Infinity; - assert.deepStrictEqual( - intersectRayPolylineAll([50, 25], [1, 0], pts, true, -I, I), - { - type: IntersectionType.INTERSECT, - isec: [ - [0, 25], - [100, 25], - ], - } - ); - assert.deepStrictEqual( - intersectRayPolylineAll([-50, 25], [1, 0], pts, true, 60), - { - type: IntersectionType.INTERSECT, - isec: [[100, 25]], - } - ); - assert.deepStrictEqual( - intersectRayPolylineAll([50, 25], [1, 0], pts, true, 0, 10), - { - type: IntersectionType.NONE, - } - ); - }, + "ray minD/maxD": () => { + const I = Infinity; + assert.deepStrictEqual( + intersectRayPolylineAll([50, 25], [1, 0], pts, true, -I, I), + { + type: IntersectionType.INTERSECT, + isec: [ + [0, 25], + [100, 25], + ], + } + ); + assert.deepStrictEqual( + intersectRayPolylineAll([-50, 25], [1, 0], pts, true, 60), + { + type: IntersectionType.INTERSECT, + isec: [[100, 25]], + } + ); + assert.deepStrictEqual( + intersectRayPolylineAll([50, 25], [1, 0], pts, true, 0, 10), + { + type: IntersectionType.NONE, + } + ); + }, }); diff --git a/packages/geom-isec/test/ray.ts b/packages/geom-isec/test/ray.ts index c824e82346..086e705315 100644 --- a/packages/geom-isec/test/ray.ts +++ b/packages/geom-isec/test/ray.ts @@ -1,72 +1,72 @@ import { group } from "@thi.ng/testament"; import { eqDelta, maddN3, mulN3, normalize, Vec } from "@thi.ng/vectors"; import * as assert from "assert"; -import { intersectRayAABB } from "../src/index.js" +import { intersectRayAABB } from "../src/index.js"; group("ray intersection", { - "rayBox inside": () => { - const dirs: Vec[] = [ - [-1, -1, -1], - [-1, -1, 0], - [-1, -1, 1], - [-1, 0, -1], - [-1, 0, 0], - [-1, 0, 1], - [-1, 1, -1], - [-1, 1, 0], - [-1, 1, 1], - [0, -1, -1], - [0, -1, 0], - [0, -1, 1], - [0, 0, -1], - [0, 0, 1], - [0, 1, -1], - [0, 1, 0], - [0, 1, 1], - [1, -1, -1], - [1, -1, 0], - [1, -1, 1], - [1, 0, -1], - [1, 0, 0], - [1, 0, 1], - [1, 1, -1], - [1, 1, 0], - [1, 1, 1], - ]; - for (let d of dirs) { - const n = normalize([], d); - const i = intersectRayAABB([5, 5, 5], n, [0, 0, 0], [10, 10, 10]); - const expected = maddN3([], n, i.alpha!, [5, 5, 5]); - assert.ok(i.inside, `inside d=${d}`); - assert.ok( - eqDelta(expected, i.isec![0]), - `d=${d} isec=${i.isec}, exp=${expected}` - ); - } - }, + "rayBox inside": () => { + const dirs: Vec[] = [ + [-1, -1, -1], + [-1, -1, 0], + [-1, -1, 1], + [-1, 0, -1], + [-1, 0, 0], + [-1, 0, 1], + [-1, 1, -1], + [-1, 1, 0], + [-1, 1, 1], + [0, -1, -1], + [0, -1, 0], + [0, -1, 1], + [0, 0, -1], + [0, 0, 1], + [0, 1, -1], + [0, 1, 0], + [0, 1, 1], + [1, -1, -1], + [1, -1, 0], + [1, -1, 1], + [1, 0, -1], + [1, 0, 0], + [1, 0, 1], + [1, 1, -1], + [1, 1, 0], + [1, 1, 1], + ]; + for (let d of dirs) { + const n = normalize([], d); + const i = intersectRayAABB([5, 5, 5], n, [0, 0, 0], [10, 10, 10]); + const expected = maddN3([], n, i.alpha!, [5, 5, 5]); + assert.ok(i.inside, `inside d=${d}`); + assert.ok( + eqDelta(expected, i.isec![0]), + `d=${d} isec=${i.isec}, exp=${expected}` + ); + } + }, - "rayBox outside": () => { - const dirs: Vec[] = [ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1], - ]; - for (let d of dirs) { - let o = mulN3([], d, -10); - let i = intersectRayAABB(o, d, [-5, -5, -5], [5, 5, 5]); - let expected = maddN3([], d, i.alpha!, o); - assert.ok( - eqDelta(expected, i.isec![0]), - `d=${d} isec=${i.isec}, exp=${expected}` - ); - d = mulN3(d, d, -1); - o = mulN3([], d, -10); - i = intersectRayAABB(o, d, [-5, -5, -5], [5, 5, 5]); - expected = maddN3([], d, i.alpha!, o); - assert.ok( - eqDelta(expected, i.isec![0]), - `d=${d} isec=${i.isec}, exp=${expected}` - ); - } - }, + "rayBox outside": () => { + const dirs: Vec[] = [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + ]; + for (let d of dirs) { + let o = mulN3([], d, -10); + let i = intersectRayAABB(o, d, [-5, -5, -5], [5, 5, 5]); + let expected = maddN3([], d, i.alpha!, o); + assert.ok( + eqDelta(expected, i.isec![0]), + `d=${d} isec=${i.isec}, exp=${expected}` + ); + d = mulN3(d, d, -1); + o = mulN3([], d, -10); + i = intersectRayAABB(o, d, [-5, -5, -5], [5, 5, 5]); + expected = maddN3([], d, i.alpha!, o); + assert.ok( + eqDelta(expected, i.isec![0]), + `d=${d} isec=${i.isec}, exp=${expected}` + ); + } + }, }); diff --git a/packages/geom-isec/tsconfig.json b/packages/geom-isec/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-isec/tsconfig.json +++ b/packages/geom-isec/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-isoline/api-extractor.json b/packages/geom-isoline/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-isoline/api-extractor.json +++ b/packages/geom-isoline/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-isoline/package.json b/packages/geom-isoline/package.json index 5b7966a2ef..50855dc17b 100644 --- a/packages/geom-isoline/package.json +++ b/packages/geom-isoline/package.json @@ -1,80 +1,80 @@ { - "name": "@thi.ng/geom-isoline", - "version": "2.1.16", - "description": "Fast 2D contour line extraction / generation", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-isoline#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "binary", - "clipping", - "contours", - "isoline", - "iterator", - "marchingsquares", - "polygon", - "threshold", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "year": 2015 - } + "name": "@thi.ng/geom-isoline", + "version": "2.1.16", + "description": "Fast 2D contour line extraction / generation", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-isoline#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "binary", + "clipping", + "contours", + "isoline", + "iterator", + "marchingsquares", + "polygon", + "threshold", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "year": 2015 + } } diff --git a/packages/geom-isoline/src/index.ts b/packages/geom-isoline/src/index.ts index fea7b731ee..61a1d5c267 100644 --- a/packages/geom-isoline/src/index.ts +++ b/packages/geom-isoline/src/index.ts @@ -18,57 +18,57 @@ const S5 = [4, 8, 0, 2, 0, 26, 4, 14]; const S10 = [6, 4, 2, 16, 6, 22, 2, 28]; export const setBorder = (src: Vec, w: number, h: number, val: number) => { - const w1 = w - 1; - const h1 = h - 1; - const idxH1 = h1 * w; - for (let x = 0; x < w; x++) { - src[x] = src[idxH1 + x] = val; - } - for (let y = 0; y < h; y++) { - const yy = y * w; - src[yy] = src[w1 + yy] = val; - } - return src; + const w1 = w - 1; + const h1 = h - 1; + const idxH1 = h1 * w; + for (let x = 0; x < w; x++) { + src[x] = src[idxH1 + x] = val; + } + for (let y = 0; y < h; y++) { + const yy = y * w; + src[yy] = src[w1 + yy] = val; + } + return src; }; const encodeCrossings = ( - src: ReadonlyVec, - w: number, - h: number, - iso: number + src: ReadonlyVec, + w: number, + h: number, + iso: number ) => { - const out = new Uint8Array(src.length); - const w1 = w - 1; - const h1 = h - 1; - for (let y = 0, i = 0; y < h1; y++) { - for (let x = 0; x < w1; i++, x++) { - out[i] = - (src[i] < iso ? 16 : 0) | - (src[i + 1] < iso ? 8 : 0) | - (src[i + 1 + w] < iso ? 4 : 0) | - (src[i + w] < iso ? 2 : 0); - } - i++; - } - return out; + const out = new Uint8Array(src.length); + const w1 = w - 1; + const h1 = h - 1; + for (let y = 0, i = 0; y < h1; y++) { + for (let x = 0; x < w1; i++, x++) { + out[i] = + (src[i] < iso ? 16 : 0) | + (src[i + 1] < iso ? 8 : 0) | + (src[i + 1 + w] < iso ? 4 : 0) | + (src[i + w] < iso ? 2 : 0); + } + i++; + } + return out; }; const cellValue = (src: ReadonlyVec, w: number, idx: number) => { - return (src[idx] + src[idx + 1] + src[idx + w] + src[idx + w + 1]) * 0.25; + return (src[idx] + src[idx + 1] + src[idx + w] + src[idx + w + 1]) * 0.25; }; const mix = ( - src: ReadonlyVec, - w: number, - x1: number, - y1: number, - x2: number, - y2: number, - iso: number + src: ReadonlyVec, + w: number, + x1: number, + y1: number, + x2: number, + y2: number, + iso: number ) => { - const a = src[y1 * w + x1]; - const b = src[y2 * w + x2]; - return a === b ? 0 : (a - iso) / (a - b); + const a = src[y1 * w + x1]; + const b = src[y2 * w + x2]; + return a === b ? 0 : (a - iso) / (a - b); }; // prettier-ignore @@ -87,84 +87,84 @@ const contourVertex: Fn5[] = [ * factor or vector. The default scale of 1.0 will result in coords in these * ranges: [0.5 .. w-0.5] (for X) and [0.5 .. h-0.5] (for Y). * - * @param src - - * @param w - - * @param h - - * @param iso - - * @param scale - + * @param src - + * @param w - + * @param h - + * @param iso - + * @param scale - */ export function* isolines( - src: ReadonlyVec, - w: number, - h: number, - iso: number, - scale: ReadonlyVec | number = 1 + src: ReadonlyVec, + w: number, + h: number, + iso: number, + scale: ReadonlyVec | number = 1 ) { - const coded = encodeCrossings(src, w, h, iso); - let curr: Vec[] = []; - let from: number; - let to = -1; - let clear: number; - let x!: number; - let y!: number; - const w1 = w - 1; - const h1 = h - 1; - const [sx, sy] = typeof scale === "number" ? [scale, scale] : scale; - const cells = range2d(h, w); - let next: boolean = true; - let idx: number; - while (true) { - from = to; - if (next) { - const c = cells.next(); - if (c.done) break; - [y, x] = c.value; - next = false; - } - if (x >= w1 || y >= h1) { - next = true; - continue; - } - const i = y * w + x; - const id = coded[i]; // * 2 - if (id === 10) { - idx = (cellValue(src, w, i) > iso ? 0 : 4) + (from === 6 ? 0 : 2); - to = S5[idx]; - clear = S5[idx + 1]; - } else if (id === 20) { - idx = - cellValue(src, w, i) > iso - ? from === 0 - ? 0 - : 2 - : from === 4 - ? 4 - : 6; - to = S10[idx]; - clear = S10[idx + 1]; - } else { - to = EDGE_INDEX[id]; - clear = EDGE_INDEX[id + 1]; - } - if (from === -1 && to > -1 && curr.length > 0) { - yield curr; - curr = []; - } - if (clear !== -1) { - coded[i] = clear; - } - if (to >= 0) { - const p = contourVertex[to >> 1](src, w, x, y, iso); - p[0] = (p[0] + 0.5) * sx; - p[1] = (p[1] + 0.5) * sy; - curr.push(p); - x += NEXT_EDGES[to]; - y += NEXT_EDGES[to + 1]; - } else { - next = true; - } - } - if (curr.length > 0) { - yield curr; - } + const coded = encodeCrossings(src, w, h, iso); + let curr: Vec[] = []; + let from: number; + let to = -1; + let clear: number; + let x!: number; + let y!: number; + const w1 = w - 1; + const h1 = h - 1; + const [sx, sy] = typeof scale === "number" ? [scale, scale] : scale; + const cells = range2d(h, w); + let next: boolean = true; + let idx: number; + while (true) { + from = to; + if (next) { + const c = cells.next(); + if (c.done) break; + [y, x] = c.value; + next = false; + } + if (x >= w1 || y >= h1) { + next = true; + continue; + } + const i = y * w + x; + const id = coded[i]; // * 2 + if (id === 10) { + idx = (cellValue(src, w, i) > iso ? 0 : 4) + (from === 6 ? 0 : 2); + to = S5[idx]; + clear = S5[idx + 1]; + } else if (id === 20) { + idx = + cellValue(src, w, i) > iso + ? from === 0 + ? 0 + : 2 + : from === 4 + ? 4 + : 6; + to = S10[idx]; + clear = S10[idx + 1]; + } else { + to = EDGE_INDEX[id]; + clear = EDGE_INDEX[id + 1]; + } + if (from === -1 && to > -1 && curr.length > 0) { + yield curr; + curr = []; + } + if (clear !== -1) { + coded[i] = clear; + } + if (to >= 0) { + const p = contourVertex[to >> 1](src, w, x, y, iso); + p[0] = (p[0] + 0.5) * sx; + p[1] = (p[1] + 0.5) * sy; + curr.push(p); + x += NEXT_EDGES[to]; + y += NEXT_EDGES[to + 1]; + } else { + next = true; + } + } + if (curr.length > 0) { + yield curr; + } } diff --git a/packages/geom-isoline/tsconfig.json b/packages/geom-isoline/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-isoline/tsconfig.json +++ b/packages/geom-isoline/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-poly-utils/api-extractor.json b/packages/geom-poly-utils/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-poly-utils/api-extractor.json +++ b/packages/geom-poly-utils/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-poly-utils/package.json b/packages/geom-poly-utils/package.json index aae07e1adc..96321636f1 100644 --- a/packages/geom-poly-utils/package.json +++ b/packages/geom-poly-utils/package.json @@ -1,113 +1,113 @@ { - "name": "@thi.ng/geom-poly-utils", - "version": "2.3.3", - "description": "2D polygon/polyline analysis & processing utilities", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-poly-utils#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/geom-api": "^3.3.2", - "@thi.ng/math": "^5.3.4", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "area", - "bbox", - "centroid", - "circumcenter", - "geometry", - "perimeter", - "polygon", - "polyline", - "triangle", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./area": { - "default": "./area.js" - }, - "./barycentric": { - "default": "./barycentric.js" - }, - "./bounds": { - "default": "./bounds.js" - }, - "./center-of-weight": { - "default": "./center-of-weight.js" - }, - "./centroid": { - "default": "./centroid.js" - }, - "./circumcenter": { - "default": "./circumcenter.js" - }, - "./convexity": { - "default": "./convexity.js" - }, - "./equilateral": { - "default": "./equilateral.js" - }, - "./perimeter": { - "default": "./perimeter.js" - }, - "./tangent": { - "default": "./tangent.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "year": 2013 - } + "name": "@thi.ng/geom-poly-utils", + "version": "2.3.3", + "description": "2D polygon/polyline analysis & processing utilities", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-poly-utils#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/geom-api": "^3.3.2", + "@thi.ng/math": "^5.3.4", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "area", + "bbox", + "centroid", + "circumcenter", + "geometry", + "perimeter", + "polygon", + "polyline", + "triangle", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./area": { + "default": "./area.js" + }, + "./barycentric": { + "default": "./barycentric.js" + }, + "./bounds": { + "default": "./bounds.js" + }, + "./center-of-weight": { + "default": "./center-of-weight.js" + }, + "./centroid": { + "default": "./centroid.js" + }, + "./circumcenter": { + "default": "./circumcenter.js" + }, + "./convexity": { + "default": "./convexity.js" + }, + "./equilateral": { + "default": "./equilateral.js" + }, + "./perimeter": { + "default": "./perimeter.js" + }, + "./tangent": { + "default": "./tangent.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "year": 2013 + } } diff --git a/packages/geom-poly-utils/src/area.ts b/packages/geom-poly-utils/src/area.ts index b0b1b3752d..e00e61cde4 100644 --- a/packages/geom-poly-utils/src/area.ts +++ b/packages/geom-poly-utils/src/area.ts @@ -9,15 +9,15 @@ import { signedArea2 } from "@thi.ng/vectors/signed-area"; * @param pts - points */ export const polyArea2 = (pts: ReadonlyVec[]) => { - const n = pts.length - 1; - if (n < 2) return 0; - let res = 0; - let a = pts[n]; - let b = pts[0]; - for (let i = 0; i <= n; a = b, b = pts[++i]) { - res += cross2(a, b); - } - return res / 2; + const n = pts.length - 1; + if (n < 2) return 0; + let res = 0; + let a = pts[n]; + let b = pts[0]; + for (let i = 0; i <= n; a = b, b = pts[++i]) { + res += cross2(a, b); + } + return res / 2; }; export const triArea2 = signedArea2; diff --git a/packages/geom-poly-utils/src/barycentric.ts b/packages/geom-poly-utils/src/barycentric.ts index 77d60e0a36..0f5372dcaf 100644 --- a/packages/geom-poly-utils/src/barycentric.ts +++ b/packages/geom-poly-utils/src/barycentric.ts @@ -6,30 +6,30 @@ import { setC3 } from "@thi.ng/vectors/setc"; import { sub } from "@thi.ng/vectors/sub"; export const toBarycentric = ( - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - p: ReadonlyVec, - out: Vec = [] + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + p: ReadonlyVec, + out: Vec = [] ) => { - const u = sub([], b, a); - const v = sub([], c, a); - const w = sub([], p, a); - const uu = magSq(u); - const vv = magSq(v); - const uv = dot(u, v); - const uw = dot(u, w); - const vw = dot(v, w); - const d = 1 / (uv * uv - uu * vv); - const s = d * (uv * vw - vv * uw); - const t = d * (uv * uw - uu * vw); - return setC3(out, 1 - (s + t), s, t); + const u = sub([], b, a); + const v = sub([], c, a); + const w = sub([], p, a); + const uu = magSq(u); + const vv = magSq(v); + const uv = dot(u, v); + const uw = dot(u, w); + const vw = dot(v, w); + const d = 1 / (uv * uv - uu * vv); + const s = d * (uv * vw - vv * uw); + const t = d * (uv * uw - uu * vw); + return setC3(out, 1 - (s + t), s, t); }; export const fromBarycentric = ( - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - p: ReadonlyVec, - out: Vec = [] + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + p: ReadonlyVec, + out: Vec = [] ) => addW3(out, a, b, c, p[0], p[1], p[2]); diff --git a/packages/geom-poly-utils/src/bounds.ts b/packages/geom-poly-utils/src/bounds.ts index f36b82b75e..034b32d441 100644 --- a/packages/geom-poly-utils/src/bounds.ts +++ b/packages/geom-poly-utils/src/bounds.ts @@ -27,27 +27,27 @@ import { mulN2, mulN3 } from "@thi.ng/vectors/muln"; * @param vmax - max result (pre-initialized to `-∞`) */ export const bounds = ( - pts: ReadonlyArray, - vmin: Vec, - vmax: Vec + pts: ReadonlyArray, + vmin: Vec, + vmax: Vec ): VecPair => { - for (let i = pts.length; i-- > 0; ) { - const p = pts[i]; - min(null, vmin, p); - max(null, vmax, p); - } - return [vmin, vmax]; + for (let i = pts.length; i-- > 0; ) { + const p = pts[i]; + min(null, vmin, p); + max(null, vmax, p); + } + return [vmin, vmax]; }; export const bounds2 = (pts: ReadonlyArray) => - bounds(pts, [Infinity, Infinity], [-Infinity, -Infinity]); + bounds(pts, [Infinity, Infinity], [-Infinity, -Infinity]); export const bounds3 = (pts: ReadonlyArray) => - bounds( - pts, - [Infinity, Infinity, Infinity], - [-Infinity, -Infinity, -Infinity] - ); + bounds( + pts, + [Infinity, Infinity, Infinity], + [-Infinity, -Infinity, -Infinity] + ); /** * Calculates a near-optimal bounding circle for a set of points in 2D. Returns @@ -63,36 +63,36 @@ export const bounds3 = (pts: ReadonlyArray) => * @param pts */ export const boundingCircle = (pts: ReadonlyVec[]): [Vec, number] => { - let xmin = MAX2; - let xmax = MIN2; - let ymin = MAX2; - let ymax = MIN2; - for (let i = pts.length; i-- > 0; ) { - const p = pts[i]; - if (p[0] < xmin[0]) xmin = p; - else if (p[0] > xmax[0]) xmax = p; - if (p[1] < ymin[1]) ymin = p; - else if (p[1] > ymax[1]) ymax = p; - } - const xspan = distSq2(xmin, xmax); - const yspan = distSq2(ymin, ymax); - const span = <[ReadonlyVec, ReadonlyVec]>( - (xspan > yspan ? [xmin, xmax] : [ymin, ymax]) - ); - let centroid = addmN2([], ...span, 0.5); - let rsq = distSq2(centroid, span[0]); - let r = Math.sqrt(rsq); - for (let i = pts.length; i-- > 0; ) { - const p = pts[i]; - const dsq = distSq2(centroid, p); - if (dsq > rsq) { - const d = Math.sqrt(dsq); - r = (r + d) / 2; - rsq = r * r; - mulN2(null, addW2(null, centroid, p, r, d - r), 1 / d); - } - } - return [centroid, r]; + let xmin = MAX2; + let xmax = MIN2; + let ymin = MAX2; + let ymax = MIN2; + for (let i = pts.length; i-- > 0; ) { + const p = pts[i]; + if (p[0] < xmin[0]) xmin = p; + else if (p[0] > xmax[0]) xmax = p; + if (p[1] < ymin[1]) ymin = p; + else if (p[1] > ymax[1]) ymax = p; + } + const xspan = distSq2(xmin, xmax); + const yspan = distSq2(ymin, ymax); + const span = <[ReadonlyVec, ReadonlyVec]>( + (xspan > yspan ? [xmin, xmax] : [ymin, ymax]) + ); + let centroid = addmN2([], ...span, 0.5); + let rsq = distSq2(centroid, span[0]); + let r = Math.sqrt(rsq); + for (let i = pts.length; i-- > 0; ) { + const p = pts[i]; + const dsq = distSq2(centroid, p); + if (dsq > rsq) { + const d = Math.sqrt(dsq); + r = (r + d) / 2; + rsq = r * r; + mulN2(null, addW2(null, centroid, p, r, d - r), 1 / d); + } + } + return [centroid, r]; }; /** @@ -109,38 +109,38 @@ export const boundingCircle = (pts: ReadonlyVec[]): [Vec, number] => { * @param pts */ export const boundingSphere = (pts: ReadonlyVec[]): [Vec, number] => { - let xmin = MAX2; - let xmax = MIN2; - let ymin = MAX2; - let ymax = MIN2; - let zmin = MAX2; - let zmax = MIN2; - for (let i = pts.length; i-- > 0; ) { - const p = pts[i]; - if (p[0] < xmin[0]) xmin = p; - else if (p[0] > xmax[0]) xmax = p; - if (p[1] < ymin[1]) ymin = p; - else if (p[1] > ymax[1]) ymax = p; - if (p[2] < zmin[1]) zmin = p; - else if (p[2] > zmax[1]) zmax = p; - } - const span = <[ReadonlyVec, ReadonlyVec]>[ - [xmin, xmax], - [ymin, ymax], - [zmin, zmax], - ][max3id(distSq3(xmin, xmax), distSq3(ymin, ymax), distSq3(zmin, zmax))]; - let centroid = addmN3([], ...span, 0.5); - let rsq = distSq3(centroid, span[0]); - let r = Math.sqrt(rsq); - for (let i = pts.length; i-- > 0; ) { - const p = pts[i]; - const dsq = distSq3(centroid, p); - if (dsq > rsq) { - const d = Math.sqrt(dsq); - r = (r + d) / 2; - rsq = r * r; - mulN3(null, addW2(null, centroid, p, r, d - r), 1 / d); - } - } - return [centroid, r]; + let xmin = MAX2; + let xmax = MIN2; + let ymin = MAX2; + let ymax = MIN2; + let zmin = MAX2; + let zmax = MIN2; + for (let i = pts.length; i-- > 0; ) { + const p = pts[i]; + if (p[0] < xmin[0]) xmin = p; + else if (p[0] > xmax[0]) xmax = p; + if (p[1] < ymin[1]) ymin = p; + else if (p[1] > ymax[1]) ymax = p; + if (p[2] < zmin[1]) zmin = p; + else if (p[2] > zmax[1]) zmax = p; + } + const span = <[ReadonlyVec, ReadonlyVec]>[ + [xmin, xmax], + [ymin, ymax], + [zmin, zmax], + ][max3id(distSq3(xmin, xmax), distSq3(ymin, ymax), distSq3(zmin, zmax))]; + let centroid = addmN3([], ...span, 0.5); + let rsq = distSq3(centroid, span[0]); + let r = Math.sqrt(rsq); + for (let i = pts.length; i-- > 0; ) { + const p = pts[i]; + const dsq = distSq3(centroid, p); + if (dsq > rsq) { + const d = Math.sqrt(dsq); + r = (r + d) / 2; + rsq = r * r; + mulN3(null, addW2(null, centroid, p, r, d - r), 1 / d); + } + } + return [centroid, r]; }; diff --git a/packages/geom-poly-utils/src/center-of-weight.ts b/packages/geom-poly-utils/src/center-of-weight.ts index d702fd22d3..f81970eb95 100644 --- a/packages/geom-poly-utils/src/center-of-weight.ts +++ b/packages/geom-poly-utils/src/center-of-weight.ts @@ -2,20 +2,20 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; import { cross2 } from "@thi.ng/vectors/cross"; export const centerOfWeight2 = (pts: ReadonlyVec[], out: Vec = []) => { - const n = pts.length - 1; - let area = 0; - let x = 0; - let y = 0; - let a = pts[n]; - let b = pts[0]; - for (let i = 0; i <= n; a = b, b = pts[++i]) { - const z = cross2(a, b); - area += z; - x += (a[0] + b[0]) * z; - y += (a[1] + b[1]) * z; - } - area = 1 / (area * 3); - out[0] = x * area; - out[1] = y * area; - return out; + const n = pts.length - 1; + let area = 0; + let x = 0; + let y = 0; + let a = pts[n]; + let b = pts[0]; + for (let i = 0; i <= n; a = b, b = pts[++i]) { + const z = cross2(a, b); + area += z; + x += (a[0] + b[0]) * z; + y += (a[1] + b[1]) * z; + } + area = 1 / (area * 3); + out[0] = x * area; + out[1] = y * area; + return out; }; diff --git a/packages/geom-poly-utils/src/centroid.ts b/packages/geom-poly-utils/src/centroid.ts index 513b483cce..eabd28233d 100644 --- a/packages/geom-poly-utils/src/centroid.ts +++ b/packages/geom-poly-utils/src/centroid.ts @@ -5,11 +5,11 @@ import { divN } from "@thi.ng/vectors/divn"; import { empty } from "@thi.ng/vectors/empty"; export const centroid = (pts: ReadonlyVec[], out?: Vec) => { - const num = pts.length; - !num && illegalArgs("no points"); - !out && (out = empty(pts[0])); - for (let i = num; i-- > 0; ) { - add(out, out, pts[i]); - } - return divN(out, out, num); + const num = pts.length; + !num && illegalArgs("no points"); + !out && (out = empty(pts[0])); + for (let i = num; i-- > 0; ) { + add(out, out, pts[i]); + } + return divN(out, out, num); }; diff --git a/packages/geom-poly-utils/src/circumcenter.ts b/packages/geom-poly-utils/src/circumcenter.ts index 112598733b..0e3959ba3a 100644 --- a/packages/geom-poly-utils/src/circumcenter.ts +++ b/packages/geom-poly-utils/src/circumcenter.ts @@ -18,54 +18,54 @@ import { sub3 } from "@thi.ng/vectors/sub"; * @param eps - epsilon value for colinear check */ export const circumCenter2 = ( - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - eps = EPS + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + eps = EPS ): Vec | undefined => { - const ax = a[0], - ay = a[1]; - const bx = b[0], - by = b[1]; - const cx = c[0], - cy = c[1]; + const ax = a[0], + ay = a[1]; + const bx = b[0], + by = b[1]; + const cx = c[0], + cy = c[1]; - const bax = bx - ax; - const bay = by - ay; - const cbx = cx - bx; - const cby = cy - by; + const bax = bx - ax; + const bay = by - ay; + const cbx = cx - bx; + const cby = cy - by; - const deltaAB = Math.abs(bay); - const deltaBC = Math.abs(cby); + const deltaAB = Math.abs(bay); + const deltaBC = Math.abs(cby); - // colinear check - if ( - (deltaAB < eps && deltaBC < eps) || - (Math.abs(bax) < eps && Math.abs(cbx) < eps) - ) { - return; - } + // colinear check + if ( + (deltaAB < eps && deltaBC < eps) || + (Math.abs(bax) < eps && Math.abs(cbx) < eps) + ) { + return; + } - const abx2 = (ax + bx) / 2; - const aby2 = (ay + by) / 2; - const bcx2 = (bx + cx) / 2; - const bcy2 = (by + cy) / 2; + const abx2 = (ax + bx) / 2; + const aby2 = (ay + by) / 2; + const bcx2 = (bx + cx) / 2; + const bcy2 = (by + cy) / 2; - if (deltaAB < eps) { - return [abx2, (-cbx / cby) * (abx2 - bcx2) + bcy2]; - } - if (deltaBC < eps) { - return [bcx2, (-bax / bay) * (bcx2 - abx2) + aby2]; - } - let m1 = -bax / bay; - let m2 = -cbx / cby; - let mx1 = abx2; - let my1 = aby2; - let mx2 = bcx2; - let my2 = bcy2; - let xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2); - let yc = deltaAB > deltaBC ? m1 * (xc - mx1) + my1 : m2 * (xc - mx2) + my2; - return [xc, yc]; + if (deltaAB < eps) { + return [abx2, (-cbx / cby) * (abx2 - bcx2) + bcy2]; + } + if (deltaBC < eps) { + return [bcx2, (-bax / bay) * (bcx2 - abx2) + aby2]; + } + let m1 = -bax / bay; + let m2 = -cbx / cby; + let mx1 = abx2; + let my1 = aby2; + let mx2 = bcx2; + let my2 = bcy2; + let xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2); + let yc = deltaAB > deltaBC ? m1 * (xc - mx1) + my1 : m2 * (xc - mx2) + my2; + return [xc, yc]; }; /** @@ -77,31 +77,31 @@ export const circumCenter2 = ( * Based on Jonathan R Shewchuk: * https://www.ics.uci.edu/~eppstein/junkyard/circumcenter.html * - * @param a - - * @param b - - * @param c - - * @param eps - + * @param a - + * @param b - + * @param c - + * @param eps - */ export const circumCenter3 = ( - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - eps = EPS + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + eps = EPS ): Vec | undefined => { - const ab = sub3([], b, a); - const ac = sub3([], c, a); - const d = cross3([], ab, ac); - const m = magSq3(d); - return m >= eps - ? add3( - null, - addmN3( - null, - mulN3(null, cross3([], d, ab), magSq3(ac)), - mulN3(null, cross3([], ac, d), magSq3(ab)), - 0.5 / m - ), - a - ) - : undefined; + const ab = sub3([], b, a); + const ac = sub3([], c, a); + const d = cross3([], ab, ac); + const m = magSq3(d); + return m >= eps + ? add3( + null, + addmN3( + null, + mulN3(null, cross3([], d, ab), magSq3(ac)), + mulN3(null, cross3([], ac, d), magSq3(ab)), + 0.5 / m + ), + a + ) + : undefined; }; diff --git a/packages/geom-poly-utils/src/convexity.ts b/packages/geom-poly-utils/src/convexity.ts index 6ec3ac00db..d81381ace9 100644 --- a/packages/geom-poly-utils/src/convexity.ts +++ b/packages/geom-poly-utils/src/convexity.ts @@ -4,21 +4,21 @@ import type { ReadonlyVec } from "@thi.ng/vectors"; import { corner2 } from "@thi.ng/vectors/clockwise"; export const convexity = (pts: ReadonlyVec[], eps = EPS) => { - let n = pts.length; - if (n < 3) { - return n < 2 ? Convexity.ILLEGAL : Convexity.COLINEAR; - } - let type = 0; - let a = pts[n - 2]; - let b = pts[n - 1]; - let c = pts[0]; - for (let i = 0; i < n && type < 3; a = b, b = c, c = pts[++i]) { - const t = corner2(a, b, c, eps); - type |= t < 0 ? 1 : t > 0 ? 2 : 0; - } - return type === 3 - ? Convexity.CONCAVE - : type > 0 - ? Convexity.CONVEX - : Convexity.COLINEAR; + let n = pts.length; + if (n < 3) { + return n < 2 ? Convexity.ILLEGAL : Convexity.COLINEAR; + } + let type = 0; + let a = pts[n - 2]; + let b = pts[n - 1]; + let c = pts[0]; + for (let i = 0; i < n && type < 3; a = b, b = c, c = pts[++i]) { + const t = corner2(a, b, c, eps); + type |= t < 0 ? 1 : t > 0 ? 2 : 0; + } + return type === 3 + ? Convexity.CONCAVE + : type > 0 + ? Convexity.CONVEX + : Convexity.COLINEAR; }; diff --git a/packages/geom-poly-utils/src/equilateral.ts b/packages/geom-poly-utils/src/equilateral.ts index 3db464376f..46084d6b78 100644 --- a/packages/geom-poly-utils/src/equilateral.ts +++ b/packages/geom-poly-utils/src/equilateral.ts @@ -9,14 +9,14 @@ import { perpendicularCCW } from "@thi.ng/vectors/perpendicular"; import { sub2 } from "@thi.ng/vectors/sub"; export const equilateralTriangle2: FnU2 = (a, b) => { - const dir = sub2([], b, a); - return [ - a, - b, - add2( - null, - normalize(null, perpendicularCCW([], dir), mag(dir) * SQRT3_2), - maddN2(dir, dir, 0.5, a) - ), - ]; + const dir = sub2([], b, a); + return [ + a, + b, + add2( + null, + normalize(null, perpendicularCCW([], dir), mag(dir) * SQRT3_2), + maddN2(dir, dir, 0.5, a) + ), + ]; }; diff --git a/packages/geom-poly-utils/src/perimeter.ts b/packages/geom-poly-utils/src/perimeter.ts index 32d451359e..d0ef570626 100644 --- a/packages/geom-poly-utils/src/perimeter.ts +++ b/packages/geom-poly-utils/src/perimeter.ts @@ -2,16 +2,16 @@ import type { ReadonlyVec } from "@thi.ng/vectors"; import { dist } from "@thi.ng/vectors/dist"; export const perimeter = ( - pts: ReadonlyVec[], - num = pts.length, - closed = false + pts: ReadonlyVec[], + num = pts.length, + closed = false ) => { - if (num < 2) return 0; - let res = 0; - let p = pts[0]; - let q = pts[1]; - for (let i = 1; i < num; p = q, q = pts[++i]) { - res += dist(p, q); - } - return closed ? res + dist(p, pts[0]) : res; + if (num < 2) return 0; + let res = 0; + let p = pts[0]; + let q = pts[1]; + for (let i = 1; i < num; p = q, q = pts[++i]) { + res += dist(p, q); + } + return closed ? res + dist(p, pts[0]) : res; }; diff --git a/packages/geom-poly-utils/src/tangent.ts b/packages/geom-poly-utils/src/tangent.ts index 84eecd8b3e..bbe407cba9 100644 --- a/packages/geom-poly-utils/src/tangent.ts +++ b/packages/geom-poly-utils/src/tangent.ts @@ -20,20 +20,20 @@ import { sub } from "@thi.ng/vectors/sub"; * * The optional `scale` arg can be used to scale the tangents (default: 1). * - * @param pts - - * @param close - - * @param scale - + * @param pts - + * @param close - + * @param scale - */ export const tangents = (pts: ReadonlyVec[], close = false, scale = 1) => { - const n = pts.length - 1; - const res: Vec[] = []; - for (let i = 1; i <= n; i++) { - res.push(direction([], pts[i - 1], pts[i], scale)); - } - res.push( - close ? direction([], pts[n], pts[0], scale) : set([], res[n - 1]) - ); - return res; + const n = pts.length - 1; + const res: Vec[] = []; + for (let i = 1; i <= n; i++) { + res.push(direction([], pts[i - 1], pts[i], scale)); + } + res.push( + close ? direction([], pts[n], pts[0], scale) : set([], res[n - 1]) + ); + return res; }; /** @@ -50,67 +50,67 @@ export const tangents = (pts: ReadonlyVec[], close = false, scale = 1) => { * * The optional `scale` arg can be used to scale the tangents (default: 1). * - * @param pts - - * @param close - - * @param proportional - - * @param scale - + * @param pts - + * @param close - + * @param proportional - + * @param scale - */ export const smoothTangents = ( - pts: ReadonlyVec[], - close = false, - proportional = true, - scale = 1 + pts: ReadonlyVec[], + close = false, + proportional = true, + scale = 1 ) => { - const res: Vec[] = []; - const n = pts.length - 1; - if (n < 1) return res; - let prev: Vec | undefined; - let plen: number | undefined; - if (close) { - prev = sub([], pts[0], pts[n]); - plen = mag(prev!); - normalize(null, prev!); - } - let t: Vec; - for (let i = 0; i <= n; i++) { - let curr: Vec; - let clen: number; - if (i === n) { - if (close) { - curr = sub([], pts[0], pts[i]); - } else { - res.push(prev!); - return res; - } - } else { - curr = sub([], pts[i + 1], pts[i]); - } - clen = mag(curr); - normalize(null, curr); - if (i > 0 || close) { - t = proportional - ? mixN( - [], - prev!, - curr, - Math.min(1, clen / (clen + plen! + EPS)) - ) - : addmN([], prev!, curr, 0.5); - } else { - t = set([], curr); - } - res.push(normalize(null, t, scale)); - prev = curr; - plen = clen; - } - return res; + const res: Vec[] = []; + const n = pts.length - 1; + if (n < 1) return res; + let prev: Vec | undefined; + let plen: number | undefined; + if (close) { + prev = sub([], pts[0], pts[n]); + plen = mag(prev!); + normalize(null, prev!); + } + let t: Vec; + for (let i = 0; i <= n; i++) { + let curr: Vec; + let clen: number; + if (i === n) { + if (close) { + curr = sub([], pts[0], pts[i]); + } else { + res.push(prev!); + return res; + } + } else { + curr = sub([], pts[i + 1], pts[i]); + } + clen = mag(curr); + normalize(null, curr); + if (i > 0 || close) { + t = proportional + ? mixN( + [], + prev!, + curr, + Math.min(1, clen / (clen + plen! + EPS)) + ) + : addmN([], prev!, curr, 0.5); + } else { + t = set([], curr); + } + res.push(normalize(null, t, scale)); + prev = curr; + plen = clen; + } + return res; }; /** * Transforms an array of 2d tangent vectors into a new array with each tangent * rotated 90 degrees counter-clockwise. * - * @param tangents - + * @param tangents - */ export const bitangents2 = (tangents: ReadonlyVec[]) => - tangents.map((t) => perpendicularCCW([], t)); + tangents.map((t) => perpendicularCCW([], t)); diff --git a/packages/geom-poly-utils/tsconfig.json b/packages/geom-poly-utils/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-poly-utils/tsconfig.json +++ b/packages/geom-poly-utils/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-resample/api-extractor.json b/packages/geom-resample/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-resample/api-extractor.json +++ b/packages/geom-resample/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-resample/package.json b/packages/geom-resample/package.json index 39974db997..5a510b160a 100644 --- a/packages/geom-resample/package.json +++ b/packages/geom-resample/package.json @@ -1,97 +1,97 @@ { - "name": "@thi.ng/geom-resample", - "version": "2.1.19", - "description": "Customizable nD polyline interpolation, re-sampling, splitting & nearest point computation", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-resample#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/checks": "^3.2.2", - "@thi.ng/geom-api": "^3.3.2", - "@thi.ng/geom-closest-point": "^2.1.16", - "@thi.ng/math": "^5.3.4", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "3d", - "curve", - "datastructure", - "distance", - "geometry", - "interpolation", - "line", - "nd", - "points", - "polygon", - "polyline", - "proximity", - "sample", - "tangent", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./resample": { - "default": "./resample.js" - }, - "./sampler": { - "default": "./sampler.js" - }, - "./simplify": { - "default": "./simplify.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "year": 2013 - } + "name": "@thi.ng/geom-resample", + "version": "2.1.19", + "description": "Customizable nD polyline interpolation, re-sampling, splitting & nearest point computation", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-resample#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/checks": "^3.2.2", + "@thi.ng/geom-api": "^3.3.2", + "@thi.ng/geom-closest-point": "^2.1.16", + "@thi.ng/math": "^5.3.4", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "3d", + "curve", + "datastructure", + "distance", + "geometry", + "interpolation", + "line", + "nd", + "points", + "polygon", + "polyline", + "proximity", + "sample", + "tangent", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./resample": { + "default": "./resample.js" + }, + "./sampler": { + "default": "./sampler.js" + }, + "./simplify": { + "default": "./simplify.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "year": 2013 + } } diff --git a/packages/geom-resample/src/resample.ts b/packages/geom-resample/src/resample.ts index 1fb0401a31..7366d17c0e 100644 --- a/packages/geom-resample/src/resample.ts +++ b/packages/geom-resample/src/resample.ts @@ -5,28 +5,28 @@ import { copyVectors } from "@thi.ng/vectors/copy"; import { Sampler } from "./sampler.js"; export const resample = ( - pts: ReadonlyVec[], - opts?: number | Partial, - closed = false, - copy = false + pts: ReadonlyVec[], + opts?: number | Partial, + closed = false, + copy = false ) => { - if (opts !== undefined) { - const sampler = new Sampler(pts, closed); - return isPlainObject(opts) - ? closed - ? opts.dist - ? sampler.sampleUniform(opts.dist, opts.last) - : sampler.sampleFixedNum( - opts.num || DEFAULT_SAMPLES, - opts.last - ) - : opts.dist - ? sampler.sampleUniform(opts.dist, opts.last !== false) - : sampler.sampleFixedNum( - opts.num || DEFAULT_SAMPLES, - opts.last !== false - ) - : sampler.sampleFixedNum(opts || DEFAULT_SAMPLES, !closed); - } - return copy ? copyVectors(pts) : pts; + if (opts !== undefined) { + const sampler = new Sampler(pts, closed); + return isPlainObject(opts) + ? closed + ? opts.dist + ? sampler.sampleUniform(opts.dist, opts.last) + : sampler.sampleFixedNum( + opts.num || DEFAULT_SAMPLES, + opts.last + ) + : opts.dist + ? sampler.sampleUniform(opts.dist, opts.last !== false) + : sampler.sampleFixedNum( + opts.num || DEFAULT_SAMPLES, + opts.last !== false + ) + : sampler.sampleFixedNum(opts || DEFAULT_SAMPLES, !closed); + } + return copy ? copyVectors(pts) : pts; }; diff --git a/packages/geom-resample/src/sampler.ts b/packages/geom-resample/src/sampler.ts index bf8cc760df..a2f7e22755 100644 --- a/packages/geom-resample/src/sampler.ts +++ b/packages/geom-resample/src/sampler.ts @@ -1,7 +1,7 @@ import { - closestPointPolyline, - closestPointSegment, - closestT, + closestPointPolyline, + closestPointSegment, + closestT, } from "@thi.ng/geom-closest-point/line"; import { fit01 } from "@thi.ng/math/fit"; import type { ReadonlyVec, Vec, VecPair } from "@thi.ng/vectors"; @@ -14,174 +14,174 @@ import { set } from "@thi.ng/vectors/set"; import { sub } from "@thi.ng/vectors/sub"; export class Sampler { - points: ReadonlyVec[]; - index!: number[]; + points: ReadonlyVec[]; + index!: number[]; - constructor(points: ReadonlyVec[], closed = false) { - if (closed) { - this.points = points.slice(); - this.points.push(points[0]); - } else { - this.points = points; - } - this.buildIndex(); - } + constructor(points: ReadonlyVec[], closed = false) { + if (closed) { + this.points = points.slice(); + this.points.push(points[0]); + } else { + this.points = points; + } + this.buildIndex(); + } - totalLength() { - const idx = this.index; - return idx ? idx[idx.length - 1] : 0; - } + totalLength() { + const idx = this.index; + return idx ? idx[idx.length - 1] : 0; + } - pointAt(t: number): Vec | undefined { - const pts = this.points; - const n = pts.length - 1; - if (n < 0) { - return; - } - if (n === 0 || t <= 0) { - return pts[0]; - } - if (t >= 1) { - return pts[n]; - } - const idx = this.index; - const t0 = t * idx[n]; - for (let i = 1; i <= n; i++) { - if (idx[i] >= t0) { - return mixN( - [], - pts[i - 1], - pts[i], - (t0 - idx[i - 1]) / (idx[i] - idx[i - 1]) - ); - } - } - } + pointAt(t: number): Vec | undefined { + const pts = this.points; + const n = pts.length - 1; + if (n < 0) { + return; + } + if (n === 0 || t <= 0) { + return pts[0]; + } + if (t >= 1) { + return pts[n]; + } + const idx = this.index; + const t0 = t * idx[n]; + for (let i = 1; i <= n; i++) { + if (idx[i] >= t0) { + return mixN( + [], + pts[i - 1], + pts[i], + (t0 - idx[i - 1]) / (idx[i] - idx[i - 1]) + ); + } + } + } - closestPoint(p: ReadonlyVec) { - return closestPointPolyline(p, this.points); - } + closestPoint(p: ReadonlyVec) { + return closestPointPolyline(p, this.points); + } - closestT(p: ReadonlyVec) { - const { index, points } = this; - const tmp: Vec = []; - const closest: Vec = []; - let minD = Infinity; - let minI = -1; - for (let i = 0, n = index.length - 1; i < n; i++) { - if (closestPointSegment(p, points[i], points[i + 1], tmp)) { - const d = distSq(p, tmp); - if (d < minD) { - minD = d; - minI = i; - set(closest, tmp); - } - } - } - return minI >= 0 - ? fit01( - closestT(p, points[minI], points[minI + 1]) || 0, - index[minI], - index[minI + 1] - ) / this.totalLength() - : undefined; - } + closestT(p: ReadonlyVec) { + const { index, points } = this; + const tmp: Vec = []; + const closest: Vec = []; + let minD = Infinity; + let minI = -1; + for (let i = 0, n = index.length - 1; i < n; i++) { + if (closestPointSegment(p, points[i], points[i + 1], tmp)) { + const d = distSq(p, tmp); + if (d < minD) { + minD = d; + minI = i; + set(closest, tmp); + } + } + } + return minI >= 0 + ? fit01( + closestT(p, points[minI], points[minI + 1]) || 0, + index[minI], + index[minI + 1] + ) / this.totalLength() + : undefined; + } - segmentAt(t: number): VecPair | undefined { - let i = this.indexAt(t); - if (i === undefined) { - return; - } - i = Math.max(1, i); - return [this.points[i - 1], this.points[i]]; - } + segmentAt(t: number): VecPair | undefined { + let i = this.indexAt(t); + if (i === undefined) { + return; + } + i = Math.max(1, i); + return [this.points[i - 1], this.points[i]]; + } - tangentAt(t: number, n = 1) { - const seg = this.segmentAt(t); - return seg ? normalize(null, sub([], seg[1], seg[0]), n) : undefined; - } + tangentAt(t: number, n = 1) { + const seg = this.segmentAt(t); + return seg ? normalize(null, sub([], seg[1], seg[0]), n) : undefined; + } - splitAt(t: number): Vec[][] | undefined { - if (t <= 0 || t >= 1) { - return [this.points]; - } - const p = this.pointAt(t); - if (!p) return; - const i = Math.max(1, this.indexAt(t)!); - const head = this.points.slice(0, i); - const tail = this.points.slice(i); - if (!eqDelta(head[i - 1], p)) { - head.push(p); - } - if (!eqDelta(tail[0], p)) { - tail.unshift(p); - } - return [head, tail]; - } + splitAt(t: number): Vec[][] | undefined { + if (t <= 0 || t >= 1) { + return [this.points]; + } + const p = this.pointAt(t); + if (!p) return; + const i = Math.max(1, this.indexAt(t)!); + const head = this.points.slice(0, i); + const tail = this.points.slice(i); + if (!eqDelta(head[i - 1], p)) { + head.push(p); + } + if (!eqDelta(tail[0], p)) { + tail.unshift(p); + } + return [head, tail]; + } - splitNear(p: ReadonlyVec) { - const t = this.closestT(p); - return t !== undefined ? this.splitAt(t) : undefined; - } + splitNear(p: ReadonlyVec) { + const t = this.closestT(p); + return t !== undefined ? this.splitAt(t) : undefined; + } - indexAt(t: number) { - const pts = this.points; - const n = pts.length - 1; - if (n < 0) { - return; - } - if (n === 0 || t <= 0) { - return 0; - } - if (t >= 1) { - return n; - } - const idx = this.index; - const t0 = t * idx[n]; - for (let i = 1; i <= n; i++) { - if (idx[i] >= t0) { - return i; - } - } - } + indexAt(t: number) { + const pts = this.points; + const n = pts.length - 1; + if (n < 0) { + return; + } + if (n === 0 || t <= 0) { + return 0; + } + if (t >= 1) { + return n; + } + const idx = this.index; + const t0 = t * idx[n]; + for (let i = 1; i <= n; i++) { + if (idx[i] >= t0) { + return i; + } + } + } - sampleUniform(dist: number, includeLast = false, result: Vec[] = []) { - const { index, points } = this; - const total = this.totalLength(); - const delta = dist / total; - const n = index.length; - for (let t = 0, i = 1; t < 1; t += delta) { - const ct = t * total; - while (ct >= index[i] && i < n) { - i++; - } - if (i >= n) break; - const p = index[i - 1]; - result.push( - mixN([], points[i - 1], points[i], (ct - p) / (index[i] - p)) - ); - } - if (includeLast) { - result.push(set([], points[points.length - 1])); - } - return result; - } + sampleUniform(dist: number, includeLast = false, result: Vec[] = []) { + const { index, points } = this; + const total = this.totalLength(); + const delta = dist / total; + const n = index.length; + for (let t = 0, i = 1; t < 1; t += delta) { + const ct = t * total; + while (ct >= index[i] && i < n) { + i++; + } + if (i >= n) break; + const p = index[i - 1]; + result.push( + mixN([], points[i - 1], points[i], (ct - p) / (index[i] - p)) + ); + } + if (includeLast) { + result.push(set([], points[points.length - 1])); + } + return result; + } - sampleFixedNum(num: number, includeLast = false, result?: Vec[]) { - return this.sampleUniform( - this.totalLength() / num, - includeLast, - result - ); - } + sampleFixedNum(num: number, includeLast = false, result?: Vec[]) { + return this.sampleUniform( + this.totalLength() / num, + includeLast, + result + ); + } - protected buildIndex() { - const idx: number[] = [0]; - const pts = this.points; - const n = pts.length; - for (let i = 0, j = 1; j < n; i = j, j++) { - idx[j] = idx[i] + dist(pts[i], pts[j]); - } - this.index = idx; - } + protected buildIndex() { + const idx: number[] = [0]; + const pts = this.points; + const n = pts.length; + for (let i = 0, j = 1; j < n; i = j, j++) { + idx[j] = idx[i] + dist(pts[i], pts[j]); + } + this.index = idx; + } } diff --git a/packages/geom-resample/src/simplify.ts b/packages/geom-resample/src/simplify.ts index ac4f932e33..77a3a3dcfe 100644 --- a/packages/geom-resample/src/simplify.ts +++ b/packages/geom-resample/src/simplify.ts @@ -11,39 +11,39 @@ import { eqDelta } from "@thi.ng/vectors/eqdelta"; * @param closed - true, if closed shape (polygon) */ export const simplify = (pts: Vec[], eps = 0, closed = false) => { - let num = pts.length; - const visited: boolean[] = []; - if (num <= 2) return pts.slice(); - if (closed && !eqDelta(pts[0], pts[num - 1], EPS)) { - pts = pts.slice(); - pts.push(pts[0]); - num++; - } + let num = pts.length; + const visited: boolean[] = []; + if (num <= 2) return pts.slice(); + if (closed && !eqDelta(pts[0], pts[num - 1], EPS)) { + pts = pts.slice(); + pts.push(pts[0]); + num++; + } - const $ = (from: number, to: number) => { - visited[from] = visited[to] = true; - if (to <= from + 1) { - return; - } - const [maxIdx, maxD] = farthestPointSegment( - pts[from], - pts[to], - pts, - from + 1, - to - ); - if (maxIdx < 0 || maxD <= eps) { - return; - } - $(from, maxIdx); - $(maxIdx, to); - }; + const $ = (from: number, to: number) => { + visited[from] = visited[to] = true; + if (to <= from + 1) { + return; + } + const [maxIdx, maxD] = farthestPointSegment( + pts[from], + pts[to], + pts, + from + 1, + to + ); + if (maxIdx < 0 || maxD <= eps) { + return; + } + $(from, maxIdx); + $(maxIdx, to); + }; - $(0, num - 1); + $(0, num - 1); - const res: Vec[] = []; - for (let i = 0, n = closed ? num - 1 : num; i < n; i++) { - visited[i] && res.push(pts[i]); - } - return res; + const res: Vec[] = []; + for (let i = 0, n = closed ? num - 1 : num; i < n; i++) { + visited[i] && res.push(pts[i]); + } + return res; }; diff --git a/packages/geom-resample/tsconfig.json b/packages/geom-resample/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-resample/tsconfig.json +++ b/packages/geom-resample/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-sdf/api-extractor.json b/packages/geom-sdf/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-sdf/api-extractor.json +++ b/packages/geom-sdf/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-sdf/package.json b/packages/geom-sdf/package.json index 8a3e3916bd..1a88eb26c8 100644 --- a/packages/geom-sdf/package.json +++ b/packages/geom-sdf/package.json @@ -1,130 +1,130 @@ { - "name": "@thi.ng/geom-sdf", - "version": "0.2.3", - "description": "2D Signed Distance Field creation from @thi.ng/geom shapes, conversions, sampling, combinators", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/geom-sdf#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/defmulti": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/geom": "^3.4.2", - "@thi.ng/geom-api": "^3.3.2", - "@thi.ng/geom-isoline": "^2.1.16", - "@thi.ng/geom-poly-utils": "^2.3.3", - "@thi.ng/geom-resample": "^2.1.19", - "@thi.ng/math": "^5.3.4", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "arc", - "circle", - "bezier", - "conversion", - "distance", - "field", - "geometry", - "line", - "rect", - "sdf", - "shape", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=14" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./as-polygons": { - "default": "./as-polygons.js" - }, - "./as-sdf": { - "default": "./as-sdf.js" - }, - "./bounds": { - "default": "./bounds.js" - }, - "./dist": { - "default": "./dist.js" - }, - "./domain": { - "default": "./domain.js" - }, - "./ops": { - "default": "./ops.js" - }, - "./sample": { - "default": "./sample.js" - }, - "./shapes": { - "default": "./shapes.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "related": [ - "distance-transform", - "geom-isoline", - "pixel", - "shader-ast-stdlib" - ], - "status": "alpha", - "year": 2022 - } + "name": "@thi.ng/geom-sdf", + "version": "0.2.3", + "description": "2D Signed Distance Field creation from @thi.ng/geom shapes, conversions, sampling, combinators", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/geom-sdf#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/defmulti": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/geom": "^3.4.2", + "@thi.ng/geom-api": "^3.3.2", + "@thi.ng/geom-isoline": "^2.1.16", + "@thi.ng/geom-poly-utils": "^2.3.3", + "@thi.ng/geom-resample": "^2.1.19", + "@thi.ng/math": "^5.3.4", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "arc", + "circle", + "bezier", + "conversion", + "distance", + "field", + "geometry", + "line", + "rect", + "sdf", + "shape", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=14" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./as-polygons": { + "default": "./as-polygons.js" + }, + "./as-sdf": { + "default": "./as-sdf.js" + }, + "./bounds": { + "default": "./bounds.js" + }, + "./dist": { + "default": "./dist.js" + }, + "./domain": { + "default": "./domain.js" + }, + "./ops": { + "default": "./ops.js" + }, + "./sample": { + "default": "./sample.js" + }, + "./shapes": { + "default": "./shapes.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "related": [ + "distance-transform", + "geom-isoline", + "pixel", + "shader-ast-stdlib" + ], + "status": "alpha", + "year": 2022 + } } diff --git a/packages/geom-sdf/src/api.ts b/packages/geom-sdf/src/api.ts index 5227fdeb75..6f41463115 100644 --- a/packages/geom-sdf/src/api.ts +++ b/packages/geom-sdf/src/api.ts @@ -22,84 +22,84 @@ export type FieldCoeff = Fn; * ``` */ export interface SDFAttribs { - /** - * If true, only the absolute (unsigned) distance will be used. For closed - * shapes the default is false, for lines/curves the default is true (since - * there's no real interior). - * - * @defaultValue false - */ - abs: boolean; - /** - * Advanced usage only. If true (default: false), the SDF will be wrapped - * with a bounding box pre-check. - * - * @remarks - * Currently only supported by some shape types and only usable in some - * circumstances, hence disabled by default. - */ - bounds: boolean; - /** - * Only used for `groups()`. Specifies the type of operation used for - * combining child SDFs. If {@link SDFAttribs.smooth} is != zero, smoothed - * versions of the operators will be used. - * - * @defaultValue "union" - */ - combine: SDFCombineOp; - /** - * If true (default: false), the sign of the resulting distance will be - * flipped. Useful for boolean operations. - * - * @defaultValue false - */ - flip: boolean; - /** - * Subtracts given value from actual distance, thereby creating an - * offsetting effect. If given as function, it will be called with the - * current SDF query point and the return value will be used as param. - * - * @defaultValue 0 - */ - offset: number | FieldCoeff; - /** - * Coefficient for smooth union, intersection, difference ops (only - * supported for `group()` shapes). If given as function, it will be called - * with the current SDF query point and the return value will be used as - * param. Ignored if zero (default). - * - * @defaultValue 0 - */ - smooth: number | FieldCoeff; - /** - * Radius coefficient for chamfered union, intersection, difference ops - * (only supported for `group()` shapes). If given as function, it will be - * called with the current SDF query point and the return value will be used - * as param. Ignored if zero (default). - * - * @defaultValue 0 - */ - chamfer: number | FieldCoeff; - /** - * Radius coefficient for rounded union, intersection, difference ops (only - * supported for `group()` shapes). If given as function, it will be called - * with the current SDF query point and the return value will be used as - * param. Ignored if zero (default). - * - * @defaultValue 0 - */ - round: number | FieldCoeff; - /** - * Coefficient tuple of `[radius, num]` used for stepped union, - * intersection, difference ops (only supported for `group()` shapes). If - * given as function, it will be called with the current SDF query point and - * the return value will be used as param. Ignored if zero (default). - */ - steps?: [number, number] | FieldCoeff<[number, number]>; - /** - * If given, this value is used to control the number of samples used for - * converting the original geometry to a polygon or polyline. See - * {@link asSDF} for more details. - */ - samples?: number; + /** + * If true, only the absolute (unsigned) distance will be used. For closed + * shapes the default is false, for lines/curves the default is true (since + * there's no real interior). + * + * @defaultValue false + */ + abs: boolean; + /** + * Advanced usage only. If true (default: false), the SDF will be wrapped + * with a bounding box pre-check. + * + * @remarks + * Currently only supported by some shape types and only usable in some + * circumstances, hence disabled by default. + */ + bounds: boolean; + /** + * Only used for `groups()`. Specifies the type of operation used for + * combining child SDFs. If {@link SDFAttribs.smooth} is != zero, smoothed + * versions of the operators will be used. + * + * @defaultValue "union" + */ + combine: SDFCombineOp; + /** + * If true (default: false), the sign of the resulting distance will be + * flipped. Useful for boolean operations. + * + * @defaultValue false + */ + flip: boolean; + /** + * Subtracts given value from actual distance, thereby creating an + * offsetting effect. If given as function, it will be called with the + * current SDF query point and the return value will be used as param. + * + * @defaultValue 0 + */ + offset: number | FieldCoeff; + /** + * Coefficient for smooth union, intersection, difference ops (only + * supported for `group()` shapes). If given as function, it will be called + * with the current SDF query point and the return value will be used as + * param. Ignored if zero (default). + * + * @defaultValue 0 + */ + smooth: number | FieldCoeff; + /** + * Radius coefficient for chamfered union, intersection, difference ops + * (only supported for `group()` shapes). If given as function, it will be + * called with the current SDF query point and the return value will be used + * as param. Ignored if zero (default). + * + * @defaultValue 0 + */ + chamfer: number | FieldCoeff; + /** + * Radius coefficient for rounded union, intersection, difference ops (only + * supported for `group()` shapes). If given as function, it will be called + * with the current SDF query point and the return value will be used as + * param. Ignored if zero (default). + * + * @defaultValue 0 + */ + round: number | FieldCoeff; + /** + * Coefficient tuple of `[radius, num]` used for stepped union, + * intersection, difference ops (only supported for `group()` shapes). If + * given as function, it will be called with the current SDF query point and + * the return value will be used as param. Ignored if zero (default). + */ + steps?: [number, number] | FieldCoeff<[number, number]>; + /** + * If given, this value is used to control the number of samples used for + * converting the original geometry to a polygon or polyline. See + * {@link asSDF} for more details. + */ + samples?: number; } diff --git a/packages/geom-sdf/src/as-polygons.ts b/packages/geom-sdf/src/as-polygons.ts index e9fc27a26b..4364a4b648 100644 --- a/packages/geom-sdf/src/as-polygons.ts +++ b/packages/geom-sdf/src/as-polygons.ts @@ -36,24 +36,24 @@ import { sample2d } from "./sample.js"; * @param eps */ export const asPolygons = ( - sdf: NumericArray | SDFn, - bounds: AABBLike, - res: ReadonlyVec, - distances: Iterable = [0], - eps = 1e-6 + sdf: NumericArray | SDFn, + bounds: AABBLike, + res: ReadonlyVec, + distances: Iterable = [0], + eps = 1e-6 ) => { - const $sdf = isFunction(sdf) ? sample2d(sdf, bounds, res) : sdf; - const { pos, size } = bounds; - const [resX, resY] = res; - setBorder($sdf, resX, resY, 1e6); - const scale = div2([], size, [resX - 1, resY - 1]); - return transduce( - comp( - mapcat((iso) => isolines($sdf, resX, resY, iso, scale)), - map((pts) => pts.map((p) => add2(null, p, pos))), - map((pts) => polygon(eps >= 0 ? simplify(pts, eps, true) : pts)) - ), - push(), - distances - ); + const $sdf = isFunction(sdf) ? sample2d(sdf, bounds, res) : sdf; + const { pos, size } = bounds; + const [resX, resY] = res; + setBorder($sdf, resX, resY, 1e6); + const scale = div2([], size, [resX - 1, resY - 1]); + return transduce( + comp( + mapcat((iso) => isolines($sdf, resX, resY, iso, scale)), + map((pts) => pts.map((p) => add2(null, p, pos))), + map((pts) => polygon(eps >= 0 ? simplify(pts, eps, true) : pts)) + ), + push(), + distances + ); }; diff --git a/packages/geom-sdf/src/as-sdf.ts b/packages/geom-sdf/src/as-sdf.ts index b8bb6a7ff1..aa3be2f9b4 100644 --- a/packages/geom-sdf/src/as-sdf.ts +++ b/packages/geom-sdf/src/as-sdf.ts @@ -4,16 +4,16 @@ import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti"; import { assert } from "@thi.ng/errors/assert"; import { unsupported } from "@thi.ng/errors/unsupported"; import type { - Circle, - Ellipse, - Group, - Line, - Path, - Points, - Polygon, - Polyline, - Quadratic, - Rect, + Circle, + Ellipse, + Group, + Line, + Path, + Points, + Polygon, + Polyline, + Quadratic, + Rect, } from "@thi.ng/geom"; import type { Attribs, IShape } from "@thi.ng/geom-api"; import { asPolygon } from "@thi.ng/geom/as-polygon"; @@ -24,41 +24,41 @@ import { add2 } from "@thi.ng/vectors/add"; import { mulN2 } from "@thi.ng/vectors/muln"; import type { FieldCoeff, SDFAttribs, SDFn } from "./api.js"; import { - chamferDiff, - chamferIsec, - chamferUnion, - diff, - isec, - roundDiff, - roundIsec, - roundUnion, - smoothDiff, - smoothIsec, - smoothUnion, - stepDiff, - stepIsec, - stepUnion, - union, + chamferDiff, + chamferIsec, + chamferUnion, + diff, + isec, + roundDiff, + roundIsec, + roundUnion, + smoothDiff, + smoothIsec, + smoothUnion, + stepDiff, + stepIsec, + stepUnion, + union, } from "./ops.js"; import { - box2, - circle2, - DEFAULT_ATTRIBS, - ellipse2, - line2, - points2, - polygon2, - polyline2, - quadratic2, - withSDFAttribs, + box2, + circle2, + DEFAULT_ATTRIBS, + ellipse2, + line2, + points2, + polygon2, + polyline2, + quadratic2, + withSDFAttribs, } from "./shapes.js"; /** @internal */ interface ParametricOps { - chamfer: Fn2; - round: Fn2; - smooth: Fn2; - steps: Fn2<[number, number] | FieldCoeff<[number, number]>, SDFn[], SDFn>; + chamfer: Fn2; + round: Fn2; + smooth: Fn2; + steps: Fn2<[number, number] | FieldCoeff<[number, number]>, SDFn[], SDFn>; } /** @@ -87,116 +87,116 @@ interface ParametricOps { * {@link @thi.ng/geom-api#setDefaultSamples}). */ export const asSDF: MultiFn1 = defmulti( - __dispatch, - { - quad: "poly", - tri: "poly", - }, - { - [DEFAULT]: ($: IShape) => unsupported(`shape type: ${$.type}`), - - circle: ($: Circle) => circle2($.pos, $.r, __sdfAttribs($.attribs)), - - cubic: ($: IShape) => - asSDF( - simplify( - asPolyline($, (__sdfAttribs($.attribs) || {}).samples), - 0 - ) - ), - - ellipse: ($: Ellipse) => ellipse2($.pos, $.r, __sdfAttribs($.attribs)), - - group: ($: Group) => { - const { attribs, children } = $; - const attr = { ...DEFAULT_ATTRIBS, ...__sdfAttribs(attribs) }; - __validateAttribs(attr); - const $children = children.map(asSDF); - let res: SDFn; - if ($children.length > 1) { - switch (attr.combine) { - case "diff": - res = __selectCombineOp(attr, $children, diff, { - chamfer: chamferDiff, - round: roundDiff, - smooth: smoothDiff, - steps: stepDiff, - }); - break; - case "isec": - res = __selectCombineOp(attr, $children, isec, { - chamfer: chamferIsec, - round: roundIsec, - smooth: smoothIsec, - steps: stepIsec, - }); - break; - case "union": - default: { - res = __selectCombineOp(attr, $children, union, { - chamfer: chamferUnion, - round: roundUnion, - smooth: smoothUnion, - steps: stepUnion, - }); - } - } - } else if ($children.length) { - res = $children[0]; - } else { - return attr.flip ? () => -Infinity : () => Infinity; - } - return withSDFAttribs(res, attr); - }, - - line: ({ points: [a, b], attribs }: Line) => - line2(a, b, __sdfAttribs(attribs)), - - path: ($: Path) => { - const n = (__sdfAttribs($.attribs) || {}).samples; - return asSDF( - simplify($.closed ? asPolygon($, n) : asPolyline($, n), 0) - ); - }, - - points: ($: Points) => points2($.points, __sdfAttribs($.attribs)), - - poly: ($: Polygon) => polygon2($.points, __sdfAttribs($.attribs)), - - polyline: ($: Polyline) => polyline2($.points, __sdfAttribs($.attribs)), - - quadratic: ({ points: [a, b, c], attribs }: Quadratic) => - quadratic2(a, b, c, __sdfAttribs(attribs)), - - rect: ({ pos, size, attribs }: Rect) => { - const s = mulN2([], size, 0.5); - return box2(add2([], s, pos), s, __sdfAttribs(attribs)); - }, - } + __dispatch, + { + quad: "poly", + tri: "poly", + }, + { + [DEFAULT]: ($: IShape) => unsupported(`shape type: ${$.type}`), + + circle: ($: Circle) => circle2($.pos, $.r, __sdfAttribs($.attribs)), + + cubic: ($: IShape) => + asSDF( + simplify( + asPolyline($, (__sdfAttribs($.attribs) || {}).samples), + 0 + ) + ), + + ellipse: ($: Ellipse) => ellipse2($.pos, $.r, __sdfAttribs($.attribs)), + + group: ($: Group) => { + const { attribs, children } = $; + const attr = { ...DEFAULT_ATTRIBS, ...__sdfAttribs(attribs) }; + __validateAttribs(attr); + const $children = children.map(asSDF); + let res: SDFn; + if ($children.length > 1) { + switch (attr.combine) { + case "diff": + res = __selectCombineOp(attr, $children, diff, { + chamfer: chamferDiff, + round: roundDiff, + smooth: smoothDiff, + steps: stepDiff, + }); + break; + case "isec": + res = __selectCombineOp(attr, $children, isec, { + chamfer: chamferIsec, + round: roundIsec, + smooth: smoothIsec, + steps: stepIsec, + }); + break; + case "union": + default: { + res = __selectCombineOp(attr, $children, union, { + chamfer: chamferUnion, + round: roundUnion, + smooth: smoothUnion, + steps: stepUnion, + }); + } + } + } else if ($children.length) { + res = $children[0]; + } else { + return attr.flip ? () => -Infinity : () => Infinity; + } + return withSDFAttribs(res, attr); + }, + + line: ({ points: [a, b], attribs }: Line) => + line2(a, b, __sdfAttribs(attribs)), + + path: ($: Path) => { + const n = (__sdfAttribs($.attribs) || {}).samples; + return asSDF( + simplify($.closed ? asPolygon($, n) : asPolyline($, n), 0) + ); + }, + + points: ($: Points) => points2($.points, __sdfAttribs($.attribs)), + + poly: ($: Polygon) => polygon2($.points, __sdfAttribs($.attribs)), + + polyline: ($: Polyline) => polyline2($.points, __sdfAttribs($.attribs)), + + quadratic: ({ points: [a, b, c], attribs }: Quadratic) => + quadratic2(a, b, c, __sdfAttribs(attribs)), + + rect: ({ pos, size, attribs }: Rect) => { + const s = mulN2([], size, 0.5); + return box2(add2([], s, pos), s, __sdfAttribs(attribs)); + }, + } ); /** @internal */ const __sdfAttribs = (attribs?: Attribs): Partial => - attribs ? attribs.__sdf : null; + attribs ? attribs.__sdf : null; const OPS = ["chamfer", "round", "smooth", "steps"]; const __validateAttribs = (attribs: SDFAttribs) => - assert( - OPS.filter((x) => attribs[x]).length < 2, - "only 1 of these options can be used at once: chamfer, round, smooth" - ); + assert( + OPS.filter((x) => attribs[x]).length < 2, + "only 1 of these options can be used at once: chamfer, round, smooth" + ); const __selectCombineOp = ( - attribs: SDFAttribs, - children: SDFn[], - op: Fn, - paramOps: ParametricOps + attribs: SDFAttribs, + children: SDFn[], + op: Fn, + paramOps: ParametricOps ) => { - for (let k of OPS) { - if (attribs[k]) { - return paramOps[k](attribs[k], children); - } - } - return op(children); + for (let k of OPS) { + if (attribs[k]) { + return paramOps[k](attribs[k], children); + } + } + return op(children); }; diff --git a/packages/geom-sdf/src/bounds.ts b/packages/geom-sdf/src/bounds.ts index 8b49c63227..d7f4af26ff 100644 --- a/packages/geom-sdf/src/bounds.ts +++ b/packages/geom-sdf/src/bounds.ts @@ -24,22 +24,22 @@ import { distBox2 } from "./dist.js"; */ export function withBoundingCircle(sdf: SDFn, pts: ReadonlyVec[]): SDFn; export function withBoundingCircle( - sdf: SDFn, - centroid: ReadonlyVec, - r: number + sdf: SDFn, + centroid: ReadonlyVec, + r: number ): SDFn; export function withBoundingCircle(sdf: SDFn, ...args: any[]): SDFn { - let [[cx, cy], r] = - args.length === 1 - ? boundingCircle(args[0]) - : <[ReadonlyVec, number]>args; - r *= r; - return (p, minD = Infinity) => { - if (minD === Infinity) return sdf(p, minD); - const dx = p[0] - cx; - const dy = p[1] - cy; - return dx * dx + dy * dy - r < minD * minD ? sdf(p, minD) : minD; - }; + let [[cx, cy], r] = + args.length === 1 + ? boundingCircle(args[0]) + : <[ReadonlyVec, number]>args; + r *= r; + return (p, minD = Infinity) => { + if (minD === Infinity) return sdf(p, minD); + const dx = p[0] - cx; + const dy = p[1] - cy; + return dx * dx + dy * dy - r < minD * minD ? sdf(p, minD) : minD; + }; } /** @@ -51,20 +51,20 @@ export function withBoundingCircle(sdf: SDFn, ...args: any[]): SDFn { */ export function withBoundingRect(sdf: SDFn, pts: ReadonlyVec[]): SDFn; export function withBoundingRect( - sdf: SDFn, - min: ReadonlyVec, - max: ReadonlyVec + sdf: SDFn, + min: ReadonlyVec, + max: ReadonlyVec ): SDFn; export function withBoundingRect(sdf: SDFn, ...args: any[]): SDFn { - const [min, max] = - args.length === 1 ? bounds2(args[0]) : <[ReadonlyVec, ReadonlyVec]>args; - const centroid = addmN2([], min, max, 0.5); - const hSize = submN2([], max, min, 0.5); - const t = [0, 0]; - return (p, minD = Infinity) => { - if (minD === Infinity) return sdf(p, minD); - return distBox2(sub2(t, p, centroid), hSize) < minD - ? sdf(p, minD) - : minD; - }; + const [min, max] = + args.length === 1 ? bounds2(args[0]) : <[ReadonlyVec, ReadonlyVec]>args; + const centroid = addmN2([], min, max, 0.5); + const hSize = submN2([], max, min, 0.5); + const t = [0, 0]; + return (p, minD = Infinity) => { + if (minD === Infinity) return sdf(p, minD); + return distBox2(sub2(t, p, centroid), hSize) < minD + ? sdf(p, minD) + : minD; + }; } diff --git a/packages/geom-sdf/src/dist.ts b/packages/geom-sdf/src/dist.ts index d01c1d824f..176d4d8c29 100644 --- a/packages/geom-sdf/src/dist.ts +++ b/packages/geom-sdf/src/dist.ts @@ -50,8 +50,8 @@ export const distCircle2 = (p: ReadonlyVec, radius: number) => mag2(p) - radius; * @param halfSize */ export const distBox2 = (p: ReadonlyVec, halfSize: ReadonlyVec) => { - const d = sub2(null, abs2(t1, p), halfSize); - return min(max(d[0], d[1]), 0) + mag2(max2(null, d, ZERO2)); + const d = sub2(null, abs2(t1, p), halfSize); + return min(max(d[0], d[1]), 0) + mag2(max2(null, d, ZERO2)); }; /** @@ -65,19 +65,19 @@ export const distBox2 = (p: ReadonlyVec, halfSize: ReadonlyVec) => { * @param pts */ export const distPolygon2 = (p: ReadonlyVec, pts: ReadonlyVec[]) => { - let d = distSq2(p, pts[0]); - let s = 1; - const py = p[1]; - for (let n = pts.length, i = 0, j = n - 1; i < n; j = i, i++) { - const pi = pts[i]; - const pj = pts[j]; - const e = sub2(t1, pj, pi); - const w = sub2(t2, p, pi); - d = min(d, distSq2(w, mulN2(t3, e, clamp01(dot2(w, e) / magSq2(e))))); - const c = [py >= pi[1], py < pj[1], e[0] * w[1] > e[1] * w[0]]; - if (every3(c) || !some3(c)) s *= -1; - } - return s * sqrt(d); + let d = distSq2(p, pts[0]); + let s = 1; + const py = p[1]; + for (let n = pts.length, i = 0, j = n - 1; i < n; j = i, i++) { + const pi = pts[i]; + const pj = pts[j]; + const e = sub2(t1, pj, pi); + const w = sub2(t2, p, pi); + d = min(d, distSq2(w, mulN2(t3, e, clamp01(dot2(w, e) / magSq2(e))))); + const c = [py >= pi[1], py < pj[1], e[0] * w[1] > e[1] * w[0]]; + if (every3(c) || !some3(c)) s *= -1; + } + return s * sqrt(d); }; /** @@ -92,15 +92,15 @@ export const distPolygon2 = (p: ReadonlyVec, pts: ReadonlyVec[]) => { * @param b */ export const distSegment2 = ( - p: ReadonlyVec, - a: ReadonlyVec, - b: ReadonlyVec + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec ) => { - const pa = sub2(t1, p, a); - const ba = sub2(t2, b, a); - return mag2( - sub2(null, pa, mulN2(null, ba, clamp01(dot2(pa, ba) / magSq2(ba)))) - ); + const pa = sub2(t1, p, a); + const ba = sub2(t2, b, a); + return mag2( + sub2(null, pa, mulN2(null, ba, clamp01(dot2(pa, ba) / magSq2(ba)))) + ); }; /** @@ -111,11 +111,11 @@ export const distSegment2 = ( * @param pts */ export const distPolyline2 = (p: ReadonlyVec, pts: ReadonlyVec[]) => { - let d = Infinity; - for (let i = 1, n = pts.length; i < n; i++) { - d = min(d, distSegment2(p, pts[i - 1], pts[i])); - } - return d; + let d = Infinity; + for (let i = 1, n = pts.length; i < n; i++) { + d = min(d, distSegment2(p, pts[i - 1], pts[i])); + } + return d; }; /** @@ -133,18 +133,18 @@ export const distPolyline2 = (p: ReadonlyVec, pts: ReadonlyVec[]) => { * @param rb - outer radius offset (thickness) */ export const distArc2 = ( - p: ReadonlyVec, - apert: ReadonlyVec, - ra: number, - rb: number + p: ReadonlyVec, + apert: ReadonlyVec, + ra: number, + rb: number ) => { - t1[0] = abs(p[0]); - t1[1] = p[1]; - return ( - (apert[1] * t1[0] > apert[0] * t1[1] - ? mag2(maddN2(t1, apert, -ra, t1)) - : abs(mag2(t1) - ra)) - rb - ); + t1[0] = abs(p[0]); + t1[1] = p[1]; + return ( + (apert[1] * t1[0] > apert[0] * t1[1] + ? mag2(maddN2(t1, apert, -ra, t1)) + : abs(mag2(t1) - ra)) - rb + ); }; /** @@ -163,52 +163,52 @@ export const distArc2 = ( * @param signed */ export const distQuadratic2 = ( - pos: ReadonlyVec, - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - signed = false + pos: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + signed = false ) => { - const aa = sub2(t1, b, a); - const bb = add2(null, maddN2(t2, b, -2, a), c); - const cc = mulN2(t3, aa, 2); - const dd = sub2(t4, a, pos); - const kk = 1 / magSq2(bb); - const kx = kk * dot2(aa, bb); - const ky = (kk * (2 * magSq2(aa) + dot2(dd, bb))) / 3; - const kz = kk * dot2(dd, aa); - const p = ky - kx * kx; - const q = kx * (2 * kx * kx - 3 * ky) + kz; - const p3 = p * p * p; - const q2 = q * q; - let h = q2 + 4 * p3; - if (h >= 0) { - h = sqrt(h); - const x = [(h - q) / 2, (-h - q) / 2]; - const uv = mul2(null, sign2(t1, x), powN2(null, abs2(null, x), 1 / 3)); - const t = clamp01(uv[0] + uv[1] - kx); - const qq = maddN2(null, maddN2(t1, bb, t, cc), t, dd); - return ( - mag2(qq) * - (signed ? sign(cross2(maddN2(bb, bb, 2 * t, cc), qq)) : 1) - ); - } else { - const z = sqrt(-p); - const v = Math.acos(q / (p * z * 2)) / 3; - const m = cos(v); - const n = sin(v) * SQRT3; - const tx = clamp01((m + m) * z - kx); - const ty = clamp01((-n - m) * z - kx); - const qx = maddN2(null, maddN2(t1, bb, tx, cc), tx, dd); - const dx = magSq2(qx); - const qy = maddN2(null, maddN2(t5, bb, ty, cc), ty, dd); - const dy = magSq2(qy); - return dx < dy - ? sqrt(dx) * - (signed ? sign(cross2(maddN2(bb, bb, 2 * tx, cc), qx)) : 1) - : sqrt(dy) * - (signed ? sign(cross2(maddN2(bb, bb, 2 * ty, cc), qy)) : 1); - } + const aa = sub2(t1, b, a); + const bb = add2(null, maddN2(t2, b, -2, a), c); + const cc = mulN2(t3, aa, 2); + const dd = sub2(t4, a, pos); + const kk = 1 / magSq2(bb); + const kx = kk * dot2(aa, bb); + const ky = (kk * (2 * magSq2(aa) + dot2(dd, bb))) / 3; + const kz = kk * dot2(dd, aa); + const p = ky - kx * kx; + const q = kx * (2 * kx * kx - 3 * ky) + kz; + const p3 = p * p * p; + const q2 = q * q; + let h = q2 + 4 * p3; + if (h >= 0) { + h = sqrt(h); + const x = [(h - q) / 2, (-h - q) / 2]; + const uv = mul2(null, sign2(t1, x), powN2(null, abs2(null, x), 1 / 3)); + const t = clamp01(uv[0] + uv[1] - kx); + const qq = maddN2(null, maddN2(t1, bb, t, cc), t, dd); + return ( + mag2(qq) * + (signed ? sign(cross2(maddN2(bb, bb, 2 * t, cc), qq)) : 1) + ); + } else { + const z = sqrt(-p); + const v = Math.acos(q / (p * z * 2)) / 3; + const m = cos(v); + const n = sin(v) * SQRT3; + const tx = clamp01((m + m) * z - kx); + const ty = clamp01((-n - m) * z - kx); + const qx = maddN2(null, maddN2(t1, bb, tx, cc), tx, dd); + const dx = magSq2(qx); + const qy = maddN2(null, maddN2(t5, bb, ty, cc), ty, dd); + const dy = magSq2(qy); + return dx < dy + ? sqrt(dx) * + (signed ? sign(cross2(maddN2(bb, bb, 2 * tx, cc), qx)) : 1) + : sqrt(dy) * + (signed ? sign(cross2(maddN2(bb, bb, 2 * ty, cc), qy)) : 1); + } }; /** @@ -222,48 +222,48 @@ export const distQuadratic2 = ( * @param radii */ export const distEllipse2 = ( - [px, py]: ReadonlyVec, - [abx, aby]: ReadonlyVec + [px, py]: ReadonlyVec, + [abx, aby]: ReadonlyVec ) => { - px = abs(px); - py = abs(py); - if (px > py) { - let t = px; - px = py; - py = t; - t = abx; - abx = aby; - aby = t; - } - const l = aby * aby - abx * abx; - const m = (abx * px) / l; - const m2 = m * m; - const n = (aby * py) / l; - const n2 = n * n; - const c = (m2 + n2 - 1) / 3; - const c3 = c * c * c; - const mn2 = m2 * n2; - const d = c3 + mn2; - const q = c3 + mn2 * 2; - const g = m + m * n2; - let co: number; - if (d < 0) { - const h = Math.acos(q / c3) / 3; - const s = cos(h); - const t = sin(h) * SQRT3; - const rx = sqrt(-c * (s + t + 2) + m2); - const ry = sqrt(-c * (s - t + 2) + m2); - co = (ry + sign(l) * rx + abs(g) / (rx * ry) - m) / 2; - } else { - const h = 2 * m * n * sqrt(d); - const s = sign(q + h) * Math.cbrt(abs(q + h)); - const u = sign(q - h) * Math.cbrt(abs(q - h)); - const rx = -s - u - c * 4 + 2 * m2; - const ry = (s - u) * SQRT3; - const rm = Math.hypot(rx, ry); - co = (ry / sqrt(rm - rx) + (2 * g) / rm - m) / 2; - } - const rx = abx * co; - const ry = aby * sqrt(1 - co * co); - return Math.hypot(rx - px, ry - py) * sign(py - ry); + px = abs(px); + py = abs(py); + if (px > py) { + let t = px; + px = py; + py = t; + t = abx; + abx = aby; + aby = t; + } + const l = aby * aby - abx * abx; + const m = (abx * px) / l; + const m2 = m * m; + const n = (aby * py) / l; + const n2 = n * n; + const c = (m2 + n2 - 1) / 3; + const c3 = c * c * c; + const mn2 = m2 * n2; + const d = c3 + mn2; + const q = c3 + mn2 * 2; + const g = m + m * n2; + let co: number; + if (d < 0) { + const h = Math.acos(q / c3) / 3; + const s = cos(h); + const t = sin(h) * SQRT3; + const rx = sqrt(-c * (s + t + 2) + m2); + const ry = sqrt(-c * (s - t + 2) + m2); + co = (ry + sign(l) * rx + abs(g) / (rx * ry) - m) / 2; + } else { + const h = 2 * m * n * sqrt(d); + const s = sign(q + h) * Math.cbrt(abs(q + h)); + const u = sign(q - h) * Math.cbrt(abs(q - h)); + const rx = -s - u - c * 4 + 2 * m2; + const ry = (s - u) * SQRT3; + const rm = Math.hypot(rx, ry); + co = (ry / sqrt(rm - rx) + (2 * g) / rm - m) / 2; + } + const rx = abx * co; + const ry = aby * sqrt(1 - co * co); + return Math.hypot(rx - px, ry - py) * sign(py - ry); }; diff --git a/packages/geom-sdf/src/domain.ts b/packages/geom-sdf/src/domain.ts index 9fd8959283..42d325df39 100644 --- a/packages/geom-sdf/src/domain.ts +++ b/packages/geom-sdf/src/domain.ts @@ -21,9 +21,9 @@ import { subN2 } from "@thi.ng/vectors/subn"; * @param size */ export const repeat2 = (size: ReadonlyVec) => { - const s2 = mulN2([], size, 0.5); - return (p: ReadonlyVec) => - sub2(null, mod2(null, add2([], s2, p), size), s2); + const s2 = mulN2([], size, 0.5); + return (p: ReadonlyVec) => + sub2(null, mod2(null, add2([], s2, p), size), s2); }; /** @@ -34,26 +34,26 @@ export const repeat2 = (size: ReadonlyVec) => { * @param size */ export const repeatMirror2 = (size: ReadonlyVec) => { - const c: Vec = []; - const s2 = mulN2([], size, 0.5); - return (p: ReadonlyVec) => - mul2( - null, - sub2(null, mod2(null, add2([], s2, p), size), s2), - subN2( - null, - mulN2( - null, - modN2( - null, - floor2(null, div2(null, add2(c, s2, p), size)), - 2 - ), - 2 - ), - 1 - ) - ); + const c: Vec = []; + const s2 = mulN2([], size, 0.5); + return (p: ReadonlyVec) => + mul2( + null, + sub2(null, mod2(null, add2([], s2, p), size), s2), + subN2( + null, + mulN2( + null, + modN2( + null, + floor2(null, div2(null, add2(c, s2, p), size)), + 2 + ), + 2 + ), + 1 + ) + ); }; /** @@ -64,12 +64,12 @@ export const repeatMirror2 = (size: ReadonlyVec) => { * @param size */ export const repeatGrid2 = (size: ReadonlyVec) => { - const s2 = mulN2([], size, 0.5); - const rm = repeatMirror2(size); - return (p: ReadonlyVec) => { - p = sub2(null, rm(p), s2); - return p[0] > p[1] ? [p[1], p[0]] : p; - }; + const s2 = mulN2([], size, 0.5); + const rm = repeatMirror2(size); + return (p: ReadonlyVec) => { + p = sub2(null, rm(p), s2); + return p[0] > p[1] ? [p[1], p[0]] : p; + }; }; /** @@ -80,11 +80,11 @@ export const repeatGrid2 = (size: ReadonlyVec) => { * @param n - number of repetition */ export const repeatPolar2 = (n: number) => { - const theta = TAU / n; - const halfTheta = theta / 2; - return (p: ReadonlyVec) => - cossin( - mod(Math.atan2(p[1], p[0]) + halfTheta, theta) - halfTheta, - mag(p) - ); + const theta = TAU / n; + const halfTheta = theta / 2; + return (p: ReadonlyVec) => + cossin( + mod(Math.atan2(p[1], p[0]) + halfTheta, theta) - halfTheta, + mag(p) + ); }; diff --git a/packages/geom-sdf/src/ops.ts b/packages/geom-sdf/src/ops.ts index 1d8651cd7b..ce1c757c79 100644 --- a/packages/geom-sdf/src/ops.ts +++ b/packages/geom-sdf/src/ops.ts @@ -7,41 +7,41 @@ import type { FieldCoeff, SDFn } from "./api.js"; const __asField = (k: T | FieldCoeff) => (isFunction(k) ? k : () => k); const __ensureChildren = (children: SDFn[], min = 1) => - assert(children.length >= 1, `require at least ${min} SDF(s)`); + assert(children.length >= 1, `require at least ${min} SDF(s)`); const { min, max } = Math; export const abs = - (sdf: SDFn): SDFn => - (p, minD?: number) => - Math.abs(sdf(p, minD)); + (sdf: SDFn): SDFn => + (p, minD?: number) => + Math.abs(sdf(p, minD)); export const flip = - (sdf: SDFn): SDFn => - (p, minD?: number) => - -sdf(p, minD); + (sdf: SDFn): SDFn => + (p, minD?: number) => + -sdf(p, minD); export const offset = (sdf: SDFn, r: number | FieldCoeff): SDFn => - isFunction(r) - ? (p, minD?: number) => sdf(p, minD) - r(p) - : (p, minD?: number) => sdf(p, minD) - r; + isFunction(r) + ? (p, minD?: number) => sdf(p, minD) - r(p) + : (p, minD?: number) => sdf(p, minD) - r; export const defOp = - (op: FnN2) => - (children: SDFn[]): SDFn => { - __ensureChildren(children); - const n = children.length; - return (p, minD = Infinity) => { - let res = children[0](p, minD); - if (res < minD) minD = res; - for (let i = 1; i < n; i++) { - const d = children[i](p, minD); - if (d < minD) minD = d; - res = op(res, d); - } - return res; - }; - }; + (op: FnN2) => + (children: SDFn[]): SDFn => { + __ensureChildren(children); + const n = children.length; + return (p, minD = Infinity) => { + let res = children[0](p, minD); + if (res < minD) minD = res; + for (let i = 1; i < n; i++) { + const d = children[i](p, minD); + if (d < minD) minD = d; + res = op(res, d); + } + return res; + }; + }; export const union = defOp(min); @@ -50,75 +50,75 @@ export const isec = defOp(max); export const diff = defOp((a, b) => max(a, -b)); export const defParamOp = - (op: Fn3) => - (k: T | FieldCoeff, children: SDFn[]): SDFn => { - __ensureChildren(children); - const kfield = __asField(k); - const n = children.length; - return (p, minD = Infinity) => { - const $k = kfield(p); - let res = children[0](p, minD); - if (res < minD) minD = res; - for (let i = 1; i < n; i++) { - const d = children[i](p, minD); - if (d < minD) minD = d; - res = op(res, d, $k); - } - return res; - }; - }; + (op: Fn3) => + (k: T | FieldCoeff, children: SDFn[]): SDFn => { + __ensureChildren(children); + const kfield = __asField(k); + const n = children.length; + return (p, minD = Infinity) => { + const $k = kfield(p); + let res = children[0](p, minD); + if (res < minD) minD = res; + for (let i = 1; i < n; i++) { + const d = children[i](p, minD); + if (d < minD) minD = d; + res = op(res, d, $k); + } + return res; + }; + }; export const smoothUnion = defParamOp((a, b, k) => { - const h = clamp01(0.5 + (0.5 * (b - a)) / k); - return mix(b, a, h) - k * h * (1 - h); + const h = clamp01(0.5 + (0.5 * (b - a)) / k); + return mix(b, a, h) - k * h * (1 - h); }); export const smoothIsec = defParamOp((a, b, k) => { - const h = clamp01(0.5 - (0.5 * (b - a)) / k); - return mix(b, a, h) + k * h * (1 - h); + const h = clamp01(0.5 - (0.5 * (b - a)) / k); + return mix(b, a, h) + k * h * (1 - h); }); export const smoothDiff = defParamOp((a, b, k) => { - const h = clamp01(0.5 - (0.5 * (a + b)) / k); - return mix(a, -b, h) + k * h * (1 - h); + const h = clamp01(0.5 - (0.5 * (a + b)) / k); + return mix(a, -b, h) + k * h * (1 - h); }); export const chamferUnion = defParamOp((a, b, k) => - min(min(a, b), (a - k + b) * SQRT2_2) + min(min(a, b), (a - k + b) * SQRT2_2) ); export const chamferIsec = defParamOp((a, b, k) => - max(max(a, b), (a + k + b) * SQRT2_2) + max(max(a, b), (a + k + b) * SQRT2_2) ); export const chamferDiff = defParamOp((a, b, k) => - max(max(a, -b), (a + k - b) * SQRT2_2) + max(max(a, -b), (a + k - b) * SQRT2_2) ); export const roundUnion = defParamOp( - (a, b, k) => max(k, min(a, b)) - Math.hypot(max(k - a, 0), max(k - b, 0)) + (a, b, k) => max(k, min(a, b)) - Math.hypot(max(k - a, 0), max(k - b, 0)) ); export const roundIsec = defParamOp( - (a, b, k) => min(-k, max(a, b)) + Math.hypot(max(k + a, 0), max(k + b, 0)) + (a, b, k) => min(-k, max(a, b)) + Math.hypot(max(k + a, 0), max(k + b, 0)) ); export const roundDiff = defParamOp( - (a, b, k) => min(-k, max(a, -b)) + Math.hypot(max(k + a, 0), max(k - b, 0)) + (a, b, k) => min(-k, max(a, -b)) + Math.hypot(max(k + a, 0), max(k - b, 0)) ); const __steps = (a: number, b: number, [r, n]: [number, number]) => { - const s = r / n; - const u = b - r; - return min(min(a, b), 0.5 * (u + a + Math.abs(mod(u - a + s, 2 * s) - s))); + const s = r / n; + const u = b - r; + return min(min(a, b), 0.5 * (u + a + Math.abs(mod(u - a + s, 2 * s) - s))); }; export const stepUnion = defParamOp(__steps); export const stepIsec = defParamOp<[number, number]>( - (a, b, k) => -__steps(-a, -b, k) + (a, b, k) => -__steps(-a, -b, k) ); export const stepDiff = defParamOp<[number, number]>( - (a, b, k) => -__steps(-a, b, k) + (a, b, k) => -__steps(-a, b, k) ); diff --git a/packages/geom-sdf/src/sample.ts b/packages/geom-sdf/src/sample.ts index 19a8105e23..63282b0eb4 100644 --- a/packages/geom-sdf/src/sample.ts +++ b/packages/geom-sdf/src/sample.ts @@ -17,26 +17,26 @@ import type { SDFn } from "./api.js"; * @param buf */ export const sample2d = ( - sdf: SDFn, - { pos: [px, py], size: [width, height] }: AABBLike, - [resX, resY]: ReadonlyVec, - domain?: Fn, - buf?: NumericArray + sdf: SDFn, + { pos: [px, py], size: [width, height] }: AABBLike, + [resX, resY]: ReadonlyVec, + domain?: Fn, + buf?: NumericArray ) => { - if (buf) { - assert(buf.length >= resX * resY, "insufficient buffer size"); - } else { - buf = new Float32Array(resX * resY); - } - const dx = width / (resX - 1); - const dy = height / (resY - 1); - const p = [0, 0]; - for (let y = 0, i = 0; y < resY; y++) { - p[1] = py + y * dy; - for (let x = 0; x < resX; x++, i++) { - p[0] = px + x * dx; - buf[i] = sdf(domain ? domain(p) : p); - } - } - return buf; + if (buf) { + assert(buf.length >= resX * resY, "insufficient buffer size"); + } else { + buf = new Float32Array(resX * resY); + } + const dx = width / (resX - 1); + const dy = height / (resY - 1); + const p = [0, 0]; + for (let y = 0, i = 0; y < resY; y++) { + p[1] = py + y * dy; + for (let x = 0; x < resX; x++, i++) { + p[0] = px + x * dx; + buf[i] = sdf(domain ? domain(p) : p); + } + } + return buf; }; diff --git a/packages/geom-sdf/src/shapes.ts b/packages/geom-sdf/src/shapes.ts index df07e7a2bb..0c54aecfcc 100644 --- a/packages/geom-sdf/src/shapes.ts +++ b/packages/geom-sdf/src/shapes.ts @@ -4,27 +4,27 @@ import { sub2 } from "@thi.ng/vectors/sub"; import type { SDFAttribs, SDFn } from "./api.js"; import { withBoundingCircle } from "./bounds.js"; import { - distArc2, - distBox2, - distCircle2, - distEllipse2, - distPolygon2, - distPolyline2, - distQuadratic2, - distSegment2, + distArc2, + distBox2, + distCircle2, + distEllipse2, + distPolygon2, + distPolyline2, + distQuadratic2, + distSegment2, } from "./dist.js"; import { abs as $abs, flip as $flip, offset as $offset } from "./ops.js"; /** @internal */ export const DEFAULT_ATTRIBS: SDFAttribs = { - abs: false, - bounds: false, - chamfer: 0, - combine: "union", - flip: false, - offset: 0, - round: 0, - smooth: 0, + abs: false, + bounds: false, + chamfer: 0, + combine: "union", + flip: false, + offset: 0, + round: 0, + smooth: 0, }; /** @@ -38,87 +38,87 @@ export const DEFAULT_ATTRIBS: SDFAttribs = { * @param attribs */ export const withSDFAttribs = (fn: SDFn, attribs?: Partial) => { - if (attribs) { - const { abs, flip, offset } = { ...DEFAULT_ATTRIBS, ...attribs }; - if (abs) fn = $abs(fn); - if (isFunction(offset) || offset > 0) fn = $offset(fn, offset); - if (flip) fn = $flip(fn); - } - return fn; + if (attribs) { + const { abs, flip, offset } = { ...DEFAULT_ATTRIBS, ...attribs }; + if (abs) fn = $abs(fn); + if (isFunction(offset) || offset > 0) fn = $offset(fn, offset); + if (flip) fn = $flip(fn); + } + return fn; }; export const arc2 = ( - center: ReadonlyVec, - apert: ReadonlyVec, - ra: number, - rb: number, - attribs: Partial = {} + center: ReadonlyVec, + apert: ReadonlyVec, + ra: number, + rb: number, + attribs: Partial = {} ): SDFn => { - const sdf = withSDFAttribs( - (p) => distArc2(sub2([], p, center), apert, ra, rb), - attribs - ); - return attribs.bounds ? withBoundingCircle(sdf, center, ra + rb) : sdf; + const sdf = withSDFAttribs( + (p) => distArc2(sub2([], p, center), apert, ra, rb), + attribs + ); + return attribs.bounds ? withBoundingCircle(sdf, center, ra + rb) : sdf; }; export const box2 = ( - center: ReadonlyVec, - size: ReadonlyVec, - attribs?: Partial + center: ReadonlyVec, + size: ReadonlyVec, + attribs?: Partial ): SDFn => withSDFAttribs((p) => distBox2(sub2([], p, center), size), attribs); export const circle2 = ( - center: ReadonlyVec, - radius: number, - attribs?: Partial + center: ReadonlyVec, + radius: number, + attribs?: Partial ): SDFn => - withSDFAttribs((p) => distCircle2(sub2([], p, center), radius), attribs); + withSDFAttribs((p) => distCircle2(sub2([], p, center), radius), attribs); export const ellipse2 = ( - center: ReadonlyVec, - radii: ReadonlyVec, - attribs: Partial = {} + center: ReadonlyVec, + radii: ReadonlyVec, + attribs: Partial = {} ): SDFn => { - const sdf = withSDFAttribs( - (p) => distEllipse2(sub2([], p, center), radii), - attribs - ); - return attribs.bounds - ? withBoundingCircle(sdf, center, Math.max(...radii)) - : sdf; + const sdf = withSDFAttribs( + (p) => distEllipse2(sub2([], p, center), radii), + attribs + ); + return attribs.bounds + ? withBoundingCircle(sdf, center, Math.max(...radii)) + : sdf; }; export const line2 = ( - a: ReadonlyVec, - b: ReadonlyVec, - attribs?: Partial + a: ReadonlyVec, + b: ReadonlyVec, + attribs?: Partial ): SDFn => withSDFAttribs((p) => distSegment2(p, a, b), attribs); export const points2 = ( - pts: ReadonlyVec[], - attribs: Partial = {} + pts: ReadonlyVec[], + attribs: Partial = {} ): SDFn => { - const sdf = withSDFAttribs( - (p) => pts.reduce((acc, q) => Math.min(acc, distSq2(p, q)), Infinity), - attribs - ); - return attribs.bounds ? withBoundingCircle(sdf, pts) : sdf; + const sdf = withSDFAttribs( + (p) => pts.reduce((acc, q) => Math.min(acc, distSq2(p, q)), Infinity), + attribs + ); + return attribs.bounds ? withBoundingCircle(sdf, pts) : sdf; }; export const polygon2 = ( - pts: ReadonlyVec[], - attribs: Partial = {} + pts: ReadonlyVec[], + attribs: Partial = {} ): SDFn => { - const sdf = withSDFAttribs((p) => distPolygon2(p, pts), attribs); - return attribs.bounds ? withBoundingCircle(sdf, pts) : sdf; + const sdf = withSDFAttribs((p) => distPolygon2(p, pts), attribs); + return attribs.bounds ? withBoundingCircle(sdf, pts) : sdf; }; export const polyline2 = ( - pts: ReadonlyVec[], - attribs: Partial = {} + pts: ReadonlyVec[], + attribs: Partial = {} ): SDFn => { - const sdf = withSDFAttribs((p) => distPolyline2(p, pts), attribs); - return attribs.bounds ? withBoundingCircle(sdf, pts) : sdf; + const sdf = withSDFAttribs((p) => distPolyline2(p, pts), attribs); + return attribs.bounds ? withBoundingCircle(sdf, pts) : sdf; }; /** @@ -136,14 +136,14 @@ export const polyline2 = ( * @param attribs */ export const quadratic2 = ( - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - attribs?: Partial + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + attribs?: Partial ): SDFn => - withSDFAttribs( - !attribs || attribs.abs !== false - ? (p) => distQuadratic2(p, a, b, c) - : (p) => distQuadratic2(p, a, b, c, true), - { ...attribs, abs: false } - ); + withSDFAttribs( + !attribs || attribs.abs !== false + ? (p) => distQuadratic2(p, a, b, c) + : (p) => distQuadratic2(p, a, b, c, true), + { ...attribs, abs: false } + ); diff --git a/packages/geom-sdf/test/index.ts b/packages/geom-sdf/test/index.ts index 61529df943..5b1368997c 100644 --- a/packages/geom-sdf/test/index.ts +++ b/packages/geom-sdf/test/index.ts @@ -2,6 +2,4 @@ import { group } from "@thi.ng/testament"; // import * as assert from "assert"; // import { } from "../src"; -group("geom-sdf", { - -}); +group("geom-sdf", {}); diff --git a/packages/geom-sdf/tools/combinators.ts b/packages/geom-sdf/tools/combinators.ts index 80d1953715..61a37da44f 100644 --- a/packages/geom-sdf/tools/combinators.ts +++ b/packages/geom-sdf/tools/combinators.ts @@ -7,46 +7,46 @@ const RES = [512, 512]; const margin = 36; for (let op of ["none", "chamfer", "round", "smooth", "steps"]) { - for (let mode of ["union", "isec", "diff"]) { - const __sdf = >{ combine: mode }; - if (op !== "none") __sdf[op] = (op === "steps" ? [50, 4] : 50); - const scene = group( - { - stroke: [0.5, 0, 1, 0.5], - weight: 3, - __sdf, - }, - [rectFromCentroid([-33, -33], 200), rectFromCentroid([33, 33], 200)] - ); + for (let mode of ["union", "isec", "diff"]) { + const __sdf = >{ combine: mode }; + if (op !== "none") __sdf[op] = (op === "steps" ? [50, 4] : 50); + const scene = group( + { + stroke: [0.5, 0, 1, 0.5], + weight: 3, + __sdf, + }, + [rectFromCentroid([-33, -33], 200), rectFromCentroid([33, 33], 200)] + ); - const sceneBounds = bounds(scene, margin)!; - const sdf = asSDF(scene); - const img = sample2d(sdf, sceneBounds, RES); - const polys = asPolygons( - img, - sceneBounds, - RES, - range(0, margin, 4), - 0.25 - ); + const sceneBounds = bounds(scene, margin)!; + const sdf = asSDF(scene); + const img = sample2d(sdf, sceneBounds, RES); + const polys = asPolygons( + img, + sceneBounds, + RES, + range(0, margin, 4), + 0.25 + ); - const path = `export/rect-${op}-${mode}.svg`; - console.log(path); + const path = `export/rect-${op}-${mode}.svg`; + console.log(path); - writeText( - path, - asSvg( - svgDoc( - { - width: 280, - height: 280, - viewBox: "-180 -180 360 360", - fill: "none", - }, - group({ stroke: "#000", weight: 0.5 }, polys), - scene - ) - ) - ); - } + writeText( + path, + asSvg( + svgDoc( + { + width: 280, + height: 280, + viewBox: "-180 -180 360 360", + fill: "none", + }, + group({ stroke: "#000", weight: 0.5 }, polys), + scene + ) + ) + ); + } } diff --git a/packages/geom-sdf/tools/index.ts b/packages/geom-sdf/tools/index.ts index a5d6482db4..e9f39331fe 100644 --- a/packages/geom-sdf/tools/index.ts +++ b/packages/geom-sdf/tools/index.ts @@ -1,12 +1,12 @@ import { writeText } from "@thi.ng/file-io"; import { - asSvg, - bounds, - circle, - group, - Polygon, - simplify, - svgDoc, + asSvg, + bounds, + circle, + group, + Polygon, + simplify, + svgDoc, } from "@thi.ng/geom"; import { asPolygons, asSDF, sample2d } from "@thi.ng/geom-sdf"; import { XsAdd } from "@thi.ng/random"; @@ -17,34 +17,34 @@ const RND = new XsAdd(0xdecafbad); const RES = [512, 512]; const scene = group({ stroke: "red", __sdf: { smooth: 20 } }, [ - ...repeatedly( - () => - circle( - randMinMax2([], [-100, -100], [100, 100], RND), - RND.minmax(5, 20) - ), - 20 - ), + ...repeatedly( + () => + circle( + randMinMax2([], [-100, -100], [100, 100], RND), + RND.minmax(5, 20) + ), + 20 + ), ]); const sceneBounds = bounds(scene, 40)!; const sdf = sample2d(asSDF(scene), sceneBounds, RES); const polys = asPolygons(sdf, sceneBounds, RES, range(0, 32, 4)).map( - (p) => simplify(p, 0.25) + (p) => simplify(p, 0.25) ); console.log(polys.map((p) => p.points.length)); writeText( - "export/points.svg", - asSvg( - svgDoc( - { fill: "none" }, - // contours - group({ stroke: "#000", weight: 0.5 }, polys), - // original scene - scene - ) - ) + "export/points.svg", + asSvg( + svgDoc( + { fill: "none" }, + // contours + group({ stroke: "#000", weight: 0.5 }, polys), + // original scene + scene + ) + ) ); diff --git a/packages/geom-sdf/tsconfig.json b/packages/geom-sdf/tsconfig.json index bd6481a5a6..e19642bf9a 100644 --- a/packages/geom-sdf/tsconfig.json +++ b/packages/geom-sdf/tsconfig.json @@ -1,9 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": [ - "./src/**/*.ts" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-splines/api-extractor.json b/packages/geom-splines/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-splines/api-extractor.json +++ b/packages/geom-splines/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-splines/package.json b/packages/geom-splines/package.json index 31245189ad..c69c9d18e4 100644 --- a/packages/geom-splines/package.json +++ b/packages/geom-splines/package.json @@ -1,147 +1,147 @@ { - "name": "@thi.ng/geom-splines", - "version": "2.1.19", - "description": "nD cubic & quadratic curve analysis, conversion, interpolation, splitting", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-splines#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc internal", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/geom-api": "^3.3.2", - "@thi.ng/geom-arc": "^2.1.19", - "@thi.ng/geom-resample": "^2.1.19", - "@thi.ng/math": "^5.3.4", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "3d", - "arc", - "bbox", - "bezier", - "conversion", - "cubic", - "curve", - "elliptic", - "geometry", - "interpolation", - "nd", - "proximity", - "quadratic", - "sample", - "shape", - "spline", - "split", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "internal" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./cubic-arc": { - "default": "./cubic-arc.js" - }, - "./cubic-bounds": { - "default": "./cubic-bounds.js" - }, - "./cubic-closest-point": { - "default": "./cubic-closest-point.js" - }, - "./cubic-from-breakpoints": { - "default": "./cubic-from-breakpoints.js" - }, - "./cubic-from-controlpoints": { - "default": "./cubic-from-controlpoints.js" - }, - "./cubic-line": { - "default": "./cubic-line.js" - }, - "./cubic-quadratic": { - "default": "./cubic-quadratic.js" - }, - "./cubic-sample": { - "default": "./cubic-sample.js" - }, - "./cubic-split": { - "default": "./cubic-split.js" - }, - "./cubic-tangent": { - "default": "./cubic-tangent.js" - }, - "./point-at": { - "default": "./point-at.js" - }, - "./quadratic-bounds": { - "default": "./quadratic-bounds.js" - }, - "./quadratic-closest-point": { - "default": "./quadratic-closest-point.js" - }, - "./quadratic-line": { - "default": "./quadratic-line.js" - }, - "./quadratic-sample": { - "default": "./quadratic-sample.js" - }, - "./quadratic-split": { - "default": "./quadratic-split.js" - }, - "./quadratic-tangent": { - "default": "./quadratic-tangent.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "related": [ - "geom-subdiv-curve" - ] - } + "name": "@thi.ng/geom-splines", + "version": "2.1.19", + "description": "nD cubic & quadratic curve analysis, conversion, interpolation, splitting", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-splines#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc internal", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/geom-api": "^3.3.2", + "@thi.ng/geom-arc": "^2.1.19", + "@thi.ng/geom-resample": "^2.1.19", + "@thi.ng/math": "^5.3.4", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "3d", + "arc", + "bbox", + "bezier", + "conversion", + "cubic", + "curve", + "elliptic", + "geometry", + "interpolation", + "nd", + "proximity", + "quadratic", + "sample", + "shape", + "spline", + "split", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "internal" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./cubic-arc": { + "default": "./cubic-arc.js" + }, + "./cubic-bounds": { + "default": "./cubic-bounds.js" + }, + "./cubic-closest-point": { + "default": "./cubic-closest-point.js" + }, + "./cubic-from-breakpoints": { + "default": "./cubic-from-breakpoints.js" + }, + "./cubic-from-controlpoints": { + "default": "./cubic-from-controlpoints.js" + }, + "./cubic-line": { + "default": "./cubic-line.js" + }, + "./cubic-quadratic": { + "default": "./cubic-quadratic.js" + }, + "./cubic-sample": { + "default": "./cubic-sample.js" + }, + "./cubic-split": { + "default": "./cubic-split.js" + }, + "./cubic-tangent": { + "default": "./cubic-tangent.js" + }, + "./point-at": { + "default": "./point-at.js" + }, + "./quadratic-bounds": { + "default": "./quadratic-bounds.js" + }, + "./quadratic-closest-point": { + "default": "./quadratic-closest-point.js" + }, + "./quadratic-line": { + "default": "./quadratic-line.js" + }, + "./quadratic-sample": { + "default": "./quadratic-sample.js" + }, + "./quadratic-split": { + "default": "./quadratic-split.js" + }, + "./quadratic-tangent": { + "default": "./quadratic-tangent.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "related": [ + "geom-subdiv-curve" + ] + } } diff --git a/packages/geom-splines/src/cubic-arc.ts b/packages/geom-splines/src/cubic-arc.ts index e56105bf8f..6abad7e8c4 100644 --- a/packages/geom-splines/src/cubic-arc.ts +++ b/packages/geom-splines/src/cubic-arc.ts @@ -21,45 +21,45 @@ import { cubicFromLine } from "./cubic-line.js"; * @param end - end angle (radians) */ export const cubicFromArc = ( - pos: ReadonlyVec, - r: ReadonlyVec, - axis: number, - start: number, - end: number + pos: ReadonlyVec, + r: ReadonlyVec, + axis: number, + start: number, + end: number ) => { - const p = pointAtTheta(pos, r, axis, start); - const q = pointAtTheta(pos, r, axis, end); - const delta = end - start; - const [rx, ry] = r; - const [s, c] = sincos(axis); - const dx = (c * (p[0] - q[0])) / 2 + (s * (p[1] - q[1])) / 2; - const dy = (-s * (p[0] - q[0])) / 2 + (c * (p[1] - q[1])) / 2; - if ((Math.abs(delta) < PI && dx === 0 && dy === 0) || magSq2(r) < EPS) { - return [cubicFromLine(p, q)]; - } + const p = pointAtTheta(pos, r, axis, start); + const q = pointAtTheta(pos, r, axis, end); + const delta = end - start; + const [rx, ry] = r; + const [s, c] = sincos(axis); + const dx = (c * (p[0] - q[0])) / 2 + (s * (p[1] - q[1])) / 2; + const dy = (-s * (p[0] - q[0])) / 2 + (c * (p[1] - q[1])) / 2; + if ((Math.abs(delta) < PI && dx === 0 && dy === 0) || magSq2(r) < EPS) { + return [cubicFromLine(p, q)]; + } - const mapP: FnU2 = (x, y) => { - x *= rx; - y *= ry; - return [c * x - s * y + pos[0], s * x + c * y + pos[1]]; - }; + const mapP: FnU2 = (x, y) => { + x *= rx; + y *= ry; + return [c * x - s * y + pos[0], s * x + c * y + pos[1]]; + }; - const res: Vec[][] = []; - const n = Math.ceil(roundEps(Math.abs(delta) / HALF_PI, 1e-3)); - const d = delta / n; - const t = (8 / 6) * Math.tan(0.25 * d); - if (!isFinite(t)) { - return [cubicFromLine(p, q)]; - } - for (let i = n, theta = start, sc = sincos(theta); i > 0; i--, theta += d) { - const [s1, c1] = sc; - const [s2, c2] = (sc = sincos(theta + d)); - res.push([ - mapP(c1, s1), - mapP(c1 - s1 * t, s1 + c1 * t), - mapP(c2 + s2 * t, s2 - c2 * t), - mapP(c2, s2), - ]); - } - return res; + const res: Vec[][] = []; + const n = Math.ceil(roundEps(Math.abs(delta) / HALF_PI, 1e-3)); + const d = delta / n; + const t = (8 / 6) * Math.tan(0.25 * d); + if (!isFinite(t)) { + return [cubicFromLine(p, q)]; + } + for (let i = n, theta = start, sc = sincos(theta); i > 0; i--, theta += d) { + const [s1, c1] = sc; + const [s2, c2] = (sc = sincos(theta + d)); + res.push([ + mapP(c1, s1), + mapP(c1 - s1 * t, s1 + c1 * t), + mapP(c2 + s2 * t, s2 - c2 * t), + mapP(c2, s2), + ]); + } + return res; }; diff --git a/packages/geom-splines/src/cubic-bounds.ts b/packages/geom-splines/src/cubic-bounds.ts index d8287c0c9d..3d9a404e35 100644 --- a/packages/geom-splines/src/cubic-bounds.ts +++ b/packages/geom-splines/src/cubic-bounds.ts @@ -17,43 +17,43 @@ import type { ReadonlyVec, Vec, VecPair } from "@thi.ng/vectors"; * @param pd - control point 4 */ const axisBounds = ( - min: Vec, - max: Vec, - i: number, - pa: number, - pb: number, - pc: number, - pd: number + min: Vec, + max: Vec, + i: number, + pa: number, + pb: number, + pc: number, + pd: number ) => { - min[i] = Math.min(pa, pd); - max[i] = Math.max(pa, pd); + min[i] = Math.min(pa, pd); + max[i] = Math.max(pa, pd); - const k0 = -pa + pb; - const k1 = pa - 2 * pb + pc; - const k2 = -pa + 3 * pb - 3 * pc + pd; - let h = k1 * k1 - k0 * k2; + const k0 = -pa + pb; + const k1 = pa - 2 * pb + pc; + const k2 = -pa + 3 * pb - 3 * pc + pd; + let h = k1 * k1 - k0 * k2; - if (h > 0) { - h = Math.sqrt(h); + if (h > 0) { + h = Math.sqrt(h); - const update = (t: number) => { - if (t > 0 && t < 1) { - const q = mixCubic(pa, pb, pc, pd, t); - min[i] = Math.min(min[i], q); - max[i] = Math.max(max[i], q); - } - }; + const update = (t: number) => { + if (t > 0 && t < 1) { + const q = mixCubic(pa, pb, pc, pd, t); + min[i] = Math.min(min[i], q); + max[i] = Math.max(max[i], q); + } + }; - update(k0 / (-k1 - h)); - update(k0 / (-k1 + h)); - } + update(k0 / (-k1 - h)); + update(k0 / (-k1 + h)); + } }; export const cubicBounds: FnU4 = (a, b, c, d) => { - const min: Vec = []; - const max: Vec = []; - for (let i = a.length; i-- > 0; ) { - axisBounds(min, max, i, a[i], b[i], c[i], d[i]); - } - return [min, max]; + const min: Vec = []; + const max: Vec = []; + for (let i = a.length; i-- > 0; ) { + axisBounds(min, max, i, a[i], b[i], c[i], d[i]); + } + return [min, max]; }; diff --git a/packages/geom-splines/src/cubic-closest-point.ts b/packages/geom-splines/src/cubic-closest-point.ts index 1974a9e2e1..2498206f00 100644 --- a/packages/geom-splines/src/cubic-closest-point.ts +++ b/packages/geom-splines/src/cubic-closest-point.ts @@ -22,16 +22,16 @@ import { mixCubic } from "@thi.ng/vectors/mix-cubic"; * @param eps - epsilon value */ export const closestPointCubic = ( - p: ReadonlyVec, - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - d: ReadonlyVec, - out: Vec = [], - res?: number, - iter?: number, - eps?: number + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + d: ReadonlyVec, + out: Vec = [], + res?: number, + iter?: number, + eps?: number ) => { - const fn = (t: number) => mixCubic(out, a, b, c, d, t); - return fn(minError(fn, distSq, p, res, iter, 0, 1, eps)); + const fn = (t: number) => mixCubic(out, a, b, c, d, t); + return fn(minError(fn, distSq, p, res, iter, 0, 1, eps)); }; diff --git a/packages/geom-splines/src/cubic-from-breakpoints.ts b/packages/geom-splines/src/cubic-from-breakpoints.ts index 2bcc4d1ac7..3d1c8ce60b 100644 --- a/packages/geom-splines/src/cubic-from-breakpoints.ts +++ b/packages/geom-splines/src/cubic-from-breakpoints.ts @@ -9,57 +9,57 @@ import { perpendicularCW } from "@thi.ng/vectors/perpendicular"; import { set } from "@thi.ng/vectors/set"; const buildSegments = (tangents: Vec[][], t: number, uniform: boolean) => { - const res: Vec[][] = []; - for (let i = 0, num = tangents.length - 1; i < num; i++) { - const [a, na] = tangents[i]; - const [b, nb] = tangents[i + 1]; - const d = uniform ? t : t * dist(a, b); - res.push([a, maddN([], na, d, a), maddN([], nb, -d, b), b]); - } - return res; + const res: Vec[][] = []; + for (let i = 0, num = tangents.length - 1; i < num; i++) { + const [a, na] = tangents[i]; + const [b, nb] = tangents[i + 1]; + const d = uniform ? t : t * dist(a, b); + res.push([a, maddN([], na, d, a), maddN([], nb, -d, b), b]); + } + return res; }; export const closedCubicFromBreakPoints = ( - points: ReadonlyVec[], - t = 1 / 3, - uniform = false + points: ReadonlyVec[], + t = 1 / 3, + uniform = false ) => { - const tangents: Vec[][] = []; - for (let num = points.length, i = num - 1, j = 0; j < num; i = j, j++) { - const a = points[i]; - const b = points[j]; - const c = points[(j + 1) % num]; - const n = mulN( - null, - perpendicularCW(null, cornerBisector([], a, b, c)), - corner2(a, b, c) - ); - tangents.push([set([], b), n]); - } - tangents.push(tangents[0]); - return buildSegments(tangents, t, uniform); + const tangents: Vec[][] = []; + for (let num = points.length, i = num - 1, j = 0; j < num; i = j, j++) { + const a = points[i]; + const b = points[j]; + const c = points[(j + 1) % num]; + const n = mulN( + null, + perpendicularCW(null, cornerBisector([], a, b, c)), + corner2(a, b, c) + ); + tangents.push([set([], b), n]); + } + tangents.push(tangents[0]); + return buildSegments(tangents, t, uniform); }; export const openCubicFromBreakPoints = ( - points: ReadonlyVec[], - t = 1 / 3, - uniform = false + points: ReadonlyVec[], + t = 1 / 3, + uniform = false ) => { - const tangents: Vec[][] = [ - [points[0], direction([], points[0], points[1])], - ]; - const num = points.length - 1; - for (let i = 1; i < num; i++) { - const a = points[i - 1]; - const b = points[i]; - const c = points[i + 1]; - const n = mulN( - null, - perpendicularCW(null, cornerBisector([], a, b, c)), - corner2(a, b, c) - ); - tangents.push([set([], b), n]); - } - tangents.push([points[num], direction([], points[num - 1], points[num])]); - return buildSegments(tangents, t, uniform); + const tangents: Vec[][] = [ + [points[0], direction([], points[0], points[1])], + ]; + const num = points.length - 1; + for (let i = 1; i < num; i++) { + const a = points[i - 1]; + const b = points[i]; + const c = points[i + 1]; + const n = mulN( + null, + perpendicularCW(null, cornerBisector([], a, b, c)), + corner2(a, b, c) + ); + tangents.push([set([], b), n]); + } + tangents.push([points[num], direction([], points[num - 1], points[num])]); + return buildSegments(tangents, t, uniform); }; diff --git a/packages/geom-splines/src/cubic-from-controlpoints.ts b/packages/geom-splines/src/cubic-from-controlpoints.ts index fcf20b5d35..1f6b1d1741 100644 --- a/packages/geom-splines/src/cubic-from-controlpoints.ts +++ b/packages/geom-splines/src/cubic-from-controlpoints.ts @@ -6,57 +6,57 @@ import { mixN } from "@thi.ng/vectors/mixn"; import { set } from "@thi.ng/vectors/set"; const buildUniform = (segments: Vec[], t: number) => { - const res: Vec[][] = []; - for (let i = 0, n = segments.length - 2; i < n; i += 2) { - const a = segments[i]; - const b = segments[i + 1]; - const c = segments[i + 2]; - res.push([ - a, - add(null, direction([], a, b, t), a), - add(null, direction([], c, b, t), c), - c, - ]); - } - return res; + const res: Vec[][] = []; + for (let i = 0, n = segments.length - 2; i < n; i += 2) { + const a = segments[i]; + const b = segments[i + 1]; + const c = segments[i + 2]; + res.push([ + a, + add(null, direction([], a, b, t), a), + add(null, direction([], c, b, t), c), + c, + ]); + } + return res; }; const buildNonUniform = (segments: Vec[], t: number) => { - const res: Vec[][] = []; - for (let i = 0, n = segments.length - 2; i < n; i += 2) { - const a = segments[i]; - const b = segments[i + 1]; - const c = segments[i + 2]; - res.push([a, mixN([], a, b, t), mixN([], c, b, t), c]); - } - return res; + const res: Vec[][] = []; + for (let i = 0, n = segments.length - 2; i < n; i += 2) { + const a = segments[i]; + const b = segments[i + 1]; + const c = segments[i + 2]; + res.push([a, mixN([], a, b, t), mixN([], c, b, t), c]); + } + return res; }; export const closedCubicFromControlPoints = ( - points: ReadonlyVec[], - t = 1, - uniform = false + points: ReadonlyVec[], + t = 1, + uniform = false ) => { - const segments: Vec[] = []; - for (let i = 0, num = points.length; i < num; i++) { - const q = points[(i + 1) % num]; - segments.push(addmN([], points[i], q, 0.5), set([], q)); - } - segments.push(segments[0]); - return uniform ? buildUniform(segments, t) : buildNonUniform(segments, t); + const segments: Vec[] = []; + for (let i = 0, num = points.length; i < num; i++) { + const q = points[(i + 1) % num]; + segments.push(addmN([], points[i], q, 0.5), set([], q)); + } + segments.push(segments[0]); + return uniform ? buildUniform(segments, t) : buildNonUniform(segments, t); }; export const openCubicFromControlPoints = ( - points: ReadonlyVec[], - t = 1, - uniform = false + points: ReadonlyVec[], + t = 1, + uniform = false ) => { - const segments: Vec[] = [set([], points[0]), set([], points[0])]; - const num = points.length - 1; - for (let i = 0; i < num; i++) { - const q = points[i + 1]; - segments.push(addmN([], points[i], q, 0.5), set([], q)); - } - segments.push(set([], points[num])); - return uniform ? buildUniform(segments, t) : buildNonUniform(segments, t); + const segments: Vec[] = [set([], points[0]), set([], points[0])]; + const num = points.length - 1; + for (let i = 0; i < num; i++) { + const q = points[i + 1]; + segments.push(addmN([], points[i], q, 0.5), set([], q)); + } + segments.push(set([], points[num])); + return uniform ? buildUniform(segments, t) : buildNonUniform(segments, t); }; diff --git a/packages/geom-splines/src/cubic-line.ts b/packages/geom-splines/src/cubic-line.ts index 464ee0ec34..0aaff830d3 100644 --- a/packages/geom-splines/src/cubic-line.ts +++ b/packages/geom-splines/src/cubic-line.ts @@ -12,8 +12,8 @@ import { set } from "@thi.ng/vectors/set"; * @param b - line endpoint */ export const cubicFromLine: FnU2 = (a, b) => [ - set([], a), - mixN([], a, b, 1 / 3), - mixN([], b, a, 1 / 3), - set([], b), + set([], a), + mixN([], a, b, 1 / 3), + mixN([], b, a, 1 / 3), + set([], b), ]; diff --git a/packages/geom-splines/src/cubic-quadratic.ts b/packages/geom-splines/src/cubic-quadratic.ts index 8e5ac604f2..e7e058c826 100644 --- a/packages/geom-splines/src/cubic-quadratic.ts +++ b/packages/geom-splines/src/cubic-quadratic.ts @@ -4,8 +4,8 @@ import { mixN } from "@thi.ng/vectors/mixn"; import { set } from "@thi.ng/vectors/set"; export const cubicFromQuadratic: FnU3 = (a, b, c) => [ - set([], a), - mixN([], a, b, 2 / 3), - mixN([], c, b, 2 / 3), - set([], c), + set([], a), + mixN([], a, b, 2 / 3), + mixN([], c, b, 2 / 3), + set([], c), ]; diff --git a/packages/geom-splines/src/cubic-sample.ts b/packages/geom-splines/src/cubic-sample.ts index 137fefd069..90151d76c1 100644 --- a/packages/geom-splines/src/cubic-sample.ts +++ b/packages/geom-splines/src/cubic-sample.ts @@ -2,10 +2,10 @@ import { mixCubic } from "@thi.ng/vectors/mix-cubic"; import { __sample, __sampleArray } from "./internal/sample.js"; export const sampleCubic = __sample((res, [a, b, c, d], num) => { - const delta = 1 / num; - for (let t = 0; t < num; t++) { - res.push(mixCubic([], a, b, c, d, t * delta)); - } + const delta = 1 / num; + for (let t = 0; t < num; t++) { + res.push(mixCubic([], a, b, c, d, t * delta)); + } }); export const sampleCubicArray = __sampleArray(sampleCubic); diff --git a/packages/geom-splines/src/cubic-split.ts b/packages/geom-splines/src/cubic-split.ts index 17a0abfeee..b22efa537f 100644 --- a/packages/geom-splines/src/cubic-split.ts +++ b/packages/geom-splines/src/cubic-split.ts @@ -6,49 +6,49 @@ import { mixN } from "@thi.ng/vectors/mixn"; import { set } from "@thi.ng/vectors/set"; export const cubicSplitAt = ( - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - d: ReadonlyVec, - t: number + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + d: ReadonlyVec, + t: number ) => { - if (t <= 0 || t >= 1) { - const p = t <= 0 ? a : d; - const c1 = [set([], p), set([], p), set([], p), set([], p)]; - const c2 = [set([], a), set([], b), set([], c), set([], d)]; - return t <= 0 ? [c1, c2] : [c2, c1]; - } - const ab = mixN([], a, b, t); - const bc = mixN([], b, c, t); - const cd = mixN([], c, d, t); - const abc = mixN([], ab, bc, t); - const bcd = mixN([], bc, cd, t); - const p = mixN([], abc, bcd, t); - return [ - [set([], a), ab, abc, set([], p)], - [p, bcd, cd, set([], d)], - ]; + if (t <= 0 || t >= 1) { + const p = t <= 0 ? a : d; + const c1 = [set([], p), set([], p), set([], p), set([], p)]; + const c2 = [set([], a), set([], b), set([], c), set([], d)]; + return t <= 0 ? [c1, c2] : [c2, c1]; + } + const ab = mixN([], a, b, t); + const bc = mixN([], b, c, t); + const cd = mixN([], c, d, t); + const abc = mixN([], ab, bc, t); + const bcd = mixN([], bc, cd, t); + const p = mixN([], abc, bcd, t); + return [ + [set([], a), ab, abc, set([], p)], + [p, bcd, cd, set([], d)], + ]; }; export const splitCubicNearPoint = ( - p: ReadonlyVec, - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - d: ReadonlyVec, - res?: number, - iter?: number + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + d: ReadonlyVec, + res?: number, + iter?: number ) => - cubicSplitAt( - a, - b, - c, - d, - minError( - (t: number) => mixCubic([], a, b, c, d, t), - distSq, - p, - res, - iter - ) - ); + cubicSplitAt( + a, + b, + c, + d, + minError( + (t: number) => mixCubic([], a, b, c, d, t), + distSq, + p, + res, + iter + ) + ); diff --git a/packages/geom-splines/src/cubic-tangent.ts b/packages/geom-splines/src/cubic-tangent.ts index 28aa43e6ec..65c60942a8 100644 --- a/packages/geom-splines/src/cubic-tangent.ts +++ b/packages/geom-splines/src/cubic-tangent.ts @@ -3,21 +3,21 @@ import { addW4 } from "@thi.ng/vectors/addw"; import { normalize } from "@thi.ng/vectors/normalize"; export const cubicTangentAt = ( - out: Vec, - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - d: ReadonlyVec, - t: number, - len = 1 + out: Vec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + d: ReadonlyVec, + t: number, + len = 1 ) => { - const s = 1 - t; - const ss = s * s; - const tt = t * t; - const ts2 = 2 * t * s; - return normalize( - out, - addW4(out, a, b, c, d, -3 * ss, 3 * (ss - ts2), 3 * (ts2 - tt), 3 * tt), - len - ); + const s = 1 - t; + const ss = s * s; + const tt = t * t; + const ts2 = 2 * t * s; + return normalize( + out, + addW4(out, a, b, c, d, -3 * ss, 3 * (ss - ts2), 3 * (ts2 - tt), 3 * tt), + len + ); }; diff --git a/packages/geom-splines/src/internal/sample.ts b/packages/geom-splines/src/internal/sample.ts index bb598bb9c1..6f0abe548b 100644 --- a/packages/geom-splines/src/internal/sample.ts +++ b/packages/geom-splines/src/internal/sample.ts @@ -7,46 +7,46 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; import { set } from "@thi.ng/vectors/set"; export const __sample = (sample: Fn3) => - function $( - pts: ReadonlyVec[], - opts?: number | Partial - ): Vec[] { - if (isPlainObject(opts) && (opts).dist !== undefined) { - return new Sampler( - $(pts, (opts).num || DEFAULT_SAMPLES) - ).sampleUniform( - (opts).dist, - (opts).last !== false - ); - } - opts = isNumber(opts) - ? { - num: opts, - last: true, - } - : { - num: DEFAULT_SAMPLES, - ...opts, - }; - const res: Vec[] = []; - sample(res, pts, opts.num!); - opts.last && res.push(set([], pts[pts.length - 1])); - return res; - }; + function $( + pts: ReadonlyVec[], + opts?: number | Partial + ): Vec[] { + if (isPlainObject(opts) && (opts).dist !== undefined) { + return new Sampler( + $(pts, (opts).num || DEFAULT_SAMPLES) + ).sampleUniform( + (opts).dist, + (opts).last !== false + ); + } + opts = isNumber(opts) + ? { + num: opts, + last: true, + } + : { + num: DEFAULT_SAMPLES, + ...opts, + }; + const res: Vec[] = []; + sample(res, pts, opts.num!); + opts.last && res.push(set([], pts[pts.length - 1])); + return res; + }; export const __sampleArray = - (fn: Fn2, Vec[]>) => - ( - segments: ReadonlyVec[][], - closed = false, - opts: number | Partial - ) => { - const _opts = isNumber(opts) ? { num: opts } : opts; - const n = segments.length - 1; - return Array.prototype.concat.apply( - [], - segments.map((seg, i) => - fn(seg, { ..._opts, last: !closed && i === n }) - ) - ); - }; + (fn: Fn2, Vec[]>) => + ( + segments: ReadonlyVec[][], + closed = false, + opts: number | Partial + ) => { + const _opts = isNumber(opts) ? { num: opts } : opts; + const n = segments.length - 1; + return Array.prototype.concat.apply( + [], + segments.map((seg, i) => + fn(seg, { ..._opts, last: !closed && i === n }) + ) + ); + }; diff --git a/packages/geom-splines/src/quadratic-bounds.ts b/packages/geom-splines/src/quadratic-bounds.ts index 7cbe98947a..0c36258520 100644 --- a/packages/geom-splines/src/quadratic-bounds.ts +++ b/packages/geom-splines/src/quadratic-bounds.ts @@ -5,28 +5,28 @@ import { max } from "@thi.ng/vectors/max"; import { min } from "@thi.ng/vectors/min"; const solveQuadratic: FnN3 = (a, b, c) => { - const t = clamp01((a - b) / (a - 2.0 * b + c)); - const s = 1 - t; - return s * s * a + 2.0 * s * t * b + t * t * c; + const t = clamp01((a - b) / (a - 2.0 * b + c)); + const s = 1 - t; + return s * s * a + 2.0 * s * t * b + t * t * c; }; const inBounds: FnU3 = (p, min, max) => { - for (let i = p.length; i-- > 0; ) { - if (!inRange(p[i], min[i], max[i])) return false; - } - return true; + for (let i = p.length; i-- > 0; ) { + if (!inRange(p[i], min[i], max[i])) return false; + } + return true; }; export const quadraticBounds: FnU3 = (a, b, c) => { - const mi = min([], a, c); - const ma = max([], a, c); - if (!inBounds(b, mi, ma)) { - const q = []; - for (let i = a.length; i-- > 0; ) { - q[i] = solveQuadratic(a[i], b[i], c[i]); - } - min(null, mi, q); - max(null, ma, q); - } - return [mi, ma]; + const mi = min([], a, c); + const ma = max([], a, c); + if (!inBounds(b, mi, ma)) { + const q = []; + for (let i = a.length; i-- > 0; ) { + q[i] = solveQuadratic(a[i], b[i], c[i]); + } + min(null, mi, q); + max(null, ma, q); + } + return [mi, ma]; }; diff --git a/packages/geom-splines/src/quadratic-closest-point.ts b/packages/geom-splines/src/quadratic-closest-point.ts index 174fd67f0b..252af1d8ff 100644 --- a/packages/geom-splines/src/quadratic-closest-point.ts +++ b/packages/geom-splines/src/quadratic-closest-point.ts @@ -21,15 +21,15 @@ import { mixQuadratic } from "@thi.ng/vectors/mix-quadratic"; * @param eps - epsilon value */ export const closestPointQuadratic = ( - p: ReadonlyVec, - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - out: Vec = [], - res?: number, - iter?: number, - eps?: number + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + out: Vec = [], + res?: number, + iter?: number, + eps?: number ) => { - const fn = (t: number) => mixQuadratic(out, a, b, c, t); - return fn(minError(fn, distSq, p, res, iter, 0, 1, eps)); + const fn = (t: number) => mixQuadratic(out, a, b, c, t); + return fn(minError(fn, distSq, p, res, iter, 0, 1, eps)); }; diff --git a/packages/geom-splines/src/quadratic-line.ts b/packages/geom-splines/src/quadratic-line.ts index 48d9f43446..428f210717 100644 --- a/packages/geom-splines/src/quadratic-line.ts +++ b/packages/geom-splines/src/quadratic-line.ts @@ -4,7 +4,7 @@ import { addmN } from "@thi.ng/vectors/addmn"; import { set } from "@thi.ng/vectors/set"; export const quadraticFromLine: FnU2 = (a, b) => [ - set([], a), - addmN([], a, b, 0.5), - set([], b), + set([], a), + addmN([], a, b, 0.5), + set([], b), ]; diff --git a/packages/geom-splines/src/quadratic-sample.ts b/packages/geom-splines/src/quadratic-sample.ts index 4325e232ad..6ac1bc330f 100644 --- a/packages/geom-splines/src/quadratic-sample.ts +++ b/packages/geom-splines/src/quadratic-sample.ts @@ -2,10 +2,10 @@ import { mixQuadratic } from "@thi.ng/vectors/mix-quadratic"; import { __sample, __sampleArray } from "./internal/sample.js"; export const sampleQuadratic = __sample((res, [a, b, c], num) => { - const delta = 1 / num; - for (let t = 0; t < num; t++) { - res.push(mixQuadratic([], a, b, c, t * delta)); - } + const delta = 1 / num; + for (let t = 0; t < num; t++) { + res.push(mixQuadratic([], a, b, c, t * delta)); + } }); export const sampleQuadraticArray = __sampleArray(sampleQuadratic); diff --git a/packages/geom-splines/src/quadratic-split.ts b/packages/geom-splines/src/quadratic-split.ts index b64ffcfd7d..f9bd153e1e 100644 --- a/packages/geom-splines/src/quadratic-split.ts +++ b/packages/geom-splines/src/quadratic-split.ts @@ -6,43 +6,43 @@ import { mixN } from "@thi.ng/vectors/mixn"; import { set } from "@thi.ng/vectors/set"; export const quadraticSplitAt = ( - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - t: number + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + t: number ) => { - if (t <= 0 || t >= 1) { - const p = t <= 0 ? a : c; - const c1 = [set([], p), set([], p), set([], p)]; - const c2 = [set([], a), set([], b), set([], c)]; - return t <= 0 ? [c1, c2] : [c2, c1]; - } - const ab = mixN([], a, b, t); - const bc = mixN([], b, c, t); - const p = mixN([], ab, bc, t); - return [ - [set([], a), ab, p], - [p, bc, set([], c)], - ]; + if (t <= 0 || t >= 1) { + const p = t <= 0 ? a : c; + const c1 = [set([], p), set([], p), set([], p)]; + const c2 = [set([], a), set([], b), set([], c)]; + return t <= 0 ? [c1, c2] : [c2, c1]; + } + const ab = mixN([], a, b, t); + const bc = mixN([], b, c, t); + const p = mixN([], ab, bc, t); + return [ + [set([], a), ab, p], + [p, bc, set([], c)], + ]; }; export const quadraticSplitNearPoint = ( - p: ReadonlyVec, - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - res?: number, - iter?: number + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + res?: number, + iter?: number ) => - quadraticSplitAt( - a, - b, - c, - minError( - (t: number) => mixQuadratic([], a, b, c, t), - distSq, - p, - res, - iter - ) - ); + quadraticSplitAt( + a, + b, + c, + minError( + (t: number) => mixQuadratic([], a, b, c, t), + distSq, + p, + res, + iter + ) + ); diff --git a/packages/geom-splines/src/quadratic-tangent.ts b/packages/geom-splines/src/quadratic-tangent.ts index 36f45f9258..9e1d86b342 100644 --- a/packages/geom-splines/src/quadratic-tangent.ts +++ b/packages/geom-splines/src/quadratic-tangent.ts @@ -4,15 +4,15 @@ import { normalize } from "@thi.ng/vectors/normalize"; import { sub } from "@thi.ng/vectors/sub"; export const quadraticTangentAt = ( - out: Vec, - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - t: number, - len = 1 + out: Vec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + t: number, + len = 1 ) => - normalize( - out, - addW2(out, sub(out, b, a), sub([], c, b), 2 * (1 - t), 2 * t), - len - ); + normalize( + out, + addW2(out, sub(out, b, a), sub([], c, b), 2 * (1 - t), 2 * t), + len + ); diff --git a/packages/geom-splines/tsconfig.json b/packages/geom-splines/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-splines/tsconfig.json +++ b/packages/geom-splines/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-subdiv-curve/api-extractor.json b/packages/geom-subdiv-curve/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-subdiv-curve/api-extractor.json +++ b/packages/geom-subdiv-curve/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-subdiv-curve/package.json b/packages/geom-subdiv-curve/package.json index 790ab63ccf..daffbb1469 100644 --- a/packages/geom-subdiv-curve/package.json +++ b/packages/geom-subdiv-curve/package.json @@ -1,94 +1,94 @@ { - "name": "@thi.ng/geom-subdiv-curve", - "version": "2.1.19", - "description": "Freely customizable, iterative nD subdivision curves for open / closed geometries", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-subdiv-curve#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/geom-api": "^3.3.2", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "3d", - "bezier", - "chaikin", - "cubic", - "curve", - "iterative", - "polyline", - "recursive", - "spline", - "subdivision", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./kernels": { - "default": "./kernels.js" - }, - "./subdivide": { - "default": "./subdivide.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "related": [ - "geom-splines" - ] - } + "name": "@thi.ng/geom-subdiv-curve", + "version": "2.1.19", + "description": "Freely customizable, iterative nD subdivision curves for open / closed geometries", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-subdiv-curve#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/geom-api": "^3.3.2", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "3d", + "bezier", + "chaikin", + "cubic", + "curve", + "iterative", + "polyline", + "recursive", + "spline", + "subdivision", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./kernels": { + "default": "./kernels.js" + }, + "./subdivide": { + "default": "./subdivide.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "related": [ + "geom-splines" + ] + } } diff --git a/packages/geom-subdiv-curve/src/api.ts b/packages/geom-subdiv-curve/src/api.ts index b7d5f568ae..3cc4be666c 100644 --- a/packages/geom-subdiv-curve/src/api.ts +++ b/packages/geom-subdiv-curve/src/api.ts @@ -8,26 +8,26 @@ import { kernel3 } from "./kernels.js"; const MIDP = ([a, b]: ReadonlyVec[]) => [a, addmN([], a, b, 0.5)]; const THIRDS = ([a, b]: ReadonlyVec[]) => [ - a, - mixN([], a, b, 1 / 3), - mixN([], a, b, 2 / 3), + a, + mixN([], a, b, 1 / 3), + mixN([], a, b, 2 / 3), ]; const wrap2 = (pts: ReadonlyVec[]) => wrapSides(pts, 0, 1); const wrap3 = (pts: ReadonlyVec[]) => wrapSides(pts, 1, 1); const subdivWith = - (fn: FnU): SubdivKernel["fn"] => - (pts, i, n) => - i < n - 2 ? fn(pts) : [...fn(pts), pts[1]]; + (fn: FnU): SubdivKernel["fn"] => + (pts, i, n) => + i < n - 2 ? fn(pts) : [...fn(pts), pts[1]]; /** * Splits each curve / line segment into halves at midpoint. Version for * open curves. */ export const SUBDIV_MID_OPEN: SubdivKernel = { - fn: subdivWith(MIDP), - size: 2, + fn: subdivWith(MIDP), + size: 2, }; /** @@ -35,9 +35,9 @@ export const SUBDIV_MID_OPEN: SubdivKernel = { * closed curves. */ export const SUBDIV_MID_CLOSED: SubdivKernel = { - fn: MIDP, - pre: wrap2, - size: 2, + fn: MIDP, + pre: wrap2, + size: 2, }; /** @@ -45,8 +45,8 @@ export const SUBDIV_MID_CLOSED: SubdivKernel = { * open curves. */ export const SUBDIV_THIRDS_OPEN: SubdivKernel = { - fn: subdivWith(THIRDS), - size: 2, + fn: subdivWith(THIRDS), + size: 2, }; /** @@ -54,9 +54,9 @@ export const SUBDIV_THIRDS_OPEN: SubdivKernel = { * open curves. */ export const SUBDIV_THIRDS_CLOSED: SubdivKernel = { - fn: THIRDS, - pre: wrap2, - size: 2, + fn: THIRDS, + pre: wrap2, + size: 2, }; const CHAIKIN_FIRST = kernel3([1 / 2, 1 / 2, 0], [0, 3 / 4, 1 / 4]); @@ -67,22 +67,22 @@ const CHAIKIN_LAST = kernel3([1 / 4, 3 / 4, 0], [0, 1 / 2, 1 / 2]); * Chaikin subdivision scheme for open curves. */ export const SUBDIV_CHAIKIN_OPEN: SubdivKernel = { - fn: (pts, i, n) => - i == 0 - ? [pts[0], ...CHAIKIN_FIRST(pts)] - : i === n - 3 - ? [...CHAIKIN_LAST(pts), pts[2]] - : CHAIKIN_MAIN(pts), - size: 3, + fn: (pts, i, n) => + i == 0 + ? [pts[0], ...CHAIKIN_FIRST(pts)] + : i === n - 3 + ? [...CHAIKIN_LAST(pts), pts[2]] + : CHAIKIN_MAIN(pts), + size: 3, }; /** * Chaikin subdivision scheme for closed curves. */ export const SUBDIV_CHAIKIN_CLOSED: SubdivKernel = { - fn: CHAIKIN_MAIN, - pre: wrap3, - size: 3, + fn: CHAIKIN_MAIN, + pre: wrap3, + size: 3, }; const CUBIC_MAIN = kernel3([1 / 8, 3 / 4, 1 / 8], [0, 1 / 2, 1 / 2]); @@ -91,7 +91,7 @@ const CUBIC_MAIN = kernel3([1 / 8, 3 / 4, 1 / 8], [0, 1 / 2, 1 / 2]); * Cubic bezier subdivision scheme for closed curves. */ export const SUBDIV_CUBIC_CLOSED: SubdivKernel = { - fn: CUBIC_MAIN, - pre: wrap3, - size: 3, + fn: CUBIC_MAIN, + pre: wrap3, + size: 3, }; diff --git a/packages/geom-subdiv-curve/src/kernels.ts b/packages/geom-subdiv-curve/src/kernels.ts index 3f219cd4b7..d417db00eb 100644 --- a/packages/geom-subdiv-curve/src/kernels.ts +++ b/packages/geom-subdiv-curve/src/kernels.ts @@ -9,9 +9,9 @@ import { addW2, addW3, addW5 } from "@thi.ng/vectors/addw"; * @param v - split coeffs */ export const kernel2 = - ([ua, ub]: number[], [va, vb]: number[]) => - ([a, b]: ReadonlyVec[]) => - [addW2([], a, b, ua, ub), addW2([], a, b, va, vb)]; + ([ua, ub]: number[], [va, vb]: number[]) => + ([a, b]: ReadonlyVec[]) => + [addW2([], a, b, ua, ub), addW2([], a, b, va, vb)]; /** * HOF subdiv kernel function for computing 2 split points from 3 source @@ -21,9 +21,9 @@ export const kernel2 = * @param v - split coeffs */ export const kernel3 = - ([ua, ub, uc]: number[], [va, vb, vc]: number[]) => - ([a, b, c]: ReadonlyVec[]) => - [addW3([], a, b, c, ua, ub, uc), addW3([], a, b, c, va, vb, vc)]; + ([ua, ub, uc]: number[], [va, vb, vc]: number[]) => + ([a, b, c]: ReadonlyVec[]) => + [addW3([], a, b, c, ua, ub, uc), addW3([], a, b, c, va, vb, vc)]; /** * HOF subdiv kernel function for computing 2 split points from 5 source @@ -33,9 +33,9 @@ export const kernel3 = * @param v - split coeffs */ export const kernel5 = - ([ua, ub, uc, ud, ue]: number[], [va, vb, vc, vd, ve]: number[]) => - ([a, b, c, d, e]: ReadonlyVec[]) => - [ - addW5([], a, b, c, d, e, ua, ub, uc, ud, ue), - addW5([], a, b, c, d, e, va, vb, vc, vd, ve), - ]; + ([ua, ub, uc, ud, ue]: number[], [va, vb, vc, vd, ve]: number[]) => + ([a, b, c, d, e]: ReadonlyVec[]) => + [ + addW5([], a, b, c, d, e, ua, ub, uc, ud, ue), + addW5([], a, b, c, d, e, va, vb, vc, vd, ve), + ]; diff --git a/packages/geom-subdiv-curve/src/subdivide.ts b/packages/geom-subdiv-curve/src/subdivide.ts index a3cf3392fb..63e56f84a8 100644 --- a/packages/geom-subdiv-curve/src/subdivide.ts +++ b/packages/geom-subdiv-curve/src/subdivide.ts @@ -14,20 +14,20 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; * @param iter - number of iterations */ export const subdivide = ( - pts: ReadonlyVec[], - { fn, pre, size }: SubdivKernel, - iter = 1 + pts: ReadonlyVec[], + { fn, pre, size }: SubdivKernel, + iter = 1 ) => { - while (iter-- > 0) { - const nump = pts.length; - pts = transduce( - comp( - partition(size, 1), - mapcatIndexed((i, pts) => fn(pts, i, nump)) - ), - push(), - pre ? pre(pts) : pts - ); - } - return pts; + while (iter-- > 0) { + const nump = pts.length; + pts = transduce( + comp( + partition(size, 1), + mapcatIndexed((i, pts) => fn(pts, i, nump)) + ), + push(), + pre ? pre(pts) : pts + ); + } + return pts; }; diff --git a/packages/geom-subdiv-curve/tsconfig.json b/packages/geom-subdiv-curve/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-subdiv-curve/tsconfig.json +++ b/packages/geom-subdiv-curve/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-tessellate/api-extractor.json b/packages/geom-tessellate/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-tessellate/api-extractor.json +++ b/packages/geom-tessellate/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-tessellate/package.json b/packages/geom-tessellate/package.json index 365e17c6e9..f58dce7a8c 100644 --- a/packages/geom-tessellate/package.json +++ b/packages/geom-tessellate/package.json @@ -1,103 +1,103 @@ { - "name": "@thi.ng/geom-tessellate", - "version": "2.1.19", - "description": "2D/3D convex polygon tessellators", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-tessellate#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/checks": "^3.2.2", - "@thi.ng/geom-api": "^3.3.2", - "@thi.ng/geom-isec": "^2.1.19", - "@thi.ng/geom-poly-utils": "^2.3.3", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "3d", - "convex", - "geometry", - "polygon", - "shape", - "subdivision", - "tessellation", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./earcut": { - "default": "./earcut.js" - }, - "./edge-split": { - "default": "./edge-split.js" - }, - "./inset": { - "default": "./inset.js" - }, - "./quad-fan": { - "default": "./quad-fan.js" - }, - "./rim-tris": { - "default": "./rim-tris.js" - }, - "./tessellate": { - "default": "./tessellate.js" - }, - "./tri-fan": { - "default": "./tri-fan.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "year": 2013 - } + "name": "@thi.ng/geom-tessellate", + "version": "2.1.19", + "description": "2D/3D convex polygon tessellators", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-tessellate#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/checks": "^3.2.2", + "@thi.ng/geom-api": "^3.3.2", + "@thi.ng/geom-isec": "^2.1.19", + "@thi.ng/geom-poly-utils": "^2.3.3", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "3d", + "convex", + "geometry", + "polygon", + "shape", + "subdivision", + "tessellation", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./earcut": { + "default": "./earcut.js" + }, + "./edge-split": { + "default": "./edge-split.js" + }, + "./inset": { + "default": "./inset.js" + }, + "./quad-fan": { + "default": "./quad-fan.js" + }, + "./rim-tris": { + "default": "./rim-tris.js" + }, + "./tessellate": { + "default": "./tessellate.js" + }, + "./tri-fan": { + "default": "./tri-fan.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "year": 2013 + } } diff --git a/packages/geom-tessellate/src/earcut.ts b/packages/geom-tessellate/src/earcut.ts index 14d1980900..41e8fb11cf 100644 --- a/packages/geom-tessellate/src/earcut.ts +++ b/packages/geom-tessellate/src/earcut.ts @@ -6,26 +6,26 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; import { signedArea2 } from "@thi.ng/vectors/signed-area"; const snip = ( - points: ReadonlyVec[], - u: number, - v: number, - w: number, - n: number, - ids: number[] + points: ReadonlyVec[], + u: number, + v: number, + w: number, + n: number, + ids: number[] ) => { - const a = points[ids[u]]; - const b = points[ids[v]]; - const c = points[ids[w]]; - if (signedArea2(a, b, c) > 0) { - for (let i = 0; i < n; i++) { - if (i !== u && i !== v && i !== w) { - if (pointInTriangle2(points[ids[i]], a, b, c)) { - return; - } - } - } - return [a, b, c]; - } + const a = points[ids[u]]; + const b = points[ids[v]]; + const c = points[ids[w]]; + if (signedArea2(a, b, c) > 0) { + for (let i = 0; i < n; i++) { + if (i !== u && i !== v && i !== w) { + if (pointInTriangle2(points[ids[i]], a, b, c)) { + return; + } + } + } + return [a, b, c]; + } }; /** @@ -34,29 +34,29 @@ const snip = ( * @param points - polygon vertices */ export const earCut2: Tessellator = (points: ReadonlyVec[]) => { - const tris: Vec[][] = []; - let n = points.length; - const ids = [...(polyArea2(points) > 0 ? range(n) : range(n - 1, -1, -1))]; - let count = 2 * n - 1; - let v = n - 1, - u, - w, - t; - while (count > 0 && n > 2) { - u = n <= v ? 0 : v; - v = u + 1; - v = n <= v ? 0 : v; - w = v + 1; - w = n <= w ? 0 : w; - t = snip(points, u, v, w, n, ids); - if (t !== undefined) { - tris.push(t); - ids.splice(v, 1); - n--; - count = 2 * n; - } else { - count--; - } - } - return tris; + const tris: Vec[][] = []; + let n = points.length; + const ids = [...(polyArea2(points) > 0 ? range(n) : range(n - 1, -1, -1))]; + let count = 2 * n - 1; + let v = n - 1, + u, + w, + t; + while (count > 0 && n > 2) { + u = n <= v ? 0 : v; + v = u + 1; + v = n <= v ? 0 : v; + w = v + 1; + w = n <= w ? 0 : w; + t = snip(points, u, v, w, n, ids); + if (t !== undefined) { + tris.push(t); + ids.splice(v, 1); + n--; + count = 2 * n; + } else { + count--; + } + } + return tris; }; diff --git a/packages/geom-tessellate/src/edge-split.ts b/packages/geom-tessellate/src/edge-split.ts index 41ee65b36e..0905464966 100644 --- a/packages/geom-tessellate/src/edge-split.ts +++ b/packages/geom-tessellate/src/edge-split.ts @@ -10,19 +10,19 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; import { mixN } from "@thi.ng/vectors/mixn"; export const edgeSplit: Tessellator = (points: ReadonlyVec[]) => { - const c = centroid(points); - return transduce( - comp( - partition(2, 1), - mapcat(([a, b]) => { - const m = mixN([], a, b, 0.5); - return [ - [a, m, c], - [m, b, c], - ]; - }) - ), - push(), - wrapSides(points, 0, 1) - ); + const c = centroid(points); + return transduce( + comp( + partition(2, 1), + mapcat(([a, b]) => { + const m = mixN([], a, b, 0.5); + return [ + [a, m, c], + [m, b, c], + ]; + }) + ), + push(), + wrapSides(points, 0, 1) + ); }; diff --git a/packages/geom-tessellate/src/inset.ts b/packages/geom-tessellate/src/inset.ts index 0eb77937f2..eb92aa8f0a 100644 --- a/packages/geom-tessellate/src/inset.ts +++ b/packages/geom-tessellate/src/inset.ts @@ -11,17 +11,17 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; import { mixN } from "@thi.ng/vectors/mixn"; export const tesselInset = - (inset = 0.5, keepInterior = false): Tessellator => - (points: ReadonlyVec[]) => { - const c = centroid(points); - const inner = points.map((p) => mixN([], p, c, inset)); - return transduce( - comp( - partition(2, 1), - map(([[a, b], [c, d]]) => [a, b, d, c]) - ), - push(), - keepInterior ? [inner] : [], - wrapSides([...zip(points, inner)], 0, 1) - ); - }; + (inset = 0.5, keepInterior = false): Tessellator => + (points: ReadonlyVec[]) => { + const c = centroid(points); + const inner = points.map((p) => mixN([], p, c, inset)); + return transduce( + comp( + partition(2, 1), + map(([[a, b], [c, d]]) => [a, b, d, c]) + ), + push(), + keepInterior ? [inner] : [], + wrapSides([...zip(points, inner)], 0, 1) + ); + }; diff --git a/packages/geom-tessellate/src/quad-fan.ts b/packages/geom-tessellate/src/quad-fan.ts index 8e9caf1fac..2378f19512 100644 --- a/packages/geom-tessellate/src/quad-fan.ts +++ b/packages/geom-tessellate/src/quad-fan.ts @@ -10,13 +10,13 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; import { mixN } from "@thi.ng/vectors/mixn"; export const quadFan: Tessellator = (points: ReadonlyVec[]) => { - const p = centroid(points); - return transduce( - comp( - partition(3, 1), - map(([a, b, c]) => [mixN([], a, b, 0.5), b, mixN([], b, c, 0.5), p]) - ), - push(), - wrapSides(points) - ); + const p = centroid(points); + return transduce( + comp( + partition(3, 1), + map(([a, b, c]) => [mixN([], a, b, 0.5), b, mixN([], b, c, 0.5), p]) + ), + push(), + wrapSides(points) + ); }; diff --git a/packages/geom-tessellate/src/rim-tris.ts b/packages/geom-tessellate/src/rim-tris.ts index 35ae9fce01..638efbc7f2 100644 --- a/packages/geom-tessellate/src/rim-tris.ts +++ b/packages/geom-tessellate/src/rim-tris.ts @@ -10,21 +10,21 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; import { mixN } from "@thi.ng/vectors/mixn"; export const rimTris: Tessellator = (points: ReadonlyVec[]) => { - const edgeCentroids = transduce( - comp( - partition(2, 1), - map((e) => mixN([], e[0], e[1], 0.5)) - ), - push(), - wrapSides(points, 0, 1) - ); - return transduce( - comp( - partition(2, 1), - map((t) => [t[0][0], t[1][1], t[1][0]]) - ), - push(), - [edgeCentroids], - wrapSides([...zip(edgeCentroids, points)], 1, 0) - ); + const edgeCentroids = transduce( + comp( + partition(2, 1), + map((e) => mixN([], e[0], e[1], 0.5)) + ), + push(), + wrapSides(points, 0, 1) + ); + return transduce( + comp( + partition(2, 1), + map((t) => [t[0][0], t[1][1], t[1][0]]) + ), + push(), + [edgeCentroids], + wrapSides([...zip(edgeCentroids, points)], 1, 0) + ); }; diff --git a/packages/geom-tessellate/src/tessellate.ts b/packages/geom-tessellate/src/tessellate.ts index 24ff6107a2..8a529ab2c8 100644 --- a/packages/geom-tessellate/src/tessellate.ts +++ b/packages/geom-tessellate/src/tessellate.ts @@ -10,24 +10,24 @@ import { transduce } from "@thi.ng/transducers/transduce"; import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; export function tessellate( - points: ReadonlyVec[], - tessFn: Tessellator, - iter?: number + points: ReadonlyVec[], + tessFn: Tessellator, + iter?: number ): Vec[][]; export function tessellate( - points: ReadonlyVec[], - tessFns: Iterable + points: ReadonlyVec[], + tessFns: Iterable ): Vec[][]; export function tessellate(...args: any[]) { - return transduce( - scan( - reducer( - () => [args[0]], - (acc: Vec[][], fn: Tessellator) => - transduce(mapcat(fn), push(), acc) - ) - ), - last(), - isFunction(args[1]) ? repeat(args[1], args[2] || 1) : args[1] - ); + return transduce( + scan( + reducer( + () => [args[0]], + (acc: Vec[][], fn: Tessellator) => + transduce(mapcat(fn), push(), acc) + ) + ), + last(), + isFunction(args[1]) ? repeat(args[1], args[2] || 1) : args[1] + ); } diff --git a/packages/geom-tessellate/src/tri-fan.ts b/packages/geom-tessellate/src/tri-fan.ts index 2868b4a0ff..69f316669c 100644 --- a/packages/geom-tessellate/src/tri-fan.ts +++ b/packages/geom-tessellate/src/tri-fan.ts @@ -9,13 +9,13 @@ import { wrapSides } from "@thi.ng/transducers/wrap-sides"; import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; export const triFan: Tessellator = (points: ReadonlyVec[]) => { - const c = centroid(points); - return transduce( - comp( - partition(2, 1), - map(([a, b]) => [a, b, c]) - ), - push(), - wrapSides(points, 0, 1) - ); + const c = centroid(points); + return transduce( + comp( + partition(2, 1), + map(([a, b]) => [a, b, c]) + ), + push(), + wrapSides(points, 0, 1) + ); }; diff --git a/packages/geom-tessellate/tsconfig.json b/packages/geom-tessellate/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-tessellate/tsconfig.json +++ b/packages/geom-tessellate/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom-voronoi/api-extractor.json b/packages/geom-voronoi/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom-voronoi/api-extractor.json +++ b/packages/geom-voronoi/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom-voronoi/package.json b/packages/geom-voronoi/package.json index dfeb6173e0..163d788169 100644 --- a/packages/geom-voronoi/package.json +++ b/packages/geom-voronoi/package.json @@ -1,94 +1,94 @@ { - "name": "@thi.ng/geom-voronoi", - "version": "2.2.19", - "description": "Fast, incremental 2D Delaunay & Voronoi mesh implementation", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-voronoi#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/bitfield": "^2.2.0", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/geom-clip-line": "^2.1.19", - "@thi.ng/geom-clip-poly": "^2.1.19", - "@thi.ng/geom-isec": "^2.1.19", - "@thi.ng/geom-poly-utils": "^2.3.3", - "@thi.ng/math": "^5.3.4", - "@thi.ng/quad-edge": "^3.1.8", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "bbox", - "clipping", - "datastructure", - "delaunay", - "dual", - "edges", - "geometry", - "graph", - "incremental", - "mesh", - "quadedge", - "typescript", - "voronoi" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/geom", - "related": [ - "quad-edge" - ], - "year": 2016 - } + "name": "@thi.ng/geom-voronoi", + "version": "2.2.19", + "description": "Fast, incremental 2D Delaunay & Voronoi mesh implementation", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom-voronoi#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/bitfield": "^2.2.0", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/geom-clip-line": "^2.1.19", + "@thi.ng/geom-clip-poly": "^2.1.19", + "@thi.ng/geom-isec": "^2.1.19", + "@thi.ng/geom-poly-utils": "^2.3.3", + "@thi.ng/math": "^5.3.4", + "@thi.ng/quad-edge": "^3.1.8", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "bbox", + "clipping", + "datastructure", + "delaunay", + "dual", + "edges", + "geometry", + "graph", + "incremental", + "mesh", + "quadedge", + "typescript", + "voronoi" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/geom", + "related": [ + "quad-edge" + ], + "year": 2016 + } } diff --git a/packages/geom-voronoi/src/index.ts b/packages/geom-voronoi/src/index.ts index 8ea91f6114..91f99e4289 100644 --- a/packages/geom-voronoi/src/index.ts +++ b/packages/geom-voronoi/src/index.ts @@ -4,9 +4,9 @@ import { isNumber } from "@thi.ng/checks/is-number"; import { liangBarsky2 } from "@thi.ng/geom-clip-line/liang-barsky"; import { sutherlandHodgeman } from "@thi.ng/geom-clip-poly"; import { - pointInCircumCircle, - pointInPolygon2, - pointInSegment, + pointInCircumCircle, + pointInPolygon2, + pointInSegment, } from "@thi.ng/geom-isec/point"; import { centroid } from "@thi.ng/geom-poly-utils/centroid"; import { circumCenter2 } from "@thi.ng/geom-poly-utils/circumcenter"; @@ -17,287 +17,288 @@ import { eqDelta2 } from "@thi.ng/vectors/eqdelta"; import { signedArea2 } from "@thi.ng/vectors/signed-area"; export type Visitor = ( - e: Edge>, - vistedEdges: BitField, - visitedVerts: BitField + e: Edge>, + vistedEdges: BitField, + visitedVerts: BitField ) => void; const rightOf = (p: ReadonlyVec, e: Edge>) => - signedArea2(p, e.dest.pos, e.origin.pos) > 0; + signedArea2(p, e.dest.pos, e.origin.pos) > 0; export interface Vertex { - pos: ReadonlyVec; - id: number; - val?: T; + pos: ReadonlyVec; + id: number; + val?: T; } export class DVMesh { - first: Edge>; - nextEID: number; - nextVID: number; + first: Edge>; + nextEID: number; + nextVID: number; - constructor(pts?: ReadonlyVec[] | Pair[], size = 1e5) { - const a: Vertex = { pos: [0, -size], id: 0 }; - const b: Vertex = { pos: [size, size], id: 1 }; - const c: Vertex = { pos: [-size, size], id: 2 }; - const eab = defEdge(0, a, b); - const ebc = defEdge(4, b, c); - const eca = defEdge(8, c, a); - eab.sym.splice(ebc); - ebc.sym.splice(eca); - eca.sym.splice(eab); - this.first = eab; - this.nextEID = 12; - this.nextVID = 3; - if (pts && pts.length) { - isNumber(pts[0][0]) - ? this.addKeys(pts) - : this.addAll([]>pts); - } else { - this.computeDual(); - } - } + constructor(pts?: ReadonlyVec[] | Pair[], size = 1e5) { + const a: Vertex = { pos: [0, -size], id: 0 }; + const b: Vertex = { pos: [size, size], id: 1 }; + const c: Vertex = { pos: [-size, size], id: 2 }; + const eab = defEdge(0, a, b); + const ebc = defEdge(4, b, c); + const eca = defEdge(8, c, a); + eab.sym.splice(ebc); + ebc.sym.splice(eca); + eca.sym.splice(eab); + this.first = eab; + this.nextEID = 12; + this.nextVID = 3; + if (pts && pts.length) { + isNumber(pts[0][0]) + ? this.addKeys(pts) + : this.addAll([]>pts); + } else { + this.computeDual(); + } + } - /** - * Adds a single new point `p` w/ optional value `val` to the mesh, unless - * there already is another point existing within radius `eps`. If `update` - * is true (default), the mesh dual will be automatically updated using - * {@link DVMesh.computeDual}. - * - * @remarks - * If adding multiple points, ensure `computeDual` will only be called - * for/after the last point insertion to avoid computational overhead. - * - * @param p - - * @param val - - * @param eps - - * @param update - - */ - add(p: ReadonlyVec, val?: T, eps = EPS, update = true) { - let [e, exists] = this.locate(p, eps); - if (exists) return false; - if (pointInSegment(p, e.origin.pos, e.dest.pos)) { - e = e.oprev; - e.onext.remove(); - } - let base = defEdge>(this.nextEID, e.origin, { - pos: p, - id: this.nextVID++, - val, - }); - base.splice(e); - this.nextEID += 4; - const first = base; - do { - base = e.connect(base.sym, this.nextEID); - e = base.oprev; - this.nextEID += 4; - } while (e.lnext !== first); - // enforce delaunay constraints - do { - const t = e.oprev; - if ( - rightOf(t.dest.pos, e) && - pointInCircumCircle(e.origin.pos, t.dest.pos, e.dest.pos, p) - ) { - e.swap(); - e = e.oprev; - } else if (e.onext !== first) { - e = e.onext.lprev; - } else { - break; - } - } while (true); - update && this.computeDual(); - return true; - } + /** + * Adds a single new point `p` w/ optional value `val` to the mesh, unless + * there already is another point existing within radius `eps`. If `update` + * is true (default), the mesh dual will be automatically updated using + * {@link DVMesh.computeDual}. + * + * @remarks + * If adding multiple points, ensure `computeDual` will only be called + * for/after the last point insertion to avoid computational overhead. + * + * @param p - + * @param val - + * @param eps - + * @param update - + */ + add(p: ReadonlyVec, val?: T, eps = EPS, update = true) { + let [e, exists] = this.locate(p, eps); + if (exists) return false; + if (pointInSegment(p, e.origin.pos, e.dest.pos)) { + e = e.oprev; + e.onext.remove(); + } + let base = defEdge>(this.nextEID, e.origin, { + pos: p, + id: this.nextVID++, + val, + }); + base.splice(e); + this.nextEID += 4; + const first = base; + do { + base = e.connect(base.sym, this.nextEID); + e = base.oprev; + this.nextEID += 4; + } while (e.lnext !== first); + // enforce delaunay constraints + do { + const t = e.oprev; + if ( + rightOf(t.dest.pos, e) && + pointInCircumCircle(e.origin.pos, t.dest.pos, e.dest.pos, p) + ) { + e.swap(); + e = e.oprev; + } else if (e.onext !== first) { + e = e.onext.lprev; + } else { + break; + } + } while (true); + update && this.computeDual(); + return true; + } - addKeys(pts: Iterable, eps?: number) { - for (let p of pts) { - this.add(p, undefined, eps, false); - } - this.computeDual(); - } + addKeys(pts: Iterable, eps?: number) { + for (let p of pts) { + this.add(p, undefined, eps, false); + } + this.computeDual(); + } - addAll(pairs: Iterable>, eps?: number) { - for (let p of pairs) { - this.add(p[0], p[1], eps, false); - } - this.computeDual(); - } + addAll(pairs: Iterable>, eps?: number) { + for (let p of pairs) { + this.add(p[0], p[1], eps, false); + } + this.computeDual(); + } - /** - * Returns tuple of the edge related to `p` and a boolean to indicate if - * `p` already exists in this triangulation (true if already present). - * - * @param p - query point - */ - locate(p: ReadonlyVec, eps = EPS): [Edge>, boolean] { - let e = this.first; - while (true) { - if ( - eqDelta2(p, e.origin.pos, eps) || - eqDelta2(p, e.dest.pos, eps) - ) { - return [e, true]; - } else if (rightOf(p, e)) { - e = e.sym; - } else if (!rightOf(p, e.onext)) { - e = e.onext; - } else if (!rightOf(p, e.dprev)) { - e = e.dprev; - } else { - return [e, false]; - } - } - } + /** + * Returns tuple of the edge related to `p` and a boolean to indicate if + * `p` already exists in this triangulation (true if already present). + * + * @param p - query point + */ + locate(p: ReadonlyVec, eps = EPS): [Edge>, boolean] { + let e = this.first; + while (true) { + if ( + eqDelta2(p, e.origin.pos, eps) || + eqDelta2(p, e.dest.pos, eps) + ) { + return [e, true]; + } else if (rightOf(p, e)) { + e = e.sym; + } else if (!rightOf(p, e.onext)) { + e = e.onext; + } else if (!rightOf(p, e.dprev)) { + e = e.dprev; + } else { + return [e, false]; + } + } + } - /** - * Syncronize / update / add dual faces (i.e. Voronoi) for current - * primary mesh (i.e. Delaunay). - */ - computeDual() { - const work = [this.first.rot]; - const visitedEdges: IObjectOf = {}; - const visitedVerts: IObjectOf = {}; - while (work.length) { - const e = work.pop()!; - if (visitedEdges[e.id]) continue; - visitedEdges[e.id] = true; - if (!e.origin || !visitedVerts[e.origin.id]) { - let t = e.rot; - const a = t.origin.pos; - let isBoundary = t.origin.id < 3; - t = t.lnext; - const b = t.origin.pos; - isBoundary = isBoundary && t.origin.id < 3; - t = t.lnext; - const c = t.origin.pos; - isBoundary = isBoundary && t.origin.id < 3; - const id = this.nextVID++; - e.origin = { - pos: !isBoundary ? circumCenter2(a, b, c)! : ZERO2, - id, - }; - visitedVerts[id] = true; - } - work.push(e.sym, e.onext, e.lnext); - } - } + /** + * Syncronize / update / add dual faces (i.e. Voronoi) for current + * primary mesh (i.e. Delaunay). + */ + computeDual() { + const work = [this.first.rot]; + const visitedEdges: IObjectOf = {}; + const visitedVerts: IObjectOf = {}; + while (work.length) { + const e = work.pop()!; + if (visitedEdges[e.id]) continue; + visitedEdges[e.id] = true; + if (!e.origin || !visitedVerts[e.origin.id]) { + let t = e.rot; + const a = t.origin.pos; + let isBoundary = t.origin.id < 3; + t = t.lnext; + const b = t.origin.pos; + isBoundary = isBoundary && t.origin.id < 3; + t = t.lnext; + const c = t.origin.pos; + isBoundary = isBoundary && t.origin.id < 3; + const id = this.nextVID++; + e.origin = { + pos: !isBoundary ? circumCenter2(a, b, c)! : ZERO2, + id, + }; + visitedVerts[id] = true; + } + work.push(e.sym, e.onext, e.lnext); + } + } - delaunay(bounds?: ReadonlyVec[]) { - const cells: Vec[][] = []; - const usedEdges = defBitField(this.nextEID); - const bc = bounds && centroid(bounds); - this.traverse((eab) => { - if (!usedEdges.at(eab.id)) { - const ebc = eab.lnext; - const eca = ebc.lnext; - const va = eab.origin.pos; - const vb = ebc.origin.pos; - const vc = eca.origin.pos; - let verts = [va, vb, vc]; - if ( - bounds && - !( - pointInPolygon2(va, bounds) && - pointInPolygon2(vb, bounds) && - pointInPolygon2(vc, bounds) - ) - ) { - verts = sutherlandHodgeman(verts, bounds, bc); - if (verts.length > 2) { - cells.push(verts); - } - } else { - cells.push(verts); - } - usedEdges.setAt(eab.id); - usedEdges.setAt(ebc.id); - usedEdges.setAt(eca.id); - } - }); - return cells; - } + delaunay(bounds?: ReadonlyVec[]) { + const cells: Vec[][] = []; + const usedEdges = defBitField(this.nextEID); + const bc = bounds && centroid(bounds); + this.traverse((eab) => { + if (!usedEdges.at(eab.id)) { + const ebc = eab.lnext; + const eca = ebc.lnext; + const va = eab.origin.pos; + const vb = ebc.origin.pos; + const vc = eca.origin.pos; + let verts = [va, vb, vc]; + if ( + bounds && + !( + pointInPolygon2(va, bounds) && + pointInPolygon2(vb, bounds) && + pointInPolygon2(vc, bounds) + ) + ) { + verts = sutherlandHodgeman(verts, bounds, bc); + if (verts.length > 2) { + cells.push(verts); + } + } else { + cells.push(verts); + } + usedEdges.setAt(eab.id); + usedEdges.setAt(ebc.id); + usedEdges.setAt(eca.id); + } + }); + return cells; + } - voronoi(bounds?: ReadonlyVec[]) { - const cells: Vec[][] = []; - const bc = bounds && centroid(bounds); - this.traverse( - bounds - ? (e) => { - const first = (e = e.rot); - let verts = []; - let needsClip = false; - let p: ReadonlyVec; - do { - p = e.origin.pos; - verts.push(p); - needsClip = needsClip || !pointInPolygon2(p, bounds); - } while ((e = e.lnext) !== first); - if (needsClip) { - verts = sutherlandHodgeman(verts, bounds, bc); - if (verts.length < 3) return; - } - cells.push(verts); - } - : (e) => { - const first = (e = e.rot); - const verts = []; - do { - verts.push(e.origin.pos); - } while ((e = e.lnext) !== first); - cells.push(verts); - }, - false - ); - return cells; - } + voronoi(bounds?: ReadonlyVec[]) { + const cells: Vec[][] = []; + const bc = bounds && centroid(bounds); + this.traverse( + bounds + ? (e) => { + const first = (e = e.rot); + let verts = []; + let needsClip = false; + let p: ReadonlyVec; + do { + p = e.origin.pos; + verts.push(p); + needsClip = + needsClip || !pointInPolygon2(p, bounds); + } while ((e = e.lnext) !== first); + if (needsClip) { + verts = sutherlandHodgeman(verts, bounds, bc); + if (verts.length < 3) return; + } + cells.push(verts); + } + : (e) => { + const first = (e = e.rot); + const verts = []; + do { + verts.push(e.origin.pos); + } while ((e = e.lnext) !== first); + cells.push(verts); + }, + false + ); + return cells; + } - edges(voronoi = false, boundsMinMax?: VecPair) { - const edges: VecPair[] = []; - this.traverse( - (e, visitedEdges) => { - if (visitedEdges.at(e.sym.id)) return; - if (e.origin.id > 2 && e.dest.id > 2) { - const a = e.origin.pos; - const b = e.dest.pos; - if (boundsMinMax) { - const clip = liangBarsky2( - a, - b, - boundsMinMax[0], - boundsMinMax[1] - ); - clip && edges.push([clip[0], clip[1]]); - } else { - edges.push([a, b]); - } - } - visitedEdges.setAt(e.id); - }, - true, - voronoi ? this.first.rot : this.first - ); - return edges; - } + edges(voronoi = false, boundsMinMax?: VecPair) { + const edges: VecPair[] = []; + this.traverse( + (e, visitedEdges) => { + if (visitedEdges.at(e.sym.id)) return; + if (e.origin.id > 2 && e.dest.id > 2) { + const a = e.origin.pos; + const b = e.dest.pos; + if (boundsMinMax) { + const clip = liangBarsky2( + a, + b, + boundsMinMax[0], + boundsMinMax[1] + ); + clip && edges.push([clip[0], clip[1]]); + } else { + edges.push([a, b]); + } + } + visitedEdges.setAt(e.id); + }, + true, + voronoi ? this.first.rot : this.first + ); + return edges; + } - traverse(proc: Visitor, edges = true, e: Edge> = this.first) { - const work = [e]; - const visitedEdges = defBitField(this.nextEID); - const visitedVerts = defBitField(this.nextVID); - while (work.length) { - e = work.pop()!; - if (visitedEdges.at(e.id)) continue; - visitedEdges.setAt(e.id); - const eoID = e.origin.id; - if (eoID > 2 && e.rot.origin.id > 2) { - if (edges || !visitedVerts.at(eoID)) { - visitedVerts.setAt(eoID); - proc(e, visitedEdges, visitedVerts); - } - } - work.push(e.sym, e.onext, e.lnext); - } - } + traverse(proc: Visitor, edges = true, e: Edge> = this.first) { + const work = [e]; + const visitedEdges = defBitField(this.nextEID); + const visitedVerts = defBitField(this.nextVID); + while (work.length) { + e = work.pop()!; + if (visitedEdges.at(e.id)) continue; + visitedEdges.setAt(e.id); + const eoID = e.origin.id; + if (eoID > 2 && e.rot.origin.id > 2) { + if (edges || !visitedVerts.at(eoID)) { + visitedVerts.setAt(eoID); + proc(e, visitedEdges, visitedVerts); + } + } + work.push(e.sym, e.onext, e.lnext); + } + } } diff --git a/packages/geom-voronoi/tsconfig.json b/packages/geom-voronoi/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/geom-voronoi/tsconfig.json +++ b/packages/geom-voronoi/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/geom/api-extractor.json b/packages/geom/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/geom/api-extractor.json +++ b/packages/geom/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/geom/package.json b/packages/geom/package.json index da59c92aaa..f5922e6557 100644 --- a/packages/geom/package.json +++ b/packages/geom/package.json @@ -1,383 +1,383 @@ { - "name": "@thi.ng/geom", - "version": "3.4.2", - "description": "Functional, polymorphic API for 2D geometry types & SVG generation", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc api ctors internal", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test", - "tool:bpatch": "tools:node-esm tools/bpatch.ts" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/arrays": "^2.3.1", - "@thi.ng/associative": "^6.2.0", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/defmulti": "^2.1.8", - "@thi.ng/equiv": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/geom-api": "^3.3.2", - "@thi.ng/geom-arc": "^2.1.19", - "@thi.ng/geom-clip-line": "^2.1.19", - "@thi.ng/geom-clip-poly": "^2.1.19", - "@thi.ng/geom-closest-point": "^2.1.16", - "@thi.ng/geom-hull": "^2.1.16", - "@thi.ng/geom-isec": "^2.1.19", - "@thi.ng/geom-poly-utils": "^2.3.3", - "@thi.ng/geom-resample": "^2.1.19", - "@thi.ng/geom-splines": "^2.1.19", - "@thi.ng/geom-subdiv-curve": "^2.1.19", - "@thi.ng/geom-tessellate": "^2.1.19", - "@thi.ng/hiccup": "^4.2.10", - "@thi.ng/hiccup-svg": "^4.3.3", - "@thi.ng/math": "^5.3.4", - "@thi.ng/matrices": "^2.1.16", - "@thi.ng/random": "^3.3.3", - "@thi.ng/strings": "^3.3.6", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/vectors": "^7.5.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "analysis", - "arc", - "area", - "bbox", - "bezier", - "centroid", - "circle", - "clipping", - "conversion", - "datastructure", - "geometry", - "graphics", - "intersection", - "polymorphic", - "sample", - "scatter", - "shape", - "svg", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "api", - "internal" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./aabb": { - "default": "./aabb.js" - }, - "./api/aabb": { - "default": "./api/aabb.js" - }, - "./api/apc": { - "default": "./api/apc.js" - }, - "./api/arc": { - "default": "./api/arc.js" - }, - "./api/bpatch": { - "default": "./api/bpatch.js" - }, - "./api/circle": { - "default": "./api/circle.js" - }, - "./api/cubic": { - "default": "./api/cubic.js" - }, - "./api/ellipse": { - "default": "./api/ellipse.js" - }, - "./api/group": { - "default": "./api/group.js" - }, - "./api/line": { - "default": "./api/line.js" - }, - "./api/path": { - "default": "./api/path.js" - }, - "./api/plane": { - "default": "./api/plane.js" - }, - "./api/points": { - "default": "./api/points.js" - }, - "./api/polygon": { - "default": "./api/polygon.js" - }, - "./api/polyline": { - "default": "./api/polyline.js" - }, - "./api/quad": { - "default": "./api/quad.js" - }, - "./api/quad3": { - "default": "./api/quad3.js" - }, - "./api/quadratic": { - "default": "./api/quadratic.js" - }, - "./api/ray": { - "default": "./api/ray.js" - }, - "./api/rect": { - "default": "./api/rect.js" - }, - "./api/sphere": { - "default": "./api/sphere.js" - }, - "./api/text": { - "default": "./api/text.js" - }, - "./api/triangle": { - "default": "./api/triangle.js" - }, - "./apply-transforms": { - "default": "./apply-transforms.js" - }, - "./arc-length": { - "default": "./arc-length.js" - }, - "./arc": { - "default": "./arc.js" - }, - "./area": { - "default": "./area.js" - }, - "./as-cubic": { - "default": "./as-cubic.js" - }, - "./as-path": { - "default": "./as-path.js" - }, - "./as-polygon": { - "default": "./as-polygon.js" - }, - "./as-polyline": { - "default": "./as-polyline.js" - }, - "./as-svg": { - "default": "./as-svg.js" - }, - "./bounds": { - "default": "./bounds.js" - }, - "./bpatch": { - "default": "./bpatch.js" - }, - "./center": { - "default": "./center.js" - }, - "./centroid": { - "default": "./centroid.js" - }, - "./circle": { - "default": "./circle.js" - }, - "./classify-point": { - "default": "./classify-point.js" - }, - "./clip-convex": { - "default": "./clip-convex.js" - }, - "./closest-point": { - "default": "./closest-point.js" - }, - "./convex-hull": { - "default": "./convex-hull.js" - }, - "./cubic": { - "default": "./cubic.js" - }, - "./edges": { - "default": "./edges.js" - }, - "./ellipse": { - "default": "./ellipse.js" - }, - "./fit-into-bounds": { - "default": "./fit-into-bounds.js" - }, - "./flip": { - "default": "./flip.js" - }, - "./group": { - "default": "./group.js" - }, - "./internal/copy": { - "default": "./internal/copy.js" - }, - "./internal/dispatch": { - "default": "./internal/dispatch.js" - }, - "./internal/transform": { - "default": "./internal/transform.js" - }, - "./intersects": { - "default": "./intersects.js" - }, - "./line": { - "default": "./line.js" - }, - "./map-point": { - "default": "./map-point.js" - }, - "./offset": { - "default": "./offset.js" - }, - "./path-builder": { - "default": "./path-builder.js" - }, - "./path-from-svg": { - "default": "./path-from-svg.js" - }, - "./path": { - "default": "./path.js" - }, - "./plane": { - "default": "./plane.js" - }, - "./point-at": { - "default": "./point-at.js" - }, - "./point-inside": { - "default": "./point-inside.js" - }, - "./points": { - "default": "./points.js" - }, - "./polygon": { - "default": "./polygon.js" - }, - "./polyline": { - "default": "./polyline.js" - }, - "./quad": { - "default": "./quad.js" - }, - "./quadratic": { - "default": "./quadratic.js" - }, - "./ray": { - "default": "./ray.js" - }, - "./rect": { - "default": "./rect.js" - }, - "./resample": { - "default": "./resample.js" - }, - "./rotate": { - "default": "./rotate.js" - }, - "./scale": { - "default": "./scale.js" - }, - "./scatter": { - "default": "./scatter.js" - }, - "./simplify": { - "default": "./simplify.js" - }, - "./sphere": { - "default": "./sphere.js" - }, - "./split-at": { - "default": "./split-at.js" - }, - "./split-near": { - "default": "./split-near.js" - }, - "./subdiv-curve": { - "default": "./subdiv-curve.js" - }, - "./tangent-at": { - "default": "./tangent-at.js" - }, - "./tessellate": { - "default": "./tessellate.js" - }, - "./text": { - "default": "./text.js" - }, - "./transform-vertices": { - "default": "./transform-vertices.js" - }, - "./transform": { - "default": "./transform.js" - }, - "./translate": { - "default": "./translate.js" - }, - "./triangle": { - "default": "./triangle.js" - }, - "./union": { - "default": "./union.js" - }, - "./unmap-point": { - "default": "./unmap-point.js" - }, - "./vertices": { - "default": "./vertices.js" - }, - "./volume": { - "default": "./volume.js" - }, - "./warp-points": { - "default": "./warp-points.js" - }, - "./with-attribs": { - "default": "./with-attribs.js" - } - }, - "thi.ng": { - "year": 2013 - } + "name": "@thi.ng/geom", + "version": "3.4.2", + "description": "Functional, polymorphic API for 2D geometry types & SVG generation", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/geom#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc api ctors internal", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test", + "tool:bpatch": "tools:node-esm tools/bpatch.ts" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/arrays": "^2.3.1", + "@thi.ng/associative": "^6.2.0", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/defmulti": "^2.1.8", + "@thi.ng/equiv": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/geom-api": "^3.3.2", + "@thi.ng/geom-arc": "^2.1.19", + "@thi.ng/geom-clip-line": "^2.1.19", + "@thi.ng/geom-clip-poly": "^2.1.19", + "@thi.ng/geom-closest-point": "^2.1.16", + "@thi.ng/geom-hull": "^2.1.16", + "@thi.ng/geom-isec": "^2.1.19", + "@thi.ng/geom-poly-utils": "^2.3.3", + "@thi.ng/geom-resample": "^2.1.19", + "@thi.ng/geom-splines": "^2.1.19", + "@thi.ng/geom-subdiv-curve": "^2.1.19", + "@thi.ng/geom-tessellate": "^2.1.19", + "@thi.ng/hiccup": "^4.2.10", + "@thi.ng/hiccup-svg": "^4.3.3", + "@thi.ng/math": "^5.3.4", + "@thi.ng/matrices": "^2.1.16", + "@thi.ng/random": "^3.3.3", + "@thi.ng/strings": "^3.3.6", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/vectors": "^7.5.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "analysis", + "arc", + "area", + "bbox", + "bezier", + "centroid", + "circle", + "clipping", + "conversion", + "datastructure", + "geometry", + "graphics", + "intersection", + "polymorphic", + "sample", + "scatter", + "shape", + "svg", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "api", + "internal" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./aabb": { + "default": "./aabb.js" + }, + "./api/aabb": { + "default": "./api/aabb.js" + }, + "./api/apc": { + "default": "./api/apc.js" + }, + "./api/arc": { + "default": "./api/arc.js" + }, + "./api/bpatch": { + "default": "./api/bpatch.js" + }, + "./api/circle": { + "default": "./api/circle.js" + }, + "./api/cubic": { + "default": "./api/cubic.js" + }, + "./api/ellipse": { + "default": "./api/ellipse.js" + }, + "./api/group": { + "default": "./api/group.js" + }, + "./api/line": { + "default": "./api/line.js" + }, + "./api/path": { + "default": "./api/path.js" + }, + "./api/plane": { + "default": "./api/plane.js" + }, + "./api/points": { + "default": "./api/points.js" + }, + "./api/polygon": { + "default": "./api/polygon.js" + }, + "./api/polyline": { + "default": "./api/polyline.js" + }, + "./api/quad": { + "default": "./api/quad.js" + }, + "./api/quad3": { + "default": "./api/quad3.js" + }, + "./api/quadratic": { + "default": "./api/quadratic.js" + }, + "./api/ray": { + "default": "./api/ray.js" + }, + "./api/rect": { + "default": "./api/rect.js" + }, + "./api/sphere": { + "default": "./api/sphere.js" + }, + "./api/text": { + "default": "./api/text.js" + }, + "./api/triangle": { + "default": "./api/triangle.js" + }, + "./apply-transforms": { + "default": "./apply-transforms.js" + }, + "./arc-length": { + "default": "./arc-length.js" + }, + "./arc": { + "default": "./arc.js" + }, + "./area": { + "default": "./area.js" + }, + "./as-cubic": { + "default": "./as-cubic.js" + }, + "./as-path": { + "default": "./as-path.js" + }, + "./as-polygon": { + "default": "./as-polygon.js" + }, + "./as-polyline": { + "default": "./as-polyline.js" + }, + "./as-svg": { + "default": "./as-svg.js" + }, + "./bounds": { + "default": "./bounds.js" + }, + "./bpatch": { + "default": "./bpatch.js" + }, + "./center": { + "default": "./center.js" + }, + "./centroid": { + "default": "./centroid.js" + }, + "./circle": { + "default": "./circle.js" + }, + "./classify-point": { + "default": "./classify-point.js" + }, + "./clip-convex": { + "default": "./clip-convex.js" + }, + "./closest-point": { + "default": "./closest-point.js" + }, + "./convex-hull": { + "default": "./convex-hull.js" + }, + "./cubic": { + "default": "./cubic.js" + }, + "./edges": { + "default": "./edges.js" + }, + "./ellipse": { + "default": "./ellipse.js" + }, + "./fit-into-bounds": { + "default": "./fit-into-bounds.js" + }, + "./flip": { + "default": "./flip.js" + }, + "./group": { + "default": "./group.js" + }, + "./internal/copy": { + "default": "./internal/copy.js" + }, + "./internal/dispatch": { + "default": "./internal/dispatch.js" + }, + "./internal/transform": { + "default": "./internal/transform.js" + }, + "./intersects": { + "default": "./intersects.js" + }, + "./line": { + "default": "./line.js" + }, + "./map-point": { + "default": "./map-point.js" + }, + "./offset": { + "default": "./offset.js" + }, + "./path-builder": { + "default": "./path-builder.js" + }, + "./path-from-svg": { + "default": "./path-from-svg.js" + }, + "./path": { + "default": "./path.js" + }, + "./plane": { + "default": "./plane.js" + }, + "./point-at": { + "default": "./point-at.js" + }, + "./point-inside": { + "default": "./point-inside.js" + }, + "./points": { + "default": "./points.js" + }, + "./polygon": { + "default": "./polygon.js" + }, + "./polyline": { + "default": "./polyline.js" + }, + "./quad": { + "default": "./quad.js" + }, + "./quadratic": { + "default": "./quadratic.js" + }, + "./ray": { + "default": "./ray.js" + }, + "./rect": { + "default": "./rect.js" + }, + "./resample": { + "default": "./resample.js" + }, + "./rotate": { + "default": "./rotate.js" + }, + "./scale": { + "default": "./scale.js" + }, + "./scatter": { + "default": "./scatter.js" + }, + "./simplify": { + "default": "./simplify.js" + }, + "./sphere": { + "default": "./sphere.js" + }, + "./split-at": { + "default": "./split-at.js" + }, + "./split-near": { + "default": "./split-near.js" + }, + "./subdiv-curve": { + "default": "./subdiv-curve.js" + }, + "./tangent-at": { + "default": "./tangent-at.js" + }, + "./tessellate": { + "default": "./tessellate.js" + }, + "./text": { + "default": "./text.js" + }, + "./transform-vertices": { + "default": "./transform-vertices.js" + }, + "./transform": { + "default": "./transform.js" + }, + "./translate": { + "default": "./translate.js" + }, + "./triangle": { + "default": "./triangle.js" + }, + "./union": { + "default": "./union.js" + }, + "./unmap-point": { + "default": "./unmap-point.js" + }, + "./vertices": { + "default": "./vertices.js" + }, + "./volume": { + "default": "./volume.js" + }, + "./warp-points": { + "default": "./warp-points.js" + }, + "./with-attribs": { + "default": "./with-attribs.js" + } + }, + "thi.ng": { + "year": 2013 + } } diff --git a/packages/geom/src/aabb.ts b/packages/geom/src/aabb.ts index 63388e31ad..6c336d0a7f 100644 --- a/packages/geom/src/aabb.ts +++ b/packages/geom/src/aabb.ts @@ -15,27 +15,27 @@ export function aabb(pos: Vec, size: number | Vec, attribs?: Attribs): AABB; export function aabb(size: number | Vec, attribs?: Attribs): AABB; export function aabb(attribs?: Attribs): AABB; export function aabb(...args: any[]) { - return new AABB(...__argsVV(args)); + return new AABB(...__argsVV(args)); } export const aabbFromMinMax = (min: Vec, max: Vec, attribs?: Attribs) => - new AABB(min, sub3([], max, min), attribs); + new AABB(min, sub3([], max, min), attribs); export const aabbFromMinMaxWithMargin = ( - min: Vec, - max: Vec, - margin: number, - attribs?: Attribs + min: Vec, + max: Vec, + margin: number, + attribs?: Attribs ) => aabbFromMinMax(min, max, attribs).offset(margin); export const aabbFromCentroid = (centroid: Vec, size: Vec, attribs?: Attribs) => - new AABB(maddN3([], size, -0.5, centroid), size, attribs); + new AABB(maddN3([], size, -0.5, centroid), size, attribs); export const aabbFromCentroidWithMargin = ( - centroid: Vec, - size: Vec, - margin: number, - attribs?: Attribs + centroid: Vec, + size: Vec, + margin: number, + attribs?: Attribs ) => aabbFromCentroid(centroid, size, attribs).offset(margin); /** @@ -46,12 +46,12 @@ export const aabbFromCentroidWithMargin = ( * @param b - */ export const intersectionAABB = (a: AABB, b: AABB) => { - const p = max3([], a.pos, b.pos); - const q = min3(null, add3([], a.pos, a.size), add3([], b.pos, b.size)); - const size = max3(null, sub3(null, q, p), ZERO3); - return size[0] > 0 && size[1] > 0 && size[2] > 0 - ? new AABB(p, size) - : undefined; + const p = max3([], a.pos, b.pos); + const q = min3(null, add3([], a.pos, a.size), add3([], b.pos, b.size)); + const size = max3(null, sub3(null, q, p), ZERO3); + return size[0] > 0 && size[1] > 0 && size[2] > 0 + ? new AABB(p, size) + : undefined; }; /** @@ -63,14 +63,14 @@ export const intersectionAABB = (a: AABB, b: AABB) => { export function inscribedAABB(sphere: Sphere): AABB; export function inscribedAABB(pos: ReadonlyVec, r: number): AABB; export function inscribedAABB(...args: any[]) { - let pos: ReadonlyVec, r: number; - if (args.length === 1) { - const c: Sphere = args[0]; - pos = c.pos; - r = c.r; - } else { - [pos, r] = args; - } - r *= SQRT2_2; - return aabb(subN3([], pos, r), r * 2); + let pos: ReadonlyVec, r: number; + if (args.length === 1) { + const c: Sphere = args[0]; + pos = c.pos; + r = c.r; + } else { + [pos, r] = args; + } + r *= SQRT2_2; + return aabb(subN3([], pos, r), r * 2); } diff --git a/packages/geom/src/api/aabb.ts b/packages/geom/src/api/aabb.ts index 2e167849a4..81a10ebee5 100644 --- a/packages/geom/src/api/aabb.ts +++ b/packages/geom/src/api/aabb.ts @@ -9,39 +9,39 @@ import { __asVec } from "../internal/args.js"; import { __copyAttribs } from "../internal/copy.js"; export class AABB implements AABBLike { - size: Vec; + size: Vec; - constructor( - public pos: Vec = [0, 0, 0], - size: number | Vec = 1, - public attribs?: Attribs - ) { - this.size = __asVec(size, 3); - } + constructor( + public pos: Vec = [0, 0, 0], + size: number | Vec = 1, + public attribs?: Attribs + ) { + this.size = __asVec(size, 3); + } - get type() { - return "aabb"; - } + get type() { + return "aabb"; + } - copy(): AABB { - return new AABB( - set3([], this.pos), - set3([], this.size), - __copyAttribs(this) - ); - } + copy(): AABB { + return new AABB( + set3([], this.pos), + set3([], this.size), + __copyAttribs(this) + ); + } - withAttribs(attribs: Attribs): AABB { - return new AABB(this.pos, this.size, attribs); - } + withAttribs(attribs: Attribs): AABB { + return new AABB(this.pos, this.size, attribs); + } - max() { - return add3([], this.pos, this.size); - } + max() { + return add3([], this.pos, this.size); + } - offset(offset: number) { - subN3(null, this.pos, offset); - max3(null, addN3(null, this.size, offset * 2), ZERO3); - return this; - } + offset(offset: number) { + subN3(null, this.pos, offset); + max3(null, addN3(null, this.size, offset * 2), ZERO3); + return this; + } } diff --git a/packages/geom/src/api/apc.ts b/packages/geom/src/api/apc.ts index 32b40a8c4b..36c2b1228f 100644 --- a/packages/geom/src/api/apc.ts +++ b/packages/geom/src/api/apc.ts @@ -2,15 +2,15 @@ import type { Attribs, PCLike } from "@thi.ng/geom-api"; import type { Vec } from "@thi.ng/vectors"; export abstract class APC implements PCLike { - constructor(public points: Vec[] = [], public attribs?: Attribs) {} + constructor(public points: Vec[] = [], public attribs?: Attribs) {} - abstract get type(): number | string; + abstract get type(): number | string; - abstract copy(): APC; + abstract copy(): APC; - abstract withAttribs(attribs: Attribs): APC; + abstract withAttribs(attribs: Attribs): APC; - *[Symbol.iterator]() { - yield* this.points; - } + *[Symbol.iterator]() { + yield* this.points; + } } diff --git a/packages/geom/src/api/arc.ts b/packages/geom/src/api/arc.ts index 78ca76391f..042cc7c937 100644 --- a/packages/geom/src/api/arc.ts +++ b/packages/geom/src/api/arc.ts @@ -1,108 +1,108 @@ import { equiv } from "@thi.ng/equiv"; import type { - Attribs, - IHiccupPathSegment, - IHiccupShape, + Attribs, + IHiccupPathSegment, + IHiccupShape, } from "@thi.ng/geom-api"; import { - pointAt as arcPointAt, - pointAtTheta as arcPointAtTheta, + pointAt as arcPointAt, + pointAtTheta as arcPointAtTheta, } from "@thi.ng/geom-arc/point-at"; import type { Vec } from "@thi.ng/vectors"; import { set } from "@thi.ng/vectors/set"; import { __copyAttribs } from "../internal/copy.js"; export class Arc implements IHiccupShape, IHiccupPathSegment { - constructor( - public pos: Vec, - public r: Vec, - public axis: number, - public start: number, - public end: number, - public xl = false, - public cw = false, - public attribs?: Attribs - ) {} + constructor( + public pos: Vec, + public r: Vec, + public axis: number, + public start: number, + public end: number, + public xl = false, + public cw = false, + public attribs?: Attribs + ) {} - get type() { - return "arc"; - } + get type() { + return "arc"; + } - copy(): Arc { - return new Arc( - set([], this.pos), - set([], this.r), - this.axis, - this.start, - this.end, - this.xl, - this.cw, - __copyAttribs(this) - ); - } + copy(): Arc { + return new Arc( + set([], this.pos), + set([], this.r), + this.axis, + this.start, + this.end, + this.xl, + this.cw, + __copyAttribs(this) + ); + } - withAttribs(attribs: Attribs): Arc { - return new Arc( - this.pos, - this.r, - this.axis, - this.start, - this.end, - this.xl, - this.cw, - attribs - ); - } + withAttribs(attribs: Attribs): Arc { + return new Arc( + this.pos, + this.r, + this.axis, + this.start, + this.end, + this.xl, + this.cw, + attribs + ); + } - equiv(o: any) { - return ( - o instanceof Arc && - equiv(this.pos, o.pos) && - equiv(this.r, o.r) && - this.start === o.start && - this.end === o.end && - this.axis === o.axis && - this.xl === o.xl && - this.cw && - o.cw - ); - } + equiv(o: any) { + return ( + o instanceof Arc && + equiv(this.pos, o.pos) && + equiv(this.r, o.r) && + this.start === o.start && + this.end === o.end && + this.axis === o.axis && + this.xl === o.xl && + this.cw && + o.cw + ); + } - pointAt(t: number, out: Vec = []) { - return arcPointAt( - this.pos, - this.r, - this.axis, - this.start, - this.end, - t, - out - ); - } + pointAt(t: number, out: Vec = []) { + return arcPointAt( + this.pos, + this.r, + this.axis, + this.start, + this.end, + t, + out + ); + } - pointAtTheta(theta: number, out: Vec = []) { - return arcPointAtTheta(this.pos, this.r, this.axis, theta, out); - } + pointAtTheta(theta: number, out: Vec = []) { + return arcPointAtTheta(this.pos, this.r, this.axis, theta, out); + } - toHiccup() { - return [ - "path", - this.attribs, - [["M", this.pointAt(0)], ...this.toHiccupPathSegments()], - ]; - } + toHiccup() { + return [ + "path", + this.attribs, + [["M", this.pointAt(0)], ...this.toHiccupPathSegments()], + ]; + } - toHiccupPathSegments() { - return [ - [ - "A", - this.r[0], - this.r[1], - this.axis, - this.xl, - this.cw, - this.pointAt(1), - ], - ]; - } + toHiccupPathSegments() { + return [ + [ + "A", + this.r[0], + this.r[1], + this.axis, + this.xl, + this.cw, + this.pointAt(1), + ], + ]; + } } diff --git a/packages/geom/src/api/bpatch.ts b/packages/geom/src/api/bpatch.ts index 0c356d9e18..473aa779e6 100644 --- a/packages/geom/src/api/bpatch.ts +++ b/packages/geom/src/api/bpatch.ts @@ -32,87 +32,87 @@ import { APC } from "./apc.js"; * */ export class BPatch extends APC implements IHiccupShape { - constructor(points: Vec[], attribs?: Attribs) { - assert(points.length === 16, "require 16 control points"); - super(points, attribs); - } + constructor(points: Vec[], attribs?: Attribs) { + assert(points.length === 16, "require 16 control points"); + super(points, attribs); + } - get type() { - return "bpatch"; - } + get type() { + return "bpatch"; + } - copy(): BPatch { - return __copyShape(BPatch, this); - } + copy(): BPatch { + return __copyShape(BPatch, this); + } - withAttribs(attribs: Attribs): BPatch { - return new BPatch(this.points, attribs); - } + withAttribs(attribs: Attribs): BPatch { + return new BPatch(this.points, attribs); + } - edges() { - const p = this.points; - return [ - [0, 1], - [1, 2], - [2, 3], - [0, 4], - [1, 5], - [2, 6], - [3, 7], - [4, 5], - [5, 6], - [6, 7], - [4, 8], - [5, 9], - [6, 10], - [7, 11], - [8, 9], - [9, 10], - [10, 11], - [8, 12], - [9, 13], - [10, 14], - [11, 15], - [12, 13], - [13, 14], - [14, 15], - ].map(([a, b]) => [p[a], p[b]]); - } + edges() { + const p = this.points; + return [ + [0, 1], + [1, 2], + [2, 3], + [0, 4], + [1, 5], + [2, 6], + [3, 7], + [4, 5], + [5, 6], + [6, 7], + [4, 8], + [5, 9], + [6, 10], + [7, 11], + [8, 9], + [9, 10], + [10, 11], + [8, 12], + [9, 13], + [10, 14], + [11, 15], + [12, 13], + [13, 14], + [14, 15], + ].map(([a, b]) => [p[a], p[b]]); + } - unmapPoint(uv: ReadonlyVec, out?: Vec) { - const cp = this.points; - const [u, v] = uv; - return mixCubic( - out || null, - mixCubic([], cp[0], cp[4], cp[8], cp[12], v), - mixCubic([], cp[1], cp[5], cp[9], cp[13], v), - mixCubic([], cp[2], cp[6], cp[10], cp[14], v), - mixCubic([], cp[3], cp[7], cp[11], cp[15], v), - u - ); - } + unmapPoint(uv: ReadonlyVec, out?: Vec) { + const cp = this.points; + const [u, v] = uv; + return mixCubic( + out || null, + mixCubic([], cp[0], cp[4], cp[8], cp[12], v), + mixCubic([], cp[1], cp[5], cp[9], cp[13], v), + mixCubic([], cp[2], cp[6], cp[10], cp[14], v), + mixCubic([], cp[3], cp[7], cp[11], cp[15], v), + u + ); + } - toHiccup() { - const attribs = this.attribs; - const acc: any[] = ["g", { fill: "none", ...attribs }]; - if (attribs && attribs.res) { - const res = attribs.res - 1; - const delta = 1 / res; - for (let u = 0; u <= res; u++) { - const col = []; - const row = []; - const uu = u * delta; - for (let v = 0; v <= res; v++) { - const p = [uu, v * delta]; - col.push(this.unmapPoint(p)); - row.push(this.unmapPoint([p[1], p[0]])); - } - acc.push(["polyline", {}, col]); - acc.push(["polyline", {}, row]); - } - } else { - this.edges().forEach((l) => acc.push(["line", {}, ...l])); - } - return acc; - } + toHiccup() { + const attribs = this.attribs; + const acc: any[] = ["g", { fill: "none", ...attribs }]; + if (attribs && attribs.res) { + const res = attribs.res - 1; + const delta = 1 / res; + for (let u = 0; u <= res; u++) { + const col = []; + const row = []; + const uu = u * delta; + for (let v = 0; v <= res; v++) { + const p = [uu, v * delta]; + col.push(this.unmapPoint(p)); + row.push(this.unmapPoint([p[1], p[0]])); + } + acc.push(["polyline", {}, col]); + acc.push(["polyline", {}, row]); + } + } else { + this.edges().forEach((l) => acc.push(["line", {}, ...l])); + } + return acc; + } } diff --git a/packages/geom/src/api/circle.ts b/packages/geom/src/api/circle.ts index d6241b2b41..a6a33e6a2c 100644 --- a/packages/geom/src/api/circle.ts +++ b/packages/geom/src/api/circle.ts @@ -4,25 +4,25 @@ import { set } from "@thi.ng/vectors/set"; import { __copyAttribs } from "../internal/copy.js"; export class Circle implements IHiccupShape { - constructor( - public pos: Vec = [0, 0], - public r = 1, - public attribs?: Attribs - ) {} + constructor( + public pos: Vec = [0, 0], + public r = 1, + public attribs?: Attribs + ) {} - get type() { - return "circle"; - } + get type() { + return "circle"; + } - copy(): Circle { - return new Circle(set([], this.pos), this.r, __copyAttribs(this)); - } + copy(): Circle { + return new Circle(set([], this.pos), this.r, __copyAttribs(this)); + } - withAttribs(attribs: Attribs): Circle { - return new Circle(this.pos, this.r, attribs); - } + withAttribs(attribs: Attribs): Circle { + return new Circle(this.pos, this.r, attribs); + } - toHiccup() { - return ["circle", this.attribs, this.pos, this.r]; - } + toHiccup() { + return ["circle", this.attribs, this.pos, this.r]; + } } diff --git a/packages/geom/src/api/cubic.ts b/packages/geom/src/api/cubic.ts index fb2edce580..b33888638d 100644 --- a/packages/geom/src/api/cubic.ts +++ b/packages/geom/src/api/cubic.ts @@ -3,28 +3,28 @@ import { __copyShape } from "../internal/copy.js"; import { APC } from "./apc.js"; export class Cubic extends APC implements IHiccupPathSegment { - get type() { - return "cubic"; - } + get type() { + return "cubic"; + } - copy(): Cubic { - return __copyShape(Cubic, this); - } + copy(): Cubic { + return __copyShape(Cubic, this); + } - withAttribs(attribs: Attribs): Cubic { - return new Cubic(this.points, attribs); - } + withAttribs(attribs: Attribs): Cubic { + return new Cubic(this.points, attribs); + } - toHiccup() { - return [ - "path", - this.attribs, - [["M", this.points[0]], ...this.toHiccupPathSegments()], - ]; - } + toHiccup() { + return [ + "path", + this.attribs, + [["M", this.points[0]], ...this.toHiccupPathSegments()], + ]; + } - toHiccupPathSegments() { - const pts = this.points; - return [["C", pts[1], pts[2], pts[3]]]; - } + toHiccupPathSegments() { + const pts = this.points; + return [["C", pts[1], pts[2], pts[3]]]; + } } diff --git a/packages/geom/src/api/ellipse.ts b/packages/geom/src/api/ellipse.ts index 82804de7f6..7a5187102c 100644 --- a/packages/geom/src/api/ellipse.ts +++ b/packages/geom/src/api/ellipse.ts @@ -5,33 +5,33 @@ import { __asVec } from "../internal/args.js"; import { __copyAttribs } from "../internal/copy.js"; export class Ellipse implements IHiccupShape { - r: Vec; + r: Vec; - constructor( - public pos: Vec = [0, 0], - r: number | Vec = [1, 1], - public attribs?: Attribs - ) { - this.r = __asVec(r); - } + constructor( + public pos: Vec = [0, 0], + r: number | Vec = [1, 1], + public attribs?: Attribs + ) { + this.r = __asVec(r); + } - get type() { - return "ellipse"; - } + get type() { + return "ellipse"; + } - copy(): Ellipse { - return new Ellipse( - set([], this.pos), - set([], this.r), - __copyAttribs(this) - ); - } + copy(): Ellipse { + return new Ellipse( + set([], this.pos), + set([], this.r), + __copyAttribs(this) + ); + } - withAttribs(attribs: Attribs): Ellipse { - return new Ellipse(this.pos, this.r, attribs); - } + withAttribs(attribs: Attribs): Ellipse { + return new Ellipse(this.pos, this.r, attribs); + } - toHiccup() { - return ["ellipse", this.attribs, this.pos, this.r]; - } + toHiccup() { + return ["ellipse", this.attribs, this.pos, this.r]; + } } diff --git a/packages/geom/src/api/group.ts b/packages/geom/src/api/group.ts index 5d3ceb93f2..91dd260d89 100644 --- a/packages/geom/src/api/group.ts +++ b/packages/geom/src/api/group.ts @@ -4,36 +4,36 @@ import type { Attribs, IHiccupShape } from "@thi.ng/geom-api"; import { __copyAttribs } from "../internal/copy.js"; export class Group implements IHiccupShape { - constructor( - public attribs: Attribs, - public children: IHiccupShape[] = [] - ) {} - - get type() { - return "group"; - } - - *[Symbol.iterator]() { - yield* this.children; - } - - copy(): Group { - return this.copyTransformed((c) => c.copy()); - } - - copyTransformed(fn: Fn) { - return new Group(__copyAttribs(this), this.children.map(fn)); - } - - withAttribs(attribs: Attribs): Group { - return new Group(attribs, this.children); - } - - equiv(o: any) { - return o instanceof Group && equiv(this.children, o.children); - } - - toHiccup() { - return ["g", this.attribs, ...this.children.map((x) => x.toHiccup())]; - } + constructor( + public attribs: Attribs, + public children: IHiccupShape[] = [] + ) {} + + get type() { + return "group"; + } + + *[Symbol.iterator]() { + yield* this.children; + } + + copy(): Group { + return this.copyTransformed((c) => c.copy()); + } + + copyTransformed(fn: Fn) { + return new Group(__copyAttribs(this), this.children.map(fn)); + } + + withAttribs(attribs: Attribs): Group { + return new Group(attribs, this.children); + } + + equiv(o: any) { + return o instanceof Group && equiv(this.children, o.children); + } + + toHiccup() { + return ["g", this.attribs, ...this.children.map((x) => x.toHiccup())]; + } } diff --git a/packages/geom/src/api/line.ts b/packages/geom/src/api/line.ts index 06af922c75..fd0ff0d34a 100644 --- a/packages/geom/src/api/line.ts +++ b/packages/geom/src/api/line.ts @@ -1,36 +1,36 @@ import type { - Attribs, - IHiccupPathSegment, - IHiccupShape, + Attribs, + IHiccupPathSegment, + IHiccupShape, } from "@thi.ng/geom-api"; import { __copyShape } from "../internal/copy.js"; import { APC } from "./apc.js"; export class Line extends APC implements IHiccupShape, IHiccupPathSegment { - get type() { - return "line"; - } + get type() { + return "line"; + } - copy(): Line { - return __copyShape(Line, this); - } + copy(): Line { + return __copyShape(Line, this); + } - withAttribs(attribs: Attribs): Line { - return new Line(this.points, attribs); - } + withAttribs(attribs: Attribs): Line { + return new Line(this.points, attribs); + } - toHiccup() { - return ["line", this.attribs, this.points[0], this.points[1]]; - } + toHiccup() { + return ["line", this.attribs, this.points[0], this.points[1]]; + } - toHiccupPathSegments() { - const [a, b] = this.points; - return [ - a[0] === b[0] - ? ["V", b[1]] - : a[1] === b[1] - ? ["H", b[0]] - : ["L", b], - ]; - } + toHiccupPathSegments() { + const [a, b] = this.points; + return [ + a[0] === b[0] + ? ["V", b[1]] + : a[1] === b[1] + ? ["H", b[0]] + : ["L", b], + ]; + } } diff --git a/packages/geom/src/api/path.ts b/packages/geom/src/api/path.ts index e517edd25a..6924696197 100644 --- a/packages/geom/src/api/path.ts +++ b/packages/geom/src/api/path.ts @@ -5,67 +5,67 @@ import { copy } from "@thi.ng/vectors/copy"; import { __copyAttribs } from "../internal/copy.js"; export class Path implements IHiccupShape { - closed = false; + closed = false; - constructor( - public segments: PathSegment[] = [], - public attribs?: Attribs - ) {} + constructor( + public segments: PathSegment[] = [], + public attribs?: Attribs + ) {} - get type() { - return "path"; - } + get type() { + return "path"; + } - *[Symbol.iterator]() { - yield* this.segments; - } + *[Symbol.iterator]() { + yield* this.segments; + } - copy(): Path { - const p = new Path( - this.segments.map((s) => { - const d: PathSegment = { type: s.type }; - s.point && (d.point = copy(s.point)); - s.geo && (d.geo = s.geo.copy()); - return d; - }), - __copyAttribs(this) - ); - p.closed = this.closed; - return p; - } + copy(): Path { + const p = new Path( + this.segments.map((s) => { + const d: PathSegment = { type: s.type }; + s.point && (d.point = copy(s.point)); + s.geo && (d.geo = s.geo.copy()); + return d; + }), + __copyAttribs(this) + ); + p.closed = this.closed; + return p; + } - withAttribs(attribs: Attribs): Path { - const res = new Path(this.segments, attribs); - res.closed = true; - return res; - } + withAttribs(attribs: Attribs): Path { + const res = new Path(this.segments, attribs); + res.closed = true; + return res; + } - equiv(o: any) { - return o instanceof Path && equiv(this.segments, o.segments); - } + equiv(o: any) { + return o instanceof Path && equiv(this.segments, o.segments); + } - add(s: PathSegment) { - if (this.closed) illegalState("path already closed"); - this.segments.push(s); - } + add(s: PathSegment) { + if (this.closed) illegalState("path already closed"); + this.segments.push(s); + } - toHiccup() { - let dest: any[] = []; - const segments = this.segments; - const n = segments.length; - if (n > 1) { - for (let i = 0; i < n; i++) { - const s = segments[i]; - if (s.geo) { - dest = dest.concat(s.geo!.toHiccupPathSegments()); - } else if (s.point) { - dest.push(["M", s.point]); - } - } - if (this.closed) { - dest.push(["Z"]); - } - } - return ["path", this.attribs || {}, dest]; - } + toHiccup() { + let dest: any[] = []; + const segments = this.segments; + const n = segments.length; + if (n > 1) { + for (let i = 0; i < n; i++) { + const s = segments[i]; + if (s.geo) { + dest = dest.concat(s.geo!.toHiccupPathSegments()); + } else if (s.point) { + dest.push(["M", s.point]); + } + } + if (this.closed) { + dest.push(["Z"]); + } + } + return ["path", this.attribs || {}, dest]; + } } diff --git a/packages/geom/src/api/plane.ts b/packages/geom/src/api/plane.ts index ae4e6d8361..3b75ffc16b 100644 --- a/packages/geom/src/api/plane.ts +++ b/packages/geom/src/api/plane.ts @@ -4,25 +4,25 @@ import { set3 } from "@thi.ng/vectors/set"; import { __copyAttribs } from "../internal/copy.js"; export class Plane implements IHiccupShape { - constructor( - public normal: Vec = [0, 1, 0], - public w = 0, - public attribs?: Attribs - ) {} + constructor( + public normal: Vec = [0, 1, 0], + public w = 0, + public attribs?: Attribs + ) {} - get type() { - return "plane"; - } + get type() { + return "plane"; + } - copy(): Plane { - return new Plane(set3([], this.normal), this.w, __copyAttribs(this)); - } + copy(): Plane { + return new Plane(set3([], this.normal), this.w, __copyAttribs(this)); + } - withAttribs(attribs: Attribs): Plane { - return new Plane(this.normal, this.w, attribs); - } + withAttribs(attribs: Attribs): Plane { + return new Plane(this.normal, this.w, attribs); + } - toHiccup() { - return ["plane", this.attribs, this.normal, this.w]; - } + toHiccup() { + return ["plane", this.attribs, this.normal, this.w]; + } } diff --git a/packages/geom/src/api/points.ts b/packages/geom/src/api/points.ts index 7f36500ecb..5155cdb937 100644 --- a/packages/geom/src/api/points.ts +++ b/packages/geom/src/api/points.ts @@ -3,37 +3,37 @@ import { __copyShape } from "../internal/copy.js"; import { APC } from "./apc.js"; export class Points extends APC implements IHiccupShape { - get type() { - return "points"; - } + get type() { + return "points"; + } - copy(): Points { - return __copyShape(Points, this); - } + copy(): Points { + return __copyShape(Points, this); + } - withAttribs(attribs: Attribs): Points { - return new Points(this.points, attribs); - } + withAttribs(attribs: Attribs): Points { + return new Points(this.points, attribs); + } - toHiccup() { - return ["points", this.attribs, this.points]; - } + toHiccup() { + return ["points", this.attribs, this.points]; + } } export class Points3 extends APC implements IHiccupShape { - get type() { - return "points3"; - } + get type() { + return "points3"; + } - copy(): Points3 { - return __copyShape(Points3, this); - } + copy(): Points3 { + return __copyShape(Points3, this); + } - withAttribs(attribs: Attribs): Points3 { - return new Points3(this.points, attribs); - } + withAttribs(attribs: Attribs): Points3 { + return new Points3(this.points, attribs); + } - toHiccup() { - return ["points3", this.attribs, this.points]; - } + toHiccup() { + return ["points3", this.attribs, this.points]; + } } diff --git a/packages/geom/src/api/polygon.ts b/packages/geom/src/api/polygon.ts index c274b71069..a4ff40b405 100644 --- a/packages/geom/src/api/polygon.ts +++ b/packages/geom/src/api/polygon.ts @@ -3,19 +3,19 @@ import { __copyShape } from "../internal/copy.js"; import { APC } from "./apc.js"; export class Polygon extends APC implements IHiccupShape { - get type() { - return "poly"; - } + get type() { + return "poly"; + } - copy(): Polygon { - return __copyShape(Polygon, this); - } + copy(): Polygon { + return __copyShape(Polygon, this); + } - withAttribs(attribs: Attribs): Polygon { - return new Polygon(this.points, attribs); - } + withAttribs(attribs: Attribs): Polygon { + return new Polygon(this.points, attribs); + } - toHiccup() { - return ["polygon", this.attribs, this.points]; - } + toHiccup() { + return ["polygon", this.attribs, this.points]; + } } diff --git a/packages/geom/src/api/polyline.ts b/packages/geom/src/api/polyline.ts index 918cb44053..1fda6333c4 100644 --- a/packages/geom/src/api/polyline.ts +++ b/packages/geom/src/api/polyline.ts @@ -1,33 +1,33 @@ import type { - Attribs, - IHiccupPathSegment, - IHiccupShape, + Attribs, + IHiccupPathSegment, + IHiccupShape, } from "@thi.ng/geom-api"; import { __copyShape } from "../internal/copy.js"; import { APC } from "./apc.js"; export class Polyline extends APC implements IHiccupShape, IHiccupPathSegment { - get type() { - return "polyline"; - } + get type() { + return "polyline"; + } - copy(): Polyline { - return __copyShape(Polyline, this); - } + copy(): Polyline { + return __copyShape(Polyline, this); + } - withAttribs(attribs: Attribs): Polyline { - return new Polyline(this.points, attribs); - } + withAttribs(attribs: Attribs): Polyline { + return new Polyline(this.points, attribs); + } - toHiccup() { - return ["polyline", { ...this.attribs, fill: "none" }, this.points]; - } + toHiccup() { + return ["polyline", { ...this.attribs, fill: "none" }, this.points]; + } - toHiccupPathSegments() { - const res: any[] = []; - for (let pts = this.points, n = pts.length, i = 1; i < n; i++) { - res.push(["L", pts[i]]); - } - return res; - } + toHiccupPathSegments() { + const res: any[] = []; + for (let pts = this.points, n = pts.length, i = 1; i < n; i++) { + res.push(["L", pts[i]]); + } + return res; + } } diff --git a/packages/geom/src/api/quad.ts b/packages/geom/src/api/quad.ts index 991808c406..c0ccc75610 100644 --- a/packages/geom/src/api/quad.ts +++ b/packages/geom/src/api/quad.ts @@ -3,19 +3,19 @@ import { __copyShape } from "../internal/copy.js"; import { APC } from "./apc.js"; export class Quad extends APC implements IHiccupShape { - get type() { - return "quad"; - } + get type() { + return "quad"; + } - copy(): Quad { - return __copyShape(Quad, this); - } + copy(): Quad { + return __copyShape(Quad, this); + } - withAttribs(attribs: Attribs): Quad { - return new Quad(this.points, attribs); - } + withAttribs(attribs: Attribs): Quad { + return new Quad(this.points, attribs); + } - toHiccup() { - return ["polygon", this.attribs, this.points]; - } + toHiccup() { + return ["polygon", this.attribs, this.points]; + } } diff --git a/packages/geom/src/api/quad3.ts b/packages/geom/src/api/quad3.ts index f7ac5cbfd9..52ead21771 100644 --- a/packages/geom/src/api/quad3.ts +++ b/packages/geom/src/api/quad3.ts @@ -3,19 +3,19 @@ import { __copyShape } from "../internal/copy.js"; import { APC } from "./apc.js"; export class Quad3 extends APC implements IHiccupShape { - get type() { - return "quad3"; - } + get type() { + return "quad3"; + } - copy(): Quad3 { - return __copyShape(Quad3, this); - } + copy(): Quad3 { + return __copyShape(Quad3, this); + } - withAttribs(attribs: Attribs): Quad3 { - return new Quad3(this.points, attribs); - } + withAttribs(attribs: Attribs): Quad3 { + return new Quad3(this.points, attribs); + } - toHiccup() { - return ["polygon", this.attribs, this.points]; - } + toHiccup() { + return ["polygon", this.attribs, this.points]; + } } diff --git a/packages/geom/src/api/quadratic.ts b/packages/geom/src/api/quadratic.ts index 0c25ce3dfe..8477140a02 100644 --- a/packages/geom/src/api/quadratic.ts +++ b/packages/geom/src/api/quadratic.ts @@ -1,34 +1,34 @@ import type { - Attribs, - IHiccupPathSegment, - IHiccupShape, + Attribs, + IHiccupPathSegment, + IHiccupShape, } from "@thi.ng/geom-api"; import { __copyShape } from "../internal/copy.js"; import { APC } from "./apc.js"; export class Quadratic extends APC implements IHiccupShape, IHiccupPathSegment { - get type() { - return "quadratic"; - } + get type() { + return "quadratic"; + } - copy(): Quadratic { - return __copyShape(Quadratic, this); - } + copy(): Quadratic { + return __copyShape(Quadratic, this); + } - withAttribs(attribs: Attribs): Quadratic { - return new Quadratic(this.points, attribs); - } + withAttribs(attribs: Attribs): Quadratic { + return new Quadratic(this.points, attribs); + } - toHiccup() { - return [ - "path", - this.attribs, - [["M", this.points[0]], ...this.toHiccupPathSegments()], - ]; - } + toHiccup() { + return [ + "path", + this.attribs, + [["M", this.points[0]], ...this.toHiccupPathSegments()], + ]; + } - toHiccupPathSegments() { - const pts = this.points; - return [["Q", pts[1], pts[2]]]; - } + toHiccupPathSegments() { + const pts = this.points; + return [["Q", pts[1], pts[2]]]; + } } diff --git a/packages/geom/src/api/ray.ts b/packages/geom/src/api/ray.ts index 619bc87e3d..a9177c3c61 100644 --- a/packages/geom/src/api/ray.ts +++ b/packages/geom/src/api/ray.ts @@ -5,30 +5,30 @@ import { set } from "@thi.ng/vectors/set"; import { __copyAttribs } from "../internal/copy.js"; export class Ray implements IHiccupShape { - constructor(public pos: Vec, public dir: Vec, public attribs?: Attribs) {} + constructor(public pos: Vec, public dir: Vec, public attribs?: Attribs) {} - get type() { - return "ray"; - } + get type() { + return "ray"; + } - copy(): Ray { - return new Ray( - set([], this.pos), - set([], this.dir), - __copyAttribs(this) - ); - } + copy(): Ray { + return new Ray( + set([], this.pos), + set([], this.dir), + __copyAttribs(this) + ); + } - withAttribs(attribs: Attribs): Ray { - return new Ray(this.pos, this.dir, attribs); - } + withAttribs(attribs: Attribs): Ray { + return new Ray(this.pos, this.dir, attribs); + } - toHiccup() { - return [ - "line", - this.attribs, - this.pos, - maddN2([], this.dir, 1e6, this.pos), - ]; - } + toHiccup() { + return [ + "line", + this.attribs, + this.pos, + maddN2([], this.dir, 1e6, this.pos), + ]; + } } diff --git a/packages/geom/src/api/rect.ts b/packages/geom/src/api/rect.ts index c076ba13d3..9c394101f2 100644 --- a/packages/geom/src/api/rect.ts +++ b/packages/geom/src/api/rect.ts @@ -9,43 +9,43 @@ import { __asVec } from "../internal/args.js"; import { __copyAttribs } from "../internal/copy.js"; export class Rect implements AABBLike, IHiccupShape { - size: Vec; - - constructor( - public pos: Vec = [0, 0], - size: number | Vec = 1, - public attribs?: Attribs - ) { - this.size = __asVec(size); - } - - get type() { - return "rect"; - } - - copy(): Rect { - return new Rect( - set2([], this.pos), - set2([], this.size), - __copyAttribs(this) - ); - } - - withAttribs(attribs: Attribs): Rect { - return new Rect(this.pos, this.size, attribs); - } - - max() { - return add2([], this.pos, this.size); - } - - offset(offset: number) { - subN2(null, this.pos, offset); - max2(null, addN2(null, this.size, offset * 2), ZERO2); - return this; - } - - toHiccup() { - return ["rect", this.attribs, this.pos, this.size[0], this.size[1]]; - } + size: Vec; + + constructor( + public pos: Vec = [0, 0], + size: number | Vec = 1, + public attribs?: Attribs + ) { + this.size = __asVec(size); + } + + get type() { + return "rect"; + } + + copy(): Rect { + return new Rect( + set2([], this.pos), + set2([], this.size), + __copyAttribs(this) + ); + } + + withAttribs(attribs: Attribs): Rect { + return new Rect(this.pos, this.size, attribs); + } + + max() { + return add2([], this.pos, this.size); + } + + offset(offset: number) { + subN2(null, this.pos, offset); + max2(null, addN2(null, this.size, offset * 2), ZERO2); + return this; + } + + toHiccup() { + return ["rect", this.attribs, this.pos, this.size[0], this.size[1]]; + } } diff --git a/packages/geom/src/api/sphere.ts b/packages/geom/src/api/sphere.ts index f9b98eb64e..9b11ee68ca 100644 --- a/packages/geom/src/api/sphere.ts +++ b/packages/geom/src/api/sphere.ts @@ -4,25 +4,25 @@ import { set3 } from "@thi.ng/vectors/set"; import { __copyAttribs } from "../internal/copy.js"; export class Sphere implements IHiccupShape { - constructor( - public pos: Vec = [0, 0, 0], - public r = 1, - public attribs?: Attribs - ) {} + constructor( + public pos: Vec = [0, 0, 0], + public r = 1, + public attribs?: Attribs + ) {} - get type() { - return "sphere"; - } + get type() { + return "sphere"; + } - copy(): Sphere { - return new Sphere(set3([], this.pos), this.r, __copyAttribs(this)); - } + copy(): Sphere { + return new Sphere(set3([], this.pos), this.r, __copyAttribs(this)); + } - withAttribs(attribs: Attribs): Sphere { - return new Sphere(this.pos, this.r, attribs); - } + withAttribs(attribs: Attribs): Sphere { + return new Sphere(this.pos, this.r, attribs); + } - toHiccup() { - return ["sphere", this.attribs, this.pos, this.r]; - } + toHiccup() { + return ["sphere", this.attribs, this.pos, this.r]; + } } diff --git a/packages/geom/src/api/text.ts b/packages/geom/src/api/text.ts index e68819603f..adcb1e3660 100644 --- a/packages/geom/src/api/text.ts +++ b/packages/geom/src/api/text.ts @@ -10,21 +10,21 @@ import { __copyAttribs } from "../internal/copy.js"; * text elements in {@link Group}s with other shape types. */ export class Text implements IHiccupShape { - constructor(public pos: Vec, public body: any, public attribs?: Attribs) {} + constructor(public pos: Vec, public body: any, public attribs?: Attribs) {} - get type() { - return "text"; - } + get type() { + return "text"; + } - copy(): Text { - return new Text(set([], this.pos), this.body, __copyAttribs(this)); - } + copy(): Text { + return new Text(set([], this.pos), this.body, __copyAttribs(this)); + } - withAttribs(attribs: Attribs): Text { - return new Text(this.pos, this.body, attribs); - } + withAttribs(attribs: Attribs): Text { + return new Text(this.pos, this.body, attribs); + } - toHiccup() { - return ["text", this.attribs, this.pos, this.body]; - } + toHiccup() { + return ["text", this.attribs, this.pos, this.body]; + } } diff --git a/packages/geom/src/api/triangle.ts b/packages/geom/src/api/triangle.ts index 9bd5c73294..545651a8e1 100644 --- a/packages/geom/src/api/triangle.ts +++ b/packages/geom/src/api/triangle.ts @@ -3,19 +3,19 @@ import { __copyShape } from "../internal/copy.js"; import { APC } from "./apc.js"; export class Triangle extends APC implements IHiccupShape { - get type() { - return "tri"; - } + get type() { + return "tri"; + } - copy(): Triangle { - return __copyShape(Triangle, this); - } + copy(): Triangle { + return __copyShape(Triangle, this); + } - withAttribs(attribs: Attribs): Triangle { - return new Triangle(this.points, attribs); - } + withAttribs(attribs: Attribs): Triangle { + return new Triangle(this.points, attribs); + } - toHiccup() { - return ["polygon", this.attribs, this.points]; - } + toHiccup() { + return ["polygon", this.attribs, this.points]; + } } diff --git a/packages/geom/src/apply-transforms.ts b/packages/geom/src/apply-transforms.ts index e31620dc61..343c936e2c 100644 --- a/packages/geom/src/apply-transforms.ts +++ b/packages/geom/src/apply-transforms.ts @@ -11,22 +11,22 @@ import { translate } from "./translate.js"; /** @internal */ const __apply = ($: IShape) => { - let attribs = $.attribs; - if (!attribs) return $; - const { transform: tx, translate: t, rotate: r, scale: s } = attribs; - if (tx) - return transform( - $.withAttribs(withoutKeysObj(attribs, ["transform"])), - tx - ); - if (!(t || r || s)) return $; - $ = $.withAttribs( - withoutKeysObj(attribs, ["translate", "rotate", "scale"]) - ); - if (r) $ = rotate($, r); - if (s) $ = scale($, s); - if (t) $ = translate($, t); - return $; + let attribs = $.attribs; + if (!attribs) return $; + const { transform: tx, translate: t, rotate: r, scale: s } = attribs; + if (tx) + return transform( + $.withAttribs(withoutKeysObj(attribs, ["transform"])), + tx + ); + if (!(t || r || s)) return $; + $ = $.withAttribs( + withoutKeysObj(attribs, ["translate", "rotate", "scale"]) + ); + if (r) $ = rotate($, r); + if (s) $ = scale($, s); + if (t) $ = translate($, t); + return $; }; /** @@ -59,12 +59,12 @@ const __apply = ($: IShape) => { * @param shape */ export const applyTransforms: MultiFn1 = defmulti( - __dispatch, - {}, - { - [DEFAULT]: __apply, + __dispatch, + {}, + { + [DEFAULT]: __apply, - group: ($: Group) => - __apply($.copyTransformed((x) => applyTransforms(x))), - } + group: ($: Group) => + __apply($.copyTransformed((x) => applyTransforms(x))), + } ); diff --git a/packages/geom/src/arc-length.ts b/packages/geom/src/arc-length.ts index 8e204f3025..80a63f3a84 100644 --- a/packages/geom/src/arc-length.ts +++ b/packages/geom/src/arc-length.ts @@ -33,33 +33,33 @@ import { __dispatch } from "./internal/dispatch.js"; * @param shape */ export const arcLength: MultiFn1 = defmulti( - __dispatch, - { - quad: "poly", - tri: "poly", - }, - { - circle: ($: Circle) => TAU * $.r, + __dispatch, + { + quad: "poly", + tri: "poly", + }, + { + circle: ($: Circle) => TAU * $.r, - ellipse: ({ r: [a, b] }: Ellipse) => - // Ramanujan approximation - // https://www.mathsisfun.com/geometry/ellipse-perimeter.html - PI * (3 * (a + b) - Math.sqrt((3 * a + b) * (3 * b + a))), + ellipse: ({ r: [a, b] }: Ellipse) => + // Ramanujan approximation + // https://www.mathsisfun.com/geometry/ellipse-perimeter.html + PI * (3 * (a + b) - Math.sqrt((3 * a + b) * (3 * b + a))), - group: ({ children }: Group) => - children.reduce((sum, $) => sum + arcLength($), 0), + group: ({ children }: Group) => + children.reduce((sum, $) => sum + arcLength($), 0), - line: ({ points }: Line) => dist(points[0], points[1]), + line: ({ points }: Line) => dist(points[0], points[1]), - poly: ({ points }: Polygon) => perimeter(points, points.length, true), + poly: ({ points }: Polygon) => perimeter(points, points.length, true), - polyline: ({ points }: Polygon) => perimeter(points, points.length), + polyline: ({ points }: Polygon) => perimeter(points, points.length), - rect: ({ size: [w, h] }: Rect) => 2 * (w + h), + rect: ({ size: [w, h] }: Rect) => 2 * (w + h), - tri: ({ points }: Triangle) => - dist(points[0], points[1]) + - dist(points[1], points[2]) + - dist(points[2], points[0]), - } + tri: ({ points }: Triangle) => + dist(points[0], points[1]) + + dist(points[1], points[2]) + + dist(points[2], points[0]), + } ); diff --git a/packages/geom/src/arc.ts b/packages/geom/src/arc.ts index 72f0b8010c..035c6cb624 100644 --- a/packages/geom/src/arc.ts +++ b/packages/geom/src/arc.ts @@ -5,35 +5,35 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; import { Arc } from "./api/arc.js"; export const arc = ( - pos: Vec, - r: number | Vec, - axis: number, - start: number, - end: number, - xl = false, - clockwise = false + pos: Vec, + r: number | Vec, + axis: number, + start: number, + end: number, + xl = false, + clockwise = false ) => new Arc(pos, isNumber(r) ? [r, r] : r, axis, start, end, xl, clockwise); export const arcFrom2Points = ( - a: ReadonlyVec, - b: ReadonlyVec, - radii: ReadonlyVec, - axis = 0, - xl = false, - cw = false, - attribs?: Attribs + a: ReadonlyVec, + b: ReadonlyVec, + radii: ReadonlyVec, + axis = 0, + xl = false, + cw = false, + attribs?: Attribs ) => { - const res = fromEndPoints(a, b, radii, axis, xl, cw); - return res - ? new Arc( - res.center, - res.r, - res.axis, - res.start, - res.end, - res.xl, - res.cw, - attribs - ) - : undefined; + const res = fromEndPoints(a, b, radii, axis, xl, cw); + return res + ? new Arc( + res.center, + res.r, + res.axis, + res.start, + res.end, + res.xl, + res.cw, + attribs + ) + : undefined; }; diff --git a/packages/geom/src/area.ts b/packages/geom/src/area.ts index 3d6e6844d3..7b2c8e1fc9 100644 --- a/packages/geom/src/area.ts +++ b/packages/geom/src/area.ts @@ -53,40 +53,40 @@ import { __dispatch } from "./internal/dispatch.js"; * @param signed - true, if signed area */ export const area: MultiFn1O = defmulti( - __dispatch, - { quad: "poly" }, - { - aabb: ({ size: [w, h, d] }: AABB) => 2 * (w * h + w * d + h * d), + __dispatch, + { quad: "poly" }, + { + aabb: ({ size: [w, h, d] }: AABB) => 2 * (w * h + w * d + h * d), - arc: - // http://cut-the-knot.org/Generalization/Cavalieri2.shtml - ($: Arc) => 0.5 * Math.abs($.start - $.end) * $.r[0] * $.r[1], + arc: + // http://cut-the-knot.org/Generalization/Cavalieri2.shtml + ($: Arc) => 0.5 * Math.abs($.start - $.end) * $.r[0] * $.r[1], - circle: ($: Circle) => PI * $.r ** 2, + circle: ($: Circle) => PI * $.r ** 2, - ellipse: ($: Ellipse) => PI * $.r[0] * $.r[1], + ellipse: ($: Ellipse) => PI * $.r[0] * $.r[1], - group: ({ children }: Group) => - children.reduce((sum, $) => sum + area($, false), 0), + group: ({ children }: Group) => + children.reduce((sum, $) => sum + area($, false), 0), - path: ($: Path) => ($.closed ? area(asPolygon($)) : 0), + path: ($: Path) => ($.closed ? area(asPolygon($)) : 0), - plane: () => Infinity, + plane: () => Infinity, - poly: ($: Polygon, signed?) => { - const area = polyArea2($.points); - return signed ? area : Math.abs(area); - }, + poly: ($: Polygon, signed?) => { + const area = polyArea2($.points); + return signed ? area : Math.abs(area); + }, - rect: ($: Rect) => $.size[0] * $.size[1], + rect: ($: Rect) => $.size[0] * $.size[1], - sphere: ($: Sphere) => 4 * PI * $.r ** 2, + sphere: ($: Sphere) => 4 * PI * $.r ** 2, - tri: ($: Triangle, signed?) => { - const area = 0.5 * signedArea2(...(<[Vec, Vec, Vec]>$.points)); - return signed ? area : Math.abs(area); - }, + tri: ($: Triangle, signed?) => { + const area = 0.5 * signedArea2(...(<[Vec, Vec, Vec]>$.points)); + return signed ? area : Math.abs(area); + }, - [DEFAULT]: () => 0, - } + [DEFAULT]: () => 0, + } ); diff --git a/packages/geom/src/as-cubic.ts b/packages/geom/src/as-cubic.ts index 75c9c2e5e5..39aa3d81c7 100644 --- a/packages/geom/src/as-cubic.ts +++ b/packages/geom/src/as-cubic.ts @@ -2,12 +2,12 @@ import type { MultiFn1O } from "@thi.ng/defmulti"; import { defmulti } from "@thi.ng/defmulti/defmulti"; import type { CubicOpts, IShape, PCLike } from "@thi.ng/geom-api"; import { - closedCubicFromBreakPoints, - openCubicFromBreakPoints, + closedCubicFromBreakPoints, + openCubicFromBreakPoints, } from "@thi.ng/geom-splines/cubic-from-breakpoints"; import { - closedCubicFromControlPoints, - openCubicFromControlPoints, + closedCubicFromControlPoints, + openCubicFromControlPoints, } from "@thi.ng/geom-splines/cubic-from-controlpoints"; import { TAU } from "@thi.ng/math/api"; import { mapcat } from "@thi.ng/transducers/mapcat"; @@ -64,54 +64,54 @@ import { __dispatch } from "./internal/dispatch.js"; * @param opts */ export const asCubic: MultiFn1O, Cubic[]> = defmulti( - __dispatch, - { - ellipse: "circle", - quad: "poly", - tri: "poly", - }, - { - arc: cubicFromArc, + __dispatch, + { + ellipse: "circle", + quad: "poly", + tri: "poly", + }, + { + arc: cubicFromArc, - circle: ($: Circle) => asCubic(arc($.pos, $.r, 0, 0, TAU, true, true)), + circle: ($: Circle) => asCubic(arc($.pos, $.r, 0, 0, TAU, true, true)), - cubic: ($: Cubic) => [$], + cubic: ($: Cubic) => [$], - group: ($: Group, opts?: Partial) => [ - ...mapcat((x) => asCubic(x, opts), $.children), - ], + group: ($: Group, opts?: Partial) => [ + ...mapcat((x) => asCubic(x, opts), $.children), + ], - line: ({ attribs, points }: Line) => [ - cubicFromLine(points[0], points[1], { ...attribs }), - ], + line: ({ attribs, points }: Line) => [ + cubicFromLine(points[0], points[1], { ...attribs }), + ], - path: ($: Path) => [ - ...mapcat((s) => (s.geo ? asCubic(s.geo) : null), $.segments), - ], + path: ($: Path) => [ + ...mapcat((s) => (s.geo ? asCubic(s.geo) : null), $.segments), + ], - poly: ($: Polygon, opts: Partial = {}) => - __polyCubic( - $, - opts, - closedCubicFromBreakPoints, - closedCubicFromControlPoints - ), + poly: ($: Polygon, opts: Partial = {}) => + __polyCubic( + $, + opts, + closedCubicFromBreakPoints, + closedCubicFromControlPoints + ), - polyline: ($: Polyline, opts: Partial = {}) => - __polyCubic( - $, - opts, - openCubicFromBreakPoints, - openCubicFromControlPoints - ), + polyline: ($: Polyline, opts: Partial = {}) => + __polyCubic( + $, + opts, + openCubicFromBreakPoints, + openCubicFromControlPoints + ), - quadratic: ({ attribs, points }: Quadratic) => [ - cubicFromQuadratic(points[0], points[1], points[2], { ...attribs }), - ], + quadratic: ({ attribs, points }: Quadratic) => [ + cubicFromQuadratic(points[0], points[1], points[2], { ...attribs }), + ], - rect: ($: Rect, opts?: Partial) => - asCubic(asPolygon($), opts), - } + rect: ($: Rect, opts?: Partial) => + asCubic(asPolygon($), opts), + } ); /** diff --git a/packages/geom/src/as-path.ts b/packages/geom/src/as-path.ts index c72ed00be5..9a95714b3e 100644 --- a/packages/geom/src/as-path.ts +++ b/packages/geom/src/as-path.ts @@ -11,4 +11,4 @@ import { pathFromCubics } from "./path.js"; * @param attribs */ export const asPath = (src: IShape, attribs?: Attribs) => - pathFromCubics(asCubic(src), attribs || __copyAttribs(src)); + pathFromCubics(asCubic(src), attribs || __copyAttribs(src)); diff --git a/packages/geom/src/as-polygon.ts b/packages/geom/src/as-polygon.ts index a0d4228ae0..67ee03381b 100644 --- a/packages/geom/src/as-polygon.ts +++ b/packages/geom/src/as-polygon.ts @@ -27,23 +27,23 @@ import { vertices } from "./vertices.js"; * @param opts */ export const asPolygon: MultiFn1O< - IShape, - number | Partial, - Polygon + IShape, + number | Partial, + Polygon > = defmulti | undefined, Polygon>( - __dispatch, - { - circle: "points", - ellipse: "points", - line: "points", - path: "points", - poly: "points", - polyline: "points", - quad: "points", - rect: "points", - tri: "points", - }, - { - points: ($, opts) => new Polygon(vertices($, opts), __copyAttribs($)), - } + __dispatch, + { + circle: "points", + ellipse: "points", + line: "points", + path: "points", + poly: "points", + polyline: "points", + quad: "points", + rect: "points", + tri: "points", + }, + { + points: ($, opts) => new Polygon(vertices($, opts), __copyAttribs($)), + } ); diff --git a/packages/geom/src/as-polyline.ts b/packages/geom/src/as-polyline.ts index a690745220..524ba168d6 100644 --- a/packages/geom/src/as-polyline.ts +++ b/packages/geom/src/as-polyline.ts @@ -32,36 +32,36 @@ import { vertices } from "./vertices.js"; * @param opts */ export const asPolyline: MultiFn1O< - IShape, - number | Partial, - Polyline + IShape, + number | Partial, + Polyline > = defmulti | undefined, Polyline>( - __dispatch, - { - arc: "points", - circle: "poly", - cubic: "points", - ellipse: "poly", - line: "points", - polyline: "points", - quad: "poly", - quadratic: "points", - rect: "poly", - tri: "poly", - }, - { - points: ($, opts) => new Polyline(vertices($, opts), __copyAttribs($)), + __dispatch, + { + arc: "points", + circle: "poly", + cubic: "points", + ellipse: "poly", + line: "points", + polyline: "points", + quad: "poly", + quadratic: "points", + rect: "poly", + tri: "poly", + }, + { + points: ($, opts) => new Polyline(vertices($, opts), __copyAttribs($)), - path: ($: Path, opts) => { - const pts = vertices($, opts); - $.closed && pts.push(set([], pts[0])); - return new Polyline(pts, __copyAttribs($)); - }, + path: ($: Path, opts) => { + const pts = vertices($, opts); + $.closed && pts.push(set([], pts[0])); + return new Polyline(pts, __copyAttribs($)); + }, - poly: ($, opts) => { - const pts = vertices($, opts); - pts.push(set([], pts[0])); - return new Polyline(pts, __copyAttribs($)); - }, - } + poly: ($, opts) => { + const pts = vertices($, opts); + pts.push(set([], pts[0])); + return new Polyline(pts, __copyAttribs($)); + }, + } ); diff --git a/packages/geom/src/as-svg.ts b/packages/geom/src/as-svg.ts index 891d1ee34f..206254817d 100644 --- a/packages/geom/src/as-svg.ts +++ b/packages/geom/src/as-svg.ts @@ -8,7 +8,7 @@ import { bounds } from "./bounds.js"; import { __collBounds } from "./internal/bounds.js"; export const asSvg = (...args: any[]) => - args.map((x) => serialize(convertTree(x))).join(""); + args.map((x) => serialize(convertTree(x))).join(""); /** * Creates a hiccup SVG doc element for given {@link IShape}s and attribs. If @@ -23,25 +23,25 @@ export const asSvg = (...args: any[]) => * @param xs */ export const svgDoc = (attribs: Attribs, ...xs: IShape[]) => { - if (xs.length > 0) { - if (!attribs.viewBox) { - const cbounds = __collBounds(xs, bounds); - if (cbounds) { - const [[x, y], [w, h]] = cbounds; - const bleed = attribs.bleed || 0; - const bleed2 = 2 * bleed; - const width = ff(w + bleed2); - const height = ff(h + bleed2); - attribs = { - width, - height, - viewBox: `${ff(x - bleed)} ${ff( - y - bleed - )} ${width} ${height}`, - ...withoutKeysObj(attribs, ["bleed"]), - }; - } - } - } - return svg(attribs, ...xs); + if (xs.length > 0) { + if (!attribs.viewBox) { + const cbounds = __collBounds(xs, bounds); + if (cbounds) { + const [[x, y], [w, h]] = cbounds; + const bleed = attribs.bleed || 0; + const bleed2 = 2 * bleed; + const width = ff(w + bleed2); + const height = ff(h + bleed2); + attribs = { + width, + height, + viewBox: `${ff(x - bleed)} ${ff( + y - bleed + )} ${width} ${height}`, + ...withoutKeysObj(attribs, ["bleed"]), + }; + } + } + } + return svg(attribs, ...xs); }; diff --git a/packages/geom/src/bounds.ts b/packages/geom/src/bounds.ts index a3aba27305..05d5f10ef0 100644 --- a/packages/geom/src/bounds.ts +++ b/packages/geom/src/bounds.ts @@ -60,85 +60,85 @@ import { rectFromMinMaxWithMargin } from "./rect.js"; * @param margin */ export const bounds: MultiFn1O = defmulti< - any, - number | undefined, - AABBLike | undefined + any, + number | undefined, + AABBLike | undefined >( - __dispatch, - { - aabb: "rect", - bpatch: "points", - poly: "points", - polyline: "points", - quad: "points", - tri: "points", - }, - { - arc: ($: Arc, margin = 0) => - rectFromMinMaxWithMargin( - ...arcBounds($.pos, $.r, $.axis, $.start, $.end), - margin - ), + __dispatch, + { + aabb: "rect", + bpatch: "points", + poly: "points", + polyline: "points", + quad: "points", + tri: "points", + }, + { + arc: ($: Arc, margin = 0) => + rectFromMinMaxWithMargin( + ...arcBounds($.pos, $.r, $.axis, $.start, $.end), + margin + ), - circle: ($: Circle, margin = 0) => - new Rect( - subN2([], $.pos, $.r + margin), - mulN2(null, [2, 2], $.r + margin) - ), + circle: ($: Circle, margin = 0) => + new Rect( + subN2([], $.pos, $.r + margin), + mulN2(null, [2, 2], $.r + margin) + ), - cubic: ({ points }: Cubic, margin = 0) => - rectFromMinMaxWithMargin( - ...cubicBounds(points[0], points[1], points[2], points[3]), - margin - ), + cubic: ({ points }: Cubic, margin = 0) => + rectFromMinMaxWithMargin( + ...cubicBounds(points[0], points[1], points[2], points[3]), + margin + ), - ellipse: ($: Ellipse, margin = 0) => { - const r = addN2([], $.r, margin); - return new Rect(sub2([], $.pos, r), mul2(null, [2, 2], r)); - }, + ellipse: ($: Ellipse, margin = 0) => { + const r = addN2([], $.r, margin); + return new Rect(sub2([], $.pos, r), mul2(null, [2, 2], r)); + }, - group: ($: Group, margin = 0) => { - const res = __collBounds($.children, bounds); - return res ? new Rect(...res).offset(margin) : undefined; - }, + group: ($: Group, margin = 0) => { + const res = __collBounds($.children, bounds); + return res ? new Rect(...res).offset(margin) : undefined; + }, - line: ({ points: [a, b] }: Line, margin = 0) => - rectFromMinMaxWithMargin(min([], a, b), max([], a, b), margin), + line: ({ points: [a, b] }: Line, margin = 0) => + rectFromMinMaxWithMargin(min([], a, b), max([], a, b), margin), - path: (path: Path, margin = 0) => { - const b = __collBounds( - [ - ...iterator1( - comp( - map((s: PathSegment) => s.geo!), - filter((s) => !!s) - ), - path.segments - ), - ], - bounds - ); - return b ? new Rect(...b).offset(margin) : undefined; - }, + path: (path: Path, margin = 0) => { + const b = __collBounds( + [ + ...iterator1( + comp( + map((s: PathSegment) => s.geo!), + filter((s) => !!s) + ), + path.segments + ), + ], + bounds + ); + return b ? new Rect(...b).offset(margin) : undefined; + }, - points: ($: PCLike, margin = 0) => - rectFromMinMaxWithMargin(...bounds2($.points), margin), + points: ($: PCLike, margin = 0) => + rectFromMinMaxWithMargin(...bounds2($.points), margin), - points3: ($: PCLike, margin = 0) => - aabbFromMinMaxWithMargin(...bounds3($.points), margin), + points3: ($: PCLike, margin = 0) => + aabbFromMinMaxWithMargin(...bounds3($.points), margin), - quadratic: ({ points }: Quadratic, margin = 0) => - rectFromMinMaxWithMargin( - ...quadraticBounds(points[0], points[1], points[2]), - margin - ), + quadratic: ({ points }: Quadratic, margin = 0) => + rectFromMinMaxWithMargin( + ...quadraticBounds(points[0], points[1], points[2]), + margin + ), - rect: ($: IShape, margin = 0) => - margin === 0 - ? $.copy() - : ($.copy()).offset(margin), + rect: ($: IShape, margin = 0) => + margin === 0 + ? $.copy() + : ($.copy()).offset(margin), - text: ($: Text, margin = 0) => - new Rect(subN2([], $.pos, margin), margin * 2), - } + text: ($: Text, margin = 0) => + new Rect(subN2([], $.pos, margin), margin * 2), + } ); diff --git a/packages/geom/src/bpatch.ts b/packages/geom/src/bpatch.ts index c16e1fde5c..7fa3d66f66 100644 --- a/packages/geom/src/bpatch.ts +++ b/packages/geom/src/bpatch.ts @@ -4,25 +4,25 @@ import { mixBilinear, Vec } from "@thi.ng/vectors"; import { BPatch } from "./api/bpatch.js"; export const bpatch = (pts: Vec[], attribs?: Attribs) => - new BPatch(pts, attribs); + new BPatch(pts, attribs); export const bpatchFromQuad = (pts: Vec[], attribs?: Attribs) => { - assert(pts.length === 4, "require 4 points"); - const [a, b, c, d] = pts; - const cps = []; - for (let u = 0; u < 4; u++) { - for (let v = 0; v < 4; v++) { - cps.push(mixBilinear([], a, b, d, c, u / 3, v / 3)); - } - } - return new BPatch(cps, attribs); + assert(pts.length === 4, "require 4 points"); + const [a, b, c, d] = pts; + const cps = []; + for (let u = 0; u < 4; u++) { + for (let v = 0; v < 4; v++) { + cps.push(mixBilinear([], a, b, d, c, u / 3, v / 3)); + } + } + return new BPatch(cps, attribs); }; export const bpatchFromHex = (pts: Vec[], attribs?: Attribs) => { - assert(pts.length === 6, "require 6 points"); - const [a, b, c, d, e, f] = pts; - return new BPatch( - [e, e, f, f, d, d, a, a, d, d, a, a, c, c, b, b], - attribs - ); + assert(pts.length === 6, "require 6 points"); + const [a, b, c, d, e, f] = pts; + return new BPatch( + [e, e, f, f, d, d, a, a, d, d, a, a, c, c, b, b], + attribs + ); }; diff --git a/packages/geom/src/center.ts b/packages/geom/src/center.ts index babbb9b463..e4ce4182f9 100644 --- a/packages/geom/src/center.ts +++ b/packages/geom/src/center.ts @@ -25,34 +25,34 @@ import { translate } from "./translate.js"; * @param p */ export const center: MultiFn1O = - defmulti( - __dispatch, - {}, - { - [DEFAULT]: ($: IShape, origin = ZERO3) => { - const c = centroid($); - return c ? translate($, submN(null, c, origin, -1)) : undefined; - }, + defmulti( + __dispatch, + {}, + { + [DEFAULT]: ($: IShape, origin = ZERO3) => { + const c = centroid($); + return c ? translate($, submN(null, c, origin, -1)) : undefined; + }, - arc: ($: Arc, origin = ZERO2) => - new Arc( - set2([], origin), - set2([], $.r), - $.axis, - $.start, - $.end, - $.xl, - $.cw, - __copyAttribs($) - ), + arc: ($: Arc, origin = ZERO2) => + new Arc( + set2([], origin), + set2([], $.r), + $.axis, + $.start, + $.end, + $.xl, + $.cw, + __copyAttribs($) + ), - circle: ($: Circle, origin = ZERO2) => - new Circle(set2([], origin), $.r, __copyAttribs($)), + circle: ($: Circle, origin = ZERO2) => + new Circle(set2([], origin), $.r, __copyAttribs($)), - ellipse: ($: Ellipse, origin = ZERO2) => - new Ellipse(set2([], origin), set2([], $.r), __copyAttribs($)), + ellipse: ($: Ellipse, origin = ZERO2) => + new Ellipse(set2([], origin), set2([], $.r), __copyAttribs($)), - sphere: ($: Sphere, origin = ZERO3) => - new Sphere(set3([], origin), $.r, __copyAttribs($)), - } - ); + sphere: ($: Sphere, origin = ZERO3) => + new Sphere(set3([], origin), $.r, __copyAttribs($)), + } + ); diff --git a/packages/geom/src/centroid.ts b/packages/geom/src/centroid.ts index 6d0cee2f7e..b4d05e7d41 100644 --- a/packages/geom/src/centroid.ts +++ b/packages/geom/src/centroid.ts @@ -47,44 +47,44 @@ import { __dispatch } from "./internal/dispatch.js"; * @param out */ export const centroid: MultiFn1O = defmulti< - any, - Vec | undefined, - Vec | undefined + any, + Vec | undefined, + Vec | undefined >( - __dispatch, - { - arc: "circle", - aabb: "rect", - bpatch: "points", - ellipse: "circle", - line3: "line", - points3: "points", - polyline: "points", - quad: "poly", - sphere: "circle", - text: "circle", - tri3: "tri", - }, - { - circle: ($: Circle, out?) => set(out || [], $.pos), + __dispatch, + { + arc: "circle", + aabb: "rect", + bpatch: "points", + ellipse: "circle", + line3: "line", + points3: "points", + polyline: "points", + quad: "poly", + sphere: "circle", + text: "circle", + tri3: "tri", + }, + { + circle: ($: Circle, out?) => set(out || [], $.pos), - group: ($: Group, out?) => { - const b = bounds($); - return b ? centroid(b, out) : undefined; - }, + group: ($: Group, out?) => { + const b = bounds($); + return b ? centroid(b, out) : undefined; + }, - line: ({ points }: Line, out?) => - mixN(out || [], points[0], points[1], 0.5), + line: ({ points }: Line, out?) => + mixN(out || [], points[0], points[1], 0.5), - points: ($: PCLike, out?) => _centroid($.points, out), + points: ($: PCLike, out?) => _centroid($.points, out), - plane: ($: Plane, out?) => mulN(out || [], $.normal, $.w), + plane: ($: Plane, out?) => mulN(out || [], $.normal, $.w), - poly: ($: Polygon, out?) => centerOfWeight2($.points, out), + poly: ($: Polygon, out?) => centerOfWeight2($.points, out), - rect: ($: AABBLike, out?) => maddN(out || [], $.size, 0.5, $.pos), + rect: ($: AABBLike, out?) => maddN(out || [], $.size, 0.5, $.pos), - tri: ({ points }: Triangle, out?) => - addmN(null, add(out || [], points[0], points[1]), points[2], 1 / 3), - } + tri: ({ points }: Triangle, out?) => + addmN(null, add(out || [], points[0], points[1]), points[2], 1 / 3), + } ); diff --git a/packages/geom/src/circle.ts b/packages/geom/src/circle.ts index b7fac8b6db..2a829c8295 100644 --- a/packages/geom/src/circle.ts +++ b/packages/geom/src/circle.ts @@ -11,21 +11,21 @@ export function circle(pos: Vec, attribs?: Attribs): Circle; export function circle(r: number, attribs?: Attribs): Circle; export function circle(attribs?: Attribs): Circle; export function circle(...args: any[]) { - return new Circle(...__argsVN(args)); + return new Circle(...__argsVN(args)); } export const circleFrom2Points = ( - a: ReadonlyVec, - b: ReadonlyVec, - attribs?: Attribs + a: ReadonlyVec, + b: ReadonlyVec, + attribs?: Attribs ) => new Circle(mixN2([], a, b, 0.5), dist(a, b) / 2, attribs); export const circleFrom3Points = ( - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - attribs?: Attribs + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + attribs?: Attribs ) => { - const o = circumCenter2(a, b, c); - return o ? new Circle(o, dist(a, o), attribs) : undefined; + const o = circumCenter2(a, b, c); + return o ? new Circle(o, dist(a, o), attribs) : undefined; }; diff --git a/packages/geom/src/classify-point.ts b/packages/geom/src/classify-point.ts index fb5beb16a4..c23379037d 100644 --- a/packages/geom/src/classify-point.ts +++ b/packages/geom/src/classify-point.ts @@ -2,8 +2,8 @@ import type { MultiFn2O } from "@thi.ng/defmulti"; import { defmulti } from "@thi.ng/defmulti/defmulti"; import type { IShape } from "@thi.ng/geom-api"; import { - classifyPointInCircle, - classifyPointInTriangle2, + classifyPointInCircle, + classifyPointInTriangle2, } from "@thi.ng/geom-isec/point"; import { sign } from "@thi.ng/math/abs"; import { EPS } from "@thi.ng/math/api"; @@ -38,22 +38,22 @@ import { __dispatch } from "./internal/dispatch.js"; * @param eps */ export const classifyPoint: MultiFn2O = - defmulti( - __dispatch, - { sphere: "circle" }, - { - circle: ($: Circle, p, eps = EPS) => - classifyPointInCircle(p, $.pos, $.r, eps), + defmulti( + __dispatch, + { sphere: "circle" }, + { + circle: ($: Circle, p, eps = EPS) => + classifyPointInCircle(p, $.pos, $.r, eps), - plane: ($: Plane, p, eps) => sign(dot($.normal, p) - $.w, eps), + plane: ($: Plane, p, eps) => sign(dot($.normal, p) - $.w, eps), - tri: ({ points }: Triangle, p: ReadonlyVec, eps = EPS) => - classifyPointInTriangle2( - p, - points[0], - points[1], - points[2], - eps - ), - } - ); + tri: ({ points }: Triangle, p: ReadonlyVec, eps = EPS) => + classifyPointInTriangle2( + p, + points[0], + points[1], + points[2], + eps + ), + } + ); diff --git a/packages/geom/src/clip-convex.ts b/packages/geom/src/clip-convex.ts index 586ccf7c49..a44d18ed96 100644 --- a/packages/geom/src/clip-convex.ts +++ b/packages/geom/src/clip-convex.ts @@ -26,60 +26,60 @@ import { ensureVertices, vertices } from "./vertices.js"; * @param boundary */ export const clipConvex: MultiFn2< - IShape, - IShape | ReadonlyVec[], - IShape | undefined + IShape, + IShape | ReadonlyVec[], + IShape | undefined > = defmulti( - __dispatch, - { - circle: "rect", - ellipse: "rect", - path: "rect", - quad: "poly", - tri: "poly", - }, - { - group: ({ children, attribs }: Group, boundary) => { - boundary = ensureVertices(boundary); - const clipped: IHiccupShape[] = []; - for (let c of children) { - const res = clipConvex(c, boundary); - if (res) clipped.push(res); - } - return clipped.length - ? new Group({ ...attribs }, clipped) - : undefined; - }, + __dispatch, + { + circle: "rect", + ellipse: "rect", + path: "rect", + quad: "poly", + tri: "poly", + }, + { + group: ({ children, attribs }: Group, boundary) => { + boundary = ensureVertices(boundary); + const clipped: IHiccupShape[] = []; + for (let c of children) { + const res = clipConvex(c, boundary); + if (res) clipped.push(res); + } + return clipped.length + ? new Group({ ...attribs }, clipped) + : undefined; + }, - line: ($: Line, boundary) => { - const segments = clipLineSegmentPoly( - $.points[0], - $.points[1], - ensureVertices(boundary) - ); - return segments && segments.length - ? new Line(segments[0], __copyAttribs($)) - : undefined; - }, + line: ($: Line, boundary) => { + const segments = clipLineSegmentPoly( + $.points[0], + $.points[1], + ensureVertices(boundary) + ); + return segments && segments.length + ? new Line(segments[0], __copyAttribs($)) + : undefined; + }, - poly: ($: Polygon, boundary) => { - boundary = ensureVertices(boundary); - const pts = sutherlandHodgeman( - $.points, - boundary, - centroid(boundary) - ); - return pts.length ? new Polygon(pts, __copyAttribs($)) : undefined; - }, + poly: ($: Polygon, boundary) => { + boundary = ensureVertices(boundary); + const pts = sutherlandHodgeman( + $.points, + boundary, + centroid(boundary) + ); + return pts.length ? new Polygon(pts, __copyAttribs($)) : undefined; + }, - rect: ($: IShape, boundary) => { - boundary = ensureVertices(boundary); - const pts = sutherlandHodgeman( - vertices($), - boundary, - centroid(boundary) - ); - return pts.length ? new Polygon(pts, __copyAttribs($)) : undefined; - }, - } + rect: ($: IShape, boundary) => { + boundary = ensureVertices(boundary); + const pts = sutherlandHodgeman( + vertices($), + boundary, + centroid(boundary) + ); + return pts.length ? new Polygon(pts, __copyAttribs($)) : undefined; + }, + } ); diff --git a/packages/geom/src/closest-point.ts b/packages/geom/src/closest-point.ts index 1a917633f7..eb01b285b0 100644 --- a/packages/geom/src/closest-point.ts +++ b/packages/geom/src/closest-point.ts @@ -3,13 +3,13 @@ import { defmulti } from "@thi.ng/defmulti/defmulti"; import type { IShape, PCLike } from "@thi.ng/geom-api"; import { closestPoint as closestPointArc } from "@thi.ng/geom-arc/closest-point"; import { - closestPointAABB, - closestPointRect, + closestPointAABB, + closestPointRect, } from "@thi.ng/geom-closest-point/box"; import { closestPointCircle } from "@thi.ng/geom-closest-point/circle"; import { - closestPointPolyline, - closestPointSegment, + closestPointPolyline, + closestPointSegment, } from "@thi.ng/geom-closest-point/line"; import { closestPointPlane } from "@thi.ng/geom-closest-point/plane"; import { closestPointArray } from "@thi.ng/geom-closest-point/points"; @@ -54,54 +54,54 @@ import { __dispatch } from "./internal/dispatch.js"; * @param out */ export const closestPoint: MultiFn2O< - IShape, - ReadonlyVec, - Vec, - Vec | undefined + IShape, + ReadonlyVec, + Vec, + Vec | undefined > = defmulti( - __dispatch, - { - quad: "poly", - points3: "points", - sphere: "circle", - tri: "poly", - }, - { - aabb: ($: AABB, p, out) => - closestPointAABB(p, $.pos, add3([], $.pos, $.size), out), + __dispatch, + { + quad: "poly", + points3: "points", + sphere: "circle", + tri: "poly", + }, + { + aabb: ($: AABB, p, out) => + closestPointAABB(p, $.pos, add3([], $.pos, $.size), out), - arc: ($: Arc, p, out) => - closestPointArc(p, $.pos, $.r, $.axis, $.start, $.end, out), + arc: ($: Arc, p, out) => + closestPointArc(p, $.pos, $.r, $.axis, $.start, $.end, out), - circle: ($: Circle, p, out) => closestPointCircle(p, $.pos, $.r, out), + circle: ($: Circle, p, out) => closestPointCircle(p, $.pos, $.r, out), - cubic: ({ points }: Cubic, p, out) => - closestPointCubic( - p, - points[0], - points[1], - points[2], - points[3], - out - ), + cubic: ({ points }: Cubic, p, out) => + closestPointCubic( + p, + points[0], + points[1], + points[2], + points[3], + out + ), - line: ({ points }: Line, p, out) => - closestPointSegment(p, points[0], points[1], out), + line: ({ points }: Line, p, out) => + closestPointSegment(p, points[0], points[1], out), - plane: ($: Plane, p, out) => closestPointPlane(p, $.normal, $.w, out), + plane: ($: Plane, p, out) => closestPointPlane(p, $.normal, $.w, out), - points: ($: PCLike, p, out) => closestPointArray(p, $.points, out), + points: ($: PCLike, p, out) => closestPointArray(p, $.points, out), - poly: ($: PCLike, p, out) => - closestPointPolyline(p, $.points, true, out), + poly: ($: PCLike, p, out) => + closestPointPolyline(p, $.points, true, out), - polyline: ($: PCLike, p, out) => - closestPointPolyline(p, $.points, false, out), + polyline: ($: PCLike, p, out) => + closestPointPolyline(p, $.points, false, out), - quadratic: ({ points }: Quadratic, p, out) => - closestPointQuadratic(p, points[0], points[1], points[2], out), + quadratic: ({ points }: Quadratic, p, out) => + closestPointQuadratic(p, points[0], points[1], points[2], out), - rect: ($: Rect, p, out) => - closestPointRect(p, $.pos, add2([], $.pos, $.size), out), - } + rect: ($: Rect, p, out) => + closestPointRect(p, $.pos, add2([], $.pos, $.size), out), + } ); diff --git a/packages/geom/src/convex-hull.ts b/packages/geom/src/convex-hull.ts index 6a48b0ea58..fe1fc498fc 100644 --- a/packages/geom/src/convex-hull.ts +++ b/packages/geom/src/convex-hull.ts @@ -29,26 +29,26 @@ import { vertices } from "./vertices.js"; * @param shape */ export const convexHull: MultiFn1 = defmulti( - __dispatch, - { - arc: "group", - circle: "tri", - cubic: "group", - ellipse: "tri", - line: "tri", - poly: "points", - polyline: "points", - quad: "points", - quadratic: "group", - rect: "tri", - }, - { - group: ($: IShape) => - new Polygon(grahamScan2(vertices($)), __copyAttribs($)), + __dispatch, + { + arc: "group", + circle: "tri", + cubic: "group", + ellipse: "tri", + line: "tri", + poly: "points", + polyline: "points", + quad: "points", + quadratic: "group", + rect: "tri", + }, + { + group: ($: IShape) => + new Polygon(grahamScan2(vertices($)), __copyAttribs($)), - points: ($: PCLike) => - new Polygon(grahamScan2($.points), __copyAttribs($)), + points: ($: PCLike) => + new Polygon(grahamScan2($.points), __copyAttribs($)), - tri: ($: IShape) => new Polygon(vertices($), __copyAttribs($)), - } + tri: ($: IShape) => new Polygon(vertices($), __copyAttribs($)), + } ); diff --git a/packages/geom/src/cubic.ts b/packages/geom/src/cubic.ts index 5607655be0..83fb685fa9 100644 --- a/packages/geom/src/cubic.ts +++ b/packages/geom/src/cubic.ts @@ -11,16 +11,16 @@ import { __pclike } from "./internal/pclike.js"; export function cubic(a: Vec, b: Vec, c: Vec, d: Vec, attribs?: Attribs): Cubic; export function cubic(pts: Vec[], attribs?: Attribs): Cubic; export function cubic(...args: any[]) { - return __pclike(Cubic, args); + return __pclike(Cubic, args); } export const cubicFromArc = (arc: Arc) => - _arc(arc.pos, arc.r, arc.axis, arc.start, arc.end).map( - (c) => new Cubic(c, __copyAttribs(arc)) - ); + _arc(arc.pos, arc.r, arc.axis, arc.start, arc.end).map( + (c) => new Cubic(c, __copyAttribs(arc)) + ); export const cubicFromLine = (a: Vec, b: Vec, attribs?: Attribs) => - new Cubic(_line(a, b), attribs); + new Cubic(_line(a, b), attribs); export const cubicFromQuadratic = (a: Vec, b: Vec, c: Vec, attribs?: Attribs) => - new Cubic(_quad(a, b, c), attribs); + new Cubic(_quad(a, b, c), attribs); diff --git a/packages/geom/src/edges.ts b/packages/geom/src/edges.ts index ae699ba5f0..c82e51c1bd 100644 --- a/packages/geom/src/edges.ts +++ b/packages/geom/src/edges.ts @@ -43,54 +43,54 @@ import { vertices } from "./vertices.js"; * @param opts */ export const edges: MultiFn1O< - IShape, - number | Partial, - Iterable + IShape, + number | Partial, + Iterable > = defmulti< - any, - number | Partial | undefined, - Iterable + any, + number | Partial | undefined, + Iterable >( - __dispatch, - { - cubic: "arc", - ellipse: "circle", - line: "polyline", - quad: "poly", - quadratic: "arc", - tri: "poly", - }, - { - aabb: ($: AABB) => { - const [a, b, c, d, e, f, g, h] = vertices($); - return [ - [a, b], - [b, c], - [c, d], - [d, a], // bottom - [e, f], - [f, g], - [g, h], - [h, e], // top - [a, e], - [b, f], // left - [c, g], - [d, h], // right - ]; - }, + __dispatch, + { + cubic: "arc", + ellipse: "circle", + line: "polyline", + quad: "poly", + quadratic: "arc", + tri: "poly", + }, + { + aabb: ($: AABB) => { + const [a, b, c, d, e, f, g, h] = vertices($); + return [ + [a, b], + [b, c], + [c, d], + [d, a], // bottom + [e, f], + [f, g], + [g, h], + [h, e], // top + [a, e], + [b, f], // left + [c, g], + [d, h], // right + ]; + }, - arc: ($: Arc, opts) => __edges(asPolyline($, opts).points, false), + arc: ($: Arc, opts) => __edges(asPolyline($, opts).points, false), - bpatch: ($: BPatch) => $.edges(), + bpatch: ($: BPatch) => $.edges(), - circle: ($: Circle, opts) => __edges(asPolygon($, opts).points, true), + circle: ($: Circle, opts) => __edges(asPolygon($, opts).points, true), - path: ($: Path, opts) => __edges(asPolygon($, opts).points, $.closed), + path: ($: Path, opts) => __edges(asPolygon($, opts).points, $.closed), - poly: ($: Polygon) => __edges($.points, true), + poly: ($: Polygon) => __edges($.points, true), - polyline: ($: Polyline) => __edges($.points), + polyline: ($: Polyline) => __edges($.points), - rect: ($: Rect) => __edges(vertices($), true), - } + rect: ($: Rect) => __edges(vertices($), true), + } ); diff --git a/packages/geom/src/ellipse.ts b/packages/geom/src/ellipse.ts index 763b7d9da1..fc7bbd3b34 100644 --- a/packages/geom/src/ellipse.ts +++ b/packages/geom/src/ellipse.ts @@ -7,5 +7,5 @@ export function ellipse(pos: Vec, r: number | Vec, attribs?: Attribs): Ellipse; export function ellipse(r: number | Vec, attribs?: Attribs): Ellipse; export function ellipse(attribs?: Attribs): Ellipse; export function ellipse(...args: any[]) { - return new Ellipse(...__argsVV(args)); + return new Ellipse(...__argsVV(args)); } diff --git a/packages/geom/src/fit-into-bounds.ts b/packages/geom/src/fit-into-bounds.ts index 0a5a2d515c..ee175038d1 100644 --- a/packages/geom/src/fit-into-bounds.ts +++ b/packages/geom/src/fit-into-bounds.ts @@ -19,17 +19,17 @@ import { unmapPoint } from "./unmap-point.js"; /** @internal */ const __translateScale = ( - tmat: MatOpV, - smat: MatOpNV, - shape: IShape, - preTrans: ReadonlyVec, - postTrans: ReadonlyVec, - scale: ReadonlyVec | number + tmat: MatOpV, + smat: MatOpNV, + shape: IShape, + preTrans: ReadonlyVec, + postTrans: ReadonlyVec, + scale: ReadonlyVec | number ) => - transform( - shape, - concat([], tmat([], postTrans), smat([], scale), tmat([], preTrans)) - ); + transform( + shape, + concat([], tmat([], postTrans), smat([], scale), tmat([], preTrans)) + ); /** * Uniformly rescales & repositions given 2D `shape` such that it fits into @@ -39,21 +39,21 @@ const __translateScale = ( * @param dest */ export const fitIntoBounds2 = (shape: IShape, dest: Rect) => { - const src = bounds(shape); - if (!src) return; - const c = centroid(src); - if (!c) return; - return __translateScale( - translation23, - scale23, - shape, - neg(null, c), - centroid(dest)!, - minNonZero2( - safeDiv(dest.size[0], src.size[0]), - safeDiv(dest.size[1], src.size[1]) - ) - ); + const src = bounds(shape); + if (!src) return; + const c = centroid(src); + if (!c) return; + return __translateScale( + translation23, + scale23, + shape, + neg(null, c), + centroid(dest)!, + minNonZero2( + safeDiv(dest.size[0], src.size[0]), + safeDiv(dest.size[1], src.size[1]) + ) + ); }; /** @@ -63,22 +63,22 @@ export const fitIntoBounds2 = (shape: IShape, dest: Rect) => { * @param dest */ export const fitIntoBounds3 = (shape: IShape, dest: AABB) => { - const src = bounds(shape); - if (!src) return; - const c = centroid(src); - if (!c) return; - return __translateScale( - translation44, - scale44, - shape, - neg(null, c), - centroid(dest)!, - minNonZero3( - safeDiv(dest.size[0], src.size[0]), - safeDiv(dest.size[1], src.size[1]), - safeDiv(dest.size[2], src.size[2]) - ) - ); + const src = bounds(shape); + if (!src) return; + const c = centroid(src); + if (!c) return; + return __translateScale( + translation44, + scale44, + shape, + neg(null, c), + centroid(dest)!, + minNonZero3( + safeDiv(dest.size[0], src.size[0]), + safeDiv(dest.size[1], src.size[1]), + safeDiv(dest.size[2], src.size[2]) + ) + ); }; /** @@ -88,35 +88,35 @@ export const fitIntoBounds3 = (shape: IShape, dest: AABB) => { * @param dest */ export const fitAllIntoBounds2 = (shapes: IShape[], dest: Rect) => { - const sbraw = __collBounds(shapes, bounds); - if (!sbraw) return; - const src = new Rect(...sbraw); - const sx = safeDiv(dest.size[0], src.size[0]); - const sy = safeDiv(dest.size[1], src.size[1]); - const scale = sx > 0 ? (sy > 0 ? Math.min(sx, sy) : sx) : sy; - const smat = scale23([], scale); - const b = center(transform(src, smat), centroid(dest))!; - const c1: Vec = []; - const c2: Vec = []; - const res: IShape[] = []; - for (let i = shapes.length; i-- > 0; ) { - const s = shapes[i]; - const sc = centroid(s, c1); - if (sc) { - unmapPoint(b, mapPoint(src, sc), c2); - res.push( - __translateScale( - translation23, - scale23, - s, - neg(null, c1), - c2, - smat - ) - ); - } else { - res.push(s); - } - } - return res; + const sbraw = __collBounds(shapes, bounds); + if (!sbraw) return; + const src = new Rect(...sbraw); + const sx = safeDiv(dest.size[0], src.size[0]); + const sy = safeDiv(dest.size[1], src.size[1]); + const scale = sx > 0 ? (sy > 0 ? Math.min(sx, sy) : sx) : sy; + const smat = scale23([], scale); + const b = center(transform(src, smat), centroid(dest))!; + const c1: Vec = []; + const c2: Vec = []; + const res: IShape[] = []; + for (let i = shapes.length; i-- > 0; ) { + const s = shapes[i]; + const sc = centroid(s, c1); + if (sc) { + unmapPoint(b, mapPoint(src, sc), c2); + res.push( + __translateScale( + translation23, + scale23, + s, + neg(null, c1), + c2, + smat + ) + ); + } else { + res.push(s); + } + } + return res; }; diff --git a/packages/geom/src/flip.ts b/packages/geom/src/flip.ts index 85de24da70..939cb19c6e 100644 --- a/packages/geom/src/flip.ts +++ b/packages/geom/src/flip.ts @@ -32,46 +32,46 @@ import { __dispatch } from "./internal/dispatch.js"; * @param shape */ export const flip: MultiFn1 = defmulti( - __dispatch, - { - cubic: "points", - line: "points", - points3: "points", - poly: "points", - polyline: "points", - quad: "points", - quadratic: "points", - tri: "points", - }, - { - [DEFAULT]: (x: IShape) => x, + __dispatch, + { + cubic: "points", + line: "points", + points3: "points", + poly: "points", + polyline: "points", + quad: "points", + quadratic: "points", + tri: "points", + }, + { + [DEFAULT]: (x: IShape) => x, - arc: ($: Arc) => { - const t = $.start; - $.start = $.end; - $.end = t; - $.cw = !$.cw; - return $; - }, + arc: ($: Arc) => { + const t = $.start; + $.start = $.end; + $.end = t; + $.cw = !$.cw; + return $; + }, - group: ($: Group) => { - $.children.forEach(flip); - return $; - }, + group: ($: Group) => { + $.children.forEach(flip); + return $; + }, - path: ($: Path) => { - // TODO - return $; - }, + path: ($: Path) => { + // TODO + return $; + }, - points: ($: PCLike) => { - $.points.reverse(); - return $; - }, + points: ($: PCLike) => { + $.points.reverse(); + return $; + }, - ray: ($: Ray) => { - $.dir = neg(null, $.dir); - return $; - }, - } + ray: ($: Ray) => { + $.dir = neg(null, $.dir); + return $; + }, + } ); diff --git a/packages/geom/src/group.ts b/packages/geom/src/group.ts index 082bd7ffb4..c443613519 100644 --- a/packages/geom/src/group.ts +++ b/packages/geom/src/group.ts @@ -2,4 +2,4 @@ import type { Attribs, IHiccupShape } from "@thi.ng/geom-api"; import { Group } from "./api/group.js"; export const group = (attribs: Attribs = {}, children?: IHiccupShape[]) => - new Group(attribs, children); + new Group(attribs, children); diff --git a/packages/geom/src/internal/args.ts b/packages/geom/src/internal/args.ts index e4d6cbd120..22929221dc 100644 --- a/packages/geom/src/internal/args.ts +++ b/packages/geom/src/internal/args.ts @@ -13,14 +13,14 @@ import { vecOf } from "@thi.ng/vectors/vec-of"; * @internal */ export const __argAttribs = (args: any[]) => { - if (args.length) { - const last = args[args.length - 1]; - return isPlainObject(last) - ? args.pop() - : last == null - ? (args.pop(), undefined) - : undefined; - } + if (args.length) { + const last = args[args.length - 1]; + return isPlainObject(last) + ? args.pop() + : last == null + ? (args.pop(), undefined) + : undefined; + } }; /** @@ -32,12 +32,12 @@ export const __argAttribs = (args: any[]) => { * @internal */ export const __argsVV = (args: any[]) => { - const attr = __argAttribs(args); - return args.length - ? args.length === 2 - ? [args[0], args[1], attr] - : [undefined, args[0], attr] - : [undefined, undefined, attr]; + const attr = __argAttribs(args); + return args.length + ? args.length === 2 + ? [args[0], args[1], attr] + : [undefined, args[0], attr] + : [undefined, undefined, attr]; }; /** @@ -49,15 +49,15 @@ export const __argsVV = (args: any[]) => { * @internal */ export const __argsVN = (args: any[]) => { - const attr = __argAttribs(args); - return args.length - ? args.length === 2 - ? [args[0], args[1], attr] - : isNumber(args[0]) - ? [undefined, args[0], attr] - : [args[0], undefined, attr] - : [undefined, undefined, attr]; + const attr = __argAttribs(args); + return args.length + ? args.length === 2 + ? [args[0], args[1], attr] + : isNumber(args[0]) + ? [undefined, args[0], attr] + : [args[0], undefined, attr] + : [undefined, undefined, attr]; }; export const __asVec = (x: number | Vec, size = 2) => - isNumber(x) ? vecOf(size, x) : x; + isNumber(x) ? vecOf(size, x) : x; diff --git a/packages/geom/src/internal/bounds.ts b/packages/geom/src/internal/bounds.ts index 7fa9e4b969..76d3662849 100644 --- a/packages/geom/src/internal/bounds.ts +++ b/packages/geom/src/internal/bounds.ts @@ -16,20 +16,20 @@ import { sub } from "@thi.ng/vectors/sub"; * @param bounds - bbox function */ export const __collBounds = ( - shapes: IShape[], - bounds: Fn + shapes: IShape[], + bounds: Fn ) => { - let n = shapes.length - 1; - if (n < 0) return; - let b = bounds(shapes[n]); - if (!b) return; - let { pos, size } = b; - for (; n-- > 0; ) { - b = bounds(shapes[n]); - if (!b) continue; - [pos, size] = __unionBounds(pos, size, b.pos, b.size); - } - return [pos, size]; + let n = shapes.length - 1; + if (n < 0) return; + let b = bounds(shapes[n]); + if (!b) return; + let { pos, size } = b; + for (; n-- > 0; ) { + b = bounds(shapes[n]); + if (!b) continue; + [pos, size] = __unionBounds(pos, size, b.pos, b.size); + } + return [pos, size]; }; /** @@ -43,13 +43,13 @@ export const __collBounds = ( * @param bsize - bbox 2 size */ export const __unionBounds = ( - apos: ReadonlyVec, - asize: ReadonlyVec, - bpos: ReadonlyVec, - bsize: ReadonlyVec + apos: ReadonlyVec, + asize: ReadonlyVec, + bpos: ReadonlyVec, + bsize: ReadonlyVec ): VecPair => { - const p = add([], apos, asize); - const q = add([], bpos, bsize); - const pos = min([], apos, bpos); - return [pos, sub(null, max(null, p, q), pos)]; + const p = add([], apos, asize); + const q = add([], bpos, bsize); + const pos = min([], apos, bpos); + return [pos, sub(null, max(null, p, q), pos)]; }; diff --git a/packages/geom/src/internal/collate.ts b/packages/geom/src/internal/collate.ts index a4fe1aa651..9f5112933b 100644 --- a/packages/geom/src/internal/collate.ts +++ b/packages/geom/src/internal/collate.ts @@ -2,58 +2,58 @@ import type { NumericArray } from "@thi.ng/api"; import type { StridedVec } from "@thi.ng/vectors"; export interface CollateOpts { - buf: NumericArray; - start: number; - cstride: number; - estride: number; + buf: NumericArray; + start: number; + cstride: number; + estride: number; } export const __remapBuffer = ( - buf: NumericArray, - pts: StridedVec[], - start: number, - cstride: number, - estride: number + buf: NumericArray, + pts: StridedVec[], + start: number, + cstride: number, + estride: number ) => { - for (let i = pts.length; i-- > 0; ) { - const p = pts[i]; - p.buf = buf; - p.offset = start + i * estride; - p.stride = cstride; - } - return buf; + for (let i = pts.length; i-- > 0; ) { + const p = pts[i]; + p.buf = buf; + p.offset = start + i * estride; + p.stride = cstride; + } + return buf; }; export const __collateWith = ( - fn: ( - buf: NumericArray, - src: Iterable>, - start: number, - cstride: number, - estride: number - ) => NumericArray, - pts: StridedVec[], - opts: Partial, - stride: number + fn: ( + buf: NumericArray, + src: Iterable>, + start: number, + cstride: number, + estride: number + ) => NumericArray, + pts: StridedVec[], + opts: Partial, + stride: number ) => { - opts = { - start: 0, - cstride: 1, - estride: stride, - ...opts, - }; - const { start, cstride, estride } = opts; - return __remapBuffer( - fn( - opts.buf || new Array(start! + pts.length * estride!).fill(0), - pts, - start!, - cstride!, - estride! - ), - pts, - start!, - cstride!, - estride! - ); + opts = { + start: 0, + cstride: 1, + estride: stride, + ...opts, + }; + const { start, cstride, estride } = opts; + return __remapBuffer( + fn( + opts.buf || new Array(start! + pts.length * estride!).fill(0), + pts, + start!, + cstride!, + estride! + ), + pts, + start!, + cstride!, + estride! + ); }; diff --git a/packages/geom/src/internal/copy.ts b/packages/geom/src/internal/copy.ts index 1afd4355be..32b4e7ae25 100644 --- a/packages/geom/src/internal/copy.ts +++ b/packages/geom/src/internal/copy.ts @@ -1,10 +1,10 @@ // thing:export import { copyVectors } from "@thi.ng/vectors/copy"; import type { - Attribs, - IShape, - PCLike, - PCLikeConstructor, + Attribs, + IShape, + PCLike, + PCLikeConstructor, } from "@thi.ng/geom-api"; /** @internal */ @@ -12,6 +12,6 @@ export const __copyAttribs = ($: IShape) => { ...$.attribs }; /** @internal */ export const __copyShape = ( - ctor: PCLikeConstructor, - inst: T + ctor: PCLikeConstructor, + inst: T ) => new ctor(copyVectors(inst.points), __copyAttribs(inst)); diff --git a/packages/geom/src/internal/edges.ts b/packages/geom/src/internal/edges.ts index f85f9df71c..c5ae3e6198 100644 --- a/packages/geom/src/internal/edges.ts +++ b/packages/geom/src/internal/edges.ts @@ -3,6 +3,6 @@ import { wrapSides } from "@thi.ng/transducers/wrap-sides"; import type { ReadonlyVec, VecPair } from "@thi.ng/vectors"; export const __edges = (vertices: Iterable, closed = false) => - >( - partition(2, 1, closed ? wrapSides(vertices, 0, 1) : vertices) - ); + >( + partition(2, 1, closed ? wrapSides(vertices, 0, 1) : vertices) + ); diff --git a/packages/geom/src/internal/pclike.ts b/packages/geom/src/internal/pclike.ts index 37172145c5..f19f353df4 100644 --- a/packages/geom/src/internal/pclike.ts +++ b/packages/geom/src/internal/pclike.ts @@ -2,6 +2,6 @@ import { __argAttribs } from "./args.js"; import type { PCLikeConstructor } from "@thi.ng/geom-api"; export const __pclike = (ctor: PCLikeConstructor, args: any[]) => { - const attr = __argAttribs(args); - return new ctor(args.length === 1 ? args[0] : args, attr); + const attr = __argAttribs(args); + return new ctor(args.length === 1 ? args[0] : args, attr); }; diff --git a/packages/geom/src/internal/points-as-shape.ts b/packages/geom/src/internal/points-as-shape.ts index 980ed5a6bb..13a6176554 100644 --- a/packages/geom/src/internal/points-as-shape.ts +++ b/packages/geom/src/internal/points-as-shape.ts @@ -4,10 +4,10 @@ import type { Vec } from "@thi.ng/vectors"; import { copyVectors } from "@thi.ng/vectors/copy"; export const __pointArraysAsShapes = ( - ctor: PCLikeConstructor, - src?: Iterable, - attribs?: Attribs + ctor: PCLikeConstructor, + src?: Iterable, + attribs?: Attribs ) => - src - ? [...map((pts) => new ctor(copyVectors(pts), { ...attribs }), src)] - : undefined; + src + ? [...map((pts) => new ctor(copyVectors(pts), { ...attribs }), src)] + : undefined; diff --git a/packages/geom/src/internal/rotate.ts b/packages/geom/src/internal/rotate.ts index 887110e30f..353c3e0e07 100644 --- a/packages/geom/src/internal/rotate.ts +++ b/packages/geom/src/internal/rotate.ts @@ -4,8 +4,8 @@ import { rotate } from "@thi.ng/vectors/rotate"; import { __copyAttribs } from "./copy.js"; export const __rotatedPoints = (pts: ReadonlyVec[], delta: number) => - pts.map((x) => rotate([], x, delta)); + pts.map((x) => rotate([], x, delta)); export const __rotatedShape = - (ctor: PCLikeConstructor) => ($: PCLike, delta: number) => - new ctor(__rotatedPoints($.points, delta), __copyAttribs($)); + (ctor: PCLikeConstructor) => ($: PCLike, delta: number) => + new ctor(__rotatedPoints($.points, delta), __copyAttribs($)); diff --git a/packages/geom/src/internal/scale.ts b/packages/geom/src/internal/scale.ts index cf7974a421..b324929d90 100644 --- a/packages/geom/src/internal/scale.ts +++ b/packages/geom/src/internal/scale.ts @@ -6,13 +6,13 @@ import { mulN } from "@thi.ng/vectors/muln"; import { __copyAttribs } from "./copy.js"; export const __scaledPoints = ( - pts: ReadonlyVec[], - delta: number | ReadonlyVec + pts: ReadonlyVec[], + delta: number | ReadonlyVec ) => - pts.map( - isNumber(delta) ? (x) => mulN([], x, delta) : (x) => mul([], x, delta) - ); + pts.map( + isNumber(delta) ? (x) => mulN([], x, delta) : (x) => mul([], x, delta) + ); export const __scaledShape = - (ctor: PCLikeConstructor) => ($: PCLike, delta: number | ReadonlyVec) => - new ctor(__scaledPoints($.points, delta), __copyAttribs($)); + (ctor: PCLikeConstructor) => ($: PCLike, delta: number | ReadonlyVec) => + new ctor(__scaledPoints($.points, delta), __copyAttribs($)); diff --git a/packages/geom/src/internal/split.ts b/packages/geom/src/internal/split.ts index 1613f1ecbd..88bfa4fb9a 100644 --- a/packages/geom/src/internal/split.ts +++ b/packages/geom/src/internal/split.ts @@ -3,9 +3,9 @@ import { mixN } from "@thi.ng/vectors/mixn"; import { set } from "@thi.ng/vectors/set"; export const __splitLine = (a: Vec, b: Vec, t: number): [VecPair, VecPair] => { - const p = mixN([], a, b, t); - return [ - [a, p], - [set([], p), b], - ]; + const p = mixN([], a, b, t); + return [ + [a, p], + [set([], p), b], + ]; }; diff --git a/packages/geom/src/internal/transform.ts b/packages/geom/src/internal/transform.ts index 703cf8fe34..593f187ea7 100644 --- a/packages/geom/src/internal/transform.ts +++ b/packages/geom/src/internal/transform.ts @@ -7,56 +7,56 @@ import type { ReadonlyVec } from "@thi.ng/vectors"; import { __copyAttribs } from "./copy.js"; export const __transformPoints = ( - pts: ReadonlyVec[], - mat: ReadonlyMat, - op: MatOpMV = mulV + pts: ReadonlyVec[], + mat: ReadonlyMat, + op: MatOpMV = mulV ) => (pts.forEach((p) => op(null, mat, p)), pts); export const __transformedPoints = ( - pts: ReadonlyVec[], - mat: ReadonlyMat, - op: MatOpMV = mulV + pts: ReadonlyVec[], + mat: ReadonlyMat, + op: MatOpMV = mulV ) => pts.map((p) => op([], mat, p)); export const __transformPointsWith = ( - pts: ReadonlyVec[], - fn: Fn, - op: MatOpMV = mulV + pts: ReadonlyVec[], + fn: Fn, + op: MatOpMV = mulV ) => (pts.forEach((p) => op(null, fn(p), p)!), pts); export const __transformedPointsWith = ( - pts: ReadonlyVec[], - fn: Fn, - op: MatOpMV = mulV + pts: ReadonlyVec[], + fn: Fn, + op: MatOpMV = mulV ) => pts.map((p) => op([], fn(p), p)!); export const __transformedShape = - (ctor: PCLikeConstructor) => ($: PCLike, mat: ReadonlyMat) => - new ctor(__transformedPoints($.points, mat), __copyAttribs($)); + (ctor: PCLikeConstructor) => ($: PCLike, mat: ReadonlyMat) => + new ctor(__transformedPoints($.points, mat), __copyAttribs($)); export const __transformedShapePoints = - (ctor: PCLikeConstructor) => - ($: PCLike, fn: Fn) => - new ctor(__transformedPointsWith($.points, fn), __copyAttribs($)); + (ctor: PCLikeConstructor) => + ($: PCLike, fn: Fn) => + new ctor(__transformedPointsWith($.points, fn), __copyAttribs($)); // 3d versions export const __transformPoints3 = (pts: ReadonlyVec[], mat: ReadonlyMat) => - __transformPoints(pts, mat, mulV344); + __transformPoints(pts, mat, mulV344); export const __transformedPoints3 = (pts: ReadonlyVec[], mat: ReadonlyMat) => - __transformedPoints(pts, mat, mulV344); + __transformedPoints(pts, mat, mulV344); export const __transformedPointsWith3 = ( - pts: ReadonlyVec[], - fn: Fn + pts: ReadonlyVec[], + fn: Fn ) => __transformedPointsWith(pts, fn, mulV344); export const __transformedShape3 = - (ctor: PCLikeConstructor) => ($: PCLike, mat: ReadonlyMat) => - new ctor(__transformedPoints3($.points, mat), __copyAttribs($)); + (ctor: PCLikeConstructor) => ($: PCLike, mat: ReadonlyMat) => + new ctor(__transformedPoints3($.points, mat), __copyAttribs($)); export const __transformedShapePoints3 = - (ctor: PCLikeConstructor) => - ($: PCLike, fn: Fn) => - new ctor(__transformedPointsWith3($.points, fn), __copyAttribs($)); + (ctor: PCLikeConstructor) => + ($: PCLike, fn: Fn) => + new ctor(__transformedPointsWith3($.points, fn), __copyAttribs($)); diff --git a/packages/geom/src/internal/translate.ts b/packages/geom/src/internal/translate.ts index b23d596a06..8bf63bb7ff 100644 --- a/packages/geom/src/internal/translate.ts +++ b/packages/geom/src/internal/translate.ts @@ -4,8 +4,8 @@ import { add } from "@thi.ng/vectors/add"; import { __copyAttribs } from "./copy.js"; export const __translatedPoints = (pts: ReadonlyVec[], delta: ReadonlyVec) => - pts.map((x) => add([], x, delta)); + pts.map((x) => add([], x, delta)); export const __translatedShape = - (ctor: PCLikeConstructor) => ($: PCLike, delta: ReadonlyVec) => - new ctor(__translatedPoints($.points, delta), __copyAttribs($)); + (ctor: PCLikeConstructor) => ($: PCLike, delta: ReadonlyVec) => + new ctor(__translatedPoints($.points, delta), __copyAttribs($)); diff --git a/packages/geom/src/intersects.ts b/packages/geom/src/intersects.ts index 17b7212e70..27d47c1376 100644 --- a/packages/geom/src/intersects.ts +++ b/packages/geom/src/intersects.ts @@ -46,58 +46,58 @@ import { __dispatch2 } from "./internal/dispatch.js"; * @param b */ export const intersects: MultiFn2 = - defmulti( - __dispatch2, - { - "ray-sphere": "ray-circle", - "ray-quad": "ray-poly", - "ray-tri": "ray-poly", - "sphere-sphere": "circle-circle", - }, - { - "aabb-aabb": (a: AABB, b: AABB) => ({ - type: testAabbAabb(a.pos, a.size, b.pos, b.size) - ? IntersectionType.INTERSECT - : IntersectionType.NONE, - }), + defmulti( + __dispatch2, + { + "ray-sphere": "ray-circle", + "ray-quad": "ray-poly", + "ray-tri": "ray-poly", + "sphere-sphere": "circle-circle", + }, + { + "aabb-aabb": (a: AABB, b: AABB) => ({ + type: testAabbAabb(a.pos, a.size, b.pos, b.size) + ? IntersectionType.INTERSECT + : IntersectionType.NONE, + }), - "circle-circle": (a: Circle, b: Circle) => - intersectCircleCircle(a.pos, b.pos, a.r, b.r), + "circle-circle": (a: Circle, b: Circle) => + intersectCircleCircle(a.pos, b.pos, a.r, b.r), - "line-line": ({ points: a }: Line, { points: b }: Line) => - intersectLineLine(a[0], a[1], b[0], b[1]), + "line-line": ({ points: a }: Line, { points: b }: Line) => + intersectLineLine(a[0], a[1], b[0], b[1]), - "plane-plane": (a: Plane, b: Plane) => - intersectPlanePlane(a.normal, a.w, b.normal, b.w), + "plane-plane": (a: Plane, b: Plane) => + intersectPlanePlane(a.normal, a.w, b.normal, b.w), - "ray-aabb": (ray: Ray, box: AABB) => - intersectRayAABB(ray.pos, ray.dir, box.pos, box.max()), + "ray-aabb": (ray: Ray, box: AABB) => + intersectRayAABB(ray.pos, ray.dir, box.pos, box.max()), - "ray-circle": (ray: Ray, sphere: Sphere) => - intersectRayCircle(ray.pos, ray.dir, sphere.pos, sphere.r), + "ray-circle": (ray: Ray, sphere: Sphere) => + intersectRayCircle(ray.pos, ray.dir, sphere.pos, sphere.r), - "ray-plane": (ray: Ray, plane: Plane) => - intersectRayPlane(ray.pos, ray.dir, plane.normal, plane.w), + "ray-plane": (ray: Ray, plane: Plane) => + intersectRayPlane(ray.pos, ray.dir, plane.normal, plane.w), - "ray-poly": (ray: Ray, poly: PCLike) => - intersectRayPolyline(ray.pos, ray.dir, poly.points, true), + "ray-poly": (ray: Ray, poly: PCLike) => + intersectRayPolyline(ray.pos, ray.dir, poly.points, true), - "ray-polyline": (ray: Ray, poly: PCLike) => - intersectRayPolyline(ray.pos, ray.dir, poly.points, false), + "ray-polyline": (ray: Ray, poly: PCLike) => + intersectRayPolyline(ray.pos, ray.dir, poly.points, false), - "ray-rect": (ray: Ray, rect: Rect) => - intersectRayRect(ray.pos, ray.dir, rect.pos, rect.max()), + "ray-rect": (ray: Ray, rect: Rect) => + intersectRayRect(ray.pos, ray.dir, rect.pos, rect.max()), - "rect-circle": (rect: Rect, circle: Circle) => ({ - type: testRectCircle(rect.pos, rect.size, circle.pos, circle.r) - ? IntersectionType.INTERSECT - : IntersectionType.NONE, - }), + "rect-circle": (rect: Rect, circle: Circle) => ({ + type: testRectCircle(rect.pos, rect.size, circle.pos, circle.r) + ? IntersectionType.INTERSECT + : IntersectionType.NONE, + }), - "rect-rect": (a: Rect, b: Rect) => ({ - type: testRectRect(a.pos, a.size, b.pos, b.size) - ? IntersectionType.INTERSECT - : IntersectionType.NONE, - }), - } - ); + "rect-rect": (a: Rect, b: Rect) => ({ + type: testRectRect(a.pos, a.size, b.pos, b.size) + ? IntersectionType.INTERSECT + : IntersectionType.NONE, + }), + } + ); diff --git a/packages/geom/src/line.ts b/packages/geom/src/line.ts index 214d8ce878..f7221ebd8b 100644 --- a/packages/geom/src/line.ts +++ b/packages/geom/src/line.ts @@ -8,15 +8,15 @@ import { __pclike } from "./internal/pclike.js"; export function line(a: Vec, b: Vec, attribs?: Attribs): Line; export function line(pts: Vec[], attribs?: Attribs): Line; export function line(...args: any[]) { - return __pclike(Line, args); + return __pclike(Line, args); } export const clippedLine = (l: Line, bounds: VecPair | Rect) => { - const res = - bounds instanceof Rect - ? liangBarsky2(l.points[0], l.points[1], bounds.pos, bounds.max()) - : liangBarsky2(l.points[0], l.points[1], bounds[0], bounds[1]); - if (res) { - return new Line([res[0], res[1]], { ...l.attribs }); - } + const res = + bounds instanceof Rect + ? liangBarsky2(l.points[0], l.points[1], bounds.pos, bounds.max()) + : liangBarsky2(l.points[0], l.points[1], bounds[0], bounds[1]); + if (res) { + return new Line([res[0], res[1]], { ...l.attribs }); + } }; diff --git a/packages/geom/src/map-point.ts b/packages/geom/src/map-point.ts index 9e08d880d9..a14a6a6527 100644 --- a/packages/geom/src/map-point.ts +++ b/packages/geom/src/map-point.ts @@ -23,15 +23,15 @@ import { __dispatch } from "./internal/dispatch.js"; * @param out */ export const mapPoint: MultiFn2O = defmulti< - any, - ReadonlyVec, - Vec | undefined, - Vec + any, + ReadonlyVec, + Vec | undefined, + Vec >( - __dispatch, - { aabb: "rect" }, - { - rect: ($: Rect, p: ReadonlyVec, out: Vec = []) => - div(null, sub(out, p, $.pos), $.size), - } + __dispatch, + { aabb: "rect" }, + { + rect: ($: Rect, p: ReadonlyVec, out: Vec = []) => + div(null, sub(out, p, $.pos), $.size), + } ); diff --git a/packages/geom/src/offset.ts b/packages/geom/src/offset.ts index 5f8425a11e..b85038a0cc 100644 --- a/packages/geom/src/offset.ts +++ b/packages/geom/src/offset.ts @@ -34,43 +34,43 @@ import { rectFromCentroidWithMargin } from "./rect.js"; * @param dist */ export const offset: MultiFn2 = defmulti< - any, - number, - IShape + any, + number, + IShape >( - __dispatch, - {}, - { - aabb: ($: AABB, n) => - aabbFromCentroidWithMargin( - centroid($)!, - $.size, - n, - __copyAttribs($) - ), + __dispatch, + {}, + { + aabb: ($: AABB, n) => + aabbFromCentroidWithMargin( + centroid($)!, + $.size, + n, + __copyAttribs($) + ), - circle: ($: Circle, n) => - new Circle(set2([], $.pos), Math.max($.r + n, 0)), + circle: ($: Circle, n) => + new Circle(set2([], $.pos), Math.max($.r + n, 0)), - line: ({ points: [a, b], attribs }: Line, n) => { - const norm = normalCW([], a, b, n); - return new Quad( - [ - add2([], a, norm), - add2([], b, norm), - sub2([], b, norm), - sub2([], a, norm), - ], - { ...attribs } - ); - }, + line: ({ points: [a, b], attribs }: Line, n) => { + const norm = normalCW([], a, b, n); + return new Quad( + [ + add2([], a, norm), + add2([], b, norm), + sub2([], b, norm), + sub2([], a, norm), + ], + { ...attribs } + ); + }, - rect: ($: Rect, n) => - rectFromCentroidWithMargin( - centroid($)!, - $.size, - n, - __copyAttribs($) - ), - } + rect: ($: Rect, n) => + rectFromCentroidWithMargin( + centroid($)!, + $.size, + n, + __copyAttribs($) + ), + } ); diff --git a/packages/geom/src/path-builder.ts b/packages/geom/src/path-builder.ts index 2b11bc0a2e..6e8d9d8910 100644 --- a/packages/geom/src/path-builder.ts +++ b/packages/geom/src/path-builder.ts @@ -15,194 +15,194 @@ import { Quadratic } from "./api/quadratic.js"; import { arcFrom2Points } from "./arc.js"; export interface PathBuilderOpts { - /** - * If true (default), "move" commands will start a new path and - * {@link PathBuilder} might produce multiple {@link Path}s. In general, - * it's NOT recommended to disable this behavior since various path related - * operations will not function properly anymore. However, there're some use - * cases where auto-splitting is undesirable and this option primarily - * exists for those. - */ - autoSplit: boolean; + /** + * If true (default), "move" commands will start a new path and + * {@link PathBuilder} might produce multiple {@link Path}s. In general, + * it's NOT recommended to disable this behavior since various path related + * operations will not function properly anymore. However, there're some use + * cases where auto-splitting is undesirable and this option primarily + * exists for those. + */ + autoSplit: boolean; } export class PathBuilder { - paths: Path[]; - protected curr!: Path; - protected currP!: Vec; - protected bezierP!: Vec; - protected startP!: Vec; - - constructor( - public attribs?: Attribs, - public opts: Partial = {} - ) { - this.paths = []; - this.attribs = attribs; - this.newPath(); - } - - *[Symbol.iterator]() { - yield* this.paths; - } - - current() { - return this.curr; - } - - newPath() { - this.curr = new Path([], this.attribs); - this.paths.push(this.curr); - this.currP = zeroes(2); - this.bezierP = zeroes(2); - this.startP = zeroes(2); - } - - moveTo(p: Vec, relative = false): PathBuilder { - if (this.opts.autoSplit !== false && this.curr.segments.length > 0) { - this.curr = new Path([], this.attribs); - this.paths.push(this.curr); - } - p = this.updateCurrent(p, relative); - set2(this.startP, p); - set2(this.bezierP, p); - this.curr.add({ - point: p, - type: "m", - }); - return this; - } - - lineTo(p: Vec, relative = false): PathBuilder { - this.curr.add({ - geo: new Line([copy(this.currP), this.updateCurrent(p, relative)]), - type: "l", - }); - set2(this.bezierP, this.currP); - return this; - } - - hlineTo(x: number, relative = false): PathBuilder { - this.addHVLine(x, 0, relative); - return this; - } - - vlineTo(y: number, relative = false): PathBuilder { - this.addHVLine(y, 1, relative); - return this; - } - - // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Cubic_B%C3%A9zier_Curve - cubicTo(cp1: Vec, cp2: Vec, p: Vec, relative = false) { - this.addCubic(this.absPoint(cp1, relative), cp2, p, relative); - return this; - } - - // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Quadratic_B%C3%A9zier_Curve - quadraticTo(cp: Vec, p: Vec, relative = false) { - this.addQuadratic(this.absPoint(cp, relative), p, relative); - return this; - } - - cubicChainTo(cp2: Vec, p: Vec, relative = false) { - const prevMode = peek(this.curr.segments).type; - const c1 = copy(this.currP); - prevMode === "c" && add2(null, sub2([], c1, this.bezierP), c1); - this.addCubic(c1, cp2, p, relative); - return this; - } - - quadraticChainTo(p: Vec, relative = false) { - const prevMode = peek(this.curr.segments).type; - const c1 = copy(this.currP); - prevMode === "q" && sub2(null, mulN2(null, c1, 2), this.bezierP); - this.addQuadratic(c1, p, relative); - return this; - } - - arcTo( - p: Vec, - r: Vec, - xaxis: number, - xl: boolean, - clockwise: boolean, - relative = false - ) { - if (eqDelta(r[0], 0) || eqDelta(r[1], 0)) { - return this.lineTo(p, relative); - } - const prev = copy(this.currP); - this.curr.add({ - geo: arcFrom2Points( - prev, - this.updateCurrent(p, relative), - r, - xaxis, - xl, - clockwise - ), - type: "a", - }); - set2(this.bezierP, this.currP); - return this; - } - - closePath() { - this.curr.add({ - geo: new Line([copy(this.currP), copy(this.startP)]), - type: "l", - }); - this.curr.closed = true; - return this; - } - - protected updateCurrent(p: Vec, relative: boolean) { - p = copy(relative ? add2(null, this.currP, p) : set2(this.currP, p)); - return p; - } - - protected absPoint(p: Vec, relative: boolean) { - return relative ? add2(null, p, this.currP) : p; - } - - protected addHVLine(p: number, i: number, relative: boolean) { - const prev = copy(this.currP); - this.currP[i] = relative ? this.currP[i] + p : p; - set2(this.bezierP, this.currP); - this.curr.add({ - geo: new Line([prev, copy(this.currP)]), - type: "l", - }); - } - - protected addCubic(cp1: Vec, cp2: Vec, p: Vec, relative: boolean) { - cp2 = this.absPoint(cp2, relative); - set2(this.bezierP, cp2); - this.curr.add({ - geo: new Cubic([ - copy(this.currP), - cp1, - cp2, - this.updateCurrent(p, relative), - ]), - type: "c", - }); - } - - protected addQuadratic(cp: Vec, p: Vec, relative: boolean) { - set2(this.bezierP, cp); - this.curr.add({ - geo: new Quadratic([ - copy(this.currP), - cp, - this.updateCurrent(p, relative), - ]), - type: "q", - }); - } + paths: Path[]; + protected curr!: Path; + protected currP!: Vec; + protected bezierP!: Vec; + protected startP!: Vec; + + constructor( + public attribs?: Attribs, + public opts: Partial = {} + ) { + this.paths = []; + this.attribs = attribs; + this.newPath(); + } + + *[Symbol.iterator]() { + yield* this.paths; + } + + current() { + return this.curr; + } + + newPath() { + this.curr = new Path([], this.attribs); + this.paths.push(this.curr); + this.currP = zeroes(2); + this.bezierP = zeroes(2); + this.startP = zeroes(2); + } + + moveTo(p: Vec, relative = false): PathBuilder { + if (this.opts.autoSplit !== false && this.curr.segments.length > 0) { + this.curr = new Path([], this.attribs); + this.paths.push(this.curr); + } + p = this.updateCurrent(p, relative); + set2(this.startP, p); + set2(this.bezierP, p); + this.curr.add({ + point: p, + type: "m", + }); + return this; + } + + lineTo(p: Vec, relative = false): PathBuilder { + this.curr.add({ + geo: new Line([copy(this.currP), this.updateCurrent(p, relative)]), + type: "l", + }); + set2(this.bezierP, this.currP); + return this; + } + + hlineTo(x: number, relative = false): PathBuilder { + this.addHVLine(x, 0, relative); + return this; + } + + vlineTo(y: number, relative = false): PathBuilder { + this.addHVLine(y, 1, relative); + return this; + } + + // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Cubic_B%C3%A9zier_Curve + cubicTo(cp1: Vec, cp2: Vec, p: Vec, relative = false) { + this.addCubic(this.absPoint(cp1, relative), cp2, p, relative); + return this; + } + + // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Quadratic_B%C3%A9zier_Curve + quadraticTo(cp: Vec, p: Vec, relative = false) { + this.addQuadratic(this.absPoint(cp, relative), p, relative); + return this; + } + + cubicChainTo(cp2: Vec, p: Vec, relative = false) { + const prevMode = peek(this.curr.segments).type; + const c1 = copy(this.currP); + prevMode === "c" && add2(null, sub2([], c1, this.bezierP), c1); + this.addCubic(c1, cp2, p, relative); + return this; + } + + quadraticChainTo(p: Vec, relative = false) { + const prevMode = peek(this.curr.segments).type; + const c1 = copy(this.currP); + prevMode === "q" && sub2(null, mulN2(null, c1, 2), this.bezierP); + this.addQuadratic(c1, p, relative); + return this; + } + + arcTo( + p: Vec, + r: Vec, + xaxis: number, + xl: boolean, + clockwise: boolean, + relative = false + ) { + if (eqDelta(r[0], 0) || eqDelta(r[1], 0)) { + return this.lineTo(p, relative); + } + const prev = copy(this.currP); + this.curr.add({ + geo: arcFrom2Points( + prev, + this.updateCurrent(p, relative), + r, + xaxis, + xl, + clockwise + ), + type: "a", + }); + set2(this.bezierP, this.currP); + return this; + } + + closePath() { + this.curr.add({ + geo: new Line([copy(this.currP), copy(this.startP)]), + type: "l", + }); + this.curr.closed = true; + return this; + } + + protected updateCurrent(p: Vec, relative: boolean) { + p = copy(relative ? add2(null, this.currP, p) : set2(this.currP, p)); + return p; + } + + protected absPoint(p: Vec, relative: boolean) { + return relative ? add2(null, p, this.currP) : p; + } + + protected addHVLine(p: number, i: number, relative: boolean) { + const prev = copy(this.currP); + this.currP[i] = relative ? this.currP[i] + p : p; + set2(this.bezierP, this.currP); + this.curr.add({ + geo: new Line([prev, copy(this.currP)]), + type: "l", + }); + } + + protected addCubic(cp1: Vec, cp2: Vec, p: Vec, relative: boolean) { + cp2 = this.absPoint(cp2, relative); + set2(this.bezierP, cp2); + this.curr.add({ + geo: new Cubic([ + copy(this.currP), + cp1, + cp2, + this.updateCurrent(p, relative), + ]), + type: "c", + }); + } + + protected addQuadratic(cp: Vec, p: Vec, relative: boolean) { + set2(this.bezierP, cp); + this.curr.add({ + geo: new Quadratic([ + copy(this.currP), + cp, + this.updateCurrent(p, relative), + ]), + type: "q", + }); + } } export const pathBuilder = ( - attribs?: Attribs, - opts?: Partial + attribs?: Attribs, + opts?: Partial ) => new PathBuilder(attribs, opts); diff --git a/packages/geom/src/path-from-svg.ts b/packages/geom/src/path-from-svg.ts index 5418fd2d26..624ca1edfc 100644 --- a/packages/geom/src/path-from-svg.ts +++ b/packages/geom/src/path-from-svg.ts @@ -9,147 +9,147 @@ const CMD_RE = /[achlmqstvz]/i; const WSC: IObjectOf = { ...WS, ",": true }; export const pathFromSvg = (svg: string) => { - const b = new PathBuilder(); - try { - let cmd = ""; - for (let n = svg.length, i = 0; i < n; ) { - i = skipWS(svg, i); - const c = svg.charAt(i); - if (CMD_RE.test(c)) { - cmd = c; - i++; - } - let p, pa, pb, t1, t2, t3; - switch (cmd.toLowerCase()) { - case "m": - [p, i] = readPoint(svg, i); - b.moveTo(p, cmd === "m"); - break; - case "l": - [p, i] = readPoint(svg, i); - b.lineTo(p, cmd === "l"); - break; - case "h": - [p, i] = readFloat(svg, i); - b.hlineTo(p, cmd === "h"); - break; - case "v": - [p, i] = readFloat(svg, i); - b.vlineTo(p, cmd === "v"); - break; - case "q": - [pa, i] = readPoint(svg, i); - [p, i] = readPoint(svg, i); - b.quadraticTo(pa, p, cmd === "q"); - break; - case "c": - [pa, i] = readPoint(svg, i); - [pb, i] = readPoint(svg, i); - [p, i] = readPoint(svg, i); - b.cubicTo(pa, pb, p, cmd === "c"); - break; - case "s": - [pa, i] = readPoint(svg, i); - [p, i] = readPoint(svg, i); - b.cubicChainTo(pa, p, cmd === "s"); - break; - case "t": - [p, i] = readPoint(svg, i); - b.quadraticChainTo(p, cmd === "t"); - break; - case "a": { - [pa, i] = readPoint(svg, i); - [t1, i] = readFloat(svg, i); - [t2, i] = readFlag(svg, i); - [t3, i] = readFlag(svg, i); - [pb, i] = readPoint(svg, i); - b.arcTo(pb, pa, rad(t1), t2, t3, cmd === "a"); - break; - } - case "z": - b.closePath(); - break; - default: - throw new Error( - `unsupported segment type: ${c} @ pos ${i}` - ); - } - } - return b.paths; - } catch (e) { - throw e instanceof Error - ? e - : new Error(`illegal char '${svg.charAt(e)}' @ ${e}`); - } + const b = new PathBuilder(); + try { + let cmd = ""; + for (let n = svg.length, i = 0; i < n; ) { + i = skipWS(svg, i); + const c = svg.charAt(i); + if (CMD_RE.test(c)) { + cmd = c; + i++; + } + let p, pa, pb, t1, t2, t3; + switch (cmd.toLowerCase()) { + case "m": + [p, i] = readPoint(svg, i); + b.moveTo(p, cmd === "m"); + break; + case "l": + [p, i] = readPoint(svg, i); + b.lineTo(p, cmd === "l"); + break; + case "h": + [p, i] = readFloat(svg, i); + b.hlineTo(p, cmd === "h"); + break; + case "v": + [p, i] = readFloat(svg, i); + b.vlineTo(p, cmd === "v"); + break; + case "q": + [pa, i] = readPoint(svg, i); + [p, i] = readPoint(svg, i); + b.quadraticTo(pa, p, cmd === "q"); + break; + case "c": + [pa, i] = readPoint(svg, i); + [pb, i] = readPoint(svg, i); + [p, i] = readPoint(svg, i); + b.cubicTo(pa, pb, p, cmd === "c"); + break; + case "s": + [pa, i] = readPoint(svg, i); + [p, i] = readPoint(svg, i); + b.cubicChainTo(pa, p, cmd === "s"); + break; + case "t": + [p, i] = readPoint(svg, i); + b.quadraticChainTo(p, cmd === "t"); + break; + case "a": { + [pa, i] = readPoint(svg, i); + [t1, i] = readFloat(svg, i); + [t2, i] = readFlag(svg, i); + [t3, i] = readFlag(svg, i); + [pb, i] = readPoint(svg, i); + b.arcTo(pb, pa, rad(t1), t2, t3, cmd === "a"); + break; + } + case "z": + b.closePath(); + break; + default: + throw new Error( + `unsupported segment type: ${c} @ pos ${i}` + ); + } + } + return b.paths; + } catch (e) { + throw e instanceof Error + ? e + : new Error(`illegal char '${svg.charAt(e)}' @ ${e}`); + } }; const skipWS = (src: string, i: number) => { - const n = src.length; - while (i < n && WSC[src.charAt(i)]) i++; - return i; + const n = src.length; + while (i < n && WSC[src.charAt(i)]) i++; + return i; }; const readPoint = (src: string, index: number): [Vec, number] => { - let x, y; - [x, index] = readFloat(src, index); - index = skipWS(src, index); - [y, index] = readFloat(src, index); - return [[x, y], index]; + let x, y; + [x, index] = readFloat(src, index); + index = skipWS(src, index); + [y, index] = readFloat(src, index); + return [[x, y], index]; }; const readFlag = (src: string, i: number): [boolean, number] => { - i = skipWS(src, i); - const c = src.charAt(i); - return [ - c === "0" - ? false - : c === "1" - ? true - : illegalState(`expected '0' or '1' @ pos: ${i}`), - i + 1, - ]; + i = skipWS(src, i); + const c = src.charAt(i); + return [ + c === "0" + ? false + : c === "1" + ? true + : illegalState(`expected '0' or '1' @ pos: ${i}`), + i + 1, + ]; }; const readFloat = (src: string, index: number) => { - index = skipWS(src, index); - let signOk = true; - let dotOk = true; - let expOk = false; - let commaOk = false; - let i = index; - for (let n = src.length; i < n; i++) { - const c = src.charAt(i); - if ("0" <= c && c <= "9") { - expOk = true; - commaOk = true; - signOk = false; - continue; - } - if (c === "-" || c === "+") { - if (!signOk) break; - signOk = false; - continue; - } - if (c === ".") { - if (!dotOk) break; - dotOk = false; - continue; - } - if (c === "e") { - if (!expOk) throw i; - expOk = false; - dotOk = false; - signOk = true; - continue; - } - if (c === ",") { - if (!commaOk) throw i; - i++; - } - break; - } - if (i === index) { - illegalState(`expected coordinate @ pos: ${i}`); - } - return [parseFloat(src.substring(index, i)), i]; + index = skipWS(src, index); + let signOk = true; + let dotOk = true; + let expOk = false; + let commaOk = false; + let i = index; + for (let n = src.length; i < n; i++) { + const c = src.charAt(i); + if ("0" <= c && c <= "9") { + expOk = true; + commaOk = true; + signOk = false; + continue; + } + if (c === "-" || c === "+") { + if (!signOk) break; + signOk = false; + continue; + } + if (c === ".") { + if (!dotOk) break; + dotOk = false; + continue; + } + if (c === "e") { + if (!expOk) throw i; + expOk = false; + dotOk = false; + signOk = true; + continue; + } + if (c === ",") { + if (!commaOk) throw i; + i++; + } + break; + } + if (i === index) { + illegalState(`expected coordinate @ pos: ${i}`); + } + return [parseFloat(src.substring(index, i)), i]; }; diff --git a/packages/geom/src/path.ts b/packages/geom/src/path.ts index a244de8f94..5c458fffdd 100644 --- a/packages/geom/src/path.ts +++ b/packages/geom/src/path.ts @@ -10,51 +10,51 @@ import { asCubic } from "./as-cubic.js"; import { PathBuilder } from "./path-builder.js"; export const path = (segments: PathSegment[], attribs?: Attribs) => - new Path(segments, attribs); + new Path(segments, attribs); export const pathFromCubics = (cubics: Cubic[], attribs?: Attribs) => { - const path = new Path([], attribs || cubics[0].attribs); - path.segments.push({ type: "m", point: cubics[0].points[0] }); - for (let c of cubics) { - path.segments.push({ type: "c", geo: c }); - } - return path; + const path = new Path([], attribs || cubics[0].attribs); + path.segments.push({ type: "m", point: cubics[0].points[0] }); + for (let c of cubics) { + path.segments.push({ type: "c", geo: c }); + } + return path; }; export const normalizedPath = (path: Path) => - new Path( - [ - ...mapcat( - (s) => - s.geo - ? map( - (c) => ({ type: "c", geo: c }), - asCubic(s.geo) - ) - : [{ ...s }], - path.segments - ), - ], - path.attribs - ); + new Path( + [ + ...mapcat( + (s) => + s.geo + ? map( + (c) => ({ type: "c", geo: c }), + asCubic(s.geo) + ) + : [{ ...s }], + path.segments + ), + ], + path.attribs + ); export const roundedRect = ( - pos: Vec, - size: Vec, - r: number | Vec, - attribs?: Attribs + pos: Vec, + size: Vec, + r: number | Vec, + attribs?: Attribs ) => { - r = isNumber(r) ? [r, r] : r; - const [w, h] = maddN2([], r, -2, size); - return new PathBuilder(attribs) - .moveTo([pos[0] + r[0], pos[1]]) - .hlineTo(w, true) - .arcTo(r, r, 0, false, true, true) - .vlineTo(h, true) - .arcTo([-r[0], r[1]], r, 0, false, true, true) - .hlineTo(-w, true) - .arcTo([-r[0], -r[1]], r, 0, false, true, true) - .vlineTo(-h, true) - .arcTo([r[0], -r[1]], r, 0, false, true, true) - .current(); + r = isNumber(r) ? [r, r] : r; + const [w, h] = maddN2([], r, -2, size); + return new PathBuilder(attribs) + .moveTo([pos[0] + r[0], pos[1]]) + .hlineTo(w, true) + .arcTo(r, r, 0, false, true, true) + .vlineTo(h, true) + .arcTo([-r[0], r[1]], r, 0, false, true, true) + .hlineTo(-w, true) + .arcTo([-r[0], -r[1]], r, 0, false, true, true) + .vlineTo(-h, true) + .arcTo([r[0], -r[1]], r, 0, false, true, true) + .current(); }; diff --git a/packages/geom/src/plane.ts b/packages/geom/src/plane.ts index 09fde9c47d..11532e7ee1 100644 --- a/packages/geom/src/plane.ts +++ b/packages/geom/src/plane.ts @@ -6,20 +6,20 @@ import { orthoNormal3 } from "@thi.ng/vectors/ortho-normal"; import { Plane } from "./api/plane.js"; export const plane = (normal: Vec, w: number, attribs?: Attribs) => - new Plane(normalize(null, normal), w, attribs); + new Plane(normalize(null, normal), w, attribs); export const planeWithPoint = ( - normal: Vec, - p: ReadonlyVec, - attribs?: Attribs + normal: Vec, + p: ReadonlyVec, + attribs?: Attribs ) => { - normal = normalize(null, normal); - return new Plane(normal, dot3(normal, p), attribs); + normal = normalize(null, normal); + return new Plane(normal, dot3(normal, p), attribs); }; export const planeFrom3Points = ( - a: ReadonlyVec, - b: ReadonlyVec, - c: ReadonlyVec, - attribs?: Attribs + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + attribs?: Attribs ) => planeWithPoint(orthoNormal3([], a, b, c), a, attribs); diff --git a/packages/geom/src/point-at.ts b/packages/geom/src/point-at.ts index 7d57a595b9..153eac3baa 100644 --- a/packages/geom/src/point-at.ts +++ b/packages/geom/src/point-at.ts @@ -50,38 +50,38 @@ import { vertices } from "./vertices.js"; * @param t */ export const pointAt: MultiFn2 = defmulti< - any, - number, - Vec | undefined + any, + number, + Vec | undefined >( - __dispatch, - { - quad: "poly", - tri: "poly", - }, - { - arc: ($: Arc, t: number) => $.pointAtTheta(fit01(t, $.start, $.end)), + __dispatch, + { + quad: "poly", + tri: "poly", + }, + { + arc: ($: Arc, t: number) => $.pointAtTheta(fit01(t, $.start, $.end)), - circle: ($: Circle, t) => cartesian2(null, [$.r, TAU * t], $.pos), + circle: ($: Circle, t) => cartesian2(null, [$.r, TAU * t], $.pos), - cubic: ({ points }: Cubic, t) => - mixCubic([], points[0], points[1], points[2], points[3], t), + cubic: ({ points }: Cubic, t) => + mixCubic([], points[0], points[1], points[2], points[3], t), - ellipse: ($: Ellipse, t) => madd2([], cossin(TAU * t), $.r, $.pos), + ellipse: ($: Ellipse, t) => madd2([], cossin(TAU * t), $.r, $.pos), - line: ({ points }: Line, t) => mixN2([], points[0], points[1], t), + line: ({ points }: Line, t) => mixN2([], points[0], points[1], t), - poly: ($: Polygon, t) => new Sampler($.points, true).pointAt(t), + poly: ($: Polygon, t) => new Sampler($.points, true).pointAt(t), - polyline: ($: Polygon, t) => new Sampler($.points).pointAt(t), + polyline: ($: Polygon, t) => new Sampler($.points).pointAt(t), - quadratic: ({ points }: Quadratic, t) => - mixQuadratic([], points[0], points[1], points[2], t), + quadratic: ({ points }: Quadratic, t) => + mixQuadratic([], points[0], points[1], points[2], t), - ray: ($: Ray, t) => pointOnRay2([], $.pos, $.dir, t), + ray: ($: Ray, t) => pointOnRay2([], $.pos, $.dir, t), - ray3: ($: Ray, t) => pointOnRay3([], $.pos, $.dir, t), + ray3: ($: Ray, t) => pointOnRay3([], $.pos, $.dir, t), - rect: ($: Rect, t) => new Sampler(vertices($), true).pointAt(t), - } + rect: ($: Rect, t) => new Sampler(vertices($), true).pointAt(t), + } ); diff --git a/packages/geom/src/point-inside.ts b/packages/geom/src/point-inside.ts index 9d84f60bb4..ac2776dfbd 100644 --- a/packages/geom/src/point-inside.ts +++ b/packages/geom/src/point-inside.ts @@ -2,12 +2,12 @@ import type { MultiFn2 } from "@thi.ng/defmulti"; import { defmulti } from "@thi.ng/defmulti/defmulti"; import type { IShape } from "@thi.ng/geom-api"; import { - pointInAABB, - pointInCircle, - pointInPolygon2, - pointInRect, - pointInSegment, - pointInTriangle2, + pointInAABB, + pointInCircle, + pointInPolygon2, + pointInRect, + pointInSegment, + pointInTriangle2, } from "@thi.ng/geom-isec/point"; import type { ReadonlyVec, Vec } from "@thi.ng/vectors"; import { isInArray } from "@thi.ng/vectors/eqdelta"; @@ -40,30 +40,30 @@ import { __dispatch } from "./internal/dispatch.js"; * @param p */ export const pointInside: MultiFn2 = defmulti< - any, - ReadonlyVec, - boolean + any, + ReadonlyVec, + boolean >( - __dispatch, - { - points3: "points", - quad: "poly", - sphere: "circle", - }, - { - aabb: ($: AABB, p: ReadonlyVec) => pointInAABB(p, $.pos, $.size), + __dispatch, + { + points3: "points", + quad: "poly", + sphere: "circle", + }, + { + aabb: ($: AABB, p: ReadonlyVec) => pointInAABB(p, $.pos, $.size), - circle: ($: Circle, p) => pointInCircle(p, $.pos, $.r), + circle: ($: Circle, p) => pointInCircle(p, $.pos, $.r), - line: ($: Line, p) => pointInSegment(p, $.points[0], $.points[1]), + line: ($: Line, p) => pointInSegment(p, $.points[0], $.points[1]), - points: ({ points }: Points, p) => isInArray(p, points), + points: ({ points }: Points, p) => isInArray(p, points), - poly: ($: Polygon, p) => pointInPolygon2(p, $.points) > 0, + poly: ($: Polygon, p) => pointInPolygon2(p, $.points) > 0, - rect: ($: Rect, p: ReadonlyVec) => pointInRect(p, $.pos, $.size), + rect: ($: Rect, p: ReadonlyVec) => pointInRect(p, $.pos, $.size), - tri: (tri: Triangle, p: ReadonlyVec) => - pointInTriangle2(p, ...(<[Vec, Vec, Vec]>tri.points)), - } + tri: (tri: Triangle, p: ReadonlyVec) => + pointInTriangle2(p, ...(<[Vec, Vec, Vec]>tri.points)), + } ); diff --git a/packages/geom/src/points.ts b/packages/geom/src/points.ts index a5a26553fd..43b6de5793 100644 --- a/packages/geom/src/points.ts +++ b/packages/geom/src/points.ts @@ -3,7 +3,7 @@ import type { Vec } from "@thi.ng/vectors"; import { Points, Points3 } from "./api/points.js"; export const points = (pts?: Vec[], attribs?: Attribs) => - new Points(pts, attribs); + new Points(pts, attribs); export const points3 = (pts?: Vec[], attribs?: Attribs) => - new Points3(pts, attribs); + new Points3(pts, attribs); diff --git a/packages/geom/src/polygon.ts b/packages/geom/src/polygon.ts index 4d22c421b8..3d33125446 100644 --- a/packages/geom/src/polygon.ts +++ b/packages/geom/src/polygon.ts @@ -11,19 +11,19 @@ import { cartesian2 } from "@thi.ng/vectors/cartesian"; import { Polygon } from "./api/polygon.js"; export const polygon = (pts?: Vec[], attribs?: Attribs) => - new Polygon(pts, attribs); + new Polygon(pts, attribs); export const star = ( - r: number, - n: number, - profile: number[], - attribs?: Attribs + r: number, + n: number, + profile: number[], + attribs?: Attribs ) => - new Polygon( - transduce( - map(([i, p]) => cartesian2(null, [r * p, i * TAU])), - push(), - zip(normRange(n * profile.length, false), cycle(profile)) - ), - attribs - ); + new Polygon( + transduce( + map(([i, p]) => cartesian2(null, [r * p, i * TAU])), + push(), + zip(normRange(n * profile.length, false), cycle(profile)) + ), + attribs + ); diff --git a/packages/geom/src/polyline.ts b/packages/geom/src/polyline.ts index 101a9f0c59..7cff4b582e 100644 --- a/packages/geom/src/polyline.ts +++ b/packages/geom/src/polyline.ts @@ -3,4 +3,4 @@ import type { Vec } from "@thi.ng/vectors"; import { Polyline } from "./api/polyline.js"; export const polyline = (pts: Vec[], attribs?: Attribs) => - new Polyline(pts, attribs); + new Polyline(pts, attribs); diff --git a/packages/geom/src/quad.ts b/packages/geom/src/quad.ts index 4935b45176..e6844ccec7 100644 --- a/packages/geom/src/quad.ts +++ b/packages/geom/src/quad.ts @@ -14,32 +14,32 @@ import { __pclike } from "./internal/pclike.js"; export function quad(a: Vec, b: Vec, c: Vec, d: Vec, attribs?: Attribs): Quad; export function quad(pts: Vec[], attribs?: Attribs): Quad; export function quad(...args: any[]) { - return __pclike(Quad, args); + return __pclike(Quad, args); } export function quad3(a: Vec, b: Vec, c: Vec, d: Vec, attribs?: Attribs): Quad; export function quad3(pts: Vec[], attribs?: Attribs): Quad; export function quad3(...args: any[]) { - const attr = __argAttribs(args); - return new Quad3(args.length === 1 ? args[0] : args, attr); + const attr = __argAttribs(args); + return new Quad3(args.length === 1 ? args[0] : args, attr); } export const quadOnPlane = ( - plane: Plane, - pos: ReadonlyVec, - size: number | ReadonlyVec, - attribs?: Attribs + plane: Plane, + pos: ReadonlyVec, + size: number | ReadonlyVec, + attribs?: Attribs ) => { - pos = closestPointPlane(pos, plane.normal, plane.w); - const [w, h] = isNumber(size) ? [size, size] : size; - const q = alignmentQuat(Z3, plane.normal); - return new Quad3( - [ - [-w, -h, 0], - [w, -h, 0], - [w, h, 0], - [-w, h, 0], - ].map((p) => add3(null, mulVQ(null, q, p), pos)), - attribs - ); + pos = closestPointPlane(pos, plane.normal, plane.w); + const [w, h] = isNumber(size) ? [size, size] : size; + const q = alignmentQuat(Z3, plane.normal); + return new Quad3( + [ + [-w, -h, 0], + [w, -h, 0], + [w, h, 0], + [-w, h, 0], + ].map((p) => add3(null, mulVQ(null, q, p), pos)), + attribs + ); }; diff --git a/packages/geom/src/quadratic.ts b/packages/geom/src/quadratic.ts index ca505d1114..97c90777da 100644 --- a/packages/geom/src/quadratic.ts +++ b/packages/geom/src/quadratic.ts @@ -7,8 +7,8 @@ import { __pclike } from "./internal/pclike.js"; export function quadratic(a: Vec, b: Vec, c: Vec, attribs?: Attribs): Quadratic; export function quadratic(pts: Vec[], attribs?: Attribs): Quadratic; export function quadratic(...args: any[]) { - return __pclike(Quadratic, args); + return __pclike(Quadratic, args); } export const quadraticFromLine = (a: Vec, b: Vec, attribs?: Attribs) => - new Quadratic(_line(a, b), attribs); + new Quadratic(_line(a, b), attribs); diff --git a/packages/geom/src/ray.ts b/packages/geom/src/ray.ts index 423b1e9ec7..62de29913e 100644 --- a/packages/geom/src/ray.ts +++ b/packages/geom/src/ray.ts @@ -4,4 +4,4 @@ import { normalize as _norm } from "@thi.ng/vectors/normalize"; import { Ray } from "./api/ray.js"; export const ray = (pos: Vec, dir: Vec, attribs?: Attribs, normalize = true) => - new Ray(pos, normalize ? _norm(null, dir) : dir, attribs); + new Ray(pos, normalize ? _norm(null, dir) : dir, attribs); diff --git a/packages/geom/src/rect.ts b/packages/geom/src/rect.ts index 092f2840dc..3eaf628c02 100644 --- a/packages/geom/src/rect.ts +++ b/packages/geom/src/rect.ts @@ -18,33 +18,33 @@ export function rect(pos: Vec, size: number | Vec, attribs?: Attribs): Rect; export function rect(size: number | Vec, attribs?: Attribs): Rect; export function rect(attribs?: Attribs): Rect; export function rect(...args: any[]) { - return new Rect(...__argsVV(args)); + return new Rect(...__argsVV(args)); } export const rectFromMinMax = (min: Vec, max: Vec, attribs?: Attribs) => - new Rect(min, sub2([], max, min), attribs); + new Rect(min, sub2([], max, min), attribs); export const rectFromMinMaxWithMargin = ( - min: Vec, - max: Vec, - margin: number, - attribs?: Attribs + min: Vec, + max: Vec, + margin: number, + attribs?: Attribs ) => rectFromMinMax(min, max, attribs).offset(margin); export const rectFromCentroid = ( - centroid: Vec, - size: number | Vec, - attribs?: Attribs + centroid: Vec, + size: number | Vec, + attribs?: Attribs ) => { - size = __asVec(size); - return new Rect(maddN2([], size, -0.5, centroid), size, attribs); + size = __asVec(size); + return new Rect(maddN2([], size, -0.5, centroid), size, attribs); }; export const rectFromCentroidWithMargin = ( - centroid: Vec, - size: number | Vec, - margin: number, - attribs?: Attribs + centroid: Vec, + size: number | Vec, + margin: number, + attribs?: Attribs ) => rectFromCentroid(centroid, size, attribs).offset(margin); /** @@ -55,10 +55,10 @@ export const rectFromCentroidWithMargin = ( * @param b - */ export const intersectionRect = (a: Rect, b: Rect) => { - const p = max2([], a.pos, b.pos); - const q = min2(null, add2([], a.pos, a.size), add2([], b.pos, b.size)); - const size = max2(null, sub2(null, q, p), ZERO2); - return size[0] > 0 && size[1] > 0 ? new Rect(p, size) : undefined; + const p = max2([], a.pos, b.pos); + const q = min2(null, add2([], a.pos, a.size), add2([], b.pos, b.size)); + const size = max2(null, sub2(null, q, p), ZERO2); + return size[0] > 0 && size[1] > 0 ? new Rect(p, size) : undefined; }; /** @@ -70,16 +70,16 @@ export const intersectionRect = (a: Rect, b: Rect) => { export function inscribedSquare(circle: Circle): Rect; export function inscribedSquare(pos: ReadonlyVec, r: number): Rect; export function inscribedSquare(...args: any[]) { - let pos: ReadonlyVec, r: number; - if (args.length === 1) { - const c: Circle = args[0]; - pos = c.pos; - r = c.r; - } else { - [pos, r] = args; - } - r *= SQRT2_2; - return rect(subN2([], pos, r), r * 2); + let pos: ReadonlyVec, r: number; + if (args.length === 1) { + const c: Circle = args[0]; + pos = c.pos; + r = c.r; + } else { + [pos, r] = args; + } + r *= SQRT2_2; + return rect(subN2([], pos, r), r * 2); } /** @@ -91,14 +91,14 @@ export function inscribedSquare(...args: any[]) { export function inscribedSquareHex(hex: Polygon): Rect; export function inscribedSquareHex(pos: ReadonlyVec, len: number): Rect; export function inscribedSquareHex(...args: any[]) { - let pos: ReadonlyVec, l: number; - if (args.length === 1) { - const pts = (args[0]).points; - pos = centroid(pts); - l = dist(pts[0], pts[1]); - } else { - [pos, l] = args; - } - l *= 3 - SQRT3; - return rect(subN2([], pos, l / 2), l); + let pos: ReadonlyVec, l: number; + if (args.length === 1) { + const pts = (args[0]).points; + pos = centroid(pts); + l = dist(pts[0], pts[1]); + } else { + [pos, l] = args; + } + l *= 3 - SQRT3; + return rect(subN2([], pos, l / 2), l); } diff --git a/packages/geom/src/resample.ts b/packages/geom/src/resample.ts index afd016a4e0..cf3f8a6b67 100644 --- a/packages/geom/src/resample.ts +++ b/packages/geom/src/resample.ts @@ -28,31 +28,31 @@ import { __dispatch } from "./internal/dispatch.js"; * @param opts */ export const resample: MultiFn2< - IShape, - number | Partial, - IShape + IShape, + number | Partial, + IShape > = defmulti, IShape>( - __dispatch, - { - ellipse: "circle", - line: "polyline", - quad: "poly", - tri: "poly", - rect: "circle", - }, - { - circle: ($: IShape, opts) => asPolygon($, opts), + __dispatch, + { + ellipse: "circle", + line: "polyline", + quad: "poly", + tri: "poly", + rect: "circle", + }, + { + circle: ($: IShape, opts) => asPolygon($, opts), - poly: ($: PCLike, opts) => - new Polygon( - _resample($.points, opts, true, true), - __copyAttribs($) - ), + poly: ($: PCLike, opts) => + new Polygon( + _resample($.points, opts, true, true), + __copyAttribs($) + ), - polyline: ($: PCLike, opts) => - new Polyline( - _resample($.points, opts, false, true), - __copyAttribs($) - ), - } + polyline: ($: PCLike, opts) => + new Polyline( + _resample($.points, opts, false, true), + __copyAttribs($) + ), + } ); diff --git a/packages/geom/src/rotate.ts b/packages/geom/src/rotate.ts index 11c0ddf0b6..92e147b2b6 100644 --- a/packages/geom/src/rotate.ts +++ b/packages/geom/src/rotate.ts @@ -51,71 +51,71 @@ import { __rotatedShape as tx } from "./internal/rotate.js"; * @param theta */ export const rotate: MultiFn2 = defmulti< - any, - number, - IShape + any, + number, + IShape >( - __dispatch, - {}, - { - arc: ($: Arc, theta) => { - const a = $.copy(); - $rotate(null, a.pos, theta); - return a; - }, + __dispatch, + {}, + { + arc: ($: Arc, theta) => { + const a = $.copy(); + $rotate(null, a.pos, theta); + return a; + }, - circle: ($: Circle, theta) => - new Circle($rotate([], $.pos, theta), $.r, __copyAttribs($)), + circle: ($: Circle, theta) => + new Circle($rotate([], $.pos, theta), $.r, __copyAttribs($)), - cubic: tx(Cubic), + cubic: tx(Cubic), - ellipse: ($: Ellipse, theta) => rotate(asPath($), theta), + ellipse: ($: Ellipse, theta) => rotate(asPath($), theta), - group: ($: Group, theta) => - $.copyTransformed((x) => rotate(x, theta)), + group: ($: Group, theta) => + $.copyTransformed((x) => rotate(x, theta)), - line: tx(Line), + line: tx(Line), - path: ($: Path, theta) => { - return new Path( - $.segments.map((s) => - s.geo - ? { - type: s.type, - geo: rotate(s.geo, theta), - } - : { - type: s.type, - point: $rotate([], s.point!, theta), - } - ), - __copyAttribs($) - ); - }, + path: ($: Path, theta) => { + return new Path( + $.segments.map((s) => + s.geo + ? { + type: s.type, + geo: rotate(s.geo, theta), + } + : { + type: s.type, + point: $rotate([], s.point!, theta), + } + ), + __copyAttribs($) + ); + }, - points: tx(Points), + points: tx(Points), - poly: tx(Polygon), + poly: tx(Polygon), - polyline: tx(Polyline), + polyline: tx(Polyline), - quad: tx(Quad), + quad: tx(Quad), - quadratic: tx(Quadratic), + quadratic: tx(Quadratic), - ray: ($: Ray, theta) => { - return new Ray( - $rotate([], $.pos, theta), - $rotate([], $.dir, theta), - __copyAttribs($) - ); - }, + ray: ($: Ray, theta) => { + return new Ray( + $rotate([], $.pos, theta), + $rotate([], $.dir, theta), + __copyAttribs($) + ); + }, - rect: ($: Rect, theta) => rotate(asPolygon($), theta), + rect: ($: Rect, theta) => rotate(asPolygon($), theta), - text: ($: Text, theta) => - new Text($rotate([], $.pos, theta), $.body, __copyAttribs($)), + text: ($: Text, theta) => + new Text($rotate([], $.pos, theta), $.body, __copyAttribs($)), - tri: tx(Triangle), - } + tri: tx(Triangle), + } ); diff --git a/packages/geom/src/scale.ts b/packages/geom/src/scale.ts index a89325af42..f7a70849b8 100644 --- a/packages/geom/src/scale.ts +++ b/packages/geom/src/scale.ts @@ -63,119 +63,119 @@ import { __scaledShape as tx } from "./internal/scale.js"; * @param factor */ export const scale: MultiFn2 = defmulti< - any, - number | ReadonlyVec, - IShape + any, + number | ReadonlyVec, + IShape >( - __dispatch, - {}, - { - aabb: ($: AABB, delta) => { - delta = __asVec(delta, 3); - return new AABB( - mul3([], $.pos, delta), - mul3([], $.size, delta), - __copyAttribs($) - ); - }, - - arc: ($: Arc, delta) => { - delta = __asVec(delta); - const a = $.copy(); - mul2(null, a.pos, delta); - mul2(null, a.r, delta); - return a; - }, - - circle: ($: Circle, delta) => - isNumber(delta) - ? new Circle( - mulN2([], $.pos, delta), - $.r * delta, - __copyAttribs($) - ) - : new Ellipse( - mul2([], $.pos, delta), - mulN2([], delta, $.r), - __copyAttribs($) - ), - - cubic: tx(Cubic), - - ellipse: ($: Ellipse, delta) => { - delta = __asVec(delta); - return new Ellipse( - mul2([], $.pos, delta), - mul2([], $.r, delta), - __copyAttribs($) - ); - }, - - group: ($: Group, delta) => - $.copyTransformed((x) => scale(x, delta)), - - line: tx(Line), - - path: ($: Path, delta) => { - delta = __asVec(delta); - return new Path( - $.segments.map((s) => - s.geo - ? { - type: s.type, - geo: scale(s.geo, delta), - } - : { - type: s.type, - point: mul2([], s.point!, delta), - } - ), - __copyAttribs($) - ); - }, - - points: tx(Points), - - points3: tx(Points3), - - poly: tx(Polygon), - - polyline: tx(Polyline), - - quad: tx(Quad), - - quadratic: tx(Quadratic), - - ray: ($: Ray, delta) => { - delta = __asVec(delta); - return new Ray( - mul2([], $.pos, delta), - normalize(null, mul2([], $.dir, delta)), - __copyAttribs($) - ); - }, - - rect: ($: Rect, delta) => { - delta = __asVec(delta); - return new Rect( - mul2([], $.pos, delta), - mul2([], $.size, delta), - __copyAttribs($) - ); - }, - - sphere: ($: Sphere, delta) => - isNumber(delta) - ? new Sphere( - mulN3([], $.pos, delta), - $.r * delta, - __copyAttribs($) - ) - : unsupported("can't non-uniformly scale sphere"), - - text: ($: Text, delta) => - new Text(mul2([], $.pos, __asVec(delta)), $.body, __copyAttribs($)), - - tri: tx(Triangle), - } + __dispatch, + {}, + { + aabb: ($: AABB, delta) => { + delta = __asVec(delta, 3); + return new AABB( + mul3([], $.pos, delta), + mul3([], $.size, delta), + __copyAttribs($) + ); + }, + + arc: ($: Arc, delta) => { + delta = __asVec(delta); + const a = $.copy(); + mul2(null, a.pos, delta); + mul2(null, a.r, delta); + return a; + }, + + circle: ($: Circle, delta) => + isNumber(delta) + ? new Circle( + mulN2([], $.pos, delta), + $.r * delta, + __copyAttribs($) + ) + : new Ellipse( + mul2([], $.pos, delta), + mulN2([], delta, $.r), + __copyAttribs($) + ), + + cubic: tx(Cubic), + + ellipse: ($: Ellipse, delta) => { + delta = __asVec(delta); + return new Ellipse( + mul2([], $.pos, delta), + mul2([], $.r, delta), + __copyAttribs($) + ); + }, + + group: ($: Group, delta) => + $.copyTransformed((x) => scale(x, delta)), + + line: tx(Line), + + path: ($: Path, delta) => { + delta = __asVec(delta); + return new Path( + $.segments.map((s) => + s.geo + ? { + type: s.type, + geo: scale(s.geo, delta), + } + : { + type: s.type, + point: mul2([], s.point!, delta), + } + ), + __copyAttribs($) + ); + }, + + points: tx(Points), + + points3: tx(Points3), + + poly: tx(Polygon), + + polyline: tx(Polyline), + + quad: tx(Quad), + + quadratic: tx(Quadratic), + + ray: ($: Ray, delta) => { + delta = __asVec(delta); + return new Ray( + mul2([], $.pos, delta), + normalize(null, mul2([], $.dir, delta)), + __copyAttribs($) + ); + }, + + rect: ($: Rect, delta) => { + delta = __asVec(delta); + return new Rect( + mul2([], $.pos, delta), + mul2([], $.size, delta), + __copyAttribs($) + ); + }, + + sphere: ($: Sphere, delta) => + isNumber(delta) + ? new Sphere( + mulN3([], $.pos, delta), + $.r * delta, + __copyAttribs($) + ) + : unsupported("can't non-uniformly scale sphere"), + + text: ($: Text, delta) => + new Text(mul2([], $.pos, __asVec(delta)), $.body, __copyAttribs($)), + + tri: tx(Triangle), + } ); diff --git a/packages/geom/src/scatter.ts b/packages/geom/src/scatter.ts index f5cd8cf09d..0c41182802 100644 --- a/packages/geom/src/scatter.ts +++ b/packages/geom/src/scatter.ts @@ -19,23 +19,23 @@ import { pointInside } from "./point-inside.js"; * @param out */ export const scatter = ( - shape: IShape, - num: number, - rnd = SYSTEM, - out: Vec[] = [] + shape: IShape, + num: number, + rnd = SYSTEM, + out: Vec[] = [] ) => { - const b = bounds(shape); - if (!b) return; - const mi = b.pos; - const mx = b.max(); - for (; num-- > 0; ) { - while (true) { - const p = randMinMax([], mi, mx, rnd); - if (pointInside(shape, p)) { - out.push(p); - break; - } - } - } - return out; + const b = bounds(shape); + if (!b) return; + const mi = b.pos; + const mx = b.max(); + for (; num-- > 0; ) { + while (true) { + const p = randMinMax([], mi, mx, rnd); + if (pointInside(shape, p)) { + out.push(p); + break; + } + } + } + return out; }; diff --git a/packages/geom/src/simplify.ts b/packages/geom/src/simplify.ts index e8a296e420..f4b98ce30b 100644 --- a/packages/geom/src/simplify.ts +++ b/packages/geom/src/simplify.ts @@ -30,51 +30,51 @@ import { vertices } from "./vertices.js"; * @param threshold */ export const simplify: MultiFn2 = defmulti< - any, - number, - IShape + any, + number, + IShape >( - __dispatch, - {}, - { - path: ($: Path, eps = 0) => { - const res: PathSegment[] = []; - const orig = $.segments; - const n = orig.length; - let points!: Vec[] | null; - let lastP!: Vec; - for (let i = 0; i < n; i++) { - const s = orig[i]; - if (s.type === "l" || s.type === "p") { - points = points - ? points.concat(vertices(s.geo!)) - : vertices(s.geo!); - lastP = peek(points); - } else if (points) { - points.push(lastP); - res.push({ - geo: new Polyline(_simplify(points, eps)), - type: "p", - }); - points = null; - } else { - res.push({ ...s }); - } - } - if (points) { - points.push(lastP); - res.push({ - geo: new Polyline(points), - type: "p", - }); - } - return new Path(res, __copyAttribs($)); - }, + __dispatch, + {}, + { + path: ($: Path, eps = 0) => { + const res: PathSegment[] = []; + const orig = $.segments; + const n = orig.length; + let points!: Vec[] | null; + let lastP!: Vec; + for (let i = 0; i < n; i++) { + const s = orig[i]; + if (s.type === "l" || s.type === "p") { + points = points + ? points.concat(vertices(s.geo!)) + : vertices(s.geo!); + lastP = peek(points); + } else if (points) { + points.push(lastP); + res.push({ + geo: new Polyline(_simplify(points, eps)), + type: "p", + }); + points = null; + } else { + res.push({ ...s }); + } + } + if (points) { + points.push(lastP); + res.push({ + geo: new Polyline(points), + type: "p", + }); + } + return new Path(res, __copyAttribs($)); + }, - poly: ($: Polygon, eps = 0) => - new Polygon(_simplify($.points, eps, true), __copyAttribs($)), + poly: ($: Polygon, eps = 0) => + new Polygon(_simplify($.points, eps, true), __copyAttribs($)), - polyline: ($: Polyline, eps = 0) => - new Polyline(_simplify($.points, eps), __copyAttribs($)), - } + polyline: ($: Polyline, eps = 0) => + new Polyline(_simplify($.points, eps), __copyAttribs($)), + } ); diff --git a/packages/geom/src/sphere.ts b/packages/geom/src/sphere.ts index 80669a1425..728a5dd65f 100644 --- a/packages/geom/src/sphere.ts +++ b/packages/geom/src/sphere.ts @@ -10,11 +10,11 @@ export function sphere(pos: Vec, attribs?: Attribs): Sphere; export function sphere(r: number, attribs?: Attribs): Sphere; export function sphere(attribs?: Attribs): Sphere; export function sphere(...args: any[]) { - return new Sphere(...__argsVN(args)); + return new Sphere(...__argsVN(args)); } export const sphereFrom2Points = ( - a: ReadonlyVec, - b: ReadonlyVec, - attribs?: Attribs + a: ReadonlyVec, + b: ReadonlyVec, + attribs?: Attribs ) => new Sphere(mixN3([], a, b, 0.5), dist(a, b) / 2, attribs); diff --git a/packages/geom/src/split-at.ts b/packages/geom/src/split-at.ts index 09e95d8226..b5e30c3c9a 100644 --- a/packages/geom/src/split-at.ts +++ b/packages/geom/src/split-at.ts @@ -32,59 +32,59 @@ import { __splitLine } from "./internal/split.js"; * @param t */ export const splitAt: MultiFn2 = defmulti< - any, - number, - IShape[] | undefined + any, + number, + IShape[] | undefined >( - __dispatch, - {}, - { - arc: ($: Arc, t: number) => { - const theta = fit01(t, $.start, $.end); - return [ - new Arc( - set([], $.pos), - set([], $.r), - $.axis, - $.start, - theta, - $.xl, - $.cw, - __copyAttribs($) - ), - new Arc( - set([], $.pos), - set([], $.r), - $.axis, - theta, - $.end, - $.xl, - $.cw, - __copyAttribs($) - ), - ]; - }, + __dispatch, + {}, + { + arc: ($: Arc, t: number) => { + const theta = fit01(t, $.start, $.end); + return [ + new Arc( + set([], $.pos), + set([], $.r), + $.axis, + $.start, + theta, + $.xl, + $.cw, + __copyAttribs($) + ), + new Arc( + set([], $.pos), + set([], $.r), + $.axis, + theta, + $.end, + $.xl, + $.cw, + __copyAttribs($) + ), + ]; + }, - cubic: ({ attribs, points }: Cubic, t: number) => - cubicSplitAt(points[0], points[1], points[2], points[3], t).map( - (pts) => new Cubic(pts, { ...attribs }) - ), + cubic: ({ attribs, points }: Cubic, t: number) => + cubicSplitAt(points[0], points[1], points[2], points[3], t).map( + (pts) => new Cubic(pts, { ...attribs }) + ), - line: ({ attribs, points }: Line, t) => - __splitLine(points[0], points[1], t).map( - (pts) => new Line(pts, { ...attribs }) - ), + line: ({ attribs, points }: Line, t) => + __splitLine(points[0], points[1], t).map( + (pts) => new Line(pts, { ...attribs }) + ), - polyline: ($: Polyline, t) => - __pointArraysAsShapes( - Polyline, - new Sampler($.points).splitAt(t), - $.attribs - ), + polyline: ($: Polyline, t) => + __pointArraysAsShapes( + Polyline, + new Sampler($.points).splitAt(t), + $.attribs + ), - quadratic: ({ attribs, points }: Quadratic, t: number) => - quadraticSplitAt(points[0], points[1], points[2], t).map( - (pts) => new Quadratic(pts, { ...attribs }) - ), - } + quadratic: ({ attribs, points }: Quadratic, t: number) => + quadraticSplitAt(points[0], points[1], points[2], t).map( + (pts) => new Quadratic(pts, { ...attribs }) + ), + } ); diff --git a/packages/geom/src/split-near.ts b/packages/geom/src/split-near.ts index 1a4f4eb50e..8be56dfbc8 100644 --- a/packages/geom/src/split-near.ts +++ b/packages/geom/src/split-near.ts @@ -32,39 +32,39 @@ import { __splitLine } from "./internal/split.js"; * @param p - split point */ export const splitNearPoint: MultiFn2< - IShape, - ReadonlyVec, - IShape[] | undefined + IShape, + ReadonlyVec, + IShape[] | undefined > = defmulti( - __dispatch, - {}, - { - cubic: ({ points, attribs }: Cubic, p) => - splitCubicNearPoint( - p, - points[0], - points[1], - points[2], - points[3] - ).map((pts) => new Cubic(pts, { ...attribs })), + __dispatch, + {}, + { + cubic: ({ points, attribs }: Cubic, p) => + splitCubicNearPoint( + p, + points[0], + points[1], + points[2], + points[3] + ).map((pts) => new Cubic(pts, { ...attribs })), - line: ($: Line, p) => { - const t = closestT(p, $.points[0], $.points[1]) || 0; - return __splitLine($.points[0], $.points[1], clamp01(t)).map( - (pts) => new Line(pts, __copyAttribs($)) - ); - }, + line: ($: Line, p) => { + const t = closestT(p, $.points[0], $.points[1]) || 0; + return __splitLine($.points[0], $.points[1], clamp01(t)).map( + (pts) => new Line(pts, __copyAttribs($)) + ); + }, - polyline: ($: Polyline, p) => - __pointArraysAsShapes( - Polyline, - new Sampler($.points).splitNear(p), - $.attribs - ), + polyline: ($: Polyline, p) => + __pointArraysAsShapes( + Polyline, + new Sampler($.points).splitNear(p), + $.attribs + ), - quadratic: ({ points, attribs }: Quadratic, p) => - quadraticSplitNearPoint(p, points[0], points[1], points[2]).map( - (pts) => new Quadratic(pts, { ...attribs }) - ), - } + quadratic: ({ points, attribs }: Quadratic, p) => + quadraticSplitNearPoint(p, points[0], points[1], points[2]).map( + (pts) => new Quadratic(pts, { ...attribs }) + ), + } ); diff --git a/packages/geom/src/subdiv-curve.ts b/packages/geom/src/subdiv-curve.ts index 35bef52462..f8e1c97b89 100644 --- a/packages/geom/src/subdiv-curve.ts +++ b/packages/geom/src/subdiv-curve.ts @@ -25,20 +25,20 @@ import { __dispatch } from "./internal/dispatch.js"; * @param iter */ export const subdivCurve: MultiFn2O = - defmulti( - __dispatch, - {}, - { - poly: ($: Polygon, kernel, iter = 1) => - new Polygon( - subdivide($.points, kernel, iter), - __copyAttribs($) - ), + defmulti( + __dispatch, + {}, + { + poly: ($: Polygon, kernel, iter = 1) => + new Polygon( + subdivide($.points, kernel, iter), + __copyAttribs($) + ), - polyline: ($: Polyline, kernel, iter = 1) => - new Polyline( - subdivide($.points, kernel, iter), - __copyAttribs($) - ), - } - ); + polyline: ($: Polyline, kernel, iter = 1) => + new Polyline( + subdivide($.points, kernel, iter), + __copyAttribs($) + ), + } + ); diff --git a/packages/geom/src/tangent-at.ts b/packages/geom/src/tangent-at.ts index 7295e6990d..48c2266e19 100644 --- a/packages/geom/src/tangent-at.ts +++ b/packages/geom/src/tangent-at.ts @@ -34,30 +34,30 @@ import { vertices } from "./vertices.js"; * @param t */ export const tangentAt: MultiFn2 = defmulti< - any, - number, - Vec | undefined + any, + number, + Vec | undefined >( - __dispatch, - { - quad: "poly", - tri: "poly", - }, - { - circle: (_, t) => cossin(TAU * t + HALF_PI), + __dispatch, + { + quad: "poly", + tri: "poly", + }, + { + circle: (_, t) => cossin(TAU * t + HALF_PI), - cubic: ({ points }: Cubic, t) => - cubicTangentAt([], points[0], points[1], points[2], points[3], t), + cubic: ({ points }: Cubic, t) => + cubicTangentAt([], points[0], points[1], points[2], points[3], t), - line: ({ points }: Line) => direction([], points[0], points[1]), + line: ({ points }: Line) => direction([], points[0], points[1]), - poly: ($: PCLike, t) => new Sampler($.points, true).tangentAt(t), + poly: ($: PCLike, t) => new Sampler($.points, true).tangentAt(t), - polyline: ($: PCLike, t) => new Sampler($.points).tangentAt(t), + polyline: ($: PCLike, t) => new Sampler($.points).tangentAt(t), - quadratic: ({ points }: Cubic, t) => - quadraticTangentAt([], points[0], points[1], points[2], t), + quadratic: ({ points }: Cubic, t) => + quadraticTangentAt([], points[0], points[1], points[2], t), - rect: ($: Rect, t) => new Sampler(vertices($), true).tangentAt(t), - } + rect: ($: Rect, t) => new Sampler(vertices($), true).tangentAt(t), + } ); diff --git a/packages/geom/src/tessellate.ts b/packages/geom/src/tessellate.ts index a658af4748..fec72f6bee 100644 --- a/packages/geom/src/tessellate.ts +++ b/packages/geom/src/tessellate.ts @@ -17,10 +17,10 @@ import { vertices } from "./vertices.js"; * @param tessellators */ export const tessellate = defmulti( - __dispatch, - {}, - { - [DEFAULT]: ($: IShape, fns: Tessellator[]) => - _tessellate(vertices($), fns), - } + __dispatch, + {}, + { + [DEFAULT]: ($: IShape, fns: Tessellator[]) => + _tessellate(vertices($), fns), + } ); diff --git a/packages/geom/src/text.ts b/packages/geom/src/text.ts index 47d9ab8f4b..b83ae6bb43 100644 --- a/packages/geom/src/text.ts +++ b/packages/geom/src/text.ts @@ -3,4 +3,4 @@ import type { Vec } from "@thi.ng/vectors"; import { Text } from "./api/text.js"; export const text = (pos: Vec, body: any, attribs?: Attribs) => - new Text(pos, body, attribs); + new Text(pos, body, attribs); diff --git a/packages/geom/src/transform-vertices.ts b/packages/geom/src/transform-vertices.ts index 903755d683..3fcd8b709d 100644 --- a/packages/geom/src/transform-vertices.ts +++ b/packages/geom/src/transform-vertices.ts @@ -22,8 +22,8 @@ import { asPolyline } from "./as-polyline.js"; import { __copyAttribs } from "./internal/copy.js"; import { __dispatch } from "./internal/dispatch.js"; import { - __transformedShapePoints as tx, - __transformedShapePoints3 as tx3, + __transformedShapePoints as tx, + __transformedShapePoints3 as tx3, } from "./internal/transform.js"; /** @@ -65,59 +65,59 @@ import { * @param fn */ export const transformVertices: MultiFn2< - IShape, - Fn, - IShape + IShape, + Fn, + IShape > = defmulti, IShape>( - __dispatch, - { - circle: "rect", - ellipse: "circle", - }, - { - arc: ($: IShape, fn) => transformVertices(asPolyline($), fn), + __dispatch, + { + circle: "rect", + ellipse: "circle", + }, + { + arc: ($: IShape, fn) => transformVertices(asPolyline($), fn), - cubic: tx(Cubic), + cubic: tx(Cubic), - group: ($: Group, fn) => - $.copyTransformed((x) => transformVertices(x, fn)), + group: ($: Group, fn) => + $.copyTransformed((x) => transformVertices(x, fn)), - line: tx(Line), + line: tx(Line), - path: ($: Path, fn) => - new Path( - [ - ...map( - (s) => - s.type === "m" - ? { - type: s.type, - point: mulV([], fn(s.point!), s.point!), - } - : { - type: s.type, - geo: transformVertices(s.geo!, fn), - }, - $.segments - ), - ], - __copyAttribs($) - ), + path: ($: Path, fn) => + new Path( + [ + ...map( + (s) => + s.type === "m" + ? { + type: s.type, + point: mulV([], fn(s.point!), s.point!), + } + : { + type: s.type, + geo: transformVertices(s.geo!, fn), + }, + $.segments + ), + ], + __copyAttribs($) + ), - points: tx(Points), + points: tx(Points), - points3: tx3(Points3), + points3: tx3(Points3), - poly: tx(Polygon), + poly: tx(Polygon), - polyline: tx(Polyline), + polyline: tx(Polyline), - quad: tx(Quad), + quad: tx(Quad), - quadratic: tx(Quadratic), + quadratic: tx(Quadratic), - rect: ($: Rect, fn) => transformVertices(asPolygon($), fn), + rect: ($: Rect, fn) => transformVertices(asPolygon($), fn), - tri: tx(Triangle), - } + tri: tx(Triangle), + } ); diff --git a/packages/geom/src/transform.ts b/packages/geom/src/transform.ts index 8f14b10c85..8f2ea49595 100644 --- a/packages/geom/src/transform.ts +++ b/packages/geom/src/transform.ts @@ -20,8 +20,8 @@ import { asPath } from "./as-path.js"; import { __copyAttribs } from "./internal/copy.js"; import { __dispatch } from "./internal/dispatch.js"; import { - __transformedShape as tx, - __transformedShape3 as tx3, + __transformedShape as tx, + __transformedShape3 as tx3, } from "./internal/transform.js"; import { vertices } from "./vertices.js"; @@ -63,63 +63,63 @@ import { vertices } from "./vertices.js"; * @param mat */ export const transform: MultiFn2 = defmulti< - any, - ReadonlyMat, - IShape + any, + ReadonlyMat, + IShape >( - __dispatch, - { - circle: "arc", - ellipse: "circle", - }, - { - arc: ($: IShape, mat) => transform(asPath($), mat), + __dispatch, + { + circle: "arc", + ellipse: "circle", + }, + { + arc: ($: IShape, mat) => transform(asPath($), mat), - cubic: tx(Cubic), + cubic: tx(Cubic), - group: ($: Group, mat) => - $.copyTransformed((x) => transform(x, mat)), + group: ($: Group, mat) => + $.copyTransformed((x) => transform(x, mat)), - line: tx(Line), + line: tx(Line), - path: ($: Path, mat) => - new Path( - [ - ...map( - (s) => - s.type === "m" - ? { - type: s.type, - point: mulV([], mat, s.point!), - } - : { - type: s.type, - geo: transform(s.geo!, mat), - }, - $.segments - ), - ], - __copyAttribs($) - ), + path: ($: Path, mat) => + new Path( + [ + ...map( + (s) => + s.type === "m" + ? { + type: s.type, + point: mulV([], mat, s.point!), + } + : { + type: s.type, + geo: transform(s.geo!, mat), + }, + $.segments + ), + ], + __copyAttribs($) + ), - points: tx(Points), + points: tx(Points), - points3: tx3(Points3), + points3: tx3(Points3), - poly: tx(Polygon), + poly: tx(Polygon), - polyline: tx(Polyline), + polyline: tx(Polyline), - quad: tx(Quad), + quad: tx(Quad), - quadratic: tx(Quadratic), + quadratic: tx(Quadratic), - rect: ($: Rect, mat) => - transform(new Quad(vertices($), __copyAttribs($)), mat), + rect: ($: Rect, mat) => + transform(new Quad(vertices($), __copyAttribs($)), mat), - text: ($: Text, mat) => - new Text(mulV([], mat, $.pos!), $.body, __copyAttribs($)), + text: ($: Text, mat) => + new Text(mulV([], mat, $.pos!), $.body, __copyAttribs($)), - tri: tx(Triangle), - } + tri: tx(Triangle), + } ); diff --git a/packages/geom/src/translate.ts b/packages/geom/src/translate.ts index 82656973d0..e98e067f3b 100644 --- a/packages/geom/src/translate.ts +++ b/packages/geom/src/translate.ts @@ -56,87 +56,87 @@ import { __translatedShape as tx } from "./internal/translate.js"; * @param offset */ export const translate: MultiFn2 = defmulti< - any, - ReadonlyVec, - IShape + any, + ReadonlyVec, + IShape >( - __dispatch, - {}, - { - aabb: ($: AABB, delta) => - new AABB( - add3([], $.pos, delta), - set3([], $.size), - __copyAttribs($) - ), - - arc: ($: Arc, delta) => { - const a = $.copy(); - add2(null, a.pos, delta); - return a; - }, - - circle: ($: Circle, delta) => - new Circle(add2([], $.pos, delta), $.r, __copyAttribs($)), - - cubic: tx(Cubic), - - ellipse: ($: Ellipse, delta) => - new Ellipse( - add2([], $.pos, delta), - set2([], $.r), - __copyAttribs($) - ), - - group: ($: Group, delta) => - $.copyTransformed((x) => translate(x, delta)), - - line: tx(Line), - - path: ($: Path, delta: ReadonlyVec) => - new Path( - $.segments.map((s) => - s.geo - ? { - type: s.type, - geo: translate(s.geo, delta), - } - : { - type: s.type, - point: add2([], s.point!, delta), - } - ), - __copyAttribs($) - ), - - points: tx(Points), - - points3: tx(Points3), - - poly: tx(Polygon), - - polyline: tx(Polyline), - - quad: tx(Quad), - - quadratic: tx(Quadratic), - - ray: ($: Ray, delta) => - new Ray(add2([], $.pos, delta), $.dir, __copyAttribs($)), - - rect: ($: Rect, delta) => - new Rect( - add2([], $.pos, delta), - set2([], $.size), - __copyAttribs($) - ), - - sphere: ($: Sphere, delta) => - new Sphere(add3([], $.pos, delta), $.r, __copyAttribs($)), - - text: ($: Text, delta) => - new Text(add2([], $.pos, delta), $.body, __copyAttribs($)), - - tri: tx(Triangle), - } + __dispatch, + {}, + { + aabb: ($: AABB, delta) => + new AABB( + add3([], $.pos, delta), + set3([], $.size), + __copyAttribs($) + ), + + arc: ($: Arc, delta) => { + const a = $.copy(); + add2(null, a.pos, delta); + return a; + }, + + circle: ($: Circle, delta) => + new Circle(add2([], $.pos, delta), $.r, __copyAttribs($)), + + cubic: tx(Cubic), + + ellipse: ($: Ellipse, delta) => + new Ellipse( + add2([], $.pos, delta), + set2([], $.r), + __copyAttribs($) + ), + + group: ($: Group, delta) => + $.copyTransformed((x) => translate(x, delta)), + + line: tx(Line), + + path: ($: Path, delta: ReadonlyVec) => + new Path( + $.segments.map((s) => + s.geo + ? { + type: s.type, + geo: translate(s.geo, delta), + } + : { + type: s.type, + point: add2([], s.point!, delta), + } + ), + __copyAttribs($) + ), + + points: tx(Points), + + points3: tx(Points3), + + poly: tx(Polygon), + + polyline: tx(Polyline), + + quad: tx(Quad), + + quadratic: tx(Quadratic), + + ray: ($: Ray, delta) => + new Ray(add2([], $.pos, delta), $.dir, __copyAttribs($)), + + rect: ($: Rect, delta) => + new Rect( + add2([], $.pos, delta), + set2([], $.size), + __copyAttribs($) + ), + + sphere: ($: Sphere, delta) => + new Sphere(add3([], $.pos, delta), $.r, __copyAttribs($)), + + text: ($: Text, delta) => + new Text(add2([], $.pos, delta), $.body, __copyAttribs($)), + + tri: tx(Triangle), + } ); diff --git a/packages/geom/src/triangle.ts b/packages/geom/src/triangle.ts index 441491084e..9a4598a594 100644 --- a/packages/geom/src/triangle.ts +++ b/packages/geom/src/triangle.ts @@ -7,8 +7,8 @@ import { __pclike } from "./internal/pclike.js"; export function triangle(a: Vec, b: Vec, c: Vec, attribs?: Attribs): Triangle; export function triangle(pts: Vec[], attribs?: Attribs): Triangle; export function triangle(...args: any[]) { - return __pclike(Triangle, args); + return __pclike(Triangle, args); } export const equilateralTriangle = (a: Vec, b: Vec, attribs?: Attribs) => - new Triangle(equilateralTriangle2(a, b), attribs); + new Triangle(equilateralTriangle2(a, b), attribs); diff --git a/packages/geom/src/union.ts b/packages/geom/src/union.ts index 70b6926f38..7c3b615eed 100644 --- a/packages/geom/src/union.ts +++ b/packages/geom/src/union.ts @@ -17,19 +17,19 @@ import { __dispatch } from "./internal/dispatch.js"; * @param b */ export const union: MultiFn2 = defmulti< - any, - any, - IShape[] + any, + any, + IShape[] >( - __dispatch, - {}, - { - aabb: (a: AABB, b: AABB) => [ - new AABB(...__unionBounds(a.pos, a.size, b.pos, b.size)), - ], + __dispatch, + {}, + { + aabb: (a: AABB, b: AABB) => [ + new AABB(...__unionBounds(a.pos, a.size, b.pos, b.size)), + ], - rect: (a: Rect, b: Rect) => [ - new Rect(...__unionBounds(a.pos, a.size, b.pos, b.size)), - ], - } + rect: (a: Rect, b: Rect) => [ + new Rect(...__unionBounds(a.pos, a.size, b.pos, b.size)), + ], + } ); diff --git a/packages/geom/src/unmap-point.ts b/packages/geom/src/unmap-point.ts index 7c2641f788..3c3fde4147 100644 --- a/packages/geom/src/unmap-point.ts +++ b/packages/geom/src/unmap-point.ts @@ -28,29 +28,29 @@ import { __dispatch } from "./internal/dispatch.js"; * @param out - result */ export const unmapPoint: MultiFn2O = defmulti< - any, - ReadonlyVec, - Vec | undefined, - Vec + any, + ReadonlyVec, + Vec | undefined, + Vec >( - __dispatch, - { - aabb: "rect", - quad3: "quad", - }, - { - quad: ({ points }: Quad, uv, out = []) => - mixBilinear( - out, - points[0], - points[1], - points[3], - points[2], - uv[0], - uv[1] - ), + __dispatch, + { + aabb: "rect", + quad3: "quad", + }, + { + quad: ({ points }: Quad, uv, out = []) => + mixBilinear( + out, + points[0], + points[1], + points[3], + points[2], + uv[0], + uv[1] + ), - rect: ($: Rect, uvw: ReadonlyVec, out = []) => - madd(out, $.size, uvw, $.pos), - } + rect: ($: Rect, uvw: ReadonlyVec, out = []) => + madd(out, $.size, uvw, $.pos), + } ); diff --git a/packages/geom/src/vertices.ts b/packages/geom/src/vertices.ts index f45f9bb2db..d9d616641a 100644 --- a/packages/geom/src/vertices.ts +++ b/packages/geom/src/vertices.ts @@ -56,114 +56,114 @@ import { __dispatch } from "./internal/dispatch.js"; * @param opts */ export const vertices: MultiFn1O< - IShape, - number | Partial, - Vec[] + IShape, + number | Partial, + Vec[] > = defmulti | undefined, Vec[]>( - __dispatch, - { - line: "polyline", - bpatch: "points", - points3: "points", - quad: "poly", - tri: "poly", - }, - { - // e +----+ h - // |\ :\ - // |f+----+ g - // | | : | - // a +-|--+d| - // \| \| - // b +----+ c - // - aabb: ({ pos, size }: AABB) => { - const [px, py, pz] = pos; - const [qx, qy, qz] = add3([], pos, size); - return [ - [px, py, pz], // a - [px, py, qz], // b - [qx, py, qz], // c - [qx, py, pz], // d - [px, qy, pz], // e - [px, qy, qz], // f - [qx, qy, qz], // g - [qx, qy, pz], // h - ]; - }, + __dispatch, + { + line: "polyline", + bpatch: "points", + points3: "points", + quad: "poly", + tri: "poly", + }, + { + // e +----+ h + // |\ :\ + // |f+----+ g + // | | : | + // a +-|--+d| + // \| \| + // b +----+ c + // + aabb: ({ pos, size }: AABB) => { + const [px, py, pz] = pos; + const [qx, qy, qz] = add3([], pos, size); + return [ + [px, py, pz], // a + [px, py, qz], // b + [qx, py, qz], // c + [qx, py, pz], // d + [px, qy, pz], // e + [px, qy, qz], // f + [qx, qy, qz], // g + [qx, qy, pz], // h + ]; + }, - arc: ($: Arc, opts?: number | Partial): Vec[] => - _arcVertices($.pos, $.r, $.axis, $.start, $.end, opts), + arc: ($: Arc, opts?: number | Partial): Vec[] => + _arcVertices($.pos, $.r, $.axis, $.start, $.end, opts), - circle: ($: Circle, opts = DEFAULT_SAMPLES) => { - const pos = $.pos; - const r = $.r; - let [num, last] = __circleOpts(opts, r); - const delta = TAU / num; - last && num++; - const buf: Vec[] = new Array(num); - for (let i = 0; i < num; i++) { - buf[i] = cartesian2(null, [r, i * delta], pos); - } - return buf; - }, + circle: ($: Circle, opts = DEFAULT_SAMPLES) => { + const pos = $.pos; + const r = $.r; + let [num, last] = __circleOpts(opts, r); + const delta = TAU / num; + last && num++; + const buf: Vec[] = new Array(num); + for (let i = 0; i < num; i++) { + buf[i] = cartesian2(null, [r, i * delta], pos); + } + return buf; + }, - cubic: ($: Cubic, opts?: number | Partial) => - sampleCubic($.points, opts), + cubic: ($: Cubic, opts?: number | Partial) => + sampleCubic($.points, opts), - ellipse: ($: Ellipse, opts = DEFAULT_SAMPLES) => { - const buf: Vec[] = []; - const pos = $.pos; - const r = $.r; - let [num, last] = __circleOpts(opts, Math.max($.r[0], $.r[1])); - const delta = TAU / num; - last && num++; - for (let i = 0; i < num; i++) { - buf[i] = madd2([], cossin(i * delta), r, pos); - } - return buf; - }, + ellipse: ($: Ellipse, opts = DEFAULT_SAMPLES) => { + const buf: Vec[] = []; + const pos = $.pos; + const r = $.r; + let [num, last] = __circleOpts(opts, Math.max($.r[0], $.r[1])); + const delta = TAU / num; + last && num++; + for (let i = 0; i < num; i++) { + buf[i] = madd2([], cossin(i * delta), r, pos); + } + return buf; + }, - group: ({ children }: Group) => - children.reduce((acc, $) => acc.concat(vertices($)), []), + group: ({ children }: Group) => + children.reduce((acc, $) => acc.concat(vertices($)), []), - path: ($: Path, opts?: number | Partial) => { - const _opts = isNumber(opts) ? { num: opts } : opts; - let verts: Vec[] = []; - for ( - let segs = $.segments, n = segs.length - 1, i = 0; - i <= n; - i++ - ) { - const s = segs[i]; - if (s.geo) { - verts = verts.concat( - vertices(s.geo, { - ..._opts, - last: i === n && !$.closed, - }) - ); - } - } - return verts; - }, + path: ($: Path, opts?: number | Partial) => { + const _opts = isNumber(opts) ? { num: opts } : opts; + let verts: Vec[] = []; + for ( + let segs = $.segments, n = segs.length - 1, i = 0; + i <= n; + i++ + ) { + const s = segs[i]; + if (s.geo) { + verts = verts.concat( + vertices(s.geo, { + ..._opts, + last: i === n && !$.closed, + }) + ); + } + } + return verts; + }, - points: ($: Points) => $.points, + points: ($: Points) => $.points, - poly: ($: Polygon, opts?) => resample($.points, opts, true), + poly: ($: Polygon, opts?) => resample($.points, opts, true), - polyline: ($: Polyline, opts?) => resample($.points, opts), + polyline: ($: Polyline, opts?) => resample($.points, opts), - quadratic: ($: Quadratic, opts?: number | Partial) => - sampleQuadratic($.points, opts), + quadratic: ($: Quadratic, opts?: number | Partial) => + sampleQuadratic($.points, opts), - rect: ($: Rect, opts) => { - const p = $.pos; - const q = add2([], p, $.size); - const verts = [set2([], p), [q[0], p[1]], q, [p[0], q[1]]]; - return opts != null ? vertices(new Polygon(verts), opts) : verts; - }, - } + rect: ($: Rect, opts) => { + const p = $.pos; + const q = add2([], p, $.size); + const verts = [set2([], p), [q[0], p[1]], q, [p[0], q[1]]]; + return opts != null ? vertices(new Polygon(verts), opts) : verts; + }, + } ); /** @@ -174,20 +174,20 @@ export const vertices: MultiFn1O< * @param shape - */ export const ensureVertices = (shape: IShape | Vec[]) => - isArray(shape) ? shape : vertices(shape); + isArray(shape) ? shape : vertices(shape); /** @internal */ const __circleOpts = ( - opts: number | Partial, - r: number + opts: number | Partial, + r: number ): [number, boolean] => - isNumber(opts) - ? [opts, false] - : [ - opts.theta - ? Math.floor(TAU / opts.theta) - : opts.dist - ? Math.floor(TAU / (opts.dist / r)) - : opts.num || DEFAULT_SAMPLES, - opts.last === true, - ]; + isNumber(opts) + ? [opts, false] + : [ + opts.theta + ? Math.floor(TAU / opts.theta) + : opts.dist + ? Math.floor(TAU / (opts.dist / r)) + : opts.num || DEFAULT_SAMPLES, + opts.last === true, + ]; diff --git a/packages/geom/src/volume.ts b/packages/geom/src/volume.ts index 258cff752f..a4359d3be8 100644 --- a/packages/geom/src/volume.ts +++ b/packages/geom/src/volume.ts @@ -18,13 +18,13 @@ import { __dispatch } from "./internal/dispatch.js"; * @param shape */ export const volume: MultiFn1 = defmulti( - __dispatch, - {}, - { - [DEFAULT]: () => 0, + __dispatch, + {}, + { + [DEFAULT]: () => 0, - aabb: ({ size }: AABB) => size[0] * size[1] * size[2], + aabb: ({ size }: AABB) => size[0] * size[1] * size[2], - sphere: ($: Sphere) => (4 / 3) * PI * $.r ** 3, - } + sphere: ($: Sphere) => (4 / 3) * PI * $.r ** 3, + } ); diff --git a/packages/geom/src/warp-points.ts b/packages/geom/src/warp-points.ts index 38e3c7aa3c..e0ef088b28 100644 --- a/packages/geom/src/warp-points.ts +++ b/packages/geom/src/warp-points.ts @@ -22,26 +22,26 @@ import { unmapPoint } from "./unmap-point.js"; * @param out */ export const warpPoints = ( - pts: ReadonlyVec[], - dest: IShape, - src: IShape, - out: Vec[] = [] + pts: ReadonlyVec[], + dest: IShape, + src: IShape, + out: Vec[] = [] ) => { - for (let n = pts.length, i = 0; i < n; i++) { - out.push(unmapPoint(dest, mapPoint(src, pts[i]))); - } - return out; + for (let n = pts.length, i = 0; i < n; i++) { + out.push(unmapPoint(dest, mapPoint(src, pts[i]))); + } + return out; }; export const warpPointsBPatch = ( - pts: ReadonlyVec[], - dest: BPatch, - src?: Rect, - out: Vec[] = [] + pts: ReadonlyVec[], + dest: BPatch, + src?: Rect, + out: Vec[] = [] ) => { - src = src || rectFromMinMax(...bounds2(pts)); - for (let i = pts.length; i-- > 0; ) { - out[i] = dest.unmapPoint(mapPoint(src, pts[i])); - } - return out; + src = src || rectFromMinMax(...bounds2(pts)); + for (let i = pts.length; i-- > 0; ) { + out[i] = dest.unmapPoint(mapPoint(src, pts[i])); + } + return out; }; diff --git a/packages/geom/src/with-attribs.ts b/packages/geom/src/with-attribs.ts index 963f78ff1f..65b89f6f85 100644 --- a/packages/geom/src/with-attribs.ts +++ b/packages/geom/src/with-attribs.ts @@ -10,7 +10,7 @@ import type { Attribs, IShape } from "@thi.ng/geom-api"; * @param replace */ export const withAttribs = ( - shape: T, - attribs: Attribs, - replace = true + shape: T, + attribs: Attribs, + replace = true ) => shape.withAttribs(replace ? attribs : { ...shape.attribs, ...attribs }); diff --git a/packages/geom/test/ctors.ts b/packages/geom/test/ctors.ts index 0295866f15..76679eeff1 100644 --- a/packages/geom/test/ctors.ts +++ b/packages/geom/test/ctors.ts @@ -3,93 +3,93 @@ import * as assert from "assert"; import { aabb, ellipse, polygon, rect } from "../src/index.js"; group("ctors", { - aabb: () => { - let e = aabb({ a: 1 }); - assert.deepStrictEqual(e.pos, [0, 0, 0]); - assert.deepStrictEqual(e.size, [1, 1, 1]); - assert.deepStrictEqual(e.attribs, { a: 1 }); - e = aabb(1); - assert.deepStrictEqual(e.pos, [0, 0, 0]); - assert.deepStrictEqual(e.size, [1, 1, 1]); - assert.deepStrictEqual(e.attribs, undefined); - e = aabb([1, 2, 3]); - assert.deepStrictEqual(e.pos, [0, 0, 0]); - assert.deepStrictEqual(e.size, [1, 2, 3]); - assert.deepStrictEqual(e.attribs, undefined); - e = aabb([1, 2, 3], { a: 2 }); - assert.deepStrictEqual(e.pos, [0, 0, 0]); - assert.deepStrictEqual(e.size, [1, 2, 3]); - assert.deepStrictEqual(e.attribs, { a: 2 }); - e = aabb([1, 2, 3], [4, 5, 6], { a: 3 }); - assert.deepStrictEqual(e.pos, [1, 2, 3]); - assert.deepStrictEqual(e.size, [4, 5, 6]); - assert.deepStrictEqual(e.attribs, { a: 3 }); - e = aabb([1, 2, 3], [4, 5, 6], undefined); - assert.deepStrictEqual(e.pos, [1, 2, 3]); - assert.deepStrictEqual(e.size, [4, 5, 6]); - assert.deepStrictEqual(e.attribs, undefined); - }, + aabb: () => { + let e = aabb({ a: 1 }); + assert.deepStrictEqual(e.pos, [0, 0, 0]); + assert.deepStrictEqual(e.size, [1, 1, 1]); + assert.deepStrictEqual(e.attribs, { a: 1 }); + e = aabb(1); + assert.deepStrictEqual(e.pos, [0, 0, 0]); + assert.deepStrictEqual(e.size, [1, 1, 1]); + assert.deepStrictEqual(e.attribs, undefined); + e = aabb([1, 2, 3]); + assert.deepStrictEqual(e.pos, [0, 0, 0]); + assert.deepStrictEqual(e.size, [1, 2, 3]); + assert.deepStrictEqual(e.attribs, undefined); + e = aabb([1, 2, 3], { a: 2 }); + assert.deepStrictEqual(e.pos, [0, 0, 0]); + assert.deepStrictEqual(e.size, [1, 2, 3]); + assert.deepStrictEqual(e.attribs, { a: 2 }); + e = aabb([1, 2, 3], [4, 5, 6], { a: 3 }); + assert.deepStrictEqual(e.pos, [1, 2, 3]); + assert.deepStrictEqual(e.size, [4, 5, 6]); + assert.deepStrictEqual(e.attribs, { a: 3 }); + e = aabb([1, 2, 3], [4, 5, 6], undefined); + assert.deepStrictEqual(e.pos, [1, 2, 3]); + assert.deepStrictEqual(e.size, [4, 5, 6]); + assert.deepStrictEqual(e.attribs, undefined); + }, - ellipse: () => { - let e = ellipse({ a: 1 }); - assert.deepStrictEqual(e.pos, [0, 0]); - assert.deepStrictEqual(e.r, [1, 1]); - assert.deepStrictEqual(e.attribs, { a: 1 }); - e = ellipse(1); - assert.deepStrictEqual(e.pos, [0, 0]); - assert.deepStrictEqual(e.r, [1, 1]); - assert.deepStrictEqual(e.attribs, undefined); - e = ellipse([1, 2]); - assert.deepStrictEqual(e.pos, [0, 0]); - assert.deepStrictEqual(e.r, [1, 2]); - assert.deepStrictEqual(e.attribs, undefined); - e = ellipse([1, 2], { a: 2 }); - assert.deepStrictEqual(e.pos, [0, 0]); - assert.deepStrictEqual(e.r, [1, 2]); - assert.deepStrictEqual(e.attribs, { a: 2 }); - e = ellipse([1, 2], [3, 4], { a: 3 }); - assert.deepStrictEqual(e.pos, [1, 2]); - assert.deepStrictEqual(e.r, [3, 4]); - assert.deepStrictEqual(e.attribs, { a: 3 }); - e = ellipse([1, 2], [3, 4], undefined); - assert.deepStrictEqual(e.pos, [1, 2]); - assert.deepStrictEqual(e.r, [3, 4]); - assert.deepStrictEqual(e.attribs, undefined); - }, + ellipse: () => { + let e = ellipse({ a: 1 }); + assert.deepStrictEqual(e.pos, [0, 0]); + assert.deepStrictEqual(e.r, [1, 1]); + assert.deepStrictEqual(e.attribs, { a: 1 }); + e = ellipse(1); + assert.deepStrictEqual(e.pos, [0, 0]); + assert.deepStrictEqual(e.r, [1, 1]); + assert.deepStrictEqual(e.attribs, undefined); + e = ellipse([1, 2]); + assert.deepStrictEqual(e.pos, [0, 0]); + assert.deepStrictEqual(e.r, [1, 2]); + assert.deepStrictEqual(e.attribs, undefined); + e = ellipse([1, 2], { a: 2 }); + assert.deepStrictEqual(e.pos, [0, 0]); + assert.deepStrictEqual(e.r, [1, 2]); + assert.deepStrictEqual(e.attribs, { a: 2 }); + e = ellipse([1, 2], [3, 4], { a: 3 }); + assert.deepStrictEqual(e.pos, [1, 2]); + assert.deepStrictEqual(e.r, [3, 4]); + assert.deepStrictEqual(e.attribs, { a: 3 }); + e = ellipse([1, 2], [3, 4], undefined); + assert.deepStrictEqual(e.pos, [1, 2]); + assert.deepStrictEqual(e.r, [3, 4]); + assert.deepStrictEqual(e.attribs, undefined); + }, - polygon: () => { - let p = polygon(); - assert.deepStrictEqual(p.points, []); - assert.deepStrictEqual(p.attribs, undefined); - p = polygon([[0, 0]], { a: 2 }); - assert.deepStrictEqual(p.points, [[0, 0]]); - assert.deepStrictEqual(p.attribs, { a: 2 }); - }, + polygon: () => { + let p = polygon(); + assert.deepStrictEqual(p.points, []); + assert.deepStrictEqual(p.attribs, undefined); + p = polygon([[0, 0]], { a: 2 }); + assert.deepStrictEqual(p.points, [[0, 0]]); + assert.deepStrictEqual(p.attribs, { a: 2 }); + }, - rect: () => { - let r = rect({ a: 1 }); - assert.deepStrictEqual(r.pos, [0, 0]); - assert.deepStrictEqual(r.size, [1, 1]); - assert.deepStrictEqual(r.attribs, { a: 1 }); - r = rect(1); - assert.deepStrictEqual(r.pos, [0, 0]); - assert.deepStrictEqual(r.size, [1, 1]); - assert.deepStrictEqual(r.attribs, undefined); - r = rect([1, 2]); - assert.deepStrictEqual(r.pos, [0, 0]); - assert.deepStrictEqual(r.size, [1, 2]); - assert.deepStrictEqual(r.attribs, undefined); - r = rect([1, 2], { a: 2 }); - assert.deepStrictEqual(r.pos, [0, 0]); - assert.deepStrictEqual(r.size, [1, 2]); - assert.deepStrictEqual(r.attribs, { a: 2 }); - r = rect([1, 2], [3, 4], { a: 3 }); - assert.deepStrictEqual(r.pos, [1, 2]); - assert.deepStrictEqual(r.size, [3, 4]); - assert.deepStrictEqual(r.attribs, { a: 3 }); - r = rect([1, 2], [3, 4], undefined); - assert.deepStrictEqual(r.pos, [1, 2]); - assert.deepStrictEqual(r.size, [3, 4]); - assert.deepStrictEqual(r.attribs, undefined); - }, + rect: () => { + let r = rect({ a: 1 }); + assert.deepStrictEqual(r.pos, [0, 0]); + assert.deepStrictEqual(r.size, [1, 1]); + assert.deepStrictEqual(r.attribs, { a: 1 }); + r = rect(1); + assert.deepStrictEqual(r.pos, [0, 0]); + assert.deepStrictEqual(r.size, [1, 1]); + assert.deepStrictEqual(r.attribs, undefined); + r = rect([1, 2]); + assert.deepStrictEqual(r.pos, [0, 0]); + assert.deepStrictEqual(r.size, [1, 2]); + assert.deepStrictEqual(r.attribs, undefined); + r = rect([1, 2], { a: 2 }); + assert.deepStrictEqual(r.pos, [0, 0]); + assert.deepStrictEqual(r.size, [1, 2]); + assert.deepStrictEqual(r.attribs, { a: 2 }); + r = rect([1, 2], [3, 4], { a: 3 }); + assert.deepStrictEqual(r.pos, [1, 2]); + assert.deepStrictEqual(r.size, [3, 4]); + assert.deepStrictEqual(r.attribs, { a: 3 }); + r = rect([1, 2], [3, 4], undefined); + assert.deepStrictEqual(r.pos, [1, 2]); + assert.deepStrictEqual(r.size, [3, 4]); + assert.deepStrictEqual(r.attribs, undefined); + }, }); diff --git a/packages/geom/tsconfig.json b/packages/geom/tsconfig.json index e1cc9c29a1..be6af4b55e 100644 --- a/packages/geom/tsconfig.json +++ b/packages/geom/tsconfig.json @@ -1,8 +1,8 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": ".", - "preserveConstEnums": false - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "preserveConstEnums": false + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/gp/api-extractor.json b/packages/gp/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/gp/api-extractor.json +++ b/packages/gp/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/gp/package.json b/packages/gp/package.json index 094cea073e..06d2c9d1d5 100644 --- a/packages/gp/package.json +++ b/packages/gp/package.json @@ -1,111 +1,111 @@ { - "name": "@thi.ng/gp", - "version": "0.4.12", - "description": "Genetic programming helpers & strategies (tree based & multi-expression programming)", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/gp#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/math": "^5.3.4", - "@thi.ng/random": "^3.3.3", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/zipper": "^2.1.11" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "agent", - "array", - "ast", - "codegen", - "crossover", - "datastructure", - "evolutionary", - "generative", - "genetic", - "mep", - "multi-expression", - "mutation", - "phenotype", - "self-organization", - "tree", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./ast": { - "default": "./ast.js" - }, - "./mep": { - "default": "./mep.js" - } - }, - "thi.ng": { - "blog": [ - { - "title": "Evolutionary failures (Part 1)", - "url": "https://medium.com/@thi.ng/evolutionary-failures-part-1-54522c69be37" - } - ], - "related": [ - "defmulti", - "pointfree", - "sexpr", - "shader-ast", - "zipper" - ], - "status": "alpha", - "year": 2019 - } + "name": "@thi.ng/gp", + "version": "0.4.12", + "description": "Genetic programming helpers & strategies (tree based & multi-expression programming)", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/gp#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/math": "^5.3.4", + "@thi.ng/random": "^3.3.3", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/zipper": "^2.1.11" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "agent", + "array", + "ast", + "codegen", + "crossover", + "datastructure", + "evolutionary", + "generative", + "genetic", + "mep", + "multi-expression", + "mutation", + "phenotype", + "self-organization", + "tree", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./ast": { + "default": "./ast.js" + }, + "./mep": { + "default": "./mep.js" + } + }, + "thi.ng": { + "blog": [ + { + "title": "Evolutionary failures (Part 1)", + "url": "https://medium.com/@thi.ng/evolutionary-failures-part-1-54522c69be37" + } + ], + "related": [ + "defmulti", + "pointfree", + "sexpr", + "shader-ast", + "zipper" + ], + "status": "alpha", + "year": 2019 + } } diff --git a/packages/gp/src/api.ts b/packages/gp/src/api.ts index 5331a64e95..c2e9ff42e9 100644 --- a/packages/gp/src/api.ts +++ b/packages/gp/src/api.ts @@ -13,62 +13,62 @@ export type MEPGene = TerminalGene | OpGene; export type MEPChromosome = MEPGene[]; export interface GPOpts { - /** - * Terminal node generator. If probabilistic, the given PRNG MUST be - * used for repeatable results. - */ - terminal: Fn; - /** - * Operator node generators. - */ - ops: OpGenSpec[]; - /** - * Possibly seeded PRNG instance to be used for AST generation / - * editing. - * - * @defaultValue {@link @thi.ng/random#SYSTEM} - */ - rnd?: IRandom; - /** - * Per-gene mutation probability. MUST be < 1 for {@link ASTOpts} to - * avoid infinite tree expansion. - */ - probMutate: number; + /** + * Terminal node generator. If probabilistic, the given PRNG MUST be + * used for repeatable results. + */ + terminal: Fn; + /** + * Operator node generators. + */ + ops: OpGenSpec[]; + /** + * Possibly seeded PRNG instance to be used for AST generation / + * editing. + * + * @defaultValue {@link @thi.ng/random#SYSTEM} + */ + rnd?: IRandom; + /** + * Per-gene mutation probability. MUST be < 1 for {@link ASTOpts} to + * avoid infinite tree expansion. + */ + probMutate: number; } export interface OpGenSpec { - /** - * Function producing/selecting an operator value. If probabilistic, - * the given PRNG MUST be used for repeatable results. The function - * is called with all argument genes/expressions to allow inform the - * operator selection. - */ - fn: Fn2; - /** - * Operator / function arity (number or args to generate). - */ - arity: number; - /** - * Probability for this generator to be used. - */ - prob: number; + /** + * Function producing/selecting an operator value. If probabilistic, + * the given PRNG MUST be used for repeatable results. The function + * is called with all argument genes/expressions to allow inform the + * operator selection. + */ + fn: Fn2; + /** + * Operator / function arity (number or args to generate). + */ + arity: number; + /** + * Probability for this generator to be used. + */ + prob: number; } export interface ASTOpts extends GPOpts> { - maxDepth: number; + maxDepth: number; } export interface MEPOpts extends GPOpts { - chromoSize: number; + chromoSize: number; } export interface TerminalGene { - type: "term"; - value: T; + type: "term"; + value: T; } export interface OpGene { - type: "op"; - op: OP; - args: A[]; + type: "op"; + op: OP; + args: A[]; } diff --git a/packages/gp/src/ast.ts b/packages/gp/src/ast.ts index c70ea96977..0bf79e312a 100644 --- a/packages/gp/src/ast.ts +++ b/packages/gp/src/ast.ts @@ -10,151 +10,151 @@ import type { ASTNode, ASTOpts, OpGene } from "./api.js"; import { opNode, probabilities, terminalNode } from "./utils.js"; export class AST { - opts: ASTOpts; - choices: IterableIterator; - probTerminal: number; + opts: ASTOpts; + choices: IterableIterator; + probTerminal: number; - constructor(opts: ASTOpts) { - this.opts = { rnd: SYSTEM, ...opts }; - assert(this.opts.probMutate < 1, "mutation probability must be < 1.0"); - const probs = probabilities(this.opts); - this.probTerminal = probs.probTerminal; - this.choices = probs.iter; - } + constructor(opts: ASTOpts) { + this.opts = { rnd: SYSTEM, ...opts }; + assert(this.opts.probMutate < 1, "mutation probability must be < 1.0"); + const probs = probabilities(this.opts); + this.probTerminal = probs.probTerminal; + this.choices = probs.iter; + } - /** - * Returns a random AST with given max tree depth. The max depth - * provided in {@link ASTOpts} is used by default. - * - * @param maxDepth - - */ - randomAST(maxDepth = this.opts.maxDepth) { - return this.randomASTNode(0, maxDepth); - } + /** + * Returns a random AST with given max tree depth. The max depth + * provided in {@link ASTOpts} is used by default. + * + * @param maxDepth - + */ + randomAST(maxDepth = this.opts.maxDepth) { + return this.randomASTNode(0, maxDepth); + } - /** - * Immutably transplants a randomly chosen subtree of `parent2` into - * a randomly chosen location in `parent1` and vice versa. Returns 2 - * new trees. - * - * @param parent1 - - * @param parent2 - - */ - crossoverSingle(parent1: ASTNode, parent2: ASTNode) { - return [ - this.selectRandomNode(parent1).replace( - this.selectRandomNode(parent2).node - ).root, - this.selectRandomNode(parent2).replace( - this.selectRandomNode(parent1).node - ).root, - ]; - } + /** + * Immutably transplants a randomly chosen subtree of `parent2` into + * a randomly chosen location in `parent1` and vice versa. Returns 2 + * new trees. + * + * @param parent1 - + * @param parent2 - + */ + crossoverSingle(parent1: ASTNode, parent2: ASTNode) { + return [ + this.selectRandomNode(parent1).replace( + this.selectRandomNode(parent2).node + ).root, + this.selectRandomNode(parent2).replace( + this.selectRandomNode(parent1).node + ).root, + ]; + } - /** - * Probilistically replaces randomly chosen tree nodes with a new random AST - * of given `maxDepth` (default: 1). Never mutates root. - * - * @remarks - * The AST's pre-configured max tree depth will always be respected, so - * depending on the depth of the selected mutation location, the randomly - * inserted/replaced subtree might be more shallow than given `maxDepth`. - * I.e. if a tree node at max depth is selected for mutation, it will always - * result in a randomly chosen terminal node only. - * - * @param tree - - * @param maxDepth - - */ - mutate(tree: ASTNode, maxDepth = 1) { - const { rnd, probMutate, maxDepth: limit } = this.opts; - let loc = this.asZipper(tree).next!; - if (!loc) return tree; - while (true) { - let nextLoc: Location> | undefined; - if (rnd!.float() < probMutate) { - loc = loc.replace( - this.randomASTNode(0, Math.min(limit - loc.depth, maxDepth)) - ); - nextLoc = loc.right; - if (!nextLoc) { - nextLoc = loc.up; - if (nextLoc) { - nextLoc = nextLoc.right; - } - } - } else { - nextLoc = loc.next; - } - if (!nextLoc) return loc.root; - loc = nextLoc; - } - } + /** + * Probilistically replaces randomly chosen tree nodes with a new random AST + * of given `maxDepth` (default: 1). Never mutates root. + * + * @remarks + * The AST's pre-configured max tree depth will always be respected, so + * depending on the depth of the selected mutation location, the randomly + * inserted/replaced subtree might be more shallow than given `maxDepth`. + * I.e. if a tree node at max depth is selected for mutation, it will always + * result in a randomly chosen terminal node only. + * + * @param tree - + * @param maxDepth - + */ + mutate(tree: ASTNode, maxDepth = 1) { + const { rnd, probMutate, maxDepth: limit } = this.opts; + let loc = this.asZipper(tree).next!; + if (!loc) return tree; + while (true) { + let nextLoc: Location> | undefined; + if (rnd!.float() < probMutate) { + loc = loc.replace( + this.randomASTNode(0, Math.min(limit - loc.depth, maxDepth)) + ); + nextLoc = loc.right; + if (!nextLoc) { + nextLoc = loc.up; + if (nextLoc) { + nextLoc = nextLoc.right; + } + } + } else { + nextLoc = loc.next; + } + if (!nextLoc) return loc.root; + loc = nextLoc; + } + } - /** - * Returns linearized/flat version of given AST as an array of - * {@link @thi.ng/zipper#Location | zipper locations}. - * - * @param tree - - */ - linearizedAST(tree: ASTNode): Location>[] { - return [ - ...iterator( - takeWhile((x) => !!x), - iterate((x) => x.next, this.asZipper(tree)) - ), - ]; - } + /** + * Returns linearized/flat version of given AST as an array of + * {@link @thi.ng/zipper#Location | zipper locations}. + * + * @param tree - + */ + linearizedAST(tree: ASTNode): Location>[] { + return [ + ...iterator( + takeWhile((x) => !!x), + iterate((x) => x.next, this.asZipper(tree)) + ), + ]; + } - /** - * Returns random {@link ASTNode} from given tree, using the user - * provided PRNG (via `opts`) or {@link @thi.ng/random#SYSTEM}. The - * returned value is a - * {@link @thi.ng/zipper#Location | zipper location} of the selected - * node. - * - * @remarks - * The actual `ASTNode` can be obtained via `.node`. - * - * Only nodes in the linearized index range `[min..max)` are - * returned (default: entire tree range). Since the linear tree - * length isn't known beforehand, `max` < 0 (default) is equivalent - * to the linearized tree end. - * - * @param opts - - * @param tree - - * @param min - - * @param max - - */ - selectRandomNode(tree: ASTNode, min = 0, max = -1) { - const rnd = this.opts.rnd; - const linTree = this.linearizedAST(tree); - let node: Location>; - max < 0 && (max = linTree.length); - node = linTree[rnd!.minmax(min, max) | 0]; - return node; - } + /** + * Returns random {@link ASTNode} from given tree, using the user + * provided PRNG (via `opts`) or {@link @thi.ng/random#SYSTEM}. The + * returned value is a + * {@link @thi.ng/zipper#Location | zipper location} of the selected + * node. + * + * @remarks + * The actual `ASTNode` can be obtained via `.node`. + * + * Only nodes in the linearized index range `[min..max)` are + * returned (default: entire tree range). Since the linear tree + * length isn't known beforehand, `max` < 0 (default) is equivalent + * to the linearized tree end. + * + * @param opts - + * @param tree - + * @param min - + * @param max - + */ + selectRandomNode(tree: ASTNode, min = 0, max = -1) { + const rnd = this.opts.rnd; + const linTree = this.linearizedAST(tree); + let node: Location>; + max < 0 && (max = linTree.length); + node = linTree[rnd!.minmax(min, max) | 0]; + return node; + } - protected randomASTNode(d: number, maxDepth: number): ASTNode { - const rnd = this.opts.rnd!; - const geneID = this.choices.next().value; - if (geneID === 0 || d >= maxDepth) - return terminalNode(this.opts.terminal(rnd)); - const op = this.opts.ops[geneID - 1]; - const children = [ - ...repeatedly(() => this.randomASTNode(d + 1, maxDepth), op.arity), - ]; - return opNode(op.fn(rnd, children), children); - } + protected randomASTNode(d: number, maxDepth: number): ASTNode { + const rnd = this.opts.rnd!; + const geneID = this.choices.next().value; + if (geneID === 0 || d >= maxDepth) + return terminalNode(this.opts.terminal(rnd)); + const op = this.opts.ops[geneID - 1]; + const children = [ + ...repeatedly(() => this.randomASTNode(d + 1, maxDepth), op.arity), + ]; + return opNode(op.fn(rnd, children), children); + } - protected asZipper(tree: ASTNode) { - return zipper>( - { - branch: (x) => x.type === "op", - children: (x) => (>>x).args, - factory: (n, args) => - opNode((>>n).op, args), - }, - tree - ); - } + protected asZipper(tree: ASTNode) { + return zipper>( + { + branch: (x) => x.type === "op", + children: (x) => (>>x).args, + factory: (n, args) => + opNode((>>n).op, args), + }, + tree + ); + } } diff --git a/packages/gp/src/mep.ts b/packages/gp/src/mep.ts index f171bc4039..146332eb44 100644 --- a/packages/gp/src/mep.ts +++ b/packages/gp/src/mep.ts @@ -5,121 +5,121 @@ import type { ASTNode, MEPChromosome, MEPGene, MEPOpts } from "./api.js"; import { opNode, probabilities, terminalNode } from "./utils.js"; export class MEP { - opts: MEPOpts; - probTerminal: number; - choices: IterableIterator; + opts: MEPOpts; + probTerminal: number; + choices: IterableIterator; - constructor(opts: MEPOpts) { - this.opts = { rnd: SYSTEM, ...opts }; - const probs = probabilities(this.opts); - this.probTerminal = probs.probTerminal; - this.choices = probs.iter; - } + constructor(opts: MEPOpts) { + this.opts = { rnd: SYSTEM, ...opts }; + const probs = probabilities(this.opts); + this.probTerminal = probs.probTerminal; + this.choices = probs.iter; + } - /** - * Returns a new random {@link MEPChromosome} with the configured - * probabilities of operator and terminal nodes (constants). - * - * @remarks - * See {@link MEP.decodeChromosome} for conversion to - * {@link ASTNode}s. - * - */ - randomChromosome(): MEPChromosome { - const res: MEPChromosome = []; - for (let i = 0, n = this.opts.chromoSize; i < n; i++) { - res[i] = this.randomGene(i); - } - return res; - } + /** + * Returns a new random {@link MEPChromosome} with the configured + * probabilities of operator and terminal nodes (constants). + * + * @remarks + * See {@link MEP.decodeChromosome} for conversion to + * {@link ASTNode}s. + * + */ + randomChromosome(): MEPChromosome { + const res: MEPChromosome = []; + for (let i = 0, n = this.opts.chromoSize; i < n; i++) { + res[i] = this.randomGene(i); + } + return res; + } - /** - * Decodes given chromosome into an array of {@link ASTNode}s and - * optionally applies tree depth filter (by default includes all). - * - * @remarks - * A {@link MEPChromosome} encodes multiple solutions (one per gene - * slot), therefore a chromosome of length `n` will produce the same - * number ASTs (less if min/max tree depth filters are applied). - * - * @param chromosome - - * @param minDepth - - * @param maxDepth - - */ - decodeChromosome( - chromosome: MEPChromosome, - minDepth = 0, - maxDepth = Infinity - ) { - const res: ASTNode[] = []; - const depths: number[] = []; - for (let i = 0; i < chromosome.length; i++) { - const gene = chromosome[i]; - if (gene.type == "term") { - res[i] = gene; - depths[i] = 1; - } else { - res[i] = opNode( - gene.op, - gene.args.map((g) => res[g]) - ); - depths[i] = - 1 + gene.args.reduce((d, a) => Math.max(d, depths[a]), 0); - } - } - return res.filter((_, i) => inRange(depths[i], minDepth, maxDepth)); - } + /** + * Decodes given chromosome into an array of {@link ASTNode}s and + * optionally applies tree depth filter (by default includes all). + * + * @remarks + * A {@link MEPChromosome} encodes multiple solutions (one per gene + * slot), therefore a chromosome of length `n` will produce the same + * number ASTs (less if min/max tree depth filters are applied). + * + * @param chromosome - + * @param minDepth - + * @param maxDepth - + */ + decodeChromosome( + chromosome: MEPChromosome, + minDepth = 0, + maxDepth = Infinity + ) { + const res: ASTNode[] = []; + const depths: number[] = []; + for (let i = 0; i < chromosome.length; i++) { + const gene = chromosome[i]; + if (gene.type == "term") { + res[i] = gene; + depths[i] = 1; + } else { + res[i] = opNode( + gene.op, + gene.args.map((g) => res[g]) + ); + depths[i] = + 1 + gene.args.reduce((d, a) => Math.max(d, depths[a]), 0); + } + } + return res.filter((_, i) => inRange(depths[i], minDepth, maxDepth)); + } - crossoverSingle( - chromo1: MEPChromosome, - chromo2: MEPChromosome, - cut?: number - ): MEPChromosome[] { - cut = - cut !== undefined - ? cut - : this.opts.rnd!.int() % - Math.min(chromo1.length, chromo2.length); - return [ - chromo1.slice(0, cut).concat(chromo2.slice(cut)), - chromo2.slice(0, cut).concat(chromo1.slice(cut)), - ]; - } + crossoverSingle( + chromo1: MEPChromosome, + chromo2: MEPChromosome, + cut?: number + ): MEPChromosome[] { + cut = + cut !== undefined + ? cut + : this.opts.rnd!.int() % + Math.min(chromo1.length, chromo2.length); + return [ + chromo1.slice(0, cut).concat(chromo2.slice(cut)), + chromo2.slice(0, cut).concat(chromo1.slice(cut)), + ]; + } - crossoverUniform( - chromo1: MEPChromosome, - chromo2: MEPChromosome - ) { - const rnd = this.opts.rnd!; - const res: MEPChromosome = []; - const minLen = Math.min(chromo1.length, chromo2.length); - for (let i = 0; i < minLen; i++) { - res[i] = rnd.float() < 0.5 ? chromo1[i] : chromo2[i]; - } - return chromo1.length > minLen - ? res.concat(chromo1.slice(minLen)) - : chromo2.length > minLen - ? res.concat(chromo2.slice(minLen)) - : res; - } + crossoverUniform( + chromo1: MEPChromosome, + chromo2: MEPChromosome + ) { + const rnd = this.opts.rnd!; + const res: MEPChromosome = []; + const minLen = Math.min(chromo1.length, chromo2.length); + for (let i = 0; i < minLen; i++) { + res[i] = rnd.float() < 0.5 ? chromo1[i] : chromo2[i]; + } + return chromo1.length > minLen + ? res.concat(chromo1.slice(minLen)) + : chromo2.length > minLen + ? res.concat(chromo2.slice(minLen)) + : res; + } - mutate(chromo: MEPChromosome) { - const { rnd, probMutate } = this.opts; - const res: MEPChromosome = new Array(chromo.length); - for (let i = chromo.length; i-- > 0; ) { - res[i] = rnd!.float() < probMutate ? this.randomGene(i) : chromo[i]; - } - return res; - } + mutate(chromo: MEPChromosome) { + const { rnd, probMutate } = this.opts; + const res: MEPChromosome = new Array(chromo.length); + for (let i = chromo.length; i-- > 0; ) { + res[i] = rnd!.float() < probMutate ? this.randomGene(i) : chromo[i]; + } + return res; + } - protected randomGene(i: number): MEPGene { - const geneID = this.choices.next().value; - const rnd = this.opts.rnd!; - if (i === 0 || geneID === 0) { - return terminalNode(this.opts.terminal(rnd)); - } - const op = this.opts.ops[geneID - 1]; - const args = [...repeatedly(() => rnd.int() % i, op.arity)]; - return opNode(op.fn(rnd, args), args); - } + protected randomGene(i: number): MEPGene { + const geneID = this.choices.next().value; + const rnd = this.opts.rnd!; + if (i === 0 || geneID === 0) { + return terminalNode(this.opts.terminal(rnd)); + } + const op = this.opts.ops[geneID - 1]; + const args = [...repeatedly(() => rnd.int() % i, op.arity)]; + return opNode(op.fn(rnd, args), args); + } } diff --git a/packages/gp/src/utils.ts b/packages/gp/src/utils.ts index b62f7c684a..e70d82eb1d 100644 --- a/packages/gp/src/utils.ts +++ b/packages/gp/src/utils.ts @@ -6,26 +6,26 @@ import { range } from "@thi.ng/transducers/range"; import type { GPOpts, OpGene, TerminalGene } from "./api.js"; export const terminalNode = (value: T): TerminalGene => ({ - type: "term", - value, + type: "term", + value, }); export const opNode = (op: OP, args: A[]): OpGene => ({ - type: "op", - op, - args, + type: "op", + op, + args, }); export const probabilities = (opts: GPOpts) => { - const probabilities = opts.ops.map((op) => op.prob); - const psum = add(probabilities); - assert(psum < 1, "total op probabilities MUST be < 1"); - return { - iter: choices( - [...range(probabilities.length + 1)], - [1 - psum, ...probabilities], - opts.rnd - ), - probTerminal: 1 - psum, - }; + const probabilities = opts.ops.map((op) => op.prob); + const psum = add(probabilities); + assert(psum < 1, "total op probabilities MUST be < 1"); + return { + iter: choices( + [...range(probabilities.length + 1)], + [1 - psum, ...probabilities], + opts.rnd + ), + probTerminal: 1 - psum, + }; }; diff --git a/packages/gp/test/ast.ts b/packages/gp/test/ast.ts index 67e1331e93..48edb493f6 100644 --- a/packages/gp/test/ast.ts +++ b/packages/gp/test/ast.ts @@ -1,65 +1,65 @@ import { group } from "@thi.ng/testament"; import { XsAdd } from "@thi.ng/random"; import * as assert from "assert"; -import { AST } from "../src/index.js" +import { AST } from "../src/index.js"; let ast: AST; group( - "gp (ast)", - { - generate: () => { - assert.deepStrictEqual(ast.randomAST(), { - type: "op", - op: "+", - args: [ - { - type: "op", - op: "-", - args: [ - { - type: "term", - value: 5, - }, - { - type: "term", - value: 1, - }, - ], - }, - { - type: "op", - op: "*", - args: [ - { - type: "term", - value: 8, - }, - { - type: "term", - value: 3, - }, - ], - }, - ], - }); - }, - }, - { - beforeEach: () => { - ast = new AST({ - terminal: (rnd) => rnd.int() % 10, - ops: [ - { - fn: (rnd) => ["+", "-", "*", "/"][rnd.int() % 4], - arity: 2, - prob: 0.9, - }, - ], - maxDepth: 2, - probMutate: 0.1, - rnd: new XsAdd(0x12345678), - }); - }, - } + "gp (ast)", + { + generate: () => { + assert.deepStrictEqual(ast.randomAST(), { + type: "op", + op: "+", + args: [ + { + type: "op", + op: "-", + args: [ + { + type: "term", + value: 5, + }, + { + type: "term", + value: 1, + }, + ], + }, + { + type: "op", + op: "*", + args: [ + { + type: "term", + value: 8, + }, + { + type: "term", + value: 3, + }, + ], + }, + ], + }); + }, + }, + { + beforeEach: () => { + ast = new AST({ + terminal: (rnd) => rnd.int() % 10, + ops: [ + { + fn: (rnd) => ["+", "-", "*", "/"][rnd.int() % 4], + arity: 2, + prob: 0.9, + }, + ], + maxDepth: 2, + probMutate: 0.1, + rnd: new XsAdd(0x12345678), + }); + }, + } ); diff --git a/packages/gp/test/mep.ts b/packages/gp/test/mep.ts index 8b7957849f..7754a175d0 100644 --- a/packages/gp/test/mep.ts +++ b/packages/gp/test/mep.ts @@ -1,432 +1,432 @@ import { XsAdd } from "@thi.ng/random"; import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { MEP } from "../src/index.js" +import { MEP } from "../src/index.js"; let ast: MEP; group( - "gp (mep)", - { - generate: () => { - assert.deepStrictEqual(ast.randomChromosome(), [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - { type: "op", op: "*", args: [1, 1] }, - { type: "op", op: "-", args: [1, 2] }, - { type: "op", op: "-", args: [0, 0] }, - { type: "op", op: "-", args: [3, 4] }, - { type: "op", op: "*", args: [4, 0] }, - { type: "op", op: "-", args: [2, 3] }, - { type: "op", op: "/", args: [1, 4] }, - { type: "op", op: "-", args: [5, 0] }, - ]); - }, + "gp (mep)", + { + generate: () => { + assert.deepStrictEqual(ast.randomChromosome(), [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + { type: "op", op: "*", args: [1, 1] }, + { type: "op", op: "-", args: [1, 2] }, + { type: "op", op: "-", args: [0, 0] }, + { type: "op", op: "-", args: [3, 4] }, + { type: "op", op: "*", args: [4, 0] }, + { type: "op", op: "-", args: [2, 3] }, + { type: "op", op: "/", args: [1, 4] }, + { type: "op", op: "-", args: [5, 0] }, + ]); + }, - decode: () => { - assert.deepStrictEqual( - ast.decodeChromosome(ast.randomChromosome()), - [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - { - type: "op", - op: "*", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { - type: "op", - op: "*", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - { - type: "op", - op: "-", - args: [ - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { - type: "op", - op: "*", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { - type: "op", - op: "*", - args: [ - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - { type: "term", value: 5 }, - ], - }, - { - type: "op", - op: "-", - args: [ - { - type: "op", - op: "*", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { - type: "op", - op: "*", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - ], - }, - { - type: "op", - op: "/", - args: [ - { type: "term", value: 5 }, - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { - type: "op", - op: "-", - args: [ - { - type: "op", - op: "-", - args: [ - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { - type: "op", - op: "*", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { type: "term", value: 5 }, - ], - }, - ] - ); - }, + decode: () => { + assert.deepStrictEqual( + ast.decodeChromosome(ast.randomChromosome()), + [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + { + type: "op", + op: "*", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { + type: "op", + op: "*", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + { + type: "op", + op: "-", + args: [ + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { + type: "op", + op: "*", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { + type: "op", + op: "*", + args: [ + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + { type: "term", value: 5 }, + ], + }, + { + type: "op", + op: "-", + args: [ + { + type: "op", + op: "*", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { + type: "op", + op: "*", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + ], + }, + { + type: "op", + op: "/", + args: [ + { type: "term", value: 5 }, + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { + type: "op", + op: "-", + args: [ + { + type: "op", + op: "-", + args: [ + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { + type: "op", + op: "*", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { type: "term", value: 5 }, + ], + }, + ] + ); + }, - "decode (filtered)": () => { - assert.deepStrictEqual( - ast.decodeChromosome(ast.randomChromosome(), 3), - [ - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { - type: "op", - op: "*", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { - type: "op", - op: "-", - args: [ - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { - type: "op", - op: "*", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { - type: "op", - op: "*", - args: [ - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - { type: "term", value: 5 }, - ], - }, - { - type: "op", - op: "-", - args: [ - { - type: "op", - op: "*", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { - type: "op", - op: "*", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - ], - }, - { - type: "op", - op: "/", - args: [ - { type: "term", value: 5 }, - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { - type: "op", - op: "-", - args: [ - { - type: "op", - op: "-", - args: [ - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { - type: "op", - op: "*", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { - type: "op", - op: "-", - args: [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - ], - }, - ], - }, - { type: "term", value: 5 }, - ], - }, - ] - ); - }, + "decode (filtered)": () => { + assert.deepStrictEqual( + ast.decodeChromosome(ast.randomChromosome(), 3), + [ + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { + type: "op", + op: "*", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { + type: "op", + op: "-", + args: [ + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { + type: "op", + op: "*", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { + type: "op", + op: "*", + args: [ + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + { type: "term", value: 5 }, + ], + }, + { + type: "op", + op: "-", + args: [ + { + type: "op", + op: "*", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { + type: "op", + op: "*", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + ], + }, + { + type: "op", + op: "/", + args: [ + { type: "term", value: 5 }, + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { + type: "op", + op: "-", + args: [ + { + type: "op", + op: "-", + args: [ + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { + type: "op", + op: "*", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { + type: "op", + op: "-", + args: [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + ], + }, + ], + }, + { type: "term", value: 5 }, + ], + }, + ] + ); + }, - mutate: () => { - assert.deepStrictEqual(ast.mutate(ast.randomChromosome()), [ - { type: "term", value: 0 }, - { type: "term", value: 5 }, - { type: "op", op: "*", args: [1, 0] }, - { type: "op", op: "+", args: [2, 1] }, - { type: "op", op: "-", args: [0, 0] }, - { type: "op", op: "+", args: [4, 2] }, - { type: "op", op: "/", args: [3, 1] }, - { type: "op", op: "/", args: [4, 3] }, - { type: "term", value: 5 }, - { type: "op", op: "/", args: [6, 4] }, - ]); - }, + mutate: () => { + assert.deepStrictEqual(ast.mutate(ast.randomChromosome()), [ + { type: "term", value: 0 }, + { type: "term", value: 5 }, + { type: "op", op: "*", args: [1, 0] }, + { type: "op", op: "+", args: [2, 1] }, + { type: "op", op: "-", args: [0, 0] }, + { type: "op", op: "+", args: [4, 2] }, + { type: "op", op: "/", args: [3, 1] }, + { type: "op", op: "/", args: [4, 3] }, + { type: "term", value: 5 }, + { type: "op", op: "/", args: [6, 4] }, + ]); + }, - "crossover (single)": () => { - const a = ast.randomChromosome(); - const b = ast.randomChromosome(); - assert.deepStrictEqual(b, [ - { type: "term", value: 5 }, - { type: "op", op: "*", args: [0, 0] }, - { type: "term", value: 5 }, - { type: "op", op: "-", args: [0, 0] }, - { type: "op", op: "/", args: [1, 0] }, - { type: "op", op: "*", args: [2, 1] }, - { type: "op", op: "-", args: [3, 2] }, - { type: "op", op: "-", args: [2, 1] }, - { type: "term", value: 1 }, - { type: "term", value: 6 }, - ]); - assert.deepStrictEqual(ast.crossoverSingle(a, b, 5), [ - [ - { type: "term", value: 5 }, - { type: "term", value: 5 }, - { type: "op", op: "*", args: [1, 1] }, - { type: "op", op: "-", args: [1, 2] }, - { type: "op", op: "-", args: [0, 0] }, - // cut - { type: "op", op: "*", args: [2, 1] }, - { type: "op", op: "-", args: [3, 2] }, - { type: "op", op: "-", args: [2, 1] }, - { type: "term", value: 1 }, - { type: "term", value: 6 }, - ], - [ - { type: "term", value: 5 }, - { type: "op", op: "*", args: [0, 0] }, - { type: "term", value: 5 }, - { type: "op", op: "-", args: [0, 0] }, - { type: "op", op: "/", args: [1, 0] }, - // cut - { type: "op", op: "-", args: [3, 4] }, - { type: "op", op: "*", args: [4, 0] }, - { type: "op", op: "-", args: [2, 3] }, - { type: "op", op: "/", args: [1, 4] }, - { type: "op", op: "-", args: [5, 0] }, - ], - ]); - }, + "crossover (single)": () => { + const a = ast.randomChromosome(); + const b = ast.randomChromosome(); + assert.deepStrictEqual(b, [ + { type: "term", value: 5 }, + { type: "op", op: "*", args: [0, 0] }, + { type: "term", value: 5 }, + { type: "op", op: "-", args: [0, 0] }, + { type: "op", op: "/", args: [1, 0] }, + { type: "op", op: "*", args: [2, 1] }, + { type: "op", op: "-", args: [3, 2] }, + { type: "op", op: "-", args: [2, 1] }, + { type: "term", value: 1 }, + { type: "term", value: 6 }, + ]); + assert.deepStrictEqual(ast.crossoverSingle(a, b, 5), [ + [ + { type: "term", value: 5 }, + { type: "term", value: 5 }, + { type: "op", op: "*", args: [1, 1] }, + { type: "op", op: "-", args: [1, 2] }, + { type: "op", op: "-", args: [0, 0] }, + // cut + { type: "op", op: "*", args: [2, 1] }, + { type: "op", op: "-", args: [3, 2] }, + { type: "op", op: "-", args: [2, 1] }, + { type: "term", value: 1 }, + { type: "term", value: 6 }, + ], + [ + { type: "term", value: 5 }, + { type: "op", op: "*", args: [0, 0] }, + { type: "term", value: 5 }, + { type: "op", op: "-", args: [0, 0] }, + { type: "op", op: "/", args: [1, 0] }, + // cut + { type: "op", op: "-", args: [3, 4] }, + { type: "op", op: "*", args: [4, 0] }, + { type: "op", op: "-", args: [2, 3] }, + { type: "op", op: "/", args: [1, 4] }, + { type: "op", op: "-", args: [5, 0] }, + ], + ]); + }, - "crossover (uniform)": () => { - const a = ast.randomChromosome(); - const b = ast.randomChromosome(); - assert.deepStrictEqual(ast.crossoverUniform(a, b), [ - { type: "term", value: 5 }, - { type: "op", op: "*", args: [0, 0] }, - { type: "term", value: 5 }, - { type: "op", op: "-", args: [1, 2] }, - { type: "op", op: "-", args: [0, 0] }, - { type: "op", op: "-", args: [3, 4] }, - { type: "op", op: "-", args: [3, 2] }, - { type: "op", op: "-", args: [2, 1] }, - { type: "term", value: 1 }, - { type: "op", op: "-", args: [5, 0] }, - ]); - }, - }, - { - beforeEach: () => { - ast = new MEP({ - terminal: (rnd) => rnd.int() % 10, - ops: [ - { - fn: (rnd) => ["+", "-", "*", "/"][rnd.int() % 4], - arity: 2, - prob: 0.9, - }, - ], - chromoSize: 10, - probMutate: 0.8, - rnd: new XsAdd(0x12345678), - }); - }, - } + "crossover (uniform)": () => { + const a = ast.randomChromosome(); + const b = ast.randomChromosome(); + assert.deepStrictEqual(ast.crossoverUniform(a, b), [ + { type: "term", value: 5 }, + { type: "op", op: "*", args: [0, 0] }, + { type: "term", value: 5 }, + { type: "op", op: "-", args: [1, 2] }, + { type: "op", op: "-", args: [0, 0] }, + { type: "op", op: "-", args: [3, 4] }, + { type: "op", op: "-", args: [3, 2] }, + { type: "op", op: "-", args: [2, 1] }, + { type: "term", value: 1 }, + { type: "op", op: "-", args: [5, 0] }, + ]); + }, + }, + { + beforeEach: () => { + ast = new MEP({ + terminal: (rnd) => rnd.int() % 10, + ops: [ + { + fn: (rnd) => ["+", "-", "*", "/"][rnd.int() % 4], + arity: 2, + prob: 0.9, + }, + ], + chromoSize: 10, + probMutate: 0.8, + rnd: new XsAdd(0x12345678), + }); + }, + } ); diff --git a/packages/gp/tsconfig.json b/packages/gp/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/gp/tsconfig.json +++ b/packages/gp/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/grid-iterators/api-extractor.json b/packages/grid-iterators/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/grid-iterators/api-extractor.json +++ b/packages/grid-iterators/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/grid-iterators/package.json b/packages/grid-iterators/package.json index 4a4de17d8b..4f835256c5 100644 --- a/packages/grid-iterators/package.json +++ b/packages/grid-iterators/package.json @@ -1,155 +1,155 @@ { - "name": "@thi.ng/grid-iterators", - "version": "2.3.6", - "description": "2D grid and shape iterators w/ multiple orderings", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/grid-iterators#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "build:assets": "node tools/build-assets", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/arrays": "^2.3.1", - "@thi.ng/binary": "^3.3.0", - "@thi.ng/bitfield": "^2.2.0", - "@thi.ng/morton": "^3.1.8", - "@thi.ng/random": "^3.3.3", - "@thi.ng/transducers": "^8.3.7" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "2d", - "binary", - "circle", - "clipping", - "diagonal", - "floodfill", - "grid", - "hilbert", - "iterator", - "line", - "morton", - "random", - "spiral", - "typescript", - "z-curve", - "zigzag" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./circle": { - "default": "./circle.js" - }, - "./clipping": { - "default": "./clipping.js" - }, - "./column-ends": { - "default": "./column-ends.js" - }, - "./columns": { - "default": "./columns.js" - }, - "./diagonal-ends": { - "default": "./diagonal-ends.js" - }, - "./diagonal": { - "default": "./diagonal.js" - }, - "./diamond-square": { - "default": "./diamond-square.js" - }, - "./flood-fill": { - "default": "./flood-fill.js" - }, - "./hilbert": { - "default": "./hilbert.js" - }, - "./hvline": { - "default": "./hvline.js" - }, - "./interleave": { - "default": "./interleave.js" - }, - "./line": { - "default": "./line.js" - }, - "./random": { - "default": "./random.js" - }, - "./row-ends": { - "default": "./row-ends.js" - }, - "./rows": { - "default": "./rows.js" - }, - "./spiral": { - "default": "./spiral.js" - }, - "./zcurve": { - "default": "./zcurve.js" - }, - "./zigzag-columns": { - "default": "./zigzag-columns.js" - }, - "./zigzag-diagonal": { - "default": "./zigzag-diagonal.js" - }, - "./zigzag-rows": { - "default": "./zigzag-rows.js" - } - }, - "thi.ng": { - "related": [ - "morton", - "rasterize", - "transducers" - ], - "year": 2019 - } + "name": "@thi.ng/grid-iterators", + "version": "2.3.6", + "description": "2D grid and shape iterators w/ multiple orderings", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/grid-iterators#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "build:assets": "node tools/build-assets", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/arrays": "^2.3.1", + "@thi.ng/binary": "^3.3.0", + "@thi.ng/bitfield": "^2.2.0", + "@thi.ng/morton": "^3.1.8", + "@thi.ng/random": "^3.3.3", + "@thi.ng/transducers": "^8.3.7" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "2d", + "binary", + "circle", + "clipping", + "diagonal", + "floodfill", + "grid", + "hilbert", + "iterator", + "line", + "morton", + "random", + "spiral", + "typescript", + "z-curve", + "zigzag" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./circle": { + "default": "./circle.js" + }, + "./clipping": { + "default": "./clipping.js" + }, + "./column-ends": { + "default": "./column-ends.js" + }, + "./columns": { + "default": "./columns.js" + }, + "./diagonal-ends": { + "default": "./diagonal-ends.js" + }, + "./diagonal": { + "default": "./diagonal.js" + }, + "./diamond-square": { + "default": "./diamond-square.js" + }, + "./flood-fill": { + "default": "./flood-fill.js" + }, + "./hilbert": { + "default": "./hilbert.js" + }, + "./hvline": { + "default": "./hvline.js" + }, + "./interleave": { + "default": "./interleave.js" + }, + "./line": { + "default": "./line.js" + }, + "./random": { + "default": "./random.js" + }, + "./row-ends": { + "default": "./row-ends.js" + }, + "./rows": { + "default": "./rows.js" + }, + "./spiral": { + "default": "./spiral.js" + }, + "./zcurve": { + "default": "./zcurve.js" + }, + "./zigzag-columns": { + "default": "./zigzag-columns.js" + }, + "./zigzag-diagonal": { + "default": "./zigzag-diagonal.js" + }, + "./zigzag-rows": { + "default": "./zigzag-rows.js" + } + }, + "thi.ng": { + "related": [ + "morton", + "rasterize", + "transducers" + ], + "year": 2019 + } } diff --git a/packages/grid-iterators/src/circle.ts b/packages/grid-iterators/src/circle.ts index 8ddcebc24e..59901571c1 100644 --- a/packages/grid-iterators/src/circle.ts +++ b/packages/grid-iterators/src/circle.ts @@ -3,48 +3,48 @@ import { clipped, intersectRectCircle } from "./clipping.js"; import { hline } from "./hvline.js"; export function* circle(cx: number, cy: number, r: number, fill = true) { - [cx, cy, r] = asInt(cx, cy, r); - if (r < 1) return; - let x = 0; - let y = r; - let y2 = r * r; - let sum = y2 + r; - let dx2 = 1; - let dy2 = 2 * r - 1; + [cx, cy, r] = asInt(cx, cy, r); + if (r < 1) return; + let x = 0; + let y = r; + let y2 = r * r; + let sum = y2 + r; + let dx2 = 1; + let dy2 = 2 * r - 1; - while (x <= y) { - if (fill) { - yield* hline(cx - y, cy + x, y << 1); - if (x) yield* hline(cx - y, cy - x, y << 1); - } else { - yield [cx - y, cy + x]; - if (y) yield [cx + y, cy + x]; - if (x) { - yield [cx - y, cy - x]; - if (y) yield [cx + y, cy - x]; - } - if (x !== y) { - yield [cx - x, cy - y]; - if (x) yield [cx + x, cy - y]; - if (y) { - yield [cx - x, cy + y]; - if (x) yield [cx + x, cy + y]; - } - } - } - sum -= dx2; - if (sum <= y2) { - if (fill && x !== y) { - yield* hline(cx - x, cy - y, x << 1); - if (y) yield* hline(cx - x, cy + y, x << 1); - } - y--; - y2 -= dy2; - dy2 -= 2; - } - x++; - dx2 += 2; - } + while (x <= y) { + if (fill) { + yield* hline(cx - y, cy + x, y << 1); + if (x) yield* hline(cx - y, cy - x, y << 1); + } else { + yield [cx - y, cy + x]; + if (y) yield [cx + y, cy + x]; + if (x) { + yield [cx - y, cy - x]; + if (y) yield [cx + y, cy - x]; + } + if (x !== y) { + yield [cx - x, cy - y]; + if (x) yield [cx + x, cy - y]; + if (y) { + yield [cx - x, cy + y]; + if (x) yield [cx + x, cy + y]; + } + } + } + sum -= dx2; + if (sum <= y2) { + if (fill && x !== y) { + yield* hline(cx - x, cy - y, x << 1); + if (y) yield* hline(cx - x, cy + y, x << 1); + } + y--; + y2 -= dy2; + dy2 -= 2; + } + x++; + dx2 += 2; + } } /** @@ -52,26 +52,26 @@ export function* circle(cx: number, cy: number, r: number, fill = true) { * `left,top`..`right,bottom`. Returns undefined if circle lies completely * outside given clip rectangle. * - * @param cx - - * @param cy - - * @param r - - * @param left - - * @param top - - * @param right - - * @param bottom - - * @param fill - + * @param cx - + * @param cy - + * @param r - + * @param left - + * @param top - + * @param right - + * @param bottom - + * @param fill - */ export const circleClipped = ( - cx: number, - cy: number, - r: number, - left: number, - top: number, - right: number, - bottom: number, - fill = true + cx: number, + cy: number, + r: number, + left: number, + top: number, + right: number, + bottom: number, + fill = true ) => { - return intersectRectCircle(left, top, right, bottom, cx, cy, r) - ? clipped(circle(cx, cy, r, fill), left, top, right, bottom) - : undefined; + return intersectRectCircle(left, top, right, bottom, cx, cy, r) + ? clipped(circle(cx, cy, r, fill), left, top, right, bottom) + : undefined; }; diff --git a/packages/grid-iterators/src/clipping.ts b/packages/grid-iterators/src/clipping.ts index a2b53b122a..a73e9bda03 100644 --- a/packages/grid-iterators/src/clipping.ts +++ b/packages/grid-iterators/src/clipping.ts @@ -4,98 +4,98 @@ import type { FnN3, FnU2, FnU7, FnU8, Tuple } from "@thi.ng/api"; * Filters points from `src` iterable to remove any falling outside the rect * defined by `left,top`..`right,bottom`. * - * @param src - - * @param left - - * @param top - - * @param right - - * @param bottom - + * @param src - + * @param left - + * @param top - + * @param right - + * @param bottom - */ export function* clipped( - src: Iterable, - left: number, - top: number, - right: number, - bottom: number + src: Iterable, + left: number, + top: number, + right: number, + bottom: number ) { - for (let p of src) { - if (p[0] >= left && p[0] < right && p[1] >= top && p[1] < bottom) - yield p; - } + for (let p of src) { + if (p[0] >= left && p[0] < right && p[1] >= top && p[1] < bottom) + yield p; + } } /** @internal */ const axis: FnN3 = (a, b, c) => - (a < b ? a - b : a > b + c ? a - b - c : 0) ** 2; + (a < b ? a - b : a > b + c ? a - b - c : 0) ** 2; /** * Based on {@link @thi.ng/geom-isec}, but inlined to avoid dependency. * - * @param x - - * @param y - - * @param w - - * @param h - - * @param cx - - * @param cy - - * @param r - + * @param x - + * @param y - + * @param w - + * @param h - + * @param cx - + * @param cy - + * @param r - * * @internal */ export const intersectRectCircle: FnU7 = ( - x, - y, - w, - h, - cx, - cy, - r + x, + y, + w, + h, + cx, + cy, + r ) => axis(cx, x, w) + axis(cy, y, h) <= r * r; /** * Based on {@link @thi.ng/geom-clip-line#liangBarsky2Raw}, but with diff return type. * - * @param ax - - * @param ay - - * @param bx - - * @param by - - * @param minx - - * @param miny - - * @param maxx - - * @param maxy - + * @param ax - + * @param ay - + * @param bx - + * @param by - + * @param minx - + * @param miny - + * @param maxx - + * @param maxy - * * @internal */ export const liangBarsky: FnU8 | undefined> = ( - ax, - ay, - bx, - by, - minx, - miny, - maxx, - maxy + ax, + ay, + bx, + by, + minx, + miny, + maxx, + maxy ) => { - const dx = bx - ax; - const dy = by - ay; - let alpha = 0; - let beta = 1; - const clip: FnU2 = (p, q) => { - if (p < 0) { - const r = q / p; - if (r > beta) return false; - r > alpha && (alpha = r); - } else if (p > 0) { - const r = q / p; - if (r < alpha) return false; - r < beta && (beta = r); - } else if (q < 0) { - return false; - } - return true; - }; - return clip(-dx, ax - minx) && - clip(dx, maxx - ax) && - clip(-dy, ay - miny) && - clip(dy, maxy - ay) - ? [alpha * dx + ax, alpha * dy + ay, beta * dx + ax, beta * dy + ay] - : undefined; + const dx = bx - ax; + const dy = by - ay; + let alpha = 0; + let beta = 1; + const clip: FnU2 = (p, q) => { + if (p < 0) { + const r = q / p; + if (r > beta) return false; + r > alpha && (alpha = r); + } else if (p > 0) { + const r = q / p; + if (r < alpha) return false; + r < beta && (beta = r); + } else if (q < 0) { + return false; + } + return true; + }; + return clip(-dx, ax - minx) && + clip(dx, maxx - ax) && + clip(-dy, ay - miny) && + clip(dy, maxy - ay) + ? [alpha * dx + ax, alpha * dy + ay, beta * dx + ax, beta * dy + ay] + : undefined; }; diff --git a/packages/grid-iterators/src/column-ends.ts b/packages/grid-iterators/src/column-ends.ts index cb0d2d790a..bc1bb33c52 100644 --- a/packages/grid-iterators/src/column-ends.ts +++ b/packages/grid-iterators/src/column-ends.ts @@ -8,10 +8,10 @@ import { asInt } from "@thi.ng/api/typedarray"; * @param rows - */ export function* columnEnds2d(cols: number, rows = cols) { - [cols, rows] = asInt(cols, rows); - rows--; - for (let x = 0; x < cols; x++) { - yield [x, 0]; - yield [x, rows]; - } + [cols, rows] = asInt(cols, rows); + rows--; + for (let x = 0; x < cols; x++) { + yield [x, 0]; + yield [x, rows]; + } } diff --git a/packages/grid-iterators/src/columns.ts b/packages/grid-iterators/src/columns.ts index b146bbf892..d6196bfe39 100644 --- a/packages/grid-iterators/src/columns.ts +++ b/packages/grid-iterators/src/columns.ts @@ -5,8 +5,8 @@ import { swapxy } from "./utils.js"; /** * Yields sequence of 2D grid coordinates in column-major order. * - * @param cols - - * @param rows - + * @param cols - + * @param rows - */ export const columns2d = (cols: number, rows = cols) => - map(swapxy, range2d(rows | 0, cols | 0)); + map(swapxy, range2d(rows | 0, cols | 0)); diff --git a/packages/grid-iterators/src/diagonal-ends.ts b/packages/grid-iterators/src/diagonal-ends.ts index cbe204e050..d2197399d8 100644 --- a/packages/grid-iterators/src/diagonal-ends.ts +++ b/packages/grid-iterators/src/diagonal-ends.ts @@ -13,16 +13,16 @@ import { diagonal2d } from "./diagonal.js"; * @param rows - */ export function* diagonalEnds2d(cols: number, rows = cols) { - [cols, rows] = asInt(cols, rows); - const num = cols * rows - 1; - const maxX = cols - 1; - const maxY = rows - 1; - let i = 0; - for (let p of diagonal2d(cols, rows)) { - if (i > 0 && i < num) { - const [x, y] = p; - if (x === 0 || x === maxX || y === 0 || y === maxY) yield p; - } - i++; - } + [cols, rows] = asInt(cols, rows); + const num = cols * rows - 1; + const maxX = cols - 1; + const maxY = rows - 1; + let i = 0; + for (let p of diagonal2d(cols, rows)) { + if (i > 0 && i < num) { + const [x, y] = p; + if (x === 0 || x === maxX || y === 0 || y === maxY) yield p; + } + i++; + } } diff --git a/packages/grid-iterators/src/diagonal.ts b/packages/grid-iterators/src/diagonal.ts index 6c8ae1a450..9af51caa0c 100644 --- a/packages/grid-iterators/src/diagonal.ts +++ b/packages/grid-iterators/src/diagonal.ts @@ -12,22 +12,22 @@ import { asInt } from "@thi.ng/api/typedarray"; * @param rows - */ export function* diagonal2d(cols: number, rows = cols) { - [cols, rows] = asInt(cols, rows); - const num = cols * rows - 1; - for (let x = 0, y = 0, nx = 1, ny = 0, i = 0; i <= num; i++) { - yield [x, y]; - if (i != num) { - do { - if (y === ny) { - y = 0; - x = nx; - ny++; - nx++; - } else { - x--; - y++; - } - } while (y >= rows || x >= cols); - } - } + [cols, rows] = asInt(cols, rows); + const num = cols * rows - 1; + for (let x = 0, y = 0, nx = 1, ny = 0, i = 0; i <= num; i++) { + yield [x, y]; + if (i != num) { + do { + if (y === ny) { + y = 0; + x = nx; + ny++; + nx++; + } else { + x--; + y++; + } + } while (y >= rows || x >= cols); + } + } } diff --git a/packages/grid-iterators/src/diamond-square.ts b/packages/grid-iterators/src/diamond-square.ts index e53cd67366..c474fd637c 100644 --- a/packages/grid-iterators/src/diamond-square.ts +++ b/packages/grid-iterators/src/diamond-square.ts @@ -25,56 +25,56 @@ import { defBitField } from "@thi.ng/bitfield/bitfield"; * @param exp */ export function* diamondSquare(exp: number) { - const size = 1 << exp; - const size1 = size + 1; - const s2 = size >> 1; - let res = size; - const idx = defBitField(size1 * size1); - const acc: number[][] = []; - const $: FnU2 = (x, y) => { - !idx.setAt(x + y * size1) && acc.push([x, y]); - }; - $(0, 0); - $(res, 0); - $(res, res); - $(0, res); - yield* acc; - while (res > 1) { - const r2 = res >> 1; - for (let y = 0; y <= s2; y += res) { - const y1 = y + r2; - const y2 = y + res; - for (let x = r2; x <= s2; x += res) { - acc.length = 0; - const x1 = x - r2; - const x2 = x + r2; - $(x, y); - $(size - x, y); - $(size - x, size - y); - $(x, size - y); + const size = 1 << exp; + const size1 = size + 1; + const s2 = size >> 1; + let res = size; + const idx = defBitField(size1 * size1); + const acc: number[][] = []; + const $: FnU2 = (x, y) => { + !idx.setAt(x + y * size1) && acc.push([x, y]); + }; + $(0, 0); + $(res, 0); + $(res, res); + $(0, res); + yield* acc; + while (res > 1) { + const r2 = res >> 1; + for (let y = 0; y <= s2; y += res) { + const y1 = y + r2; + const y2 = y + res; + for (let x = r2; x <= s2; x += res) { + acc.length = 0; + const x1 = x - r2; + const x2 = x + r2; + $(x, y); + $(size - x, y); + $(size - x, size - y); + $(x, size - y); - $(x2, y1); - $(size - x2, y1); - $(size - x2, size - y1); - $(x2, size - y1); + $(x2, y1); + $(size - x2, y1); + $(size - x2, size - y1); + $(x2, size - y1); - $(x, y2); - $(size - x, y2); - $(size - x, size - y2); - $(x, size - y2); + $(x, y2); + $(size - x, y2); + $(size - x, size - y2); + $(x, size - y2); - $(x1, y1); - $(size - x1, y1); - $(size - x1, size - y1); - $(x1, size - y1); + $(x1, y1); + $(size - x1, y1); + $(size - x1, size - y1); + $(x1, size - y1); - $(x, y1); - $(size - x, y1); - $(size - x, size - y1); - $(x, size - y1); - yield* acc; - } - } - res = r2; - } + $(x, y1); + $(size - x, y1); + $(size - x, size - y1); + $(x, size - y1); + yield* acc; + } + } + res = r2; + } } diff --git a/packages/grid-iterators/src/flood-fill.ts b/packages/grid-iterators/src/flood-fill.ts index ab41233f96..86b1affedf 100644 --- a/packages/grid-iterators/src/flood-fill.ts +++ b/packages/grid-iterators/src/flood-fill.ts @@ -29,67 +29,67 @@ import { BitField, defBitField } from "@thi.ng/bitfield/bitfield"; * // ] * ``` * - * @param pred - - * @param x - - * @param y - - * @param width - - * @param height - + * @param pred - + * @param x - + * @param y - + * @param width - + * @param height - */ export function* floodFill( - pred: Predicate2, - x: number, - y: number, - width: number, - height: number + pred: Predicate2, + x: number, + y: number, + width: number, + height: number ) { - x |= 0; - y |= 0; - if (!pred(x, y)) return; - const queue: number[][] = [[x, y]]; - const visited = defBitField(width * height); - height--; - while (queue.length) { - [x, y] = queue.pop()!; - yield* partialRow(pred, queue, visited, x, y, width, height, -1); - yield* partialRow(pred, queue, visited, x + 1, y, width, height, 1); - } + x |= 0; + y |= 0; + if (!pred(x, y)) return; + const queue: number[][] = [[x, y]]; + const visited = defBitField(width * height); + height--; + while (queue.length) { + [x, y] = queue.pop()!; + yield* partialRow(pred, queue, visited, x, y, width, height, -1); + yield* partialRow(pred, queue, visited, x + 1, y, width, height, 1); + } } /** @internal */ function* partialRow( - pred: Predicate2, - queue: number[][], - visited: BitField, - x: number, - y: number, - width: number, - height1: number, - step: number + pred: Predicate2, + queue: number[][], + visited: BitField, + x: number, + y: number, + width: number, + height1: number, + step: number ) { - let idx = y * width + x; - if (visited.at(idx)) return; - let scanUp = false; - let scanDown = false; - while (x >= 0 && x < width && pred(x, y)) { - visited.setAt(idx); - yield [x, y]; - if (y > 0) { - if (pred(x, y - 1) && !scanUp) { - queue.push([x, y - 1]); - scanUp = true; - } else { - scanUp = false; - } - } - if (y < height1) { - if (pred(x, y + 1) && !scanDown) { - queue.push([x, y + 1]); - scanDown = true; - } else { - scanDown = false; - } - } - x += step; - idx += step; - } + let idx = y * width + x; + if (visited.at(idx)) return; + let scanUp = false; + let scanDown = false; + while (x >= 0 && x < width && pred(x, y)) { + visited.setAt(idx); + yield [x, y]; + if (y > 0) { + if (pred(x, y - 1) && !scanUp) { + queue.push([x, y - 1]); + scanUp = true; + } else { + scanUp = false; + } + } + if (y < height1) { + if (pred(x, y + 1) && !scanDown) { + queue.push([x, y + 1]); + scanDown = true; + } else { + scanDown = false; + } + } + x += step; + idx += step; + } } diff --git a/packages/grid-iterators/src/hilbert.ts b/packages/grid-iterators/src/hilbert.ts index 5315cce79e..33635c7ad2 100644 --- a/packages/grid-iterators/src/hilbert.ts +++ b/packages/grid-iterators/src/hilbert.ts @@ -11,57 +11,57 @@ import { asInt } from "@thi.ng/api/typedarray"; * @param rows - */ export function* hilbert2d(cols: number, rows = cols) { - [cols, rows] = asInt(cols, rows); - let hIndex = 0; // hilbert curve index - let hOrder = 0; // hilbert curve order - // fit to number of buckets - while ((1 << hOrder < cols || 1 << hOrder < rows) && hOrder < 15) hOrder++; - const numBuckets = 1 << (2 * hOrder); - for (let i = 0, n = cols * rows; i < n; i++) { - let hx, hy; - do { - // adapted from Hacker's Delight - let s, t, comp, swap, cs, sr; - // s is the hilbert index, shifted to start in the middle - s = hIndex | (0x55555555 << (2 * hOrder)); // Pad s on left with 01 - sr = (s >>> 1) & 0x55555555; // (no change) groups. - cs = ((s & 0x55555555) + sr) ^ 0x55555555; - // Compute complement & swap info in two-bit groups. - // Parallel prefix xor op to propagate both complement and - // swap info together from left to right (there is no step - // "cs ^= cs >> 1", so in effect it computes two independent - // parallel prefix operations on two interleaved sets of - // sixteen bits). - cs = cs ^ (cs >>> 2); - cs = cs ^ (cs >>> 4); - cs = cs ^ (cs >>> 8); - cs = cs ^ (cs >>> 16); - // Separate the swap and complement bits. - swap = cs & 0x55555555; - comp = (cs >>> 1) & 0x55555555; - // Calculate x and y in the odd & even bit positions - t = (s & swap) ^ comp; - s = s ^ sr ^ t ^ (t << 1); - // Clear out any junk on the left (unpad). - s = s & ((1 << (2 * hOrder)) - 1); - // Now "unshuffle" to separate the x and y bits. - t = (s ^ (s >>> 1)) & 0x22222222; - s = s ^ t ^ (t << 1); - t = (s ^ (s >>> 2)) & 0x0c0c0c0c; - s = s ^ t ^ (t << 2); - t = (s ^ (s >>> 4)) & 0x00f000f0; - s = s ^ t ^ (t << 4); - t = (s ^ (s >>> 8)) & 0x0000ff00; - s = s ^ t ^ (t << 8); - // Assign the two halves to x and y. - hx = s >>> 16; - hy = s & 0xffff; - hIndex++; - } while ( - // Dont't emit any outside cells - (hx >= cols || hy >= rows || hx < 0 || hy < 0) && - hIndex < numBuckets - ); - yield [hx, hy]; - } + [cols, rows] = asInt(cols, rows); + let hIndex = 0; // hilbert curve index + let hOrder = 0; // hilbert curve order + // fit to number of buckets + while ((1 << hOrder < cols || 1 << hOrder < rows) && hOrder < 15) hOrder++; + const numBuckets = 1 << (2 * hOrder); + for (let i = 0, n = cols * rows; i < n; i++) { + let hx, hy; + do { + // adapted from Hacker's Delight + let s, t, comp, swap, cs, sr; + // s is the hilbert index, shifted to start in the middle + s = hIndex | (0x55555555 << (2 * hOrder)); // Pad s on left with 01 + sr = (s >>> 1) & 0x55555555; // (no change) groups. + cs = ((s & 0x55555555) + sr) ^ 0x55555555; + // Compute complement & swap info in two-bit groups. + // Parallel prefix xor op to propagate both complement and + // swap info together from left to right (there is no step + // "cs ^= cs >> 1", so in effect it computes two independent + // parallel prefix operations on two interleaved sets of + // sixteen bits). + cs = cs ^ (cs >>> 2); + cs = cs ^ (cs >>> 4); + cs = cs ^ (cs >>> 8); + cs = cs ^ (cs >>> 16); + // Separate the swap and complement bits. + swap = cs & 0x55555555; + comp = (cs >>> 1) & 0x55555555; + // Calculate x and y in the odd & even bit positions + t = (s & swap) ^ comp; + s = s ^ sr ^ t ^ (t << 1); + // Clear out any junk on the left (unpad). + s = s & ((1 << (2 * hOrder)) - 1); + // Now "unshuffle" to separate the x and y bits. + t = (s ^ (s >>> 1)) & 0x22222222; + s = s ^ t ^ (t << 1); + t = (s ^ (s >>> 2)) & 0x0c0c0c0c; + s = s ^ t ^ (t << 2); + t = (s ^ (s >>> 4)) & 0x00f000f0; + s = s ^ t ^ (t << 4); + t = (s ^ (s >>> 8)) & 0x0000ff00; + s = s ^ t ^ (t << 8); + // Assign the two halves to x and y. + hx = s >>> 16; + hy = s & 0xffff; + hIndex++; + } while ( + // Dont't emit any outside cells + (hx >= cols || hy >= rows || hx < 0 || hy < 0) && + hIndex < numBuckets + ); + yield [hx, hy]; + } } diff --git a/packages/grid-iterators/src/hvline.ts b/packages/grid-iterators/src/hvline.ts index bf3255a783..952b4a748d 100644 --- a/packages/grid-iterators/src/hvline.ts +++ b/packages/grid-iterators/src/hvline.ts @@ -1,51 +1,51 @@ import { asInt } from "@thi.ng/api/typedarray"; export function* hline(x: number, y: number, len: number) { - [x, y, len] = asInt(x, y, len); - for (const xmax = x + len; x < xmax; x++) { - yield [x, y]; - } + [x, y, len] = asInt(x, y, len); + for (const xmax = x + len; x < xmax; x++) { + yield [x, y]; + } } export function* vline(x: number, y: number, len: number) { - [x, y, len] = asInt(x, y, len); - for (const ymax = y + len; y < ymax; y++) { - yield [x, y]; - } + [x, y, len] = asInt(x, y, len); + for (const ymax = y + len; y < ymax; y++) { + yield [x, y]; + } } export function* hlineClipped( - x: number, - y: number, - len: number, - left: number, - top: number, - right: number, - bottom: number + x: number, + y: number, + len: number, + left: number, + top: number, + right: number, + bottom: number ) { - [x, y, len] = asInt(x, y, len); - if (x >= right || y < top || y >= bottom) return; - if (x < left) { - len += x - left; - x = left; - } - yield* hline(x, y, Math.min(len, right - x)); + [x, y, len] = asInt(x, y, len); + if (x >= right || y < top || y >= bottom) return; + if (x < left) { + len += x - left; + x = left; + } + yield* hline(x, y, Math.min(len, right - x)); } export function* vlineClipped( - x: number, - y: number, - len: number, - left: number, - top: number, - right: number, - bottom: number + x: number, + y: number, + len: number, + left: number, + top: number, + right: number, + bottom: number ) { - [x, y, len] = asInt(x, y, len); - if (x < left || x >= right || y >= bottom) return; - if (y < top) { - len += y - top; - y = top; - } - yield* vline(x, y, Math.min(len, bottom - y)); + [x, y, len] = asInt(x, y, len); + if (x < left || x >= right || y >= bottom) return; + if (y < top) { + len += y - top; + y = top; + } + yield* vline(x, y, Math.min(len, bottom - y)); } diff --git a/packages/grid-iterators/src/interleave.ts b/packages/grid-iterators/src/interleave.ts index ff80f0819c..1054bb5153 100644 --- a/packages/grid-iterators/src/interleave.ts +++ b/packages/grid-iterators/src/interleave.ts @@ -18,9 +18,9 @@ import { swapxy } from "./utils.js"; * @param step - column stride */ export function* interleaveColumns2d(cols: number, rows = cols, step = 2) { - for (let j = 0; j < step; j++) { - yield* map(swapxy, range2d(0, rows | 0, j, cols | 0, 1, step | 0)); - } + for (let j = 0; j < step; j++) { + yield* map(swapxy, range2d(0, rows | 0, j, cols | 0, 1, step | 0)); + } } /** @@ -39,7 +39,7 @@ export function* interleaveColumns2d(cols: number, rows = cols, step = 2) { * @param step - row stride */ export function* interleaveRows2d(cols: number, rows = cols, step = 2) { - for (let j = 0; j < step; j++) { - yield* range2d(0, cols | 0, j, rows | 0, 1, step | 0); - } + for (let j = 0; j < step; j++) { + yield* range2d(0, cols | 0, j, rows | 0, 1, step | 0); + } } diff --git a/packages/grid-iterators/src/line.ts b/packages/grid-iterators/src/line.ts index 9bd6ca8503..515ce44647 100644 --- a/packages/grid-iterators/src/line.ts +++ b/packages/grid-iterators/src/line.ts @@ -2,26 +2,26 @@ import { asInt } from "@thi.ng/api/typedarray"; import { liangBarsky } from "./clipping.js"; export function* line(ax: number, ay: number, bx: number, by: number) { - [ax, ay, bx, by] = asInt(ax, ay, bx, by); - const dx = Math.abs(bx - ax); - const dy = -Math.abs(by - ay); - const sx = ax < bx ? 1 : -1; - const sy = ay < by ? 1 : -1; - let err = dx + dy; + [ax, ay, bx, by] = asInt(ax, ay, bx, by); + const dx = Math.abs(bx - ax); + const dy = -Math.abs(by - ay); + const sx = ax < bx ? 1 : -1; + const sy = ay < by ? 1 : -1; + let err = dx + dy; - while (true) { - yield [ax, ay]; - if (ax === bx && ay === by) return; - let t = err << 1; - if (t < dx) { - err += dx; - ay += sy; - } - if (t > dy) { - err += dy; - ax += sx; - } - } + while (true) { + yield [ax, ay]; + if (ax === bx && ay === by) return; + let t = err << 1; + if (t < dx) { + err += dx; + ay += sy; + } + if (t > dy) { + err += dy; + ax += sx; + } + } } /** @@ -29,25 +29,25 @@ export function* line(ax: number, ay: number, bx: number, by: number) { * `left,top`..`right,bottom`. Returns undefined if circle lies completely * outside given clip rectangle. * - * @param x1 - - * @param y1 - - * @param x2 - - * @param y2 - - * @param left - - * @param top - - * @param right - - * @param bottom - + * @param x1 - + * @param y1 - + * @param x2 - + * @param y2 - + * @param left - + * @param top - + * @param right - + * @param bottom - */ export const lineClipped = ( - x1: number, - y1: number, - x2: number, - y2: number, - left: number, - top: number, - right: number, - bottom: number + x1: number, + y1: number, + x2: number, + y2: number, + left: number, + top: number, + right: number, + bottom: number ) => { - const res = liangBarsky(x1, y1, x2, y2, left, top, right - 1, bottom - 1); - return res ? line(res[0], res[1], res[2], res[3]) : undefined; + const res = liangBarsky(x1, y1, x2, y2, left, top, right - 1, bottom - 1); + return res ? line(res[0], res[1], res[2], res[3]) : undefined; }; diff --git a/packages/grid-iterators/src/random.ts b/packages/grid-iterators/src/random.ts index 9b9bcf4787..aa3d4d6a46 100644 --- a/packages/grid-iterators/src/random.ts +++ b/packages/grid-iterators/src/random.ts @@ -14,8 +14,8 @@ import { range } from "@thi.ng/transducers/range"; * @param rnd - PRNG */ export function* random2d(cols: number, rows = cols, rnd: IRandom = SYSTEM) { - [cols, rows] = asInt(cols, rows); - for (let i of shuffle([...range(cols * rows)], undefined, rnd)) { - yield [i % cols, (i / cols) | 0]; - } + [cols, rows] = asInt(cols, rows); + for (let i of shuffle([...range(cols * rows)], undefined, rnd)) { + yield [i % cols, (i / cols) | 0]; + } } diff --git a/packages/grid-iterators/src/row-ends.ts b/packages/grid-iterators/src/row-ends.ts index 020aad61c5..99edc19bb8 100644 --- a/packages/grid-iterators/src/row-ends.ts +++ b/packages/grid-iterators/src/row-ends.ts @@ -8,10 +8,10 @@ import { asInt } from "@thi.ng/api/typedarray"; * @param rows - */ export function* rowEnds2d(cols: number, rows = cols) { - [cols, rows] = asInt(cols, rows); - cols--; - for (let y = 0; y < rows; y++) { - yield [0, y]; - yield [cols, y]; - } + [cols, rows] = asInt(cols, rows); + cols--; + for (let y = 0; y < rows; y++) { + yield [0, y]; + yield [cols, y]; + } } diff --git a/packages/grid-iterators/src/rows.ts b/packages/grid-iterators/src/rows.ts index 697fee27c5..f87cc75e19 100644 --- a/packages/grid-iterators/src/rows.ts +++ b/packages/grid-iterators/src/rows.ts @@ -4,8 +4,8 @@ import { range2d } from "@thi.ng/transducers/range2d"; * Yields sequence of 2D grid coordinates in row-major order. Same as * {@link @thi.ng/transducers#range2d}. * - * @param cols - - * @param rows - + * @param cols - + * @param rows - */ export const rows2d = (cols: number, rows = cols) => - range2d(cols | 0, rows | 0); + range2d(cols | 0, rows | 0); diff --git a/packages/grid-iterators/src/spiral.ts b/packages/grid-iterators/src/spiral.ts index 2f5296ae88..81b8553428 100644 --- a/packages/grid-iterators/src/spiral.ts +++ b/packages/grid-iterators/src/spiral.ts @@ -11,37 +11,37 @@ import { asInt } from "@thi.ng/api/typedarray"; * @param rows - */ export function* spiral2d(cols: number, rows = cols) { - [cols, rows] = asInt(cols, rows); - const num = cols * rows; - const center = (Math.min(cols, rows) - 1) >> 1; - for (let i = 0; i < num; i++) { - let nx = cols; - let ny = rows; - while (i < nx * ny) { - nx--; - ny--; - } - const nxny = nx * ny; - const minnxny = Math.min(nx, ny); - const m2 = minnxny >> 1; - let bx, by; - if (minnxny & 1) { - if (i <= nxny + ny) { - bx = nx - m2; - by = -m2 + i - nxny; - } else { - bx = nx - m2 - (i - (nxny + ny)); - by = ny - m2; - } - } else { - if (i <= nxny + ny) { - bx = -m2; - by = ny - m2 - (i - nxny); - } else { - bx = -m2 + (i - (nxny + ny)); - by = -m2; - } - } - yield [bx + center, by + center]; - } + [cols, rows] = asInt(cols, rows); + const num = cols * rows; + const center = (Math.min(cols, rows) - 1) >> 1; + for (let i = 0; i < num; i++) { + let nx = cols; + let ny = rows; + while (i < nx * ny) { + nx--; + ny--; + } + const nxny = nx * ny; + const minnxny = Math.min(nx, ny); + const m2 = minnxny >> 1; + let bx, by; + if (minnxny & 1) { + if (i <= nxny + ny) { + bx = nx - m2; + by = -m2 + i - nxny; + } else { + bx = nx - m2 - (i - (nxny + ny)); + by = ny - m2; + } + } else { + if (i <= nxny + ny) { + bx = -m2; + by = ny - m2 - (i - nxny); + } else { + bx = -m2 + (i - (nxny + ny)); + by = -m2; + } + } + yield [bx + center, by + center]; + } } diff --git a/packages/grid-iterators/src/utils.ts b/packages/grid-iterators/src/utils.ts index 679aa370ba..9f959dbc88 100644 --- a/packages/grid-iterators/src/utils.ts +++ b/packages/grid-iterators/src/utils.ts @@ -2,13 +2,13 @@ /** * Swaps XY in-place. * - * @param p - + * @param p - * * @internal */ export const swapxy = (p: number[]) => { - const t = p[0]; - p[0] = p[1]; - p[1] = t; - return p; + const t = p[0]; + p[0] = p[1]; + p[1] = t; + return p; }; diff --git a/packages/grid-iterators/src/zcurve.ts b/packages/grid-iterators/src/zcurve.ts index 1fb29a9f3e..82f517880d 100644 --- a/packages/grid-iterators/src/zcurve.ts +++ b/packages/grid-iterators/src/zcurve.ts @@ -12,12 +12,12 @@ import { demux2 } from "@thi.ng/morton/mux"; * @param rows - */ export function* zcurve2d(cols: number, rows = cols) { - [cols, rows] = asInt(cols, rows); - const max = ceilPow2(Math.pow(Math.max(cols, rows), 2)); - for (let i = 0; i < max; i++) { - const p = demux2(i); - if (p[0] < cols && p[1] < rows) { - yield p; - } - } + [cols, rows] = asInt(cols, rows); + const max = ceilPow2(Math.pow(Math.max(cols, rows), 2)); + for (let i = 0; i < max; i++) { + const p = demux2(i); + if (p[0] < cols && p[1] < rows) { + yield p; + } + } } diff --git a/packages/grid-iterators/src/zigzag-columns.ts b/packages/grid-iterators/src/zigzag-columns.ts index 9e82cb440e..58aac0d6b3 100644 --- a/packages/grid-iterators/src/zigzag-columns.ts +++ b/packages/grid-iterators/src/zigzag-columns.ts @@ -12,12 +12,12 @@ import { asInt } from "@thi.ng/api/typedarray"; * */ export function* zigzagColumns2d(cols: number, rows = cols) { - [cols, rows] = asInt(cols, rows); - const num = cols * rows; - for (let i = 0; i < num; i++) { - const x = (i / rows) | 0; - let y = i % rows; - x & 1 && (y = rows - 1 - y); - yield [x, y]; - } + [cols, rows] = asInt(cols, rows); + const num = cols * rows; + for (let i = 0; i < num; i++) { + const x = (i / rows) | 0; + let y = i % rows; + x & 1 && (y = rows - 1 - y); + yield [x, y]; + } } diff --git a/packages/grid-iterators/src/zigzag-diagonal.ts b/packages/grid-iterators/src/zigzag-diagonal.ts index 6ff6027195..6650f24583 100644 --- a/packages/grid-iterators/src/zigzag-diagonal.ts +++ b/packages/grid-iterators/src/zigzag-diagonal.ts @@ -8,33 +8,33 @@ import { asInt } from "@thi.ng/api/typedarray"; * @param rows - */ export function* zigzagDiagonal2d(cols: number, rows = cols) { - [cols, rows] = asInt(cols, rows); - const num = cols * rows - 1; - for ( - let x = 0, y = 0, ny = 0, dx = -1, dy = 1, d = 0, down = true, i = 0; - i <= num; - i++ - ) { - yield [x, y]; - if (i !== num) { - do { - if (y === ny) { - if (down) { - y++; - d++; - ny = 0; - } else { - x++; - ny = ++d; - } - down = !down; - dx *= -1; - dy *= -1; - } else { - x += dx; - y += dy; - } - } while (x >= cols || y >= rows); - } - } + [cols, rows] = asInt(cols, rows); + const num = cols * rows - 1; + for ( + let x = 0, y = 0, ny = 0, dx = -1, dy = 1, d = 0, down = true, i = 0; + i <= num; + i++ + ) { + yield [x, y]; + if (i !== num) { + do { + if (y === ny) { + if (down) { + y++; + d++; + ny = 0; + } else { + x++; + ny = ++d; + } + down = !down; + dx *= -1; + dy *= -1; + } else { + x += dx; + y += dy; + } + } while (x >= cols || y >= rows); + } + } } diff --git a/packages/grid-iterators/src/zigzag-rows.ts b/packages/grid-iterators/src/zigzag-rows.ts index 0eb2349c50..afc09abcb2 100644 --- a/packages/grid-iterators/src/zigzag-rows.ts +++ b/packages/grid-iterators/src/zigzag-rows.ts @@ -12,12 +12,12 @@ import { asInt } from "@thi.ng/api/typedarray"; * */ export function* zigzagRows2d(cols: number, rows = cols) { - [cols, rows] = asInt(cols, rows); - const num = cols * rows; - for (let i = 0; i < num; i++) { - let x = i % cols; - const y = (i / cols) | 0; - y & 1 && (x = cols - 1 - x); - yield [x, y]; - } + [cols, rows] = asInt(cols, rows); + const num = cols * rows; + for (let i = 0; i < num; i++) { + let x = i % cols; + const y = (i / cols) | 0; + y & 1 && (x = cols - 1 - x); + yield [x, y]; + } } diff --git a/packages/grid-iterators/tools/build-assets.js b/packages/grid-iterators/tools/build-assets.js index c3f30347be..e9a9c1800a 100644 --- a/packages/grid-iterators/tools/build-assets.js +++ b/packages/grid-iterators/tools/build-assets.js @@ -5,64 +5,64 @@ const fs = require("fs"); const execSync = require("child_process").execSync; try { - fs.mkdirSync("export"); + fs.mkdirSync("export"); } catch (e) {} [ - gi.columns2d, - gi.rows2d, - gi.diagonal2d, - gi.hilbert2d, - gi.interleaveColumns2d, - gi.interleaveRows2d, - gi.random2d, - gi.spiral2d, - gi.zcurve2d, - gi.zigzagColumns2d, - gi.zigzagDiagonal2d, - gi.zigzagRows2d, + gi.columns2d, + gi.rows2d, + gi.diagonal2d, + gi.hilbert2d, + gi.interleaveColumns2d, + gi.interleaveRows2d, + gi.random2d, + gi.spiral2d, + gi.zcurve2d, + gi.zigzagColumns2d, + gi.zigzagDiagonal2d, + gi.zigzagRows2d, ].forEach((fn) => { - console.log(`generating ${fn.name}...`); - const pts = [...fn(16)]; - const base = `export/${fn.name.toLowerCase()}`; - for (let i = 1; i <= 128; i++) { - fs.writeFileSync( - `${base}-${str.Z3(i)}.svg`, - g.asSvg( - g.svgDoc( - { - width: 600, - height: 600, - viewBox: "-1 -1 18 18", - stroke: "black", - "stroke-width": 0.1, - }, - g.polyline( - pts.slice(0, i * 2).map(([x, y]) => [x + 0.5, y + 0.5]) - ), - g.group( - { fill: [0, 1, 0.5, 0.25], stroke: "none" }, - pts.slice(0, i * 2 - 1).map((p) => g.rect(p, 1)) - ), - g.rect(pts[i * 2 - 1], 1, { - fill: [0, 1, 0.5, 0.85], - stroke: "none", - }) - ) - ) - ); - } - console.log(`\tconverting to PNG...`); - execSync(`../../scripts/svg2png ${base}*.svg`); - console.log(`\tremoving SVG files...`); - execSync(`rm ${base}*.svg`); - console.log(`\tbuilding GIFs...`); - execSync(`gm convert -delay 6 ${base}*.png ${base}.gif`); - execSync( - `gm convert -delay 6 -resize 200x200 ${base}*.png ${base}-small.gif` - ); - console.log(`\tbuilding MP4...`); - execSync( - `ffmpeg -r 30 -i ${base}-%03d.png -c:v libx264 -pix_fmt yuv420p -y ${base}.mp4` - ); + console.log(`generating ${fn.name}...`); + const pts = [...fn(16)]; + const base = `export/${fn.name.toLowerCase()}`; + for (let i = 1; i <= 128; i++) { + fs.writeFileSync( + `${base}-${str.Z3(i)}.svg`, + g.asSvg( + g.svgDoc( + { + width: 600, + height: 600, + viewBox: "-1 -1 18 18", + stroke: "black", + "stroke-width": 0.1, + }, + g.polyline( + pts.slice(0, i * 2).map(([x, y]) => [x + 0.5, y + 0.5]) + ), + g.group( + { fill: [0, 1, 0.5, 0.25], stroke: "none" }, + pts.slice(0, i * 2 - 1).map((p) => g.rect(p, 1)) + ), + g.rect(pts[i * 2 - 1], 1, { + fill: [0, 1, 0.5, 0.85], + stroke: "none", + }) + ) + ) + ); + } + console.log(`\tconverting to PNG...`); + execSync(`../../scripts/svg2png ${base}*.svg`); + console.log(`\tremoving SVG files...`); + execSync(`rm ${base}*.svg`); + console.log(`\tbuilding GIFs...`); + execSync(`gm convert -delay 6 ${base}*.png ${base}.gif`); + execSync( + `gm convert -delay 6 -resize 200x200 ${base}*.png ${base}-small.gif` + ); + console.log(`\tbuilding MP4...`); + execSync( + `ffmpeg -r 30 -i ${base}-%03d.png -c:v libx264 -pix_fmt yuv420p -y ${base}.mp4` + ); }); diff --git a/packages/grid-iterators/tsconfig.json b/packages/grid-iterators/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/grid-iterators/tsconfig.json +++ b/packages/grid-iterators/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/hdiff/api-extractor.json b/packages/hdiff/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/hdiff/api-extractor.json +++ b/packages/hdiff/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/hdiff/package.json b/packages/hdiff/package.json index 019eef07b5..8796f022f8 100644 --- a/packages/hdiff/package.json +++ b/packages/hdiff/package.json @@ -1,96 +1,96 @@ { - "name": "@thi.ng/hdiff", - "version": "0.3.13", - "description": "String diffing w/ hiccup output for further processing, e.g. with @thi.ng/hdom, @thi.ng/hiccup. Includes CLI util to generate HTML, with theme support and code folding", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "bin": "bin/hdiff", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/hdiff#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/diff": "^5.1.8", - "@thi.ng/hiccup": "^4.2.10", - "@thi.ng/hiccup-css": "^2.1.12", - "@thi.ng/strings": "^3.3.6" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "@types/node": "^17.0.41", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "cli", - "css", - "diff", - "file", - "git", - "hiccup", - "html", - "theme", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "bin" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./diff": { - "default": "./diff.js" - }, - "./html": { - "default": "./html.js" - }, - "./theme": { - "default": "./theme.js" - } - }, - "thi.ng": { - "status": "alpha", - "year": 2018 - } + "name": "@thi.ng/hdiff", + "version": "0.3.13", + "description": "String diffing w/ hiccup output for further processing, e.g. with @thi.ng/hdom, @thi.ng/hiccup. Includes CLI util to generate HTML, with theme support and code folding", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "bin": "bin/hdiff", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/hdiff#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/diff": "^5.1.8", + "@thi.ng/hiccup": "^4.2.10", + "@thi.ng/hiccup-css": "^2.1.12", + "@thi.ng/strings": "^3.3.6" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "@types/node": "^17.0.41", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "cli", + "css", + "diff", + "file", + "git", + "hiccup", + "html", + "theme", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "bin" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./diff": { + "default": "./diff.js" + }, + "./html": { + "default": "./html.js" + }, + "./theme": { + "default": "./theme.js" + } + }, + "thi.ng": { + "status": "alpha", + "year": 2018 + } } diff --git a/packages/hdiff/src/api.ts b/packages/hdiff/src/api.ts index 55d77dbfc8..c26b6d3964 100644 --- a/packages/hdiff/src/api.ts +++ b/packages/hdiff/src/api.ts @@ -4,57 +4,57 @@ export type BgFg = [string, string]; export type BgFgBorder = [string, string, string]; export interface Theme { - selection: BgFg; - header: BgFg; - diff: { - add: { - main: BgFg; - word: BgFg; - side: BgFgBorder; - }; - del: { - main: BgFg; - word: BgFg; - side: BgFgBorder; - }; - nochange: { - main: BgFg; - side: BgFgBorder; - }; - fold: BgFg; - hover: { - main: BgFg; - side: BgFgBorder; - }; - }; + selection: BgFg; + header: BgFg; + diff: { + add: { + main: BgFg; + word: BgFg; + side: BgFgBorder; + }; + del: { + main: BgFg; + word: BgFg; + side: BgFgBorder; + }; + nochange: { + main: BgFg; + side: BgFgBorder; + }; + fold: BgFg; + hover: { + main: BgFg; + side: BgFgBorder; + }; + }; } export const THEMES: IObjectOf = { - default: { - selection: ["black", "white"], - header: ["ghostwhite", "black"], - diff: { - add: { - main: ["#dfe", "#000"], - side: ["lightgreen", "#393", "#9c9"], - word: ["lightgreen", "#000"], - }, - del: { - main: ["#fde", "#000"], - side: ["lightcoral", "#933", "#c99"], - word: ["orangered", "#000"], - }, - nochange: { - main: ["#fff", "#000"], - side: ["#eee", "#999", "#ccc"], - }, - fold: ["whitesmoke", "#666"], - hover: { - main: ["papayawhip", "#000"], - side: ["papayawhip", "#993", "#cc9"], - }, - }, - }, + default: { + selection: ["black", "white"], + header: ["ghostwhite", "black"], + diff: { + add: { + main: ["#dfe", "#000"], + side: ["lightgreen", "#393", "#9c9"], + word: ["lightgreen", "#000"], + }, + del: { + main: ["#fde", "#000"], + side: ["lightcoral", "#933", "#c99"], + word: ["orangered", "#000"], + }, + nochange: { + main: ["#fff", "#000"], + side: ["#eee", "#999", "#ccc"], + }, + fold: ["whitesmoke", "#666"], + hover: { + main: ["papayawhip", "#000"], + side: ["papayawhip", "#993", "#cc9"], + }, + }, + }, }; export const DEFAULT_THEME = THEMES.default; diff --git a/packages/hdiff/src/cli.ts b/packages/hdiff/src/cli.ts index b7641933a0..c78e199209 100644 --- a/packages/hdiff/src/cli.ts +++ b/packages/hdiff/src/cli.ts @@ -10,18 +10,18 @@ let headerBody; const args = process.argv.slice(2); if (args.length === 3) { - const [path, rev1, rev2] = args; - src1 = execSync(`git show ${rev1}:${path}`).toString(); - src2 = execSync(`git show ${rev2}:${path}`).toString(); - headerBody = ["header", ["h1", path], ["code", `${rev1} ⇌ ${rev2}`]]; + const [path, rev1, rev2] = args; + src1 = execSync(`git show ${rev1}:${path}`).toString(); + src2 = execSync(`git show ${rev2}:${path}`).toString(); + headerBody = ["header", ["h1", path], ["code", `${rev1} ⇌ ${rev2}`]]; } else if (args.length === 2) { - const [rev1, rev2] = args; - src1 = readFileSync(rev1).toString(); - src2 = readFileSync(rev2).toString(); - headerBody = ["header", ["h1", "File diff"], ["code", `${rev1} ⇌ ${rev2}`]]; + const [rev1, rev2] = args; + src1 = readFileSync(rev1).toString(); + src2 = readFileSync(rev2).toString(); + headerBody = ["header", ["h1", "File diff"], ["code", `${rev1} ⇌ ${rev2}`]]; } else { - console.log("Usage:\n\thdiff file1 file2\n\thdiff relpath gitrev1 gitrev2"); - process.exit(1); + console.log("Usage:\n\thdiff file1 file2\n\thdiff relpath gitrev1 gitrev2"); + process.exit(1); } console.log(generateHtml(computeDiff(src1, src2), headerBody)); diff --git a/packages/hdiff/src/diff.ts b/packages/hdiff/src/diff.ts index d648f24b56..c1e7de7437 100644 --- a/packages/hdiff/src/diff.ts +++ b/packages/hdiff/src/diff.ts @@ -5,74 +5,74 @@ import { padLeft } from "@thi.ng/strings/pad-left"; const FMT_LN = padLeft(4, " "); export const computeDiff = (a: string, b: string) => { - const edits = diffArray( - a.split("\n"), - b.split("\n"), - "only-distance-linear" - ).linear!; - for (let i = 0; i < edits.length; i += 3) { - const lineID = edits[i]; - if (lineID) updateOffset(edits, i, lineID); - } - const result: any[] = ["div", {}]; - let block: any[] | undefined; - let numSame = 0; - for (let i = 0; i < edits.length; i += 3) { - if (!block) block = ["pre", {}]; - const lineID = edits[i]; - if (lineID == 0) { - numSame++; - if (numSame > 2) { - numSame = 0; - // scan forward to check if foldable - let j = i; - do { - j += 3; - } while (j < edits.length && edits[j] === 0); - if (j - i > 12) { - result.push(block, foldedBlock(edits, i, j - 6)); - block = undefined; - i = j - 9; - continue; - } - } - } else { - numSame = 0; - } - block!.push(codeLine(edits, i, true)); - } - if (block) result.push(block); - return result; + const edits = diffArray( + a.split("\n"), + b.split("\n"), + "only-distance-linear" + ).linear!; + for (let i = 0; i < edits.length; i += 3) { + const lineID = edits[i]; + if (lineID) updateOffset(edits, i, lineID); + } + const result: any[] = ["div", {}]; + let block: any[] | undefined; + let numSame = 0; + for (let i = 0; i < edits.length; i += 3) { + if (!block) block = ["pre", {}]; + const lineID = edits[i]; + if (lineID == 0) { + numSame++; + if (numSame > 2) { + numSame = 0; + // scan forward to check if foldable + let j = i; + do { + j += 3; + } while (j < edits.length && edits[j] === 0); + if (j - i > 12) { + result.push(block, foldedBlock(edits, i, j - 6)); + block = undefined; + i = j - 9; + continue; + } + } + } else { + numSame = 0; + } + block!.push(codeLine(edits, i, true)); + } + if (block) result.push(block); + return result; }; const updateOffset = (edits: any[], i: number, delta: number) => { - for (; i < edits.length; i += 3) { - if (edits[i] === 0) edits[i + 1] += delta; - } + for (; i < edits.length; i += 3) { + if (edits[i] === 0) edits[i + 1] += delta; + } }; const codeLine = (edits: any[], i: number, body = false): any[] => [ - "code", - { - "data-diff": ["-", " ", "+"][edits[i] + 1], - "data-lnum": FMT_LN(edits[i + 1] + 1), - }, - body ? escapeEntities(edits[i + 2]) : null, + "code", + { + "data-diff": ["-", " ", "+"][edits[i] + 1], + "data-lnum": FMT_LN(edits[i + 1] + 1), + }, + body ? escapeEntities(edits[i + 2]) : null, ]; const foldedBlock = (edits: any[], i: number, j: number): any[] => { - const block = [ - "pre", - { "data-fold": true }, - [ - "code", - { - "data-fold-range": `${edits[i + 4]} - ${edits[j + 1]}`, - }, - ], - ]; - for (; i < j; i += 3) { - block.push(codeLine(edits, i, true)); - } - return block; + const block = [ + "pre", + { "data-fold": true }, + [ + "code", + { + "data-fold-range": `${edits[i + 4]} - ${edits[j + 1]}`, + }, + ], + ]; + for (; i < j; i += 3) { + block.push(codeLine(edits, i, true)); + } + return block; }; diff --git a/packages/hdiff/src/html.ts b/packages/hdiff/src/html.ts index c9082b2553..52a0d1fd3f 100644 --- a/packages/hdiff/src/html.ts +++ b/packages/hdiff/src/html.ts @@ -3,31 +3,31 @@ import { DEFAULT_THEME, Theme } from "./api.js"; import { compileTheme } from "./theme.js"; export const generateHtml = ( - diff: any[], - headerBody: any[], - theme: Theme = DEFAULT_THEME + diff: any[], + headerBody: any[], + theme: Theme = DEFAULT_THEME ) => - serialize([ - ["!DOCTYPE", "html"], - [ - "html", - [ - "head", - ["meta", { charset: "UTF-8" }], - [ - "meta", - { - name: "viewport", - content: "width=device-width, initial-scale=1.0", - }, - ], - [ - "meta", - { name: "generator", content: "https://thi.ng/hdiff" }, - ], - ["title", "hdiff"], - ["style", compileTheme(theme)], - ], - ["body", headerBody, ["main", diff]], - ], - ]); + serialize([ + ["!DOCTYPE", "html"], + [ + "html", + [ + "head", + ["meta", { charset: "UTF-8" }], + [ + "meta", + { + name: "viewport", + content: "width=device-width, initial-scale=1.0", + }, + ], + [ + "meta", + { name: "generator", content: "https://thi.ng/hdiff" }, + ], + ["title", "hdiff"], + ["style", compileTheme(theme)], + ], + ["body", headerBody, ["main", diff]], + ], + ]); diff --git a/packages/hdiff/src/theme.ts b/packages/hdiff/src/theme.ts index d30663f15d..657347f310 100644 --- a/packages/hdiff/src/theme.ts +++ b/packages/hdiff/src/theme.ts @@ -6,130 +6,130 @@ const none = { display: "none" }; const lnum = "attr(data-lnum)"; export const compileTheme = (theme: Theme): string => - css( - [ - [ - "body", - "div", - "pre", - "code", - { - "box-sizing": "border-box", - margin: 0, - }, - ], - ["body", { "font-family": "sans-serif" }], - [ - "header", - bgfg(theme.header), - { - position: "fixed", - top: 0, - width: "100%", - padding: "0.5rem", - "box-shadow": "-8px 0 8px #666", - }, - [ - "h1", - { - margin: "0 0 0.25rem 0", - padding: "0 0 0.25rem 0", - "border-bottom": "1px dotted currentColor", - "font-weight": 100, - }, - ], - ["code", { "font-size": "66%" }], - ], - ["main", { "margin-top": "5rem" }], - [ - "pre", - { - "font-size": "0.8rem", - "line-height": "1.2rem", - }, - [ - "> code", - block, - [ - ":before", - bgfgBorder(theme.diff.nochange.side), - { - padding: "0.23rem 0.5rem 0.23rem 1rem", - "margin-right": "1rem", - "font-size": "0.64rem", - }, - ], - [ - ":hover", - bgfg(theme.diff.hover.main), - [":before", bgfgBorder(theme.diff.hover.side)], - ], - ], - [ - "[data-fold]", - [ - "> code[data-fold-range]:before", - block, - bgfg(theme.diff.fold), - { - content: `"⥣ ⋯⋯ Folded lines: " attr(data-fold-range) " ⋯⋯ ⥥"`, - width: "100%", - border: "1px dotted", - }, - ], - [`> code${diffAttr(" ")}`, none], - [ - ":hover", - [ - `> code${diffAttr(" ")}`, - block, - bgfg(theme.diff.hover.main), - ], - [`> code[data-fold-range]:before`, none], - ], - ], - ], - diffMode(theme.diff.add, "+", `" " ${lnum} " +"`), - diffMode(theme.diff.del, "-", `${lnum} " -"`), - diffMode(theme.diff.nochange, " ", `" " ${lnum} " "`), - [ - "code", - { - "font-size": "1em", - "font-family": "Consolas, monaco, monospace", - }, - ], - ["*::selection", bgfg(theme.selection)], - ] - // { format: PRETTY } - ); + css( + [ + [ + "body", + "div", + "pre", + "code", + { + "box-sizing": "border-box", + margin: 0, + }, + ], + ["body", { "font-family": "sans-serif" }], + [ + "header", + bgfg(theme.header), + { + position: "fixed", + top: 0, + width: "100%", + padding: "0.5rem", + "box-shadow": "-8px 0 8px #666", + }, + [ + "h1", + { + margin: "0 0 0.25rem 0", + padding: "0 0 0.25rem 0", + "border-bottom": "1px dotted currentColor", + "font-weight": 100, + }, + ], + ["code", { "font-size": "66%" }], + ], + ["main", { "margin-top": "5rem" }], + [ + "pre", + { + "font-size": "0.8rem", + "line-height": "1.2rem", + }, + [ + "> code", + block, + [ + ":before", + bgfgBorder(theme.diff.nochange.side), + { + padding: "0.23rem 0.5rem 0.23rem 1rem", + "margin-right": "1rem", + "font-size": "0.64rem", + }, + ], + [ + ":hover", + bgfg(theme.diff.hover.main), + [":before", bgfgBorder(theme.diff.hover.side)], + ], + ], + [ + "[data-fold]", + [ + "> code[data-fold-range]:before", + block, + bgfg(theme.diff.fold), + { + content: `"⥣ ⋯⋯ Folded lines: " attr(data-fold-range) " ⋯⋯ ⥥"`, + width: "100%", + border: "1px dotted", + }, + ], + [`> code${diffAttr(" ")}`, none], + [ + ":hover", + [ + `> code${diffAttr(" ")}`, + block, + bgfg(theme.diff.hover.main), + ], + [`> code[data-fold-range]:before`, none], + ], + ], + ], + diffMode(theme.diff.add, "+", `" " ${lnum} " +"`), + diffMode(theme.diff.del, "-", `${lnum} " -"`), + diffMode(theme.diff.nochange, " ", `" " ${lnum} " "`), + [ + "code", + { + "font-size": "1em", + "font-family": "Consolas, monaco, monospace", + }, + ], + ["*::selection", bgfg(theme.selection)], + ] + // { format: PRETTY } + ); const bgfg = ([background, color]: BgFg) => ({ - background, - color, + background, + color, }); const bgfgBorder = ([background, color, br]: BgFgBorder) => ({ - background, - color, - "border-right": `1px solid ${br}`, + background, + color, + "border-right": `1px solid ${br}`, }); const diffAttr = (id: string) => `[data-diff="${id}"]`; const diffMode = ( - { main, side, word }: { main: BgFg; word?: BgFg; side: BgFgBorder }, - mode: string, - content: string + { main, side, word }: { main: BgFg; word?: BgFg; side: BgFgBorder }, + mode: string, + content: string ) => [ - `code${diffAttr(mode)}`, - bgfg(main), - word ? [`> span${diffAttr(mode)}`, bgfg(word)] : null, - [ - ":before", - { - ...bgfgBorder(side), - content, - }, - ], + `code${diffAttr(mode)}`, + bgfg(main), + word ? [`> span${diffAttr(mode)}`, bgfg(word)] : null, + [ + ":before", + { + ...bgfgBorder(side), + content, + }, + ], ]; diff --git a/packages/hdiff/tsconfig.json b/packages/hdiff/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/hdiff/tsconfig.json +++ b/packages/hdiff/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/hdom-canvas/api-extractor.json b/packages/hdom-canvas/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/hdom-canvas/api-extractor.json +++ b/packages/hdom-canvas/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/hdom-canvas/package.json b/packages/hdom-canvas/package.json index 9a79775b5c..f56521d954 100644 --- a/packages/hdom-canvas/package.json +++ b/packages/hdom-canvas/package.json @@ -1,92 +1,92 @@ { - "name": "@thi.ng/hdom-canvas", - "version": "4.1.17", - "description": "@thi.ng/hdom component wrapper for declarative canvas scenegraphs", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-canvas#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc draw", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/diff": "^5.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/hdom": "^9.1.10", - "@thi.ng/hiccup-canvas": "^2.1.17" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "array", - "browser", - "canvas", - "circle", - "datadriven", - "drawing", - "graphics", - "hdom", - "hiccup", - "scenegraph", - "tree", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/hdom", - "related": [ - "geom", - "hdom", - "hiccup-canvas", - "hiccup-svg", - "rdom-canvas" - ], - "year": 2018 - } + "name": "@thi.ng/hdom-canvas", + "version": "4.1.17", + "description": "@thi.ng/hdom component wrapper for declarative canvas scenegraphs", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-canvas#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc draw", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/diff": "^5.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/hdom": "^9.1.10", + "@thi.ng/hiccup-canvas": "^2.1.17" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "array", + "browser", + "canvas", + "circle", + "datadriven", + "drawing", + "graphics", + "hdom", + "hiccup", + "scenegraph", + "tree", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/hdom", + "related": [ + "geom", + "hdom", + "hiccup-canvas", + "hiccup-svg", + "rdom-canvas" + ], + "year": 2018 + } } diff --git a/packages/hdom-canvas/src/index.ts b/packages/hdom-canvas/src/index.ts index 5ccb2740db..b4eee6c5d1 100644 --- a/packages/hdom-canvas/src/index.ts +++ b/packages/hdom-canvas/src/index.ts @@ -49,136 +49,136 @@ const STR = "string"; * @param shapes - shape components */ export const canvas = { - render(_: any, attribs: any, ...body: any[]) { - const cattribs = { ...attribs }; - delete cattribs.__diff; - delete cattribs.__normalize; - const dpr = window.devicePixelRatio || 1; - if (dpr !== 1) { - !cattribs.style && (cattribs.style = {}); - cattribs.style.width = `${cattribs.width}px`; - cattribs.style.height = `${cattribs.height}px`; - cattribs.width *= dpr; - cattribs.height *= dpr; - } - return [ - "canvas", - cattribs, - [ - "g", - { - __impl: IMPL, - __diff: attribs.__diff !== false, - __normalize: attribs.__normalize !== false, - __release: attribs.__release === true, - __serialize: false, - __clear: attribs.__clear, - scale: dpr !== 1 ? dpr : null, - }, - ...body, - ], - ]; - }, + render(_: any, attribs: any, ...body: any[]) { + const cattribs = { ...attribs }; + delete cattribs.__diff; + delete cattribs.__normalize; + const dpr = window.devicePixelRatio || 1; + if (dpr !== 1) { + !cattribs.style && (cattribs.style = {}); + cattribs.style.width = `${cattribs.width}px`; + cattribs.style.height = `${cattribs.height}px`; + cattribs.width *= dpr; + cattribs.height *= dpr; + } + return [ + "canvas", + cattribs, + [ + "g", + { + __impl: IMPL, + __diff: attribs.__diff !== false, + __normalize: attribs.__normalize !== false, + __release: attribs.__release === true, + __serialize: false, + __clear: attribs.__clear, + scale: dpr !== 1 ? dpr : null, + }, + ...body, + ], + ]; + }, }; /** @internal */ export const createTree = ( - _: Partial, - canvas: HTMLCanvasElement, - tree: any + _: Partial, + canvas: HTMLCanvasElement, + tree: any ) => { - // console.log(Date.now(), "draw"); - const ctx = canvas.getContext("2d"); - assert(!!ctx, "canvas ctx unavailable"); - const attribs = tree[1]; - if (attribs) { - if (attribs.__skip) return; - if (attribs.__clear !== false) { - ctx!.clearRect(0, 0, canvas.width, canvas.height); - } - } - draw(ctx!, tree); + // console.log(Date.now(), "draw"); + const ctx = canvas.getContext("2d"); + assert(!!ctx, "canvas ctx unavailable"); + const attribs = tree[1]; + if (attribs) { + if (attribs.__skip) return; + if (attribs.__clear !== false) { + ctx!.clearRect(0, 0, canvas.width, canvas.height); + } + } + draw(ctx!, tree); }; /** @internal */ export const normalizeTree = (opts: Partial, tree: any): any => { - if (tree == null) { - return tree; - } - if (isArray(tree)) { - const tag = tree[0]; - if (typeof tag === FN) { - return normalizeTree( - opts, - tag.apply(null, [opts.ctx, ...tree.slice(1)]) - ); - } - if (typeof tag === STR) { - const attribs = tree[1]; - if (attribs && attribs.__normalize === false) { - return tree; - } - const res = [tree[0], attribs]; - for (let i = 2, n = tree.length; i < n; i++) { - const n = normalizeTree(opts, tree[i]); - n != null && res.push(n); - } - return res; - } - } else if (typeof tree === FN) { - return normalizeTree(opts, tree(opts.ctx)); - } else if (typeof tree.toHiccup === FN) { - return normalizeTree(opts, tree.toHiccup(opts.ctx)); - } else if (typeof tree.deref === FN) { - return normalizeTree(opts, tree.deref()); - } else if (isNotStringAndIterable(tree)) { - const res = []; - for (let t of tree) { - const n = normalizeTree(opts, t); - n != null && res.push(n); - } - return res; - } - return tree; + if (tree == null) { + return tree; + } + if (isArray(tree)) { + const tag = tree[0]; + if (typeof tag === FN) { + return normalizeTree( + opts, + tag.apply(null, [opts.ctx, ...tree.slice(1)]) + ); + } + if (typeof tag === STR) { + const attribs = tree[1]; + if (attribs && attribs.__normalize === false) { + return tree; + } + const res = [tree[0], attribs]; + for (let i = 2, n = tree.length; i < n; i++) { + const n = normalizeTree(opts, tree[i]); + n != null && res.push(n); + } + return res; + } + } else if (typeof tree === FN) { + return normalizeTree(opts, tree(opts.ctx)); + } else if (typeof tree.toHiccup === FN) { + return normalizeTree(opts, tree.toHiccup(opts.ctx)); + } else if (typeof tree.deref === FN) { + return normalizeTree(opts, tree.deref()); + } else if (isNotStringAndIterable(tree)) { + const res = []; + for (let t of tree) { + const n = normalizeTree(opts, t); + n != null && res.push(n); + } + return res; + } + return tree; }; /** @internal */ export const diffTree = ( - opts: Partial, - parent: HTMLCanvasElement, - prev: any[], - curr: any[], - child: number + opts: Partial, + parent: HTMLCanvasElement, + prev: any[], + curr: any[], + child: number ) => { - const attribs = curr[1]; - if (attribs.__skip) return; - if (attribs.__diff === false) { - releaseTree(prev); - return createTree(opts, parent, curr); - } - // delegate to branch-local implementation - let impl: HDOMImplementation = attribs.__impl; - if (impl && impl !== IMPL) { - return impl.diffTree(opts, parent, prev, curr, child); - } - const delta = diffArray(prev, curr, "only-distance", equiv); - if (delta.distance > 0) { - return createTree(opts, parent, curr); - } + const attribs = curr[1]; + if (attribs.__skip) return; + if (attribs.__diff === false) { + releaseTree(prev); + return createTree(opts, parent, curr); + } + // delegate to branch-local implementation + let impl: HDOMImplementation = attribs.__impl; + if (impl && impl !== IMPL) { + return impl.diffTree(opts, parent, prev, curr, child); + } + const delta = diffArray(prev, curr, "only-distance", equiv); + if (delta.distance > 0) { + return createTree(opts, parent, curr); + } }; export const IMPL: HDOMImplementation = { - createTree, - normalizeTree, - diffTree, - hydrateTree: NO_OP, - getElementById: NO_OP, - createElement: NO_OP, - createTextElement: NO_OP, - replaceChild: NO_OP, - getChild: NO_OP, - removeAttribs: NO_OP, - removeChild: NO_OP, - setAttrib: NO_OP, - setContent: NO_OP, + createTree, + normalizeTree, + diffTree, + hydrateTree: NO_OP, + getElementById: NO_OP, + createElement: NO_OP, + createTextElement: NO_OP, + replaceChild: NO_OP, + getChild: NO_OP, + removeAttribs: NO_OP, + removeChild: NO_OP, + setAttrib: NO_OP, + setContent: NO_OP, }; diff --git a/packages/hdom-canvas/tsconfig.json b/packages/hdom-canvas/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/hdom-canvas/tsconfig.json +++ b/packages/hdom-canvas/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/hdom-components/api-extractor.json b/packages/hdom-components/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/hdom-components/api-extractor.json +++ b/packages/hdom-components/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/hdom-components/package.json b/packages/hdom-components/package.json index fd6926e1e5..0746415846 100644 --- a/packages/hdom-components/package.json +++ b/packages/hdom-components/package.json @@ -1,113 +1,113 @@ { - "name": "@thi.ng/hdom-components", - "version": "5.1.12", - "description": "Raw, skinnable UI & SVG components for @thi.ng/hdom", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-components#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc utils", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/adapt-dpi": "^2.1.8", - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/math": "^5.3.4", - "@thi.ng/transducers": "^8.3.7", - "@thi.ng/transducers-stats": "^2.1.12" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "browser", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts", - "utils" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./button-group": { - "default": "./button-group.js" - }, - "./button": { - "default": "./button.js" - }, - "./canvas": { - "default": "./canvas.js" - }, - "./dropdown": { - "default": "./dropdown.js" - }, - "./fps-counter": { - "default": "./fps-counter.js" - }, - "./link": { - "default": "./link.js" - }, - "./notification": { - "default": "./notification.js" - }, - "./pager": { - "default": "./pager.js" - }, - "./sparkline": { - "default": "./sparkline.js" - }, - "./title": { - "default": "./title.js" - }, - "./toggle": { - "default": "./toggle.js" - }, - "./utils/merge-attribs": { - "default": "./utils/merge-attribs.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/hdom", - "status": "beta", - "year": 2018 - } + "name": "@thi.ng/hdom-components", + "version": "5.1.12", + "description": "Raw, skinnable UI & SVG components for @thi.ng/hdom", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-components#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc utils", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/adapt-dpi": "^2.1.8", + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/math": "^5.3.4", + "@thi.ng/transducers": "^8.3.7", + "@thi.ng/transducers-stats": "^2.1.12" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "browser", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts", + "utils" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./button-group": { + "default": "./button-group.js" + }, + "./button": { + "default": "./button.js" + }, + "./canvas": { + "default": "./canvas.js" + }, + "./dropdown": { + "default": "./dropdown.js" + }, + "./fps-counter": { + "default": "./fps-counter.js" + }, + "./link": { + "default": "./link.js" + }, + "./notification": { + "default": "./notification.js" + }, + "./pager": { + "default": "./pager.js" + }, + "./sparkline": { + "default": "./sparkline.js" + }, + "./title": { + "default": "./title.js" + }, + "./toggle": { + "default": "./toggle.js" + }, + "./utils/merge-attribs": { + "default": "./utils/merge-attribs.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/hdom", + "status": "beta", + "year": 2018 + } } diff --git a/packages/hdom-components/src/button-group.ts b/packages/hdom-components/src/button-group.ts index 916ac9c141..1c9b7899c4 100644 --- a/packages/hdom-components/src/button-group.ts +++ b/packages/hdom-components/src/button-group.ts @@ -6,38 +6,38 @@ import type { Button, ButtonArgs } from "./button.js"; * Button group component config options. */ export interface ButtonGroupOpts { - /** - * Pre-configured stateless button component function for first - * button in group. MUST be provided. - */ - first: Button; - /** - * Pre-configured stateless button component function for inner - * buttons in group. Only used if at least 3 buttons in group. - * If not specified, `first` will be used. - */ - inner?: Button; - /** - * Pre-configured stateless button component function for last - * button in group. If not specified, `first` will be used. - */ - last?: Button; - /** - * Attribs for button group container. - */ - attribs?: IObjectOf; + /** + * Pre-configured stateless button component function for first + * button in group. MUST be provided. + */ + first: Button; + /** + * Pre-configured stateless button component function for inner + * buttons in group. Only used if at least 3 buttons in group. + * If not specified, `first` will be used. + */ + inner?: Button; + /** + * Pre-configured stateless button component function for last + * button in group. If not specified, `first` will be used. + */ + last?: Button; + /** + * Attribs for button group container. + */ + attribs?: IObjectOf; } export interface ButtonGroupArgs { - /** - * User supplied attribute overrides. - */ - attribs: IObjectOf; - /** - * Disabled flag for entire button group. - * Default: none - */ - disabled: boolean; + /** + * User supplied attribute overrides. + */ + attribs: IObjectOf; + /** + * Disabled flag for entire button group. + * Default: none + */ + disabled: boolean; } /** @@ -48,14 +48,14 @@ export interface ButtonGroupArgs { * ``` */ export interface ButtonGroupItem extends Array { - [0]: ButtonArgs; - [id: number]: any; + [0]: ButtonArgs; + [id: number]: any; } export type ButtonGroup = ( - _: any, - args: ButtonGroupArgs, - ...buttons: ButtonGroupItem[] + _: any, + args: ButtonGroupArgs, + ...buttons: ButtonGroupItem[] ) => any; /** @@ -76,41 +76,41 @@ export type ButtonGroup = ( * @param opts - */ export const buttonGroup = - (opts: ButtonGroupOpts): ButtonGroup => - (_, args: ButtonGroupArgs, ...buttons: ButtonGroupItem[]) => - [ - "div", - mergeAttribs(opts.attribs, args.attribs), - ...groupBody(opts, args.disabled, buttons), - ]; + (opts: ButtonGroupOpts): ButtonGroup => + (_, args: ButtonGroupArgs, ...buttons: ButtonGroupItem[]) => + [ + "div", + mergeAttribs(opts.attribs, args.attribs), + ...groupBody(opts, args.disabled, buttons), + ]; const groupBody = ( - opts: ButtonGroupOpts, - disabled: boolean, - buttons: ButtonGroupItem[] + opts: ButtonGroupOpts, + disabled: boolean, + buttons: ButtonGroupItem[] ) => { - switch (buttons.length) { - case 0: - return []; - case 1: - return [bt(opts.inner || opts.first, disabled, buttons[0])]; - case 2: - return [ - bt(opts.first, disabled, buttons[0]), - bt(opts.last || opts.first, disabled, buttons[1]), - ]; - default: { - const res = [bt(opts.first, disabled, buttons[0])]; - const el = opts.inner || opts.first; - const n = buttons.length - 1; - for (let i = 1; i < n; i++) { - res[i] = bt(el, disabled, buttons[i]); - } - res[n] = bt(opts.last || opts.first, disabled, buttons[n]); - return res; - } - } + switch (buttons.length) { + case 0: + return []; + case 1: + return [bt(opts.inner || opts.first, disabled, buttons[0])]; + case 2: + return [ + bt(opts.first, disabled, buttons[0]), + bt(opts.last || opts.first, disabled, buttons[1]), + ]; + default: { + const res = [bt(opts.first, disabled, buttons[0])]; + const el = opts.inner || opts.first; + const n = buttons.length - 1; + for (let i = 1; i < n; i++) { + res[i] = bt(el, disabled, buttons[i]); + } + res[n] = bt(opts.last || opts.first, disabled, buttons[n]); + return res; + } + } }; const bt = (el: Button, disabled: boolean, bt: ButtonGroupItem) => - disabled ? [el, { ...bt[0], disabled: true }, ...bt.slice(1)] : [el, ...bt]; + disabled ? [el, { ...bt[0], disabled: true }, ...bt.slice(1)] : [el, ...bt]; diff --git a/packages/hdom-components/src/button.ts b/packages/hdom-components/src/button.ts index 59c488f1a9..38d9ff7560 100644 --- a/packages/hdom-components/src/button.ts +++ b/packages/hdom-components/src/button.ts @@ -2,39 +2,39 @@ import { mergeAttribs } from "./utils/merge-attribs.js"; import type { IObjectOf } from "@thi.ng/api"; export interface ButtonOpts { - /** - * Element name to use for enabled buttons. - * Default: "a" - */ - tag: string; - /** - * Element name to use for disabled buttons. - * Default: "span" - */ - tagDisabled: string; - /** - * Attribute object to use for enabled buttons. - * Default: none - */ - attribs: any; - /** - * Attribute object to use for disabled buttons. - * Default: none - */ - attribsDisabled: any; - /** - * Flag to indicate if user supplied `onclick` handler - * should be wrapped in a function which automatically - * calls `preventDefault()`. - * Default: true - */ - preventDefault: boolean; + /** + * Element name to use for enabled buttons. + * Default: "a" + */ + tag: string; + /** + * Element name to use for disabled buttons. + * Default: "span" + */ + tagDisabled: string; + /** + * Attribute object to use for enabled buttons. + * Default: none + */ + attribs: any; + /** + * Attribute object to use for disabled buttons. + * Default: none + */ + attribsDisabled: any; + /** + * Flag to indicate if user supplied `onclick` handler + * should be wrapped in a function which automatically + * calls `preventDefault()`. + * Default: true + */ + preventDefault: boolean; } export interface ButtonArgs { - onclick: EventListener; - attribs?: IObjectOf; - disabled?: boolean; + onclick: EventListener; + attribs?: IObjectOf; + disabled?: boolean; } export type Button = (_: any, args: ButtonArgs, ...body: any[]) => any; @@ -54,33 +54,35 @@ export type Button = (_: any, args: ButtonArgs, ...body: any[]) => any; * elements (e.g. icon and label), given as varargs. */ export const button = (opts?: Partial): Button => { - // init with defaults - opts = { - tag: "a", - tagDisabled: "span", - preventDefault: true, - attribs: {}, - ...opts, - }; - !opts.attribs.role && (opts.attribs.role = "button"); - return (_: any, args: ButtonArgs, ...body: any[]) => - args.disabled - ? [ - opts!.tagDisabled, - { - ...mergeAttribs(opts!.attribsDisabled, args.attribs), - disabled: true, - }, - ...body, - ] - : [ - opts!.tag, - { - ...mergeAttribs(opts!.attribs, args.attribs), - onclick: opts!.preventDefault - ? (e: Event) => (e.preventDefault(), args.onclick(e)) - : args.onclick, - }, - ...body, - ]; + // init with defaults + opts = { + tag: "a", + tagDisabled: "span", + preventDefault: true, + attribs: {}, + ...opts, + }; + !opts.attribs.role && (opts.attribs.role = "button"); + return (_: any, args: ButtonArgs, ...body: any[]) => + args.disabled + ? [ + opts!.tagDisabled, + { + ...mergeAttribs(opts!.attribsDisabled, args.attribs), + disabled: true, + }, + ...body, + ] + : [ + opts!.tag, + { + ...mergeAttribs(opts!.attribs, args.attribs), + onclick: opts!.preventDefault + ? (e: Event) => ( + e.preventDefault(), args.onclick(e) + ) + : args.onclick, + }, + ...body, + ]; }; diff --git a/packages/hdom-components/src/canvas.ts b/packages/hdom-components/src/canvas.ts index 69af44582b..64d606d316 100644 --- a/packages/hdom-components/src/canvas.ts +++ b/packages/hdom-components/src/canvas.ts @@ -1,15 +1,15 @@ import { adaptDPI } from "@thi.ng/adapt-dpi"; export type CanvasContext = - | CanvasRenderingContext2D - | WebGLRenderingContext - | WebGL2RenderingContext; + | CanvasRenderingContext2D + | WebGLRenderingContext + | WebGL2RenderingContext; interface Canvas2DContextAttributes { - alpha?: boolean; - storage?: boolean; - willReadFrequently?: boolean; - [attribute: string]: boolean | string | undefined; + alpha?: boolean; + storage?: boolean; + willReadFrequently?: boolean; + [attribute: string]: boolean | string | undefined; } /** @@ -19,26 +19,26 @@ interface Canvas2DContextAttributes { * Not all handlers need to be implemented. */ export interface CanvasHandlers { - /** - * User init handler (called only once when canvas first) - */ - init(el: HTMLCanvasElement, ctx: T, hctx: any, ...args: any[]): void; - /** - * Update handler (called for each hdom update iteration) - */ - update( - el: HTMLCanvasElement, - ctx: T, - hctx: any, - time: number, - frame: number, - ...args: any[] - ): void; - /** - * release handler (called only once when canvas element is removed - * from DOM) - */ - release(el: HTMLCanvasElement, ctx: T, hctx: any, ...args: any[]): void; + /** + * User init handler (called only once when canvas first) + */ + init(el: HTMLCanvasElement, ctx: T, hctx: any, ...args: any[]): void; + /** + * Update handler (called for each hdom update iteration) + */ + update( + el: HTMLCanvasElement, + ctx: T, + hctx: any, + time: number, + frame: number, + ...args: any[] + ): void; + /** + * release handler (called only once when canvas element is removed + * from DOM) + */ + release(el: HTMLCanvasElement, ctx: T, hctx: any, ...args: any[]): void; } /** @@ -50,41 +50,41 @@ export interface CanvasHandlers { * @param opts - canvas context creation options */ const _canvas = ( - type: string, - handlers: Partial>, - opts: Canvas2DContextAttributes | WebGLContextAttributes | undefined + type: string, + handlers: Partial>, + opts: Canvas2DContextAttributes | WebGLContextAttributes | undefined ) => { - let el: HTMLCanvasElement; - let ctx: any; - let frame = 0; - let time = 0; - return { - init(_el: HTMLCanvasElement, hctx: any, ...args: any[]) { - el = _el; - adaptDPI(el, el.width, el.height); - ctx = el.getContext(type, opts); - time = Date.now(); - handlers.init && handlers.init(el, ctx, hctx, ...args); - handlers.update && - handlers.update(el, ctx, hctx, time, frame++, ...args); - }, - render(hctx: any, ...args: any[]) { - ctx && - handlers.update && - handlers.update( - el, - ctx, - hctx, - Date.now() - time, - frame++, - ...args - ); - return ["canvas", args[0]]; - }, - release(hctx: any, ...args: any[]) { - handlers.release && handlers.release(el, ctx, hctx, ...args); - }, - }; + let el: HTMLCanvasElement; + let ctx: any; + let frame = 0; + let time = 0; + return { + init(_el: HTMLCanvasElement, hctx: any, ...args: any[]) { + el = _el; + adaptDPI(el, el.width, el.height); + ctx = el.getContext(type, opts); + time = Date.now(); + handlers.init && handlers.init(el, ctx, hctx, ...args); + handlers.update && + handlers.update(el, ctx, hctx, time, frame++, ...args); + }, + render(hctx: any, ...args: any[]) { + ctx && + handlers.update && + handlers.update( + el, + ctx, + hctx, + Date.now() - time, + frame++, + ...args + ); + return ["canvas", args[0]]; + }, + release(hctx: any, ...args: any[]) { + handlers.release && handlers.release(el, ctx, hctx, ...args); + }, + }; }; /** @@ -113,8 +113,8 @@ const _canvas = ( * @param opts - canvas context creation options */ export const canvasWebGL = ( - handlers: Partial>, - opts?: WebGLContextAttributes + handlers: Partial>, + opts?: WebGLContextAttributes ) => _canvas("webgl", handlers, opts); /** @@ -124,8 +124,8 @@ export const canvasWebGL = ( * @param opts - canvas context creation options */ export const canvasWebGL2 = ( - handlers: Partial>, - opts?: WebGLContextAttributes + handlers: Partial>, + opts?: WebGLContextAttributes ) => _canvas("webgl2", handlers, opts); /** @@ -135,6 +135,6 @@ export const canvasWebGL2 = ( * @param glopts - canvas context creation options */ export const canvas2D = ( - handlers: Partial>, - opts?: Canvas2DContextAttributes + handlers: Partial>, + opts?: Canvas2DContextAttributes ) => _canvas("2d", handlers, opts); diff --git a/packages/hdom-components/src/dropdown.ts b/packages/hdom-components/src/dropdown.ts index 024f2f4eb5..bf3f4a457c 100644 --- a/packages/hdom-components/src/dropdown.ts +++ b/packages/hdom-components/src/dropdown.ts @@ -1,43 +1,43 @@ export interface DropDownOption extends Array { - [0]: string | number; - [1]: string | number; - [2]?: boolean; + [0]: string | number; + [1]: string | number; + [2]?: boolean; } export interface DropDownOptionGroup extends Array { - [0]: { label?: string; [id: string]: any }; - [1]: DropDownOption[]; + [0]: { label?: string; [id: string]: any }; + [1]: DropDownOption[]; } export const option = ( - [value, label, disabled]: DropDownOption, - sel: string | number | undefined + [value, label, disabled]: DropDownOption, + sel: string | number | undefined ) => [ - "option", - { value, disabled: !!disabled, selected: value === sel }, - label, + "option", + { value, disabled: !!disabled, selected: value === sel }, + label, ]; export const optgroup = ( - attribs: any, - options: DropDownOption[], - sel?: string | number + attribs: any, + options: DropDownOption[], + sel?: string | number ) => [ - "optgroup", - { ...attribs, label: attribs.label || "--" }, - ...options.map((o) => option(o, sel)), + "optgroup", + { ...attribs, label: attribs.label || "--" }, + ...options.map((o) => option(o, sel)), ]; export const dropdown = ( - _: any, - attribs: any, - options: DropDownOption[], - sel?: string | number + _: any, + attribs: any, + options: DropDownOption[], + sel?: string | number ) => ["select", attribs, ...options.map((o) => option(o, sel))]; export const groupedDropdown = ( - _: any, - attribs: any, - groups: DropDownOptionGroup[], - sel?: string | number + _: any, + attribs: any, + groups: DropDownOptionGroup[], + sel?: string | number ) => ["select", attribs, ...groups.map((o) => optgroup(o[0], o[1], sel))]; diff --git a/packages/hdom-components/src/fps-counter.ts b/packages/hdom-components/src/fps-counter.ts index bb2c6bd3fc..72eb0b7bd4 100644 --- a/packages/hdom-components/src/fps-counter.ts +++ b/packages/hdom-components/src/fps-counter.ts @@ -3,26 +3,26 @@ import { sma } from "@thi.ng/transducers-stats/sma"; import { sparkline, SparklineOpts } from "./sparkline.js"; export interface FpsCounterOpts { - /** - * Number of recorded samples. - * Default: 25 - */ - history: number; - /** - * Moving average smoothing period. - * Default: 5 - */ - smooth: number; - /** - * Period (in ms) between label updates. - * Default: 250 - */ - labelPeriod: number; - /** - * Sparkline config options. - * Default: sparkline defaults - */ - sparkline?: Partial; + /** + * Number of recorded samples. + * Default: 25 + */ + history: number; + /** + * Moving average smoothing period. + * Default: 5 + */ + smooth: number; + /** + * Period (in ms) between label updates. + * Default: 250 + */ + labelPeriod: number; + /** + * Sparkline config options. + * Default: sparkline defaults + */ + sparkline?: Partial; } /** @@ -32,44 +32,44 @@ export interface FpsCounterOpts { * @param opts - */ export const fpsCounter = (_opts?: Partial) => { - const opts = { - history: 25, - smooth: 5, - labelPeriod: 250, - sparkline: {}, - ..._opts, - }; - return { - init() { - this.last = Date.now(); - this.lastLabel = this.last; - this.buffer = []; - this.ma = step(sma(opts.smooth)); - }, - render() { - const t = Date.now(); - const fps = 1000 / (t - this.last); - this.last = t; - if (!this.buffer) return ["div"]; - const smoothFps = this.ma(fps); - if (!smoothFps) return ["div"]; - this.buffer.push(smoothFps); - this.buffer.length > opts.history && this.buffer.shift(); - const updateLabel = t - this.lastLabel > opts.labelPeriod; - updateLabel && (this.lastLabel = t); - return [ - "div", - [ - sparkline, - { min: 0, max: 65, ...opts.sparkline }, - this.buffer, - ], - [ - "span", - { __skip: !updateLabel }, - smoothFps ? smoothFps.toFixed(2) + " fps" : "", - ], - ]; - }, - }; + const opts = { + history: 25, + smooth: 5, + labelPeriod: 250, + sparkline: {}, + ..._opts, + }; + return { + init() { + this.last = Date.now(); + this.lastLabel = this.last; + this.buffer = []; + this.ma = step(sma(opts.smooth)); + }, + render() { + const t = Date.now(); + const fps = 1000 / (t - this.last); + this.last = t; + if (!this.buffer) return ["div"]; + const smoothFps = this.ma(fps); + if (!smoothFps) return ["div"]; + this.buffer.push(smoothFps); + this.buffer.length > opts.history && this.buffer.shift(); + const updateLabel = t - this.lastLabel > opts.labelPeriod; + updateLabel && (this.lastLabel = t); + return [ + "div", + [ + sparkline, + { min: 0, max: 65, ...opts.sparkline }, + this.buffer, + ], + [ + "span", + { __skip: !updateLabel }, + smoothFps ? smoothFps.toFixed(2) + " fps" : "", + ], + ]; + }, + }; }; diff --git a/packages/hdom-components/src/link.ts b/packages/hdom-components/src/link.ts index 2e21cb9849..fb27638404 100644 --- a/packages/hdom-components/src/link.ts +++ b/packages/hdom-components/src/link.ts @@ -1,25 +1,25 @@ import { isString } from "@thi.ng/checks/is-string"; export const link = (attribs: any, body: any) => [ - "a", - isString(attribs) ? { href: attribs } : attribs, - body, + "a", + isString(attribs) ? { href: attribs } : attribs, + body, ]; export const appLink = ( - _: any, - attribs: any, - onclick: EventListener, - body: any + _: any, + attribs: any, + onclick: EventListener, + body: any ) => [ - "a", - { - href: "#", - onclick: (e: Event) => { - e.preventDefault(); - onclick(e); - }, - ...attribs, - }, - body, + "a", + { + href: "#", + onclick: (e: Event) => { + e.preventDefault(); + onclick(e); + }, + ...attribs, + }, + body, ]; diff --git a/packages/hdom-components/src/notification.ts b/packages/hdom-components/src/notification.ts index 68eb1dc306..21b56643ec 100644 --- a/packages/hdom-components/src/notification.ts +++ b/packages/hdom-components/src/notification.ts @@ -2,41 +2,41 @@ import { appLink } from "./link.js"; import type { IObjectOf } from "@thi.ng/api"; export interface NotificationOpts { - /** - * Attribute object to use for notification. - * Default: none - */ - attribs: IObjectOf; - /** - * Attribute object for link wrapper of `close` element. - * Default: none - */ - attribsClose: IObjectOf; - /** - * Icon element to use for notification. - * Default: none - */ - icon: any[]; - /** - * Icon element to use for close button. - * Default: none - */ - close: any[]; + /** + * Attribute object to use for notification. + * Default: none + */ + attribs: IObjectOf; + /** + * Attribute object for link wrapper of `close` element. + * Default: none + */ + attribsClose: IObjectOf; + /** + * Icon element to use for notification. + * Default: none + */ + icon: any[]; + /** + * Icon element to use for close button. + * Default: none + */ + close: any[]; } /** * Runtime supplied user args for individual notification instances. */ export interface NotificationArgs { - /** - * Extra attribs to merge with (or override) configured default attribs. - */ - attribs: IObjectOf; - /** - * Event handler called when user closes notification. Only used if - * {@link NotificationOpts} has `close` option configured. - */ - onclose: EventListener; + /** + * Extra attribs to merge with (or override) configured default attribs. + */ + attribs: IObjectOf; + /** + * Event handler called when user closes notification. Only used if + * {@link NotificationOpts} has `close` option configured. + */ + onclose: EventListener; } /** @@ -56,13 +56,13 @@ export interface NotificationArgs { * @param opts - */ export const notification = (opts: Partial = {}) => { - return (_: any, args: Partial, body: any) => [ - "div", - { ...opts.attribs, ...args.attribs }, - opts.icon, - body, - opts.close && args.onclose - ? [appLink, opts.attribsClose, args.onclose, opts.close] - : undefined, - ]; + return (_: any, args: Partial, body: any) => [ + "div", + { ...opts.attribs, ...args.attribs }, + opts.icon, + body, + opts.close && args.onclose + ? [appLink, opts.attribsClose, args.onclose, opts.close] + : undefined, + ]; }; diff --git a/packages/hdom-components/src/pager.ts b/packages/hdom-components/src/pager.ts index 7d2cb86451..2b00326d58 100644 --- a/packages/hdom-components/src/pager.ts +++ b/packages/hdom-components/src/pager.ts @@ -5,78 +5,78 @@ import { range } from "@thi.ng/transducers/range"; * Configuration options for pager components. */ export interface PagerOpts { - /** - * Function producing a single page nav or counter element. MUST be - * provided by user. - * - * The function is called with: - * - * - target page ID - * - current page ID - * - max pageID - * - page label (page number or sourced from these options here) - * - disabled flag as determined by the pager - * - * If `disabled` is true, the function should return a version of - * the button component reflecting this state to the user. E.g. the - * "prev page" buttons should be disabled if the first page is - * currently active. Likewise, the currently selected page button - * will be set to disabled as well. - * - * Page IDs are zero-based indices, whereas page number labels are - * one-based. The currently active page ID is only provided for - * special highlighting cases (optional). - */ - button( - page: number, - curr: number, - max: number, - label: any, - disabled: boolean - ): any; - /** - * Pager root component function. Receives all 3 button groups as - * arguments. Optional. Default: `["div.pager", ...body]` - */ - root(ctx: any, ...body: any[]): any; - /** - * Component function to provide wrapper for the first / prev nav - * button group. The `first` / `prev` args are button components. - * Optional. Default: `["div.pager-prev", first, prev]` - */ - groupPrev(ctx: any, first: any, prev: any): any; - /** - * Component function to provide wrapper for the page buttons group. - * The `buttons` argument is an array of button components. - * Optional. Default: `["div.pager-pages", ...buttons]` - */ - groupPages(ctx: any, buttons: any[]): any; - /** - * Component function to provide wrapper for the next / last nav - * button group. The `next` / `last` args are button components. - * Optional. Default: `["div.pager-next", next, last]` - */ - groupNext(ctx: any, next: any, last: any): any; - /** - * Page increment for prev / next page buttons. Default: 1 - */ - navStep?: number; - /** - * Label for "first page" button. Default: `"<<""` - */ - labelFirst?: any; - /** - * Label for "last page" button. Default: `">>"` - */ - labelLast?: any; - /** - * Label for "prev page" button. Default: `"<"` - */ - labelPrev?: any; - /** - * Label for "next page" button. Default: `">"` - */ - labelNext?: any; + /** + * Function producing a single page nav or counter element. MUST be + * provided by user. + * + * The function is called with: + * + * - target page ID + * - current page ID + * - max pageID + * - page label (page number or sourced from these options here) + * - disabled flag as determined by the pager + * + * If `disabled` is true, the function should return a version of + * the button component reflecting this state to the user. E.g. the + * "prev page" buttons should be disabled if the first page is + * currently active. Likewise, the currently selected page button + * will be set to disabled as well. + * + * Page IDs are zero-based indices, whereas page number labels are + * one-based. The currently active page ID is only provided for + * special highlighting cases (optional). + */ + button( + page: number, + curr: number, + max: number, + label: any, + disabled: boolean + ): any; + /** + * Pager root component function. Receives all 3 button groups as + * arguments. Optional. Default: `["div.pager", ...body]` + */ + root(ctx: any, ...body: any[]): any; + /** + * Component function to provide wrapper for the first / prev nav + * button group. The `first` / `prev` args are button components. + * Optional. Default: `["div.pager-prev", first, prev]` + */ + groupPrev(ctx: any, first: any, prev: any): any; + /** + * Component function to provide wrapper for the page buttons group. + * The `buttons` argument is an array of button components. + * Optional. Default: `["div.pager-pages", ...buttons]` + */ + groupPages(ctx: any, buttons: any[]): any; + /** + * Component function to provide wrapper for the next / last nav + * button group. The `next` / `last` args are button components. + * Optional. Default: `["div.pager-next", next, last]` + */ + groupNext(ctx: any, next: any, last: any): any; + /** + * Page increment for prev / next page buttons. Default: 1 + */ + navStep?: number; + /** + * Label for "first page" button. Default: `"<<""` + */ + labelFirst?: any; + /** + * Label for "last page" button. Default: `">>"` + */ + labelLast?: any; + /** + * Label for "prev page" button. Default: `"<"` + */ + labelPrev?: any; + /** + * Label for "next page" button. Default: `">"` + */ + labelNext?: any; } /** @@ -124,55 +124,55 @@ export interface PagerOpts { * @param opts - */ export const pager = (_opts: PagerOpts) => { - const opts = { - navStep: 1, - labelFirst: "<<", - labelPrev: "<", - labelNext: ">", - labelLast: ">>", - ..._opts, - }; - return (_: any, id: number, num: number, pageLen = 10, maxBts = 5) => { - const bt = opts.button; - const step = opts.navStep; - const maxID = Math.floor(Math.max(0, num - 1) / pageLen); - id = Math.max(Math.min(id, maxID), 0); - return [ - opts.root, - [ - opts.groupPrev, - bt(0, id, maxID, opts.labelFirst, !id), - bt(Math.max(id - step!, 0), id, maxID, opts.labelPrev, !id), - ], - [ - opts.groupPages, - map( - (i: number) => bt(i, id, maxID, i + 1, i === id), - pageRange(id, maxID, maxBts) - ), - ], - [ - opts.groupNext, - bt( - Math.min(id + step!, maxID), - id, - maxID, - opts.labelNext, - id >= maxID - ), - bt(maxID, id, maxID, opts.labelLast, id >= maxID), - ], - ]; - }; + const opts = { + navStep: 1, + labelFirst: "<<", + labelPrev: "<", + labelNext: ">", + labelLast: ">>", + ..._opts, + }; + return (_: any, id: number, num: number, pageLen = 10, maxBts = 5) => { + const bt = opts.button; + const step = opts.navStep; + const maxID = Math.floor(Math.max(0, num - 1) / pageLen); + id = Math.max(Math.min(id, maxID), 0); + return [ + opts.root, + [ + opts.groupPrev, + bt(0, id, maxID, opts.labelFirst, !id), + bt(Math.max(id - step!, 0), id, maxID, opts.labelPrev, !id), + ], + [ + opts.groupPages, + map( + (i: number) => bt(i, id, maxID, i + 1, i === id), + pageRange(id, maxID, maxBts) + ), + ], + [ + opts.groupNext, + bt( + Math.min(id + step!, maxID), + id, + maxID, + opts.labelNext, + id >= maxID + ), + bt(maxID, id, maxID, opts.labelLast, id >= maxID), + ], + ]; + }; }; const pageRange = (id: number, maxID: number, maxBt: number) => { - if (maxID > maxBt - 1) { - const from = Math.max( - Math.min(id - (maxBt >> 1), maxID - maxBt + 1), - 0 - ); - return range(from, from + maxBt); - } - return range(0, maxID + 1); + if (maxID > maxBt - 1) { + const from = Math.max( + Math.min(id - (maxBt >> 1), maxID - maxBt + 1), + 0 + ); + return range(from, from + maxBt); + } + return range(0, maxID + 1); }; diff --git a/packages/hdom-components/src/sparkline.ts b/packages/hdom-components/src/sparkline.ts index fa482412f2..c4e3222e6f 100644 --- a/packages/hdom-components/src/sparkline.ts +++ b/packages/hdom-components/src/sparkline.ts @@ -3,36 +3,36 @@ import { mapIndexed } from "@thi.ng/transducers/map-indexed"; import { str } from "@thi.ng/transducers/str"; export interface SparklineOpts { - /** - * Sparkline width - * Default: 50 - */ - width: number; - /** - * Sparkline height - * Default: 16 - */ - height: number; - /** - * Min domain value. - * Default: 0 - */ - min: number; - /** - * Max domain value. - * Default: 100 - */ - max: number; - /** - * Sparkline CSS color - * Default: red - */ - col: string; - /** - * Radius of sparkline head marker circle. - * Default: 1.5 - */ - r: number; + /** + * Sparkline width + * Default: 50 + */ + width: number; + /** + * Sparkline height + * Default: 16 + */ + height: number; + /** + * Min domain value. + * Default: 0 + */ + min: number; + /** + * Max domain value. + * Default: 100 + */ + max: number; + /** + * Sparkline CSS color + * Default: red + */ + col: string; + /** + * Radius of sparkline head marker circle. + * Default: 1.5 + */ + r: number; } /** @@ -43,55 +43,55 @@ export interface SparklineOpts { * @param vals - data values */ export const sparkline = ( - _: any, - _opts: Partial, - vals: number[] + _: any, + _opts: Partial, + vals: number[] ) => { - const opts = { - min: 0, - max: 100, - width: 50, - height: 16, - col: "red", - r: 1.5, - ..._opts, - }; - const n = vals.length; - const s = opts.width / n; - const r = opts.r; - const h = opts.height - r; - return [ - "svg", - { - width: opts.width + 2 * r, - height: opts.height, - stroke: opts.col, - fill: "none", - }, - [ - "polyline", - { - points: str( - ",", - mapIndexed( - (i, y: number) => [ - (i * s) | 0, - fitClamped(y, opts.min, opts.max, h, r) | 0, - ], - 0, - vals - ) - ), - }, - ], - [ - "circle", - { - cx: ((n - 1) * s) | 0, - cy: fitClamped(vals[n - 1], opts.min, opts.max, h, r) | 0, - r, - fill: opts.col, - }, - ], - ]; + const opts = { + min: 0, + max: 100, + width: 50, + height: 16, + col: "red", + r: 1.5, + ..._opts, + }; + const n = vals.length; + const s = opts.width / n; + const r = opts.r; + const h = opts.height - r; + return [ + "svg", + { + width: opts.width + 2 * r, + height: opts.height, + stroke: opts.col, + fill: "none", + }, + [ + "polyline", + { + points: str( + ",", + mapIndexed( + (i, y: number) => [ + (i * s) | 0, + fitClamped(y, opts.min, opts.max, h, r) | 0, + ], + 0, + vals + ) + ), + }, + ], + [ + "circle", + { + cx: ((n - 1) * s) | 0, + cy: fitClamped(vals[n - 1], opts.min, opts.max, h, r) | 0, + r, + fill: opts.col, + }, + ], + ]; }; diff --git a/packages/hdom-components/src/title.ts b/packages/hdom-components/src/title.ts index 9974e5a99b..7931e11449 100644 --- a/packages/hdom-components/src/title.ts +++ b/packages/hdom-components/src/title.ts @@ -1,20 +1,20 @@ export interface TitleOpts { - /** - * Element name for main title. Default: `h1` - */ - element: string; - /** - * Attribs for main title: Default: none - */ - attribs: any; - /** - * Element name for subtitle: Default: `small` - */ - subElement: string; - /** - * Attribs for subtitle: Default: none - */ - subAttribs: any; + /** + * Element name for main title. Default: `h1` + */ + element: string; + /** + * Attribs for main title: Default: none + */ + attribs: any; + /** + * Element name for subtitle: Default: `small` + */ + subElement: string; + /** + * Attribs for subtitle: Default: none + */ + subAttribs: any; } /** @@ -34,17 +34,17 @@ export interface TitleOpts { * @param opts - */ export const title = (_opts?: Partial) => { - const opts = { - element: "h1", - attribs: {}, - subElement: "small", - subAttribs: {}, - ..._opts, - }; - return (_: any, title: any, subtitle: any) => [ - opts.element, - opts.attribs, - title, - subtitle ? [opts.subElement, opts.subAttribs, subtitle] : undefined, - ]; + const opts = { + element: "h1", + attribs: {}, + subElement: "small", + subAttribs: {}, + ..._opts, + }; + return (_: any, title: any, subtitle: any) => [ + opts.element, + opts.attribs, + title, + subtitle ? [opts.subElement, opts.subAttribs, subtitle] : undefined, + ]; }; diff --git a/packages/hdom-components/src/toggle.ts b/packages/hdom-components/src/toggle.ts index a6e8fc4b74..29a48a5607 100644 --- a/packages/hdom-components/src/toggle.ts +++ b/packages/hdom-components/src/toggle.ts @@ -1,127 +1,127 @@ export interface ToggleOpts { - anim: number; - vertical: boolean; - pad: number; - margin: number; - bgOn: object; - bgOff: object; - fgOn: object; - fgOff: object; + anim: number; + vertical: boolean; + pad: number; + margin: number; + bgOn: object; + bgOff: object; + fgOn: object; + fgOff: object; } export interface ToggleDotOpts extends ToggleOpts { - r: number; + r: number; } export interface ToggleRectOpts extends ToggleOpts { - w: number; - h: number; + w: number; + h: number; } const DEFAULT_OPTS: ToggleOpts = { - anim: 100, - pad: 1, - margin: 0, - vertical: false, - bgOn: { fill: "#000" }, - bgOff: { fill: "#999" }, - fgOn: { fill: "#fff" }, - fgOff: { fill: "#fff" }, + anim: 100, + pad: 1, + margin: 0, + vertical: false, + bgOn: { fill: "#000" }, + bgOff: { fill: "#999" }, + fgOn: { fill: "#fff" }, + fgOff: { fill: "#fff" }, }; export const slideToggleDot = (opts: Partial = {}) => { - const _opts: ToggleDotOpts = { - r: 5, - ...DEFAULT_OPTS, - ...opts, - }; - const { r, pad, margin, vertical } = _opts; - const m2 = margin * 2; - const br = r + pad; - const cx = br + margin; - const width = (r * 2 + pad) * 2; - const height = br * 2; - const totalW = width + m2; - const totalH = height + m2; - const svgSize = vertical - ? { width: totalH, height: totalW } - : { width: totalW, height: totalH }; - const style = { transition: `all ${_opts.anim}ms ease-out` }; - const bgOn = { - x: margin, - y: margin, - rx: br, - ry: br, - ...(vertical ? { width: height, height: width } : { width, height }), - ..._opts.bgOn, - }; - const bgOff = { ...bgOn, ..._opts.bgOff }; - const shapeOn: any = { - ...(vertical ? { cx, cy: cx } : { cx: width + margin - br, cy: cx }), - ..._opts.fgOn, - style, - r, - }; - const shapeOff: any = { - ...shapeOn, - ...(vertical ? { cy: width + margin - br } : { cx }), - ..._opts.fgOff, - }; - return $toggle("circle", svgSize, bgOn, bgOff, shapeOn, shapeOff); + const _opts: ToggleDotOpts = { + r: 5, + ...DEFAULT_OPTS, + ...opts, + }; + const { r, pad, margin, vertical } = _opts; + const m2 = margin * 2; + const br = r + pad; + const cx = br + margin; + const width = (r * 2 + pad) * 2; + const height = br * 2; + const totalW = width + m2; + const totalH = height + m2; + const svgSize = vertical + ? { width: totalH, height: totalW } + : { width: totalW, height: totalH }; + const style = { transition: `all ${_opts.anim}ms ease-out` }; + const bgOn = { + x: margin, + y: margin, + rx: br, + ry: br, + ...(vertical ? { width: height, height: width } : { width, height }), + ..._opts.bgOn, + }; + const bgOff = { ...bgOn, ..._opts.bgOff }; + const shapeOn: any = { + ...(vertical ? { cx, cy: cx } : { cx: width + margin - br, cy: cx }), + ..._opts.fgOn, + style, + r, + }; + const shapeOff: any = { + ...shapeOn, + ...(vertical ? { cy: width + margin - br } : { cx }), + ..._opts.fgOff, + }; + return $toggle("circle", svgSize, bgOn, bgOff, shapeOn, shapeOff); }; export const slideToggleRect = (opts: Partial = {}) => { - const _opts: ToggleRectOpts = { - w: 10, - h: 10, - ...DEFAULT_OPTS, - ...opts, - }; - const { w, h, pad, margin, vertical } = _opts; - const m2 = margin * 2; - const pm = pad + margin; - const width = vertical ? w + pad * 2 : (w + pad) * 2; - const height = vertical ? (h + pad) * 2 : h + pad * 2; - const svgSize = { width: width + m2, height: height + m2 }; - const style = { transition: `all ${_opts.anim}ms ease-out` }; - const bgOn = { - ..._opts.bgOn, - width, - height, - x: margin, - y: margin, - }; - const bgOff = { ...bgOn, ..._opts.bgOff }; - const shapeOn: any = { - ...(vertical - ? { x: pm, y: pm } - : { x: width + margin - pad - w, y: pm }), - ..._opts.fgOn, - style, - width: w, - height: h, - }; - const shapeOff: any = { - ...shapeOn, - ...(vertical ? { y: height + margin - pad - h } : { x: pm }), - ..._opts.fgOff, - }; - return $toggle("rect", svgSize, bgOn, bgOff, shapeOn, shapeOff); + const _opts: ToggleRectOpts = { + w: 10, + h: 10, + ...DEFAULT_OPTS, + ...opts, + }; + const { w, h, pad, margin, vertical } = _opts; + const m2 = margin * 2; + const pm = pad + margin; + const width = vertical ? w + pad * 2 : (w + pad) * 2; + const height = vertical ? (h + pad) * 2 : h + pad * 2; + const svgSize = { width: width + m2, height: height + m2 }; + const style = { transition: `all ${_opts.anim}ms ease-out` }; + const bgOn = { + ..._opts.bgOn, + width, + height, + x: margin, + y: margin, + }; + const bgOff = { ...bgOn, ..._opts.bgOff }; + const shapeOn: any = { + ...(vertical + ? { x: pm, y: pm } + : { x: width + margin - pad - w, y: pm }), + ..._opts.fgOn, + style, + width: w, + height: h, + }; + const shapeOff: any = { + ...shapeOn, + ...(vertical ? { y: height + margin - pad - h } : { x: pm }), + ..._opts.fgOff, + }; + return $toggle("rect", svgSize, bgOn, bgOff, shapeOn, shapeOff); }; const $toggle = - ( - shape: string, - size: any, - bgOn: any, - bgOff: any, - shapeOn: any, - shapeOff: any - ) => - (_: any, attribs: any, state: boolean) => - [ - "svg", - { ...size, ...attribs }, - ["rect", state ? bgOn : bgOff], - [shape, state ? shapeOn : shapeOff], - ]; + ( + shape: string, + size: any, + bgOn: any, + bgOff: any, + shapeOn: any, + shapeOff: any + ) => + (_: any, attribs: any, state: boolean) => + [ + "svg", + { ...size, ...attribs }, + ["rect", state ? bgOn : bgOff], + [shape, state ? shapeOn : shapeOff], + ]; diff --git a/packages/hdom-components/src/utils/merge-attribs.ts b/packages/hdom-components/src/utils/merge-attribs.ts index ec8941157e..cc1645cf87 100644 --- a/packages/hdom-components/src/utils/merge-attribs.ts +++ b/packages/hdom-components/src/utils/merge-attribs.ts @@ -9,9 +9,9 @@ * @internal */ export const mergeAttribs = (base: any, xs: any) => { - if (!xs) return base; - const res = { ...base, ...xs }; - base.class && xs.class && (res.class = base.class + " " + xs.class); - base.style && xs.style && (res.style = { ...base.style, ...xs.style }); - return res; + if (!xs) return base; + const res = { ...base, ...xs }; + base.class && xs.class && (res.class = base.class + " " + xs.class); + base.style && xs.style && (res.style = { ...base.style, ...xs.style }); + return res; }; diff --git a/packages/hdom-components/tsconfig.json b/packages/hdom-components/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/hdom-components/tsconfig.json +++ b/packages/hdom-components/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/hdom-mock/api-extractor.json b/packages/hdom-mock/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/hdom-mock/api-extractor.json +++ b/packages/hdom-mock/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/hdom-mock/package.json b/packages/hdom-mock/package.json index 985405acf0..6450d04225 100644 --- a/packages/hdom-mock/package.json +++ b/packages/hdom-mock/package.json @@ -1,73 +1,73 @@ { - "name": "@thi.ng/hdom-mock", - "version": "2.1.10", - "description": "Mock base implementation for @thi.ng/hdom API", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-mock#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/hdom": "^9.1.10" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "datastructure", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - } - }, - "thi.ng": { - "parent": "@thi.ng/hdom", - "status": "alpha", - "year": 2018 - } + "name": "@thi.ng/hdom-mock", + "version": "2.1.10", + "description": "Mock base implementation for @thi.ng/hdom API", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-mock#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/hdom": "^9.1.10" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "datastructure", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + } + }, + "thi.ng": { + "parent": "@thi.ng/hdom", + "status": "alpha", + "year": 2018 + } } diff --git a/packages/hdom-mock/src/index.ts b/packages/hdom-mock/src/index.ts index bb79d9ff3d..1d39f2e712 100644 --- a/packages/hdom-mock/src/index.ts +++ b/packages/hdom-mock/src/index.ts @@ -8,254 +8,254 @@ import { normalizeTree } from "@thi.ng/hdom/normalize"; export const TEXT = Symbol(); export class HDOMNode { - /** - * Only real child nodes - */ - children: HDOMNode[]; - /** - * Includes real children AND text nodes - */ - _children: HDOMNode[]; + /** + * Only real child nodes + */ + children: HDOMNode[]; + /** + * Includes real children AND text nodes + */ + _children: HDOMNode[]; - listeners: IObjectOf; + listeners: IObjectOf; - value: any; - checked: boolean | undefined; + value: any; + checked: boolean | undefined; - tag: string | symbol; - attribs: IObjectOf; - style: IObjectOf | undefined; - body: string | undefined; + tag: string | symbol; + attribs: IObjectOf; + style: IObjectOf | undefined; + body: string | undefined; - constructor(tag: string | symbol, attribs = {}) { - this.tag = tag; - this.children = []; - this._children = []; - this.attribs = attribs; - this.listeners = {}; - } + constructor(tag: string | symbol, attribs = {}) { + this.tag = tag; + this.children = []; + this._children = []; + this.attribs = attribs; + this.listeners = {}; + } - get textContent() { - const res = []; - for (let c of this._children) { - if (c.isText()) { - res.push(c.body); - } - } - return res.join(""); - } + get textContent() { + const res = []; + for (let c of this._children) { + if (c.isText()) { + res.push(c.body); + } + } + return res.join(""); + } - set textContent(body: string) { - const txt = new HDOMNode(TEXT); - txt.body = body; - this._children = [txt]; - this.children = []; - } + set textContent(body: string) { + const txt = new HDOMNode(TEXT); + txt.body = body; + this._children = [txt]; + this.children = []; + } - isText() { - return this.tag === TEXT; - } + isText() { + return this.tag === TEXT; + } - insertBefore(c: HDOMNode, i: number) { - const existing = this.children[i]; - if (existing) { - !this.isText() && this.children.splice(i, 0, c); - this._children.splice(this._children.indexOf(existing), 0, c); - } else { - this.appendChild(c); - } - return c; - } + insertBefore(c: HDOMNode, i: number) { + const existing = this.children[i]; + if (existing) { + !this.isText() && this.children.splice(i, 0, c); + this._children.splice(this._children.indexOf(existing), 0, c); + } else { + this.appendChild(c); + } + return c; + } - appendChild(c: HDOMNode) { - !c.isText() && this.children.push(c); - this._children.push(c); - return c; - } + appendChild(c: HDOMNode) { + !c.isText() && this.children.push(c); + this._children.push(c); + return c; + } - removeChild(i: number) { - const c = this.children[i]; - if (c) { - this.children.splice(i, 1); - this._children.splice(this._children.indexOf(c), 1); - } - } + removeChild(i: number) { + const c = this.children[i]; + if (c) { + this.children.splice(i, 1); + this._children.splice(this._children.indexOf(c), 1); + } + } - getElementById(id: string): HDOMNode | null { - if (this.attribs.id === id) return this; - let c: HDOMNode | null; - for (c of this.children) { - c = c.getElementById(id); - if (c) return c; - } - return null; - } + getElementById(id: string): HDOMNode | null { + if (this.attribs.id === id) return this; + let c: HDOMNode | null; + for (c of this.children) { + c = c.getElementById(id); + if (c) return c; + } + return null; + } - toHiccup(): any { - if (this.isText()) { - return this.body; - } - const attr = { ...this.attribs }; - this.style && (attr.style = this.style); - this.value != null && (attr.value = this.value); - this.checked && (attr.checked = true); - return [this.tag, attr, ...this._children.map((c) => c.toHiccup())]; - } + toHiccup(): any { + if (this.isText()) { + return this.body; + } + const attr = { ...this.attribs }; + this.style && (attr.style = this.style); + this.value != null && (attr.value = this.value); + this.checked && (attr.checked = true); + return [this.tag, attr, ...this._children.map((c) => c.toHiccup())]; + } } export class MockHDOM implements HDOMImplementation { - root: HDOMNode; + root: HDOMNode; - constructor(root: HDOMNode) { - this.root = root; - } + constructor(root: HDOMNode) { + this.root = root; + } - normalizeTree(opts: Partial, tree: any): any[] { - return normalizeTree(opts, tree); - } + normalizeTree(opts: Partial, tree: any): any[] { + return normalizeTree(opts, tree); + } - createTree( - opts: Partial, - parent: HDOMNode, - tree: any, - child?: number - ): HDOMNode | HDOMNode[] { - return createTree(opts, this, parent, tree, child); - } + createTree( + opts: Partial, + parent: HDOMNode, + tree: any, + child?: number + ): HDOMNode | HDOMNode[] { + return createTree(opts, this, parent, tree, child); + } - hydrateTree( - opts: Partial, - parent: HDOMNode, - tree: any, - child?: number - ) { - return hydrateTree(opts, this, parent, tree, child); - } + hydrateTree( + opts: Partial, + parent: HDOMNode, + tree: any, + child?: number + ) { + return hydrateTree(opts, this, parent, tree, child); + } - diffTree( - opts: Partial, - parent: HDOMNode, - prev: any[], - curr: any[], - child?: number - ) { - diffTree(opts, this, parent, prev, curr, child); - } + diffTree( + opts: Partial, + parent: HDOMNode, + prev: any[], + curr: any[], + child?: number + ) { + diffTree(opts, this, parent, prev, curr, child); + } - createElement( - parent: HDOMNode, - tag: string, - attribs?: any, - insert?: number - ) { - const el = new HDOMNode(tag); - if (parent) { - if (insert == null) { - parent.appendChild(el); - } else { - parent.insertBefore(el, insert); - } - } - if (attribs) { - this.setAttribs(el, attribs); - } - return el; - } + createElement( + parent: HDOMNode, + tag: string, + attribs?: any, + insert?: number + ) { + const el = new HDOMNode(tag); + if (parent) { + if (insert == null) { + parent.appendChild(el); + } else { + parent.insertBefore(el, insert); + } + } + if (attribs) { + this.setAttribs(el, attribs); + } + return el; + } - createTextElement(parent: HDOMNode, content: string) { - const el = new HDOMNode(TEXT); - el.body = content; - parent && parent.appendChild(el); - return el; - } + createTextElement(parent: HDOMNode, content: string) { + const el = new HDOMNode(TEXT); + el.body = content; + parent && parent.appendChild(el); + return el; + } - getElementById(id: string): HDOMNode | null { - return this.root.getElementById(id); - } + getElementById(id: string): HDOMNode | null { + return this.root.getElementById(id); + } - replaceChild( - opts: Partial, - parent: HDOMNode, - child: number, - tree: any - ) { - this.removeChild(parent, child); - return this.createTree(opts, parent, tree, child); - } + replaceChild( + opts: Partial, + parent: HDOMNode, + child: number, + tree: any + ) { + this.removeChild(parent, child); + return this.createTree(opts, parent, tree, child); + } - getChild(parent: HDOMNode, i: number) { - return parent.children[i]; - } + getChild(parent: HDOMNode, i: number) { + return parent.children[i]; + } - removeChild(parent: HDOMNode, i: number) { - parent.removeChild(i); - } + removeChild(parent: HDOMNode, i: number) { + parent.removeChild(i); + } - setAttribs(el: HDOMNode, attribs: any) { - for (let k in attribs) { - this.setAttrib(el, k, attribs[k], attribs); - } - return el; - } + setAttribs(el: HDOMNode, attribs: any) { + for (let k in attribs) { + this.setAttrib(el, k, attribs[k], attribs); + } + return el; + } - setAttrib(el: HDOMNode, id: string, val: any, attribs?: any) { - if (id.startsWith("__")) return; - const isListener = id.indexOf("on") === 0; - if (!isListener && typeof val === "function") { - val = val(attribs); - } - if (val !== undefined && val !== false) { - switch (id) { - case "style": - this.setStyle(el, val); - break; - case "value": - el.value = val; - break; - case "checked": - el[id] = val; - break; - default: - if (isListener) { - const lid = id.substring(2); - const listeners = el.listeners[lid]; - (listeners || (el.listeners[lid] = [])).push(val); - } else { - el.attribs[id] = val; - } - } - } else { - (el)[id] != null - ? ((el)[id] = null) - : delete el.attribs[id]; - } - return el; - } + setAttrib(el: HDOMNode, id: string, val: any, attribs?: any) { + if (id.startsWith("__")) return; + const isListener = id.indexOf("on") === 0; + if (!isListener && typeof val === "function") { + val = val(attribs); + } + if (val !== undefined && val !== false) { + switch (id) { + case "style": + this.setStyle(el, val); + break; + case "value": + el.value = val; + break; + case "checked": + el[id] = val; + break; + default: + if (isListener) { + const lid = id.substring(2); + const listeners = el.listeners[lid]; + (listeners || (el.listeners[lid] = [])).push(val); + } else { + el.attribs[id] = val; + } + } + } else { + (el)[id] != null + ? ((el)[id] = null) + : delete el.attribs[id]; + } + return el; + } - removeAttribs(el: HDOMNode, attribs: string[], prev: any) { - for (let i = attribs.length; i-- > 0; ) { - const a = attribs[i]; - if (a.indexOf("on") === 0) { - const listeners = el.listeners[a.substring(2)]; - if (listeners) { - const i = listeners.indexOf(prev[a]); - i >= 0 && listeners.splice(i, 1); - } - } else { - (el)[a] ? ((el)[a] = null) : delete el.attribs[a]; - } - } - } + removeAttribs(el: HDOMNode, attribs: string[], prev: any) { + for (let i = attribs.length; i-- > 0; ) { + const a = attribs[i]; + if (a.indexOf("on") === 0) { + const listeners = el.listeners[a.substring(2)]; + if (listeners) { + const i = listeners.indexOf(prev[a]); + i >= 0 && listeners.splice(i, 1); + } + } else { + (el)[a] ? ((el)[a] = null) : delete el.attribs[a]; + } + } + } - setContent(el: HDOMNode, value: any) { - el.textContent = value; - } + setContent(el: HDOMNode, value: any) { + el.textContent = value; + } - setStyle(el: HDOMNode, rules: IObjectOf) { - for (let r in rules) { - let v = rules[r]; - isFunction(v) && (v = v(rules)); - v != null && ((el.style || (el.style = {}))[r] = v); - } - } + setStyle(el: HDOMNode, rules: IObjectOf) { + for (let r in rules) { + let v = rules[r]; + isFunction(v) && (v = v(rules)); + v != null && ((el.style || (el.style = {}))[r] = v); + } + } } diff --git a/packages/hdom-mock/test/index.ts b/packages/hdom-mock/test/index.ts index fe729a5d54..a85c26ff03 100644 --- a/packages/hdom-mock/test/index.ts +++ b/packages/hdom-mock/test/index.ts @@ -1,82 +1,82 @@ import { group } from "@thi.ng/testament"; import * as assert from "assert"; -import { HDOMNode, MockHDOM } from "../src/index.js" +import { HDOMNode, MockHDOM } from "../src/index.js"; group("hdom-mock", { - node: () => { - const a = new HDOMNode("div"); - const impl = new MockHDOM(a); - impl.createTextElement(a, "foo"); - a.appendChild(new HDOMNode("span")); - impl.createTextElement(a, "bar"); - assert.deepStrictEqual(a.toHiccup(), [ - "div", - {}, - "foo", - ["span", {}], - "bar", - ]); - assert.deepStrictEqual(impl.getChild(a, 0).toHiccup(), ["span", {}]); - a.textContent = "foobar"; - assert.strictEqual(impl.getChild(a, 0), undefined); - assert.deepStrictEqual(a.toHiccup(), ["div", {}, "foobar"]); - }, + node: () => { + const a = new HDOMNode("div"); + const impl = new MockHDOM(a); + impl.createTextElement(a, "foo"); + a.appendChild(new HDOMNode("span")); + impl.createTextElement(a, "bar"); + assert.deepStrictEqual(a.toHiccup(), [ + "div", + {}, + "foo", + ["span", {}], + "bar", + ]); + assert.deepStrictEqual(impl.getChild(a, 0).toHiccup(), ["span", {}]); + a.textContent = "foobar"; + assert.strictEqual(impl.getChild(a, 0), undefined); + assert.deepStrictEqual(a.toHiccup(), ["div", {}, "foobar"]); + }, - "basic diff": () => { - const opts = { ctx: { button: { class: "bt" } } }; - const impl = new MockHDOM(new HDOMNode("root")); + "basic diff": () => { + const opts = { ctx: { button: { class: "bt" } } }; + const impl = new MockHDOM(new HDOMNode("root")); - const step = (prev: any[], curr: any[], expected: any[]) => { - impl.diffTree(opts, impl.root, prev, curr); - assert.deepStrictEqual(impl.root.toHiccup(), expected); - }; + const step = (prev: any[], curr: any[], expected: any[]) => { + impl.diffTree(opts, impl.root, prev, curr); + assert.deepStrictEqual(impl.root.toHiccup(), expected); + }; - const a = impl.normalizeTree(opts, (ctx: any) => [ - "div#foo.bar", - ["button", { ...ctx.button }, "hi"], - ]); - const b = impl.normalizeTree(opts, [ - "div#foo2.bar.baz", - [ - (ctx: any, label: any) => ["button", { ...ctx.button }, label], - "hello", - ], - ["div", "extra"], - ]); - const c = impl.normalizeTree(opts, [ - "div#foo3.baz.bux", - ["div", "extra"], - ]); + const a = impl.normalizeTree(opts, (ctx: any) => [ + "div#foo.bar", + ["button", { ...ctx.button }, "hi"], + ]); + const b = impl.normalizeTree(opts, [ + "div#foo2.bar.baz", + [ + (ctx: any, label: any) => ["button", { ...ctx.button }, label], + "hello", + ], + ["div", "extra"], + ]); + const c = impl.normalizeTree(opts, [ + "div#foo3.baz.bux", + ["div", "extra"], + ]); - step([], a, [ - "root", - {}, - [ - "div", - { id: "foo", class: "bar", key: "0" }, - ["button", { class: "bt", key: "0-0" }, "hi"], - ], - ]); + step([], a, [ + "root", + {}, + [ + "div", + { id: "foo", class: "bar", key: "0" }, + ["button", { class: "bt", key: "0-0" }, "hi"], + ], + ]); - step(a, b, [ - "root", - {}, - [ - "div", - { id: "foo2", class: "bar baz", key: "0" }, - ["button", { class: "bt", key: "0-0" }, "hello"], - ["div", { key: "0-1" }, ["span", { key: "0-1-0" }, "extra"]], - ], - ]); + step(a, b, [ + "root", + {}, + [ + "div", + { id: "foo2", class: "bar baz", key: "0" }, + ["button", { class: "bt", key: "0-0" }, "hello"], + ["div", { key: "0-1" }, ["span", { key: "0-1-0" }, "extra"]], + ], + ]); - step(b, c, [ - "root", - {}, - [ - "div", - { id: "foo3", class: "baz bux", key: "0" }, - ["div", { key: "0-0" }, ["span", { key: "0-0-0" }, "extra"]], - ], - ]); - }, + step(b, c, [ + "root", + {}, + [ + "div", + { id: "foo3", class: "baz bux", key: "0" }, + ["div", { key: "0-0" }, ["span", { key: "0-0-0" }, "extra"]], + ], + ]); + }, }); diff --git a/packages/hdom-mock/tsconfig.json b/packages/hdom-mock/tsconfig.json index ba3b382df2..e19642bf9a 100644 --- a/packages/hdom-mock/tsconfig.json +++ b/packages/hdom-mock/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "." - }, - "include": ["./src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] } diff --git a/packages/hdom/api-extractor.json b/packages/hdom/api-extractor.json index 94972e6bed..bc73f2cc02 100644 --- a/packages/hdom/api-extractor.json +++ b/packages/hdom/api-extractor.json @@ -1,3 +1,3 @@ { - "extends": "../../api-extractor.json" + "extends": "../../api-extractor.json" } diff --git a/packages/hdom/package.json b/packages/hdom/package.json index 43a1379244..6ef809af4e 100644 --- a/packages/hdom/package.json +++ b/packages/hdom/package.json @@ -1,127 +1,127 @@ { - "name": "@thi.ng/hdom", - "version": "9.1.10", - "description": "Lightweight vanilla ES6 UI component trees with customizable branch-local behaviors", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/hdom#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public", - "test": "testament test" - }, - "dependencies": { - "@thi.ng/api": "^8.3.8", - "@thi.ng/checks": "^3.2.2", - "@thi.ng/diff": "^5.1.8", - "@thi.ng/equiv": "^2.1.8", - "@thi.ng/errors": "^2.1.8", - "@thi.ng/hiccup": "^4.2.10", - "@thi.ng/logger": "^1.1.8", - "@thi.ng/prefixes": "^2.1.8" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "@thi.ng/atom": "^5.1.9", - "@thi.ng/testament": "^0.2.9", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "array", - "browser", - "component", - "datadriven", - "declarative", - "diff", - "dom", - "functional", - "ioc", - "iterator", - "reactive", - "tree", - "typescript", - "ui", - "vdom" - ], - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./default": { - "default": "./default.js" - }, - "./diff": { - "default": "./diff.js" - }, - "./dom": { - "default": "./dom.js" - }, - "./logger": { - "default": "./logger.js" - }, - "./normalize": { - "default": "./normalize.js" - }, - "./render-once": { - "default": "./render-once.js" - }, - "./resolve": { - "default": "./resolve.js" - }, - "./start": { - "default": "./start.js" - } - }, - "thi.ng": { - "blog": [ - { - "title": "How to UI in 2018", - "url": "https://medium.com/@thi.ng/how-to-ui-in-2018-ac2ae02acdf3" - }, - { - "title": "Of umbrellas, transducers, reactive streams & mushrooms (Pt.1)", - "url": "https://medium.com/@thi.ng/of-umbrellas-transducers-reactive-streams-mushrooms-pt-1-a8717ce3a170" - } - ], - "year": 2015 - } + "name": "@thi.ng/hdom", + "version": "9.1.10", + "description": "Lightweight vanilla ES6 UI component trees with customizable branch-local behaviors", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/hdom#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/api": "^8.3.8", + "@thi.ng/checks": "^3.2.2", + "@thi.ng/diff": "^5.1.8", + "@thi.ng/equiv": "^2.1.8", + "@thi.ng/errors": "^2.1.8", + "@thi.ng/hiccup": "^4.2.10", + "@thi.ng/logger": "^1.1.8", + "@thi.ng/prefixes": "^2.1.8" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "@thi.ng/atom": "^5.1.9", + "@thi.ng/testament": "^0.2.9", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "array", + "browser", + "component", + "datadriven", + "declarative", + "diff", + "dom", + "functional", + "ioc", + "iterator", + "reactive", + "tree", + "typescript", + "ui", + "vdom" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./api": { + "default": "./api.js" + }, + "./default": { + "default": "./default.js" + }, + "./diff": { + "default": "./diff.js" + }, + "./dom": { + "default": "./dom.js" + }, + "./logger": { + "default": "./logger.js" + }, + "./normalize": { + "default": "./normalize.js" + }, + "./render-once": { + "default": "./render-once.js" + }, + "./resolve": { + "default": "./resolve.js" + }, + "./start": { + "default": "./start.js" + } + }, + "thi.ng": { + "blog": [ + { + "title": "How to UI in 2018", + "url": "https://medium.com/@thi.ng/how-to-ui-in-2018-ac2ae02acdf3" + }, + { + "title": "Of umbrellas, transducers, reactive streams & mushrooms (Pt.1)", + "url": "https://medium.com/@thi.ng/of-umbrellas-transducers-reactive-streams-mushrooms-pt-1-a8717ce3a170" + } + ], + "year": 2015 + } } diff --git a/packages/hdom/src/api.ts b/packages/hdom/src/api.ts index 199b3d9e3c..024f37c551 100644 --- a/packages/hdom/src/api.ts +++ b/packages/hdom/src/api.ts @@ -1,163 +1,163 @@ import type { IObjectOf } from "@thi.ng/api"; export interface ILifecycle { - /** - * Component init method. Called with the actual DOM element, hdom - * user context and any other args when the component is first used, - * but **after** `render()` has been called once already AND all of - * the components children have been realized. Therefore, if any - * children have their own `init` lifecycle method, these hooks will - * be executed before that of the parent. - */ - init?(el: Element, ctx: any, ...args: any[]): void; + /** + * Component init method. Called with the actual DOM element, hdom + * user context and any other args when the component is first used, + * but **after** `render()` has been called once already AND all of + * the components children have been realized. Therefore, if any + * children have their own `init` lifecycle method, these hooks will + * be executed before that of the parent. + */ + init?(el: Element, ctx: any, ...args: any[]): void; - /** - * Returns the hdom tree of this component. - * Note: Always will be called first (prior to `init`/`release`) - * to obtain the actual component definition used for diffing. - * Therefore might have to include checks if any local state - * has already been initialized via `init`. This is the only - * mandatory method which MUST be implemented. - * - * `render` is executed before `init` because `normalizeTree()` - * must obtain the component's hdom tree first before it can - * determine if an `init` is necessary. `init` itself will be - * called from `diffTree`, `createDOM` or `hydrateDOM()` in a later - * phase of processing. - * - * `render` should ALWAYS return an array or another function, - * else the component's `init` or `release` fns will NOT be able - * to be called later. E.g. If the return value of `render` - * evaluates as a string or number, the return value should be - * wrapped as `["span", "foo"]`. If no `init` or `release` are - * used, this requirement is relaxed. - */ - render(ctx: any, ...args: any[]): any; + /** + * Returns the hdom tree of this component. + * Note: Always will be called first (prior to `init`/`release`) + * to obtain the actual component definition used for diffing. + * Therefore might have to include checks if any local state + * has already been initialized via `init`. This is the only + * mandatory method which MUST be implemented. + * + * `render` is executed before `init` because `normalizeTree()` + * must obtain the component's hdom tree first before it can + * determine if an `init` is necessary. `init` itself will be + * called from `diffTree`, `createDOM` or `hydrateDOM()` in a later + * phase of processing. + * + * `render` should ALWAYS return an array or another function, + * else the component's `init` or `release` fns will NOT be able + * to be called later. E.g. If the return value of `render` + * evaluates as a string or number, the return value should be + * wrapped as `["span", "foo"]`. If no `init` or `release` are + * used, this requirement is relaxed. + */ + render(ctx: any, ...args: any[]): any; - /** - * Called when the underlying DOM of this component is removed - * (or replaced). Intended for cleanup tasks. - */ - release?(ctx: any, ...args: any[]): void; + /** + * Called when the underlying DOM of this component is removed + * (or replaced). Intended for cleanup tasks. + */ + release?(ctx: any, ...args: any[]): void; } export interface HDOMBehaviorAttribs { - /** - * HDOM behavior control attribute. If true (default), the element - * will be fully processed by `diffTree()`. If false, no diff will - * be computed and the `replaceChild()` operation will be called in - * the currently active hdom target implementation. - */ - __diff?: boolean; - /** - * HDOM behavior control attribute. If true, the element will not be - * diffed and simply skipped. IMPORTANT: This attribute is only - * intended for cases when a component / tree branch should not be - * updated, but MUST NEVER be enabled when that component is first - * included in the tree. Doing so will result in undefined future - * behavior. - * - * Note, skipped elements and their children are being normalized, - * but are ignored during diffing. Therefore, if this attribute is - * enabled the element should either have no children OR the - * children are the same (type) as when the attribute is disabled - * (i.e. when `__skip` is falsy). - */ - __skip?: boolean; - /** - * HDOM behavior control attribute. If present, the element and all - * of its children will be processed by the given - * `HDOMImplementation` instead of the default implementation. - */ - __impl?: HDOMImplementation; - /** - * HDOM behavior control attribute. If `false`, the current - * element's children will not be normalized. Use this when you're - * sure that all children are already in canonical format (incl. - * `key` attributes). See `normalizeTree()` for details. - */ - __normalize?: boolean; - /** - * HDOM behavior control attribute. If `false`, hdom will not - * attempt to call `release()` lifecycle methods on this element or - * any of its children. - */ - __release?: boolean; - /** - * Currently only used by {@link @thi.ng/hiccup# | @thi.ng/hiccup}. No relevance for hdom. If - * `false`, the element and its children will be omitted from the - * serialized result. - */ - __serialize?: boolean; + /** + * HDOM behavior control attribute. If true (default), the element + * will be fully processed by `diffTree()`. If false, no diff will + * be computed and the `replaceChild()` operation will be called in + * the currently active hdom target implementation. + */ + __diff?: boolean; + /** + * HDOM behavior control attribute. If true, the element will not be + * diffed and simply skipped. IMPORTANT: This attribute is only + * intended for cases when a component / tree branch should not be + * updated, but MUST NEVER be enabled when that component is first + * included in the tree. Doing so will result in undefined future + * behavior. + * + * Note, skipped elements and their children are being normalized, + * but are ignored during diffing. Therefore, if this attribute is + * enabled the element should either have no children OR the + * children are the same (type) as when the attribute is disabled + * (i.e. when `__skip` is falsy). + */ + __skip?: boolean; + /** + * HDOM behavior control attribute. If present, the element and all + * of its children will be processed by the given + * `HDOMImplementation` instead of the default implementation. + */ + __impl?: HDOMImplementation; + /** + * HDOM behavior control attribute. If `false`, the current + * element's children will not be normalized. Use this when you're + * sure that all children are already in canonical format (incl. + * `key` attributes). See `normalizeTree()` for details. + */ + __normalize?: boolean; + /** + * HDOM behavior control attribute. If `false`, hdom will not + * attempt to call `release()` lifecycle methods on this element or + * any of its children. + */ + __release?: boolean; + /** + * Currently only used by {@link @thi.ng/hiccup# | @thi.ng/hiccup}. No relevance for hdom. If + * `false`, the element and its children will be omitted from the + * serialized result. + */ + __serialize?: boolean; } export interface ComponentAttribs extends HDOMBehaviorAttribs { - class?: string; - disabled?: boolean; - href?: string; - id?: string; - key?: string; - style?: string | IObjectOf; + class?: string; + disabled?: boolean; + href?: string; + id?: string; + key?: string; + style?: string | IObjectOf; - [_: string]: any; + [_: string]: any; } export interface HDOMOpts { - /** - * Root element or ID - * @defaultValue "app" - */ - root?: Element | string; - /** - * Arbitrary user context object, passed to all component functions - * embedded in the tree. - */ - ctx?: any; - /** - * Attempts to auto-expand/deref the given keys in the user supplied - * context object (`ctx` option) prior to *each* tree normalization. - * All of these values should implement the - * {@link @thi.ng/api#IDeref} interface (e.g. atoms, cursors, views, - * rstreams etc.). This feature can be used to define dynamic - * contexts linked to the main app state, e.g. using derived views - * provided by {@link @thi.ng/atom# | @thi.ng/atom}. - * - * @defaultValue none - */ - autoDerefKeys: PropertyKey[]; - /** - * If true, each elements will receive an auto-generated - * `key` attribute (unless one already exists). - * - * @defaultValue true - */ - keys?: boolean; - /** - * If true, all text content will be wrapped in `` - * elements. Spans will never be created inside