@metabase/custom-viz 0.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/cli.js +39 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/types/data.d.ts +80 -0
- package/dist/types/data.d.ts.map +1 -0
- package/dist/types/date-time.d.ts +7 -0
- package/dist/types/date-time.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/measure-text.d.ts +13 -0
- package/dist/types/measure-text.d.ts.map +1 -0
- package/dist/types/viz-settings.d.ts +76 -0
- package/dist/types/viz-settings.d.ts.map +1 -0
- package/dist/types/viz.d.ts +153 -0
- package/dist/types/viz.d.ts.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @metabase/custom-viz
|
|
2
|
+
|
|
3
|
+
CLI for creating and bundling custom visualizations for Metabase.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node.js >= 20
|
|
8
|
+
- npm >= 10
|
|
9
|
+
|
|
10
|
+
## Development
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Install dependencies
|
|
14
|
+
npm install
|
|
15
|
+
|
|
16
|
+
# Build in watch mode
|
|
17
|
+
npm run dev
|
|
18
|
+
|
|
19
|
+
# Production build
|
|
20
|
+
npm run build
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Linting & Formatting
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Lint
|
|
27
|
+
npm run lint
|
|
28
|
+
|
|
29
|
+
# Format code
|
|
30
|
+
npm run format
|
|
31
|
+
|
|
32
|
+
# Check formatting
|
|
33
|
+
npm run format:check
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Publishing
|
|
37
|
+
|
|
38
|
+
This package uses [np](https://github.com/sindresorhus/np) for publishing.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm run release
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`np` will guide you through version bumping, run the build, and publish to npm.
|
|
45
|
+
|
|
46
|
+
## Project Structure
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
src/
|
|
50
|
+
cli.ts # CLI entry point (commander)
|
|
51
|
+
dist/ # Build output
|
|
52
|
+
vite.config.ts # Vite build configuration
|
|
53
|
+
```
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync as e } from "node:fs";
|
|
3
|
+
import { mkdir as t, writeFile as n } from "node:fs/promises";
|
|
4
|
+
import { join as r } from "node:path";
|
|
5
|
+
import { Command as i } from "commander";
|
|
6
|
+
//#region src/templates/.gitignore?raw
|
|
7
|
+
var a = "node_modules/\n.DS_Store\n# dist must be committed\n", o = "import type {\n CreateCustomVisualization,\n CustomStaticVisualizationProps,\n CustomVisualizationProps,\n} from \"@metabase/custom-viz\";\n\ntype Settings = {\n threshold?: number;\n};\n\nconst createVisualization: CreateCustomVisualization<Settings> = () => {\n return {\n id: \"__CUSTOM_VIZ_NAME__\",\n getName: () => \"__CUSTOM_VIZ_NAME__\",\n minSize: { width: 1, height: 1 },\n defaultSize: { width: 2, height: 2 },\n isSensible({ cols, rows }) {\n return cols.length === 1 && rows.length === 1 && typeof rows[0][0] === \"number\";\n },\n checkRenderable(series, settings) {\n if (series.length !== 1) {\n throw new Error(\"Only 1 series is supported\");\n }\n\n const [\n {\n data: { cols, rows },\n },\n ] = series;\n\n if (cols.length !== 1) {\n throw new Error(\"Query results should only have 1 column\");\n }\n\n if (rows.length !== 1) {\n throw new Error(\"Query results should only have 1 row\");\n }\n\n if (typeof rows[0][0] !== \"number\") {\n throw new Error(\"Result is not a number\");\n }\n\n if (typeof settings.threshold !== \"number\") {\n throw new Error(\"Threshold setting is not set\");\n }\n },\n settings: {\n threshold: {\n id: \"1\",\n title: \"Threshold\",\n widget: \"number\",\n getDefault() {\n return 0;\n },\n getProps() {\n return {\n options: {\n isInteger: false,\n isNonNegative: false,\n },\n placeholder: \"Set threshold\",\n };\n },\n },\n },\n VisualizationComponent,\n StaticVisualizationComponent,\n };\n};\n\nconst VisualizationComponent = (props: CustomVisualizationProps<Settings>) => {\n const { height, series, settings, width } = props;\n const { threshold } = settings;\n const value = series[0].data.rows[0][0];\n\n if (typeof value !== \"number\" || typeof threshold !== \"number\") {\n throw new Error(\"Value and threshold need to be numbers\");\n }\n\n const emoji = value >= threshold ? \"👍\" : \"👎\";\n\n return (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n width,\n height,\n fontSize: \"10rem\",\n }}\n >\n {emoji}\n </div>\n );\n};\n\nconst StaticVisualizationComponent = (\n props: CustomStaticVisualizationProps<Settings>,\n) => {\n const width = 540;\n const height = 360;\n const { series, settings } = props;\n const { threshold } = settings;\n const value = series[0].data.rows[0][0];\n\n if (typeof value !== \"number\" || typeof threshold !== \"number\") {\n throw new Error(\"Value and threshold need to be numbers\");\n }\n\n const emoji =\n value >= threshold ? (\n <img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAYAAACLz2ctAAAA0mVYSWZJSSoACAAAAAoAAAEEAAEAAACgAAAAAQEEAAEAAACgAAAAAgEDAAMAAACGAAAAEgEDAAEAAAABAAAAGgEFAAEAAACMAAAAGwEFAAEAAACUAAAAKAEDAAEAAAACAAAAMQECAA8AAACcAAAAMgECABQAAACsAAAAaYcEAAEAAADAAAAAAAAAAAgACAAIACwBAAABAAAALAEAAAEAAABHSU1QIDMuMC4wLVJDMQAAMjAyNjowMzoxOCAxNjoyNzo1OQABAAGgAwABAAAAAQAAAAAAAAD8VR8dAAABhWlDQ1BJQ0MgcHJvZmlsZQAAeJx9kb9Lw0AcxV9bpSoVQTuIPyBDdbKLijjWKhShQqgVWnUwufQXNGlIUlwcBdeCgz8Wqw4uzro6uAqC4A8Q/wBxUnSREr+XFFrEeHDch3f3HnfvAH+9zFSzIwaommWkEnEhk10Vgq8IoB/dGMaoxEx9ThST8Bxf9/Dx9S7Ks7zP/Tl6lZzJAJ9AHGO6YRFvEM9sWjrnfeIwK0oK8TnxhEEXJH7kuuzyG+eCw36eGTbSqXniMLFQaGO5jVnRUImniSOKqlG+P+OywnmLs1qusuY9+QtDOW1lmes0R5DAIpYgQoCMKkoow0KUVo0UEynaj3v4hxy/SC6ZXCUwciygAhWS4wf/g9/dmvmpSTcpFAc6X2z7YwwI7gKNmm1/H9t24wQIPANXWstfqQOzn6TXWlrkCOjbBi6uW5q8B1zuAINPumRIjhSg6c/ngfcz+qYsMHAL9Ky5vTX3cfoApKmr5A1wcAiMFyh73ePdXe29/Xum2d8PeZVyqWZhNiAAAA16aVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOjUyMGY3ZWIzLTNiNjktNDlkMy04NjZjLWIwOWI4YzQ4MzgwZSIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozZDFlNjVmZS1kYWQ5LTQ4MjQtYmE5MS1hNGMyYzhmOWNjMmIiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4YzU1Zjg3MC02NjE4LTQ1MjAtYTE2Yy0wZmVlYWJjODE4ZTIiCiAgIEdJTVA6QVBJPSIzLjAiCiAgIEdJTVA6UGxhdGZvcm09IkxpbnV4IgogICBHSU1QOlRpbWVTdGFtcD0iMTc3MzgyNjA4MDI4Mzg3NSIKICAgR0lNUDpWZXJzaW9uPSIzLjAuMC1SQzEiCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICB0aWZmOk9yaWVudGF0aW9uPSIxIgogICB4bXA6Q3JlYXRvclRvb2w9IkdJTVAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjY6MDM6MThUMTY6Mjc6NTkrMDc6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI2OjAzOjE4VDE2OjI3OjU5KzA3OjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YTc4M2Q4MzktMTY2Yy00MTM2LWI3ZDgtMWJlNGU1ZTcxNjFjIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHSU1QIDMuMC4wLVJDMSAoTGludXgpIgogICAgICBzdEV2dDp3aGVuPSIyMDI2LTAzLTE4VDE2OjI4OjAwKzA3OjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PlfBSK4AAAAGYktHRAAAAEgAkHfF9EMAAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfqAxIJHAAvHRFSAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAACzdJREFUeNrtnXuQ1lUZxz/LLrHcZLnIgoYNN02oNGAyCUqUS2o6aRenZlKwEawxGa0ZJhswrZFwYsCKGqapyHEatYYIzUshTWqCFGCajFxWwEkEgWW5yHXh7Y/z21iWRZ7zvuf3vr/L9zNzZvjj8J7fec53z/08TxWiFGqBScBEYCQwCKgDCkAjsAFYC7wAPA/slMlECHoCDwC7IrFZ0olIjPcDQ2RCUSw3A7s9hNdeOg48BlwgcwornYDflii8tmk/MEWmFWejM/BcYPG1TrNlYnEmOgBLYhRfS5olU4v2uKcM4mtZpFwtc4vWXAwcKZMAC8A2oFueDFwtjb0viyIRlovuwHu4fUORcy6JhsVCmdO2PHUMHaSzMzIF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\" />\n ) : (\n <img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAYAAACLz2ctAAAA0mVYSWZJSSoACAAAAAoAAAEEAAEAAACgAAAAAQEEAAEAAACgAAAAAgEDAAMAAACGAAAAEgEDAAEAAAABAAAAGgEFAAEAAACMAAAAGwEFAAEAAACUAAAAKAEDAAEAAAACAAAAMQECAA8AAACcAAAAMgECABQAAACsAAAAaYcEAAEAAADAAAAAAAAAAAgACAAIACwBAAABAAAALAEAAAEAAABHSU1QIDMuMC4wLVJDMQAAMjAyNjowMzoxOCAxNjoyODowOAABAAGgAwABAAAAAQAAAAAAAABiEXj2AAABhWlDQ1BJQ0MgcHJvZmlsZQAAeJx9kb9Lw0AcxV9bpSoVQTuIPyBDdbKLijjWKhShQqgVWnUwufQXNGlIUlwcBdeCgz8Wqw4uzro6uAqC4A8Q/wBxUnSREr+XFFrEeHDch3f3HnfvAH+9zFSzIwaommWkEnEhk10Vgq8IoB/dGMaoxEx9ThST8Bxf9/Dx9S7Ks7zP/Tl6lZzJAJ9AHGO6YRFvEM9sWjrnfeIwK0oK8TnxhEEXJH7kuuzyG+eCw36eGTbSqXniMLFQaGO5jVnRUImniSOKqlG+P+OywnmLs1qusuY9+QtDOW1lmes0R5DAIpYgQoCMKkoow0KUVo0UEynaj3v4hxy/SC6ZXCUwciygAhWS4wf/g9/dmvmpSTcpFAc6X2z7YwwI7gKNmm1/H9t24wQIPANXWstfqQOzn6TXWlrkCOjbBi6uW5q8B1zuAINPumRIjhSg6c/ngfcz+qYsMHAL9Ky5vTX3cfoApKmr5A1wcAiMFyh73ePdXe29/Xum2d8PeZVyqWZhNiAAAA16aVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOmI5OGQ1OTk1LTRkZjktNDdkMS1hMGRmLTJjYWJhOTU2NjFjZSIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4MzNlNzUzYS0zZDhmLTRhNTAtODA1NC0wNmQ0N2JhNzQ1NWMiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoyYzJiM2I3Zi04OTU0LTQyZTgtOWNkOS1lOTc3Y2ExZmMwMGYiCiAgIEdJTVA6QVBJPSIzLjAiCiAgIEdJTVA6UGxhdGZvcm09IkxpbnV4IgogICBHSU1QOlRpbWVTdGFtcD0iMTc3MzgyNjA4OTM4NzIzMiIKICAgR0lNUDpWZXJzaW9uPSIzLjAuMC1SQzEiCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICB0aWZmOk9yaWVudGF0aW9uPSIxIgogICB4bXA6Q3JlYXRvclRvb2w9IkdJTVAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjY6MDM6MThUMTY6Mjg6MDgrMDc6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI2OjAzOjE4VDE2OjI4OjA4KzA3OjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NDM0NWQ1MjktYzlmYi00NjQ5LTlmZDctYmVkMDNiYjMzNjUzIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHSU1QIDMuMC4wLVJDMSAoTGludXgpIgogICAgICBzdEV2dDp3aGVuPSIyMDI2LTAzLTE4VDE2OjI4OjA5KzA3OjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PkUgIFwAAAAGYktHRAAAAEgAkHfF9EMAAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfqAxIJHAlWwan2AAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAACwtJREFUeNrtnXuwVlUZh5+DAnI9ykWBJC9cdHAgRSUh1Kwhi6KG0TTorpjTDScNRm1Kx/5wqhHTAjRMssbRaaBxiDIgyUKHSASV0FIUUFFB4CBXhQNff6x1GOZ08Lzr+9be3778npk1wMzi23ut97fXfb1vA6ItOgBDgHP9nwOBk/2fXYDjfb4TMvbe7wI7gCZgC7AaeBpYAbwks2abYcB0YCmwE6gULD0L3AR8UKbODgOBHwHrCii4o6X9wL0SYn0ZAywAmkskvNbpPeAHwLGSQ3qMAh4rsejaSsuB0ySNZOnvu52DElyb6S1gpGQSn+P8wHuXRNZu2gmMlWTiMaFkk4sYaatfdhI1MBR4VGKqOv0X6CEZhdML+Jmf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\" />\n );\n\n return (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n width,\n height,\n fontSize: \"10rem\",\n }}\n >\n {emoji}\n </div>\n );\n};\n\nexport default createVisualization;\n", s = "{\n \"name\": \"__CUSTOM_VIZ_NAME__\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"build\": \"vite build\",\n \"dev\": \"vite build --watch & vite preview\",\n \"type-check\": \"tsc --noEmit\"\n },\n \"devDependencies\": {\n \"@metabase/custom-viz\": \"^0.0.1\",\n \"@types/react\": \"^19.2.14\",\n \"react\": \"^19.1.0\",\n \"typescript\": \"^5.9.3\",\n \"vite\": \"^8.0.0\"\n }\n}\n", c = "{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"jsx\": \"react-jsx\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"outDir\": \"dist\",\n \"declaration\": true\n },\n \"include\": [\"src\"]\n}\n", l = "import { resolve } from \"path\";\nimport { createServer } from \"http\";\nimport { defineConfig } from \"vite\";\n\n/**\n * Vite plugin that replaces `react` and `react/jsx-runtime` imports with\n * virtual modules that read from Metabase's `window.__METABASE_VIZ_API__`.\n *\n * This is necessary because the ES module output format cannot use\n * `output.globals`, and bare `import 'react'` would fail in the browser.\n */\nfunction metabaseVizExternals() {\n const VIRTUAL_REACT = \"\\0virtual:react\";\n const VIRTUAL_JSX_RUNTIME = \"\\0virtual:react/jsx-runtime\";\n\n return {\n name: \"metabase-viz-externals\",\n enforce: \"pre\" as const,\n\n resolveId(source) {\n if (source === \"react\") {\n return VIRTUAL_REACT;\n }\n if (source === \"react/jsx-runtime\") {\n return VIRTUAL_JSX_RUNTIME;\n }\n return null;\n },\n\n load(id) {\n if (id === VIRTUAL_REACT) {\n return [\n \"const React = window.__METABASE_VIZ_API__.React;\",\n \"export default React;\",\n \"export const { useState, useEffect, useRef, useCallback, useMemo, useReducer, useContext, createElement, Fragment } = React;\",\n ].join(\"\\n\");\n }\n if (id === VIRTUAL_JSX_RUNTIME) {\n return [\n \"const jsxRuntime = window.__METABASE_VIZ_API__.jsxRuntime;\",\n \"export const { jsx, jsxs, Fragment } = jsxRuntime;\",\n ].join(\"\\n\");\n }\n return null;\n },\n };\n}\n\nconst NOTIFY_PORT = 5175;\n\n/**\n * Vite plugin that starts a tiny SSE server and sends a \"reload\" event\n * to all connected clients after each rebuild completes.\n * Metabase's frontend connects to this to live-reload the custom viz.\n */\nfunction metabaseNotifyReload() {\n const clients = new Set<import(\"http\").ServerResponse>();\n let server: ReturnType<typeof createServer> | null = null;\n\n return {\n name: \"metabase-notify-reload\",\n\n buildStart() {\n if (server) {\n return;\n }\n server = createServer((req, res) => {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n clients.add(res);\n req.on(\"close\", () => clients.delete(res));\n });\n server.listen(NOTIFY_PORT, () => {\n console.log(\n `[metabase-notify] SSE server listening on http://localhost:${NOTIFY_PORT}`,\n );\n });\n },\n\n closeBundle() {\n for (const client of clients) {\n client.write(\"data: reload\\n\\n\");\n }\n console.log(\n `[metabase-notify] Build complete, notified ${clients.size} client(s)`,\n );\n },\n };\n}\n\nconst isWatch = process.argv.includes(\"--watch\");\n\nexport default defineConfig({\n plugins: [metabaseVizExternals(), ...(isWatch ? [metabaseNotifyReload()] : [])],\n build: {\n outDir: \"dist\",\n lib: {\n entry: resolve(__dirname, \"src/index.tsx\"),\n formats: [\"iife\"],\n fileName: () => \"index.js\",\n name: \"__customVizPlugin__\",\n },\n },\n preview: {\n port: 5174,\n host: true,\n cors: true,\n },\n});\n", u = "__CUSTOM_VIZ_NAME__";
|
|
8
|
+
function d(e, t) {
|
|
9
|
+
return e.split(u).join(t);
|
|
10
|
+
}
|
|
11
|
+
function f(e) {
|
|
12
|
+
return d(s, e);
|
|
13
|
+
}
|
|
14
|
+
function p() {
|
|
15
|
+
return l;
|
|
16
|
+
}
|
|
17
|
+
function m() {
|
|
18
|
+
return c;
|
|
19
|
+
}
|
|
20
|
+
function h(e) {
|
|
21
|
+
return d(o, e);
|
|
22
|
+
}
|
|
23
|
+
function g() {
|
|
24
|
+
return a;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/cli.ts
|
|
28
|
+
var _ = new i();
|
|
29
|
+
_.name("metabase-custom-viz").description("CLI for creating custom visualizations for Metabase").version("0.0.1"), _.command("init").description("Scaffold a new custom visualization").argument("<name>", "Name of the custom visualization").action(async (i) => {
|
|
30
|
+
let a = i.trim().replace(/\s+/g, "-").toLowerCase();
|
|
31
|
+
(!a || !/^[a-z0-9@][a-z0-9._\-/]*$/.test(a)) && (console.error(`Error: "${i}" is not a valid project name. Use letters, numbers, hyphens, and dots.`), process.exit(1)), e(a) && (console.error(`Error: Directory "${a}" already exists.`), process.exit(1)), console.log(`Scaffolding custom visualization: ${a}\n`), await t(r(a, "src"), { recursive: !0 }), await Promise.all([
|
|
32
|
+
n(r(a, "package.json"), f(a)),
|
|
33
|
+
n(r(a, "vite.config.ts"), p()),
|
|
34
|
+
n(r(a, "tsconfig.json"), m()),
|
|
35
|
+
n(r(a, "src", "index.tsx"), h(a)),
|
|
36
|
+
n(r(a, ".gitignore"), g())
|
|
37
|
+
]), console.log("Created files:"), console.log(` ${a}/package.json`), console.log(` ${a}/vite.config.ts`), console.log(` ${a}/tsconfig.json`), console.log(` ${a}/src/index.tsx`), console.log(` ${a}/.gitignore`), console.log(), console.log("Next steps:"), console.log(` cd ${a}`), console.log(" npm install"), console.log(" npm run dev # Watch mode"), console.log(" npm run build # Production build");
|
|
38
|
+
}), _.parse();
|
|
39
|
+
//#endregion
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/types/date-time.ts
|
|
2
|
+
var e = [
|
|
3
|
+
"minute",
|
|
4
|
+
"hour",
|
|
5
|
+
"day",
|
|
6
|
+
"week",
|
|
7
|
+
"month",
|
|
8
|
+
"quarter",
|
|
9
|
+
"year"
|
|
10
|
+
], t = [
|
|
11
|
+
"minute-of-hour",
|
|
12
|
+
"hour-of-day",
|
|
13
|
+
"day-of-week",
|
|
14
|
+
"day-of-month",
|
|
15
|
+
"day-of-year",
|
|
16
|
+
"week-of-year",
|
|
17
|
+
"month-of-year",
|
|
18
|
+
"quarter-of-year"
|
|
19
|
+
], n = [...e, ...t];
|
|
20
|
+
//#endregion
|
|
21
|
+
export { e as dateTimeAbsoluteUnits, t as dateTimeRelativeUnits, n as dateTimeUnits };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { DateTimeUnit } from './date-time';
|
|
2
|
+
export type RowValue = string | number | null | boolean | object;
|
|
3
|
+
export type Row = RowValue[];
|
|
4
|
+
export type DatasetData = {
|
|
5
|
+
rows: Row[];
|
|
6
|
+
cols: Column[];
|
|
7
|
+
/**
|
|
8
|
+
* How many results have been truncated. Present only when truncation occurred.
|
|
9
|
+
*/
|
|
10
|
+
rows_truncated?: number;
|
|
11
|
+
};
|
|
12
|
+
export type DatasetError = string | {
|
|
13
|
+
status: number;
|
|
14
|
+
data?: string;
|
|
15
|
+
};
|
|
16
|
+
export type SingleSeries = {
|
|
17
|
+
data: DatasetData;
|
|
18
|
+
error?: DatasetError;
|
|
19
|
+
};
|
|
20
|
+
export type Series = SingleSeries[];
|
|
21
|
+
export type BinningInfo = {
|
|
22
|
+
binning_strategy?: "default" | "bin-width" | "num-bins";
|
|
23
|
+
bin_width?: number;
|
|
24
|
+
num_bins?: number;
|
|
25
|
+
max_value?: number;
|
|
26
|
+
min_value?: number;
|
|
27
|
+
};
|
|
28
|
+
export type ColumnId = number;
|
|
29
|
+
export type Column = {
|
|
30
|
+
/**
|
|
31
|
+
* Metabase identifier.
|
|
32
|
+
*/
|
|
33
|
+
id: ColumnId;
|
|
34
|
+
/**
|
|
35
|
+
* Name of the column in the database.
|
|
36
|
+
*/
|
|
37
|
+
name: string;
|
|
38
|
+
/**
|
|
39
|
+
* Name of the column shown in the UI.
|
|
40
|
+
*/
|
|
41
|
+
display_name: string;
|
|
42
|
+
/**
|
|
43
|
+
* Description of the column set in Metabase.
|
|
44
|
+
*/
|
|
45
|
+
description: string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Base type of the column in Metabase type system.
|
|
48
|
+
*/
|
|
49
|
+
base_type?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Semantic type of the column in Metabase type system.
|
|
52
|
+
*/
|
|
53
|
+
semantic_type?: string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Effective type of the column in Metabase type system.
|
|
56
|
+
*/
|
|
57
|
+
effective_type?: string;
|
|
58
|
+
/**
|
|
59
|
+
* If the column value has been remapped, this is a name of the column it's been remapped from.
|
|
60
|
+
*/
|
|
61
|
+
remapped_from?: string;
|
|
62
|
+
/**
|
|
63
|
+
* If the column value has been remapped, this is a name of the column it's been remapped to.
|
|
64
|
+
*/
|
|
65
|
+
remapped_to?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Present if column represents date and/or time.
|
|
68
|
+
*/
|
|
69
|
+
unit?: DateTimeUnit;
|
|
70
|
+
/**
|
|
71
|
+
* Present if column is binned.
|
|
72
|
+
*/
|
|
73
|
+
binning_info?: BinningInfo | null;
|
|
74
|
+
/**
|
|
75
|
+
* Column's visualization settings set in Metabase.
|
|
76
|
+
*/
|
|
77
|
+
settings?: ColumnVisualizationSettings;
|
|
78
|
+
};
|
|
79
|
+
export type ColumnVisualizationSettings = Record<string, unknown>;
|
|
80
|
+
//# sourceMappingURL=data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/types/data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjE,MAAM,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;AAE7B,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,GAAG,EAAE,CAAC;IAEZ,IAAI,EAAE,MAAM,EAAE,CAAC;IAEf;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,YAAY,GACpB,MAAM,GACN;IACE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEN,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;AAEpC,MAAM,MAAM,WAAW,GAAG;IACxB,gBAAgB,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B,MAAM,MAAM,MAAM,GAAG;IACnB;;OAEG;IACH,EAAE,EAAE,QAAQ,CAAC;IAEb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAE3B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;IAEpB;;OAEG;IACH,YAAY,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAElC;;OAEG;IACH,QAAQ,CAAC,EAAE,2BAA2B,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const dateTimeAbsoluteUnits: readonly ["minute", "hour", "day", "week", "month", "quarter", "year"];
|
|
2
|
+
export declare const dateTimeRelativeUnits: readonly ["minute-of-hour", "hour-of-day", "day-of-week", "day-of-month", "day-of-year", "week-of-year", "month-of-year", "quarter-of-year"];
|
|
3
|
+
export declare const dateTimeUnits: readonly ["minute", "hour", "day", "week", "month", "quarter", "year", "minute-of-hour", "hour-of-day", "day-of-week", "day-of-month", "day-of-year", "week-of-year", "month-of-year", "quarter-of-year"];
|
|
4
|
+
export type DateTimeAbsoluteUnit = (typeof dateTimeAbsoluteUnits)[number];
|
|
5
|
+
export type DateTimeRelativeUnit = (typeof dateTimeRelativeUnits)[number];
|
|
6
|
+
export type DateTimeUnit = 'default' | DateTimeAbsoluteUnit | DateTimeRelativeUnit;
|
|
7
|
+
//# sourceMappingURL=date-time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-time.d.ts","sourceRoot":"","sources":["../../src/types/date-time.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,wEAQxB,CAAC;AAEX,eAAO,MAAM,qBAAqB,8IASxB,CAAC;AAEX,eAAO,MAAM,aAAa,2MAAgE,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,oBAAoB,GAAG,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,OAAO,CAAC;AACtB,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type FontStyle = {
|
|
2
|
+
size: string | number;
|
|
3
|
+
family: string;
|
|
4
|
+
weight: string | number;
|
|
5
|
+
};
|
|
6
|
+
export interface TextSize {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
export type TextWidthMeasurer = (text: string, style: FontStyle) => TextSize["width"];
|
|
11
|
+
export type TextHeightMeasurer = (text: string, style: FontStyle) => TextSize["height"];
|
|
12
|
+
export type TextMeasurer = (text: string, style: FontStyle) => TextSize;
|
|
13
|
+
//# sourceMappingURL=measure-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure-text.d.ts","sourceRoot":"","sources":["../../src/types/measure-text.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC9B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,KACb,QAAQ,CAAC,OAAO,CAAC,CAAC;AAEvB,MAAM,MAAM,kBAAkB,GAAG,CAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,KACb,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAExB,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,KAAK,QAAQ,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { Column } from './data';
|
|
3
|
+
export type WidgetName = keyof Widgets;
|
|
4
|
+
export type Widgets = {
|
|
5
|
+
input: InputProps;
|
|
6
|
+
number: NumberProps;
|
|
7
|
+
radio: RadioProps;
|
|
8
|
+
select: SelectProps;
|
|
9
|
+
toggle: ToggleProps;
|
|
10
|
+
segmentedControl: SegmentedControlProps;
|
|
11
|
+
field: FieldProps;
|
|
12
|
+
fields: FieldsProps;
|
|
13
|
+
color: ColorProps;
|
|
14
|
+
multiselect: MultiselectProps;
|
|
15
|
+
};
|
|
16
|
+
export type InputProps = {
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
};
|
|
19
|
+
export type NumberProps = {
|
|
20
|
+
options?: {
|
|
21
|
+
isInteger?: boolean;
|
|
22
|
+
isNonNegative?: boolean;
|
|
23
|
+
};
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
};
|
|
26
|
+
export type RadioProps = {
|
|
27
|
+
options: {
|
|
28
|
+
name: string;
|
|
29
|
+
value: boolean | string | null;
|
|
30
|
+
}[];
|
|
31
|
+
};
|
|
32
|
+
export type SelectProps = {
|
|
33
|
+
options: {
|
|
34
|
+
name: string;
|
|
35
|
+
value: boolean | string | null;
|
|
36
|
+
}[];
|
|
37
|
+
placeholder?: string;
|
|
38
|
+
placeholderNoOptions?: string;
|
|
39
|
+
};
|
|
40
|
+
export type ToggleProps = {};
|
|
41
|
+
export type SegmentedControlProps = {
|
|
42
|
+
options: {
|
|
43
|
+
name: string;
|
|
44
|
+
value: string;
|
|
45
|
+
}[];
|
|
46
|
+
};
|
|
47
|
+
export type FieldProps = {
|
|
48
|
+
columns: Column[];
|
|
49
|
+
options: {
|
|
50
|
+
name: string;
|
|
51
|
+
value: string;
|
|
52
|
+
}[];
|
|
53
|
+
showColumnSetting?: boolean;
|
|
54
|
+
};
|
|
55
|
+
export type FieldsProps = {
|
|
56
|
+
addAnother?: ReactNode;
|
|
57
|
+
columns: Column[];
|
|
58
|
+
options: {
|
|
59
|
+
name: string;
|
|
60
|
+
value: string;
|
|
61
|
+
}[];
|
|
62
|
+
showColumnSetting?: boolean;
|
|
63
|
+
showColumnSettingForIndicies?: number[];
|
|
64
|
+
};
|
|
65
|
+
export type ColorProps = {
|
|
66
|
+
title?: string;
|
|
67
|
+
};
|
|
68
|
+
export type MultiselectProps = {
|
|
69
|
+
options: {
|
|
70
|
+
label: string;
|
|
71
|
+
value: string;
|
|
72
|
+
}[];
|
|
73
|
+
placeholder?: string;
|
|
74
|
+
placeholderNoOptions?: string;
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=viz-settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viz-settings.d.ts","sourceRoot":"","sources":["../../src/types/viz-settings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,gBAAgB,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,aAAa,CAAC,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;KAChC,EAAE,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;KAChC,EAAE,CAAC;IACJ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,EAAE,CAAC;AAE7B,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;IACJ,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;IACJ,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,4BAA4B,CAAC,EAAE,MAAM,EAAE,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;IACJ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
import { Column, DatasetData, RowValue, Series } from './data';
|
|
3
|
+
import { TextHeightMeasurer, TextWidthMeasurer } from './measure-text';
|
|
4
|
+
import { WidgetName, Widgets } from './viz-settings';
|
|
5
|
+
/**
|
|
6
|
+
* Export this function to define a custom visualization.
|
|
7
|
+
*/
|
|
8
|
+
export type CreateCustomVisualization<CustomVisualizationSettings> = (props: CreateCustomVisualizationProps) => CustomVisualization<CustomVisualizationSettings>;
|
|
9
|
+
export type CreateCustomVisualizationProps = {
|
|
10
|
+
/**
|
|
11
|
+
* Translates text using ttag function used in Metabase.
|
|
12
|
+
*/
|
|
13
|
+
translate: (text: string) => string;
|
|
14
|
+
};
|
|
15
|
+
export type CustomVisualization<CustomVisualizationSettings> = {
|
|
16
|
+
/**
|
|
17
|
+
* A unique visualization identifier. It's not shown in the UI.
|
|
18
|
+
*/
|
|
19
|
+
id: string;
|
|
20
|
+
/**
|
|
21
|
+
* Returns visualization name to be shown in the UI.
|
|
22
|
+
*/
|
|
23
|
+
getName(): string;
|
|
24
|
+
/**
|
|
25
|
+
* Set to false to disable saving the question as PNG.
|
|
26
|
+
*/
|
|
27
|
+
canSavePng?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Set to true to disable the default visulization header.
|
|
30
|
+
*/
|
|
31
|
+
noHeader?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Min size on dashboard grid.
|
|
34
|
+
*/
|
|
35
|
+
minSize: VisualizationGridSize;
|
|
36
|
+
/**
|
|
37
|
+
* Default size on dashboard grid.
|
|
38
|
+
*/
|
|
39
|
+
defaultSize: VisualizationGridSize;
|
|
40
|
+
/**
|
|
41
|
+
* Visualization settings definitions.
|
|
42
|
+
*/
|
|
43
|
+
settings?: CustomVisualizationSettingsDefinitions<CustomVisualizationSettings>;
|
|
44
|
+
/**
|
|
45
|
+
* This function should return true if the data shape makes sense for this visualization.
|
|
46
|
+
* TODO: should it get series: Series instead?
|
|
47
|
+
*/
|
|
48
|
+
isSensible: (data: DatasetData) => boolean;
|
|
49
|
+
/**
|
|
50
|
+
* This function should throw if the visualization cannot be rendered with given data and settings.
|
|
51
|
+
*/
|
|
52
|
+
checkRenderable: (series: Series, settings: CustomVisualizationSettings) => void | never;
|
|
53
|
+
/**
|
|
54
|
+
* Component that renders the visualization.
|
|
55
|
+
*/
|
|
56
|
+
VisualizationComponent: ComponentType<CustomVisualizationProps<CustomVisualizationSettings>>;
|
|
57
|
+
/**
|
|
58
|
+
* Component that renders the visualization.
|
|
59
|
+
*/
|
|
60
|
+
StaticVisualizationComponent: ComponentType<CustomStaticVisualizationProps<CustomVisualizationSettings>>;
|
|
61
|
+
};
|
|
62
|
+
export type CustomVisualizationSettingsDefinitions<CustomVisualizationSettings, K extends keyof CustomVisualizationSettings = keyof CustomVisualizationSettings> = {
|
|
63
|
+
[Key in K]-?: VisualizationSettingDefinition<unknown, CustomVisualizationSettings[Key], Record<string, unknown>, CustomVisualizationSettings>;
|
|
64
|
+
};
|
|
65
|
+
export type BaseWidgetProps<TValue, CustomVisualizationSettings> = {
|
|
66
|
+
id: string;
|
|
67
|
+
value: TValue | undefined;
|
|
68
|
+
onChange: (value?: TValue | null) => void;
|
|
69
|
+
onChangeSettings: (settings: Partial<CustomVisualizationSettings>) => void;
|
|
70
|
+
};
|
|
71
|
+
type VisualizationSettingDefinitionBase<T, TValue, CustomVisualizationSettings> = {
|
|
72
|
+
id: string;
|
|
73
|
+
section?: string;
|
|
74
|
+
title?: string;
|
|
75
|
+
group?: string;
|
|
76
|
+
index?: number;
|
|
77
|
+
inline?: boolean;
|
|
78
|
+
default?: TValue;
|
|
79
|
+
persistDefault?: boolean;
|
|
80
|
+
set?: boolean;
|
|
81
|
+
value?: TValue;
|
|
82
|
+
readDependencies?: string[];
|
|
83
|
+
writeDependencies?: string[];
|
|
84
|
+
eraseDependencies?: string[];
|
|
85
|
+
isValid?: (object: T, settings: CustomVisualizationSettings) => boolean;
|
|
86
|
+
getDefault?: (object: T, settings: CustomVisualizationSettings) => TValue;
|
|
87
|
+
getValue?: (object: T, settings: CustomVisualizationSettings) => TValue;
|
|
88
|
+
};
|
|
89
|
+
type VisualizationSettingDefinitionWithBuiltInWidget<T, TValue, CustomVisualizationSettings> = {
|
|
90
|
+
[Key in WidgetName]: VisualizationSettingDefinitionBase<T, TValue, CustomVisualizationSettings> & {
|
|
91
|
+
widget: Key;
|
|
92
|
+
getProps?: (object: T, vizSettings: CustomVisualizationSettings) => Widgets[Key];
|
|
93
|
+
};
|
|
94
|
+
}[WidgetName];
|
|
95
|
+
type VisualizationSettingDefinitionWithCustomWidget<T, TValue, TProps, CustomVisualizationSettings> = VisualizationSettingDefinitionBase<T, TValue, CustomVisualizationSettings> & {
|
|
96
|
+
widget: ComponentType<TProps & BaseWidgetProps<TValue, CustomVisualizationSettings>>;
|
|
97
|
+
getProps?: (object: T, vizSettings: CustomVisualizationSettings) => TProps;
|
|
98
|
+
};
|
|
99
|
+
type VisualizationSettingDefinitionWithoutWidget<T, TValue, CustomVisualizationSettings> = VisualizationSettingDefinitionBase<T, TValue, CustomVisualizationSettings> & {
|
|
100
|
+
widget?: never;
|
|
101
|
+
getProps?: never;
|
|
102
|
+
};
|
|
103
|
+
export type VisualizationSettingDefinition<T, TValue, TProps, CustomVisualizationSettings> = VisualizationSettingDefinitionWithBuiltInWidget<T, TValue, CustomVisualizationSettings> | VisualizationSettingDefinitionWithCustomWidget<T, TValue, TProps, CustomVisualizationSettings> | VisualizationSettingDefinitionWithoutWidget<T, TValue, CustomVisualizationSettings>;
|
|
104
|
+
export type VisualizationGridSize = {
|
|
105
|
+
/**
|
|
106
|
+
* Number of grid columns in a Metabase dashboard.
|
|
107
|
+
*/
|
|
108
|
+
width: number;
|
|
109
|
+
/**
|
|
110
|
+
* Number of grid rows in a Metabase dashboard.
|
|
111
|
+
*/
|
|
112
|
+
height: number;
|
|
113
|
+
};
|
|
114
|
+
export type CustomVisualizationProps<CustomVisualizationSettings> = {
|
|
115
|
+
width: number;
|
|
116
|
+
height: number;
|
|
117
|
+
series: Series;
|
|
118
|
+
settings: CustomVisualizationSettings;
|
|
119
|
+
onClick: (clickObject: ClickObject<CustomVisualizationSettings> | null) => void;
|
|
120
|
+
};
|
|
121
|
+
export type ColorGetter = (colorName: string) => string;
|
|
122
|
+
export interface RenderingContext {
|
|
123
|
+
getColor: ColorGetter;
|
|
124
|
+
measureText: TextWidthMeasurer;
|
|
125
|
+
measureTextHeight: TextHeightMeasurer;
|
|
126
|
+
fontFamily: string;
|
|
127
|
+
}
|
|
128
|
+
export type CustomStaticVisualizationProps<CustomVisualizationSettings> = {
|
|
129
|
+
series: Series;
|
|
130
|
+
renderingContext: RenderingContext;
|
|
131
|
+
isStorybook?: boolean;
|
|
132
|
+
settings: CustomVisualizationSettings;
|
|
133
|
+
hasDevWatermark?: boolean;
|
|
134
|
+
};
|
|
135
|
+
export type CustomVisualizationSettingsProps = {};
|
|
136
|
+
export type ClickObject<CustomVisualizationSettings> = {
|
|
137
|
+
value?: RowValue;
|
|
138
|
+
column?: Column;
|
|
139
|
+
dimensions?: ClickObjectDimension[];
|
|
140
|
+
event?: MouseEvent;
|
|
141
|
+
element?: Element;
|
|
142
|
+
settings?: CustomVisualizationSettings;
|
|
143
|
+
origin?: {
|
|
144
|
+
row: RowValue[];
|
|
145
|
+
cols: Column[];
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
export interface ClickObjectDimension {
|
|
149
|
+
value: RowValue;
|
|
150
|
+
column: Column;
|
|
151
|
+
}
|
|
152
|
+
export {};
|
|
153
|
+
//# sourceMappingURL=viz.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viz.d.ts","sourceRoot":"","sources":["../../src/types/viz.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@metabase/custom-viz",
|
|
3
|
+
"version": "0.0.1-alpha.1",
|
|
4
|
+
"description": "Creating custom visualizations for Metabase",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"metabase-custom-viz": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"dev": "vite build --watch",
|
|
25
|
+
"build": "vite build",
|
|
26
|
+
"lint": "oxlint src/",
|
|
27
|
+
"format": "prettier --write .",
|
|
28
|
+
"format:check": "prettier --check .",
|
|
29
|
+
"release": "npm run build && np"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"commander": "^13.1.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.13.10",
|
|
36
|
+
"@types/react": "^19.2.14",
|
|
37
|
+
"lefthook": "^2.1.4",
|
|
38
|
+
"np": "^10.2.0",
|
|
39
|
+
"prettier": "^3.5.3",
|
|
40
|
+
"typescript": "^5.9.3",
|
|
41
|
+
"vite": "^8.0.0",
|
|
42
|
+
"vite-plugin-dts": "^4.5.4"
|
|
43
|
+
}
|
|
44
|
+
}
|