@devtable/dashboard 1.22.0 → 1.24.0

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.
@@ -36,7 +36,7 @@ var __publicField = (obj, key, value) => {
36
36
  import React from "react";
37
37
  import _ from "lodash";
38
38
  import RGL, { WidthProvider } from "react-grid-layout";
39
- import { Popover, Tooltip, Group, Text, ActionIcon, Box, Button, TextInput, LoadingOverlay, Table, Select, useMantineTheme, ColorSwatch, Switch, Slider, SegmentedControl, NumberInput, Accordion, ColorInput, JsonInput, Modal, AppShell, Tabs, Menu, Divider, Container, Textarea } from "@mantine/core";
39
+ import { Popover, Tooltip, Group, Text, ActionIcon, Box, Button, TextInput, LoadingOverlay, Table, Select, useMantineTheme, ColorSwatch, Switch, Slider, SegmentedControl, NumberInput, Accordion, ColorInput, Divider, JsonInput, Modal, AppShell, Tabs, Menu, Container, Textarea } from "@mantine/core";
40
40
  import { useRequest } from "ahooks";
41
41
  import axios from "axios";
42
42
  import { InfoCircle, DeviceFloppy, Refresh, Trash, PlaylistAdd, Settings, Resize, Paint, PlayerPlay, Database, Recycle, Share } from "tabler-icons-react";
@@ -109,20 +109,20 @@ function explainSQL(sql, context, definitions) {
109
109
  const APIClient = {
110
110
  baseURL: "http://localhost:31200",
111
111
  getRequest(method) {
112
- return (url, data, options = {}) => {
112
+ return (url, data, options2 = {}) => {
113
113
  const headers = __spreadValues({
114
114
  "X-Requested-With": "XMLHttpRequest",
115
- "Content-Type": options.string ? "application/x-www-form-urlencoded" : "application/json"
116
- }, options.headers);
115
+ "Content-Type": options2.string ? "application/x-www-form-urlencoded" : "application/json"
116
+ }, options2.headers);
117
117
  const conf = {
118
118
  baseURL: this.baseURL,
119
119
  method,
120
120
  url,
121
- params: method === "GET" ? data : options.params,
121
+ params: method === "GET" ? data : options2.params,
122
122
  headers
123
123
  };
124
124
  if (method === "POST") {
125
- conf.data = options.string ? JSON.stringify(data) : data;
125
+ conf.data = options2.string ? JSON.stringify(data) : data;
126
126
  }
127
127
  return axios(conf).then((res) => {
128
128
  return res.data;
@@ -591,7 +591,7 @@ function PickQuery({}) {
591
591
  data,
592
592
  loading
593
593
  } = React.useContext(PanelContext);
594
- const options = React.useMemo(() => {
594
+ const options2 = React.useMemo(() => {
595
595
  return queries.map((d) => ({
596
596
  value: d.id,
597
597
  label: d.id
@@ -610,7 +610,7 @@ function PickQuery({}) {
610
610
  children: [/* @__PURE__ */ jsx(Text, {
611
611
  children: "Select a Query"
612
612
  }), /* @__PURE__ */ jsx(Select, {
613
- data: options,
613
+ data: options2,
614
614
  value: queryID,
615
615
  onChange: setQueryID,
616
616
  allowDeselect: false,
@@ -1567,10 +1567,10 @@ var ecStat = { exports: {} };
1567
1567
  var __WEBPACK_AMD_DEFINE_RESULT__;
1568
1568
  !(__WEBPACK_AMD_DEFINE_RESULT__ = function(require2) {
1569
1569
  var quantile = __webpack_require__(13);
1570
- function median(data) {
1570
+ function median2(data) {
1571
1571
  return quantile(data, 0.5);
1572
1572
  }
1573
- return median;
1573
+ return median2;
1574
1574
  }.call(exports2, __webpack_require__, exports2, module2), __WEBPACK_AMD_DEFINE_RESULT__ !== void 0 && (module2.exports = __WEBPACK_AMD_DEFINE_RESULT__));
1575
1575
  },
1576
1576
  function(module2, exports2, __webpack_require__) {
@@ -2460,6 +2460,32 @@ function VizPie({
2460
2460
  }
2461
2461
  });
2462
2462
  }
2463
+ function median(numbers) {
2464
+ const sorted = Array.from(numbers).sort((a, b) => a - b);
2465
+ const middle = Math.floor(sorted.length / 2);
2466
+ if (sorted.length % 2 === 0) {
2467
+ return (sorted[middle - 1] + sorted[middle]) / 2;
2468
+ }
2469
+ return sorted[middle];
2470
+ }
2471
+ function aggregateValue(data, data_field, aggregation) {
2472
+ var _a, _b, _c;
2473
+ const numbers = data.map((d) => d[data_field]);
2474
+ switch (aggregation) {
2475
+ case "sum":
2476
+ return _.sum(numbers);
2477
+ case "mean":
2478
+ return _.mean(numbers);
2479
+ case "median":
2480
+ return median(numbers);
2481
+ case "max":
2482
+ return (_a = _.max(numbers)) != null ? _a : 0;
2483
+ case "min":
2484
+ return (_b = _.min(numbers)) != null ? _b : 0;
2485
+ default:
2486
+ return (_c = data[0]) == null ? void 0 : _c[data_field];
2487
+ }
2488
+ }
2463
2489
  var invariant = function() {
2464
2490
  };
2465
2491
  const clamp$1 = (min, max, v) => Math.min(Math.max(v, min), max);
@@ -2826,14 +2852,12 @@ class InterpolateColor {
2826
2852
  return this.mapper(value);
2827
2853
  }
2828
2854
  }
2829
- function getColorByColorConf(conf, dataRow) {
2855
+ function getColorByColorConf(conf, value) {
2830
2856
  if (conf.type === "static") {
2831
2857
  return conf.staticColor;
2832
2858
  }
2833
2859
  if (conf.type === "continuous") {
2834
- const mapper = new InterpolateColor(conf);
2835
- const value = dataRow[conf.valueField];
2836
- return mapper.getColor(value);
2860
+ return new InterpolateColor(conf).getColor(value);
2837
2861
  }
2838
2862
  return "black";
2839
2863
  }
@@ -2849,46 +2873,108 @@ function getNonStatsDataText(data) {
2849
2873
  }
2850
2874
  return data.toString();
2851
2875
  }
2852
- function VizStats(_a) {
2853
- var _b = _a, {
2854
- conf: _c
2855
- } = _b, _d = _c, {
2856
- content,
2876
+ function variablesToElements(variables, data) {
2877
+ const ret = {};
2878
+ variables.forEach(({
2879
+ name,
2880
+ color: color2,
2881
+ data_field,
2882
+ aggregation,
2857
2883
  size,
2858
- color: color2
2859
- } = _d, rest = __objRest(_d, [
2860
- "content",
2861
- "size",
2862
- "color"
2863
- ]), {
2864
- data
2865
- } = _b;
2866
- const finalColor = React.useMemo(() => {
2867
- return getColorByColorConf(color2, data[0]);
2868
- }, [color2, data]);
2869
- const finalContent = React.useMemo(() => {
2870
- var _a2;
2871
- const {
2872
- prefix,
2873
- postfix,
2874
- data_field,
2875
- formatter
2876
- } = content;
2877
- const contentData = (_a2 = data == null ? void 0 : data[0]) == null ? void 0 : _a2[data_field];
2878
- if (!["string", "number"].includes(typeof contentData)) {
2879
- return getNonStatsDataText(contentData);
2884
+ weight,
2885
+ formatter
2886
+ }) => {
2887
+ const value = aggregateValue(data, data_field, aggregation);
2888
+ let valueContent = "";
2889
+ if (!["string", "number"].includes(typeof value)) {
2890
+ valueContent = getNonStatsDataText(value);
2891
+ } else {
2892
+ valueContent = numbro(value).format(formatter);
2880
2893
  }
2881
- const contents = [prefix, numbro(contentData).format(formatter), postfix];
2882
- return contents.join(" ");
2883
- }, [content, data]);
2884
- return /* @__PURE__ */ jsx(Text, __spreadProps(__spreadValues({}, rest), {
2885
- color: finalColor,
2886
- sx: {
2887
- fontSize: size
2888
- },
2889
- children: finalContent
2894
+ ret[name] = /* @__PURE__ */ jsx(Text, {
2895
+ sx: {
2896
+ fontSize: size,
2897
+ display: "inline"
2898
+ },
2899
+ color: getColorByColorConf(color2, value),
2900
+ weight,
2901
+ children: valueContent
2902
+ });
2903
+ });
2904
+ return ret;
2905
+ }
2906
+ function preserveWhiteSpaces(text) {
2907
+ return text.split(" ").map((s) => /* @__PURE__ */ jsxs(Fragment, {
2908
+ children: [s, "\xA0"]
2890
2909
  }));
2891
2910
  }
2911
+ function withLineBreaks(text) {
2912
+ const normalized = text.replaceAll("<br />", "<br/>").replaceAll("\n", "<br/>");
2913
+ const splitted = normalized.split("<br/>");
2914
+ const ret = splitted.map((t, i) => {
2915
+ const arr = [preserveWhiteSpaces(t)];
2916
+ if (i !== splitted.length - 1) {
2917
+ arr.push(/* @__PURE__ */ jsx("br", {}));
2918
+ }
2919
+ return arr;
2920
+ }).flat().filter((t) => t !== void 0);
2921
+ return ret;
2922
+ }
2923
+ function textToJSX(text) {
2924
+ return withLineBreaks(text);
2925
+ }
2926
+ function templateToJSX(template, variables, data) {
2927
+ const variableElements = variablesToElements(variables, data);
2928
+ const regx = /^\{(.+)\}(.*)$/;
2929
+ return template.split("$").map((text) => {
2930
+ var _a;
2931
+ const match = regx.exec(text);
2932
+ if (!match) {
2933
+ return textToJSX(text);
2934
+ }
2935
+ const element = variableElements[match[1]];
2936
+ if (!element) {
2937
+ return textToJSX(text);
2938
+ }
2939
+ const rest = (_a = match[2]) != null ? _a : "";
2940
+ return /* @__PURE__ */ jsxs(Fragment, {
2941
+ children: [element, textToJSX(rest)]
2942
+ });
2943
+ });
2944
+ }
2945
+ function VizStats({
2946
+ conf: {
2947
+ template,
2948
+ variables,
2949
+ align
2950
+ },
2951
+ data
2952
+ }) {
2953
+ const contents = React.useMemo(() => {
2954
+ return templateToJSX(template, variables, data);
2955
+ }, [template, variables, data]);
2956
+ return /* @__PURE__ */ jsx(Text, {
2957
+ align,
2958
+ children: Object.values(contents).map((c) => c)
2959
+ });
2960
+ }
2961
+ function VizRichText({
2962
+ conf,
2963
+ width,
2964
+ height
2965
+ }) {
2966
+ if (!width || !height) {
2967
+ return null;
2968
+ }
2969
+ return /* @__PURE__ */ jsx(RichTextEditor, {
2970
+ readOnly: true,
2971
+ value: conf.content,
2972
+ onChange: _.noop,
2973
+ sx: {
2974
+ border: "none"
2975
+ }
2976
+ });
2977
+ }
2892
2978
  function renderViz(width, height, data, viz) {
2893
2979
  const props = {
2894
2980
  width,
@@ -2907,6 +2993,8 @@ function renderViz(width, height, data, viz) {
2907
2993
  return /* @__PURE__ */ jsx(VizText, __spreadValues({}, props));
2908
2994
  case "stats":
2909
2995
  return /* @__PURE__ */ jsx(VizStats, __spreadValues({}, props));
2996
+ case "rich-text":
2997
+ return /* @__PURE__ */ jsx(VizRichText, __spreadValues({}, props));
2910
2998
  case "bar-3d":
2911
2999
  return /* @__PURE__ */ jsx(VizBar3D, __spreadValues({}, props));
2912
3000
  case "pie":
@@ -2915,6 +3003,7 @@ function renderViz(width, height, data, viz) {
2915
3003
  return null;
2916
3004
  }
2917
3005
  }
3006
+ const typesDontNeedData = ["rich-text"];
2918
3007
  function Viz({
2919
3008
  viz,
2920
3009
  data,
@@ -2925,6 +3014,16 @@ function Viz({
2925
3014
  width,
2926
3015
  height
2927
3016
  } = useElementSize();
3017
+ const needData = !typesDontNeedData.includes(viz.type);
3018
+ if (!needData) {
3019
+ return /* @__PURE__ */ jsx("div", {
3020
+ className: "viz-root",
3021
+ ref,
3022
+ children: /* @__PURE__ */ jsx(ErrorBoundary, {
3023
+ children: renderViz(width, height, data, viz)
3024
+ })
3025
+ });
3026
+ }
2928
3027
  const empty = React.useMemo(() => !Array.isArray(data) || data.length === 0, [data]);
2929
3028
  if (loading) {
2930
3029
  return /* @__PURE__ */ jsx("div", {
@@ -2968,7 +3067,7 @@ function _DataFieldSelector({
2968
3067
  data,
2969
3068
  sx
2970
3069
  }, ref) {
2971
- const options = React.useMemo(() => {
3070
+ const options2 = React.useMemo(() => {
2972
3071
  if (!Array.isArray(data) || data.length === 0) {
2973
3072
  return [];
2974
3073
  }
@@ -2981,7 +3080,7 @@ function _DataFieldSelector({
2981
3080
  return /* @__PURE__ */ jsx(Select, {
2982
3081
  ref,
2983
3082
  label,
2984
- data: options,
3083
+ data: options2,
2985
3084
  value,
2986
3085
  onChange,
2987
3086
  required,
@@ -3164,7 +3263,7 @@ function _MantineColorSelector({
3164
3263
  }, [value, themeColors]);
3165
3264
  return /* @__PURE__ */ jsxs(Group, {
3166
3265
  position: "apart",
3167
- spacing: "xs",
3266
+ spacing: 4,
3168
3267
  children: [/* @__PURE__ */ jsx(TextInput, {
3169
3268
  placeholder: "Set any color",
3170
3269
  value: !isThemeColor ? value : "",
@@ -3175,7 +3274,7 @@ function _MantineColorSelector({
3175
3274
  }),
3176
3275
  variant: !isThemeColor ? "default" : "filled",
3177
3276
  sx: {
3178
- maxWidth: "100%",
3277
+ maxWidth: "46%",
3179
3278
  flexGrow: 1
3180
3279
  }
3181
3280
  }), /* @__PURE__ */ jsx(Text, {
@@ -3194,7 +3293,7 @@ function _MantineColorSelector({
3194
3293
  radius: 4
3195
3294
  }),
3196
3295
  sx: {
3197
- maxWidth: "100%",
3296
+ maxWidth: "46%",
3198
3297
  flexGrow: 1
3199
3298
  }
3200
3299
  })]
@@ -4195,55 +4294,147 @@ function VizPiePanel({
4195
4294
  })
4196
4295
  });
4197
4296
  }
4198
- const marks = [{
4199
- label: "initial",
4200
- value: 0
4297
+ function VizRichTextPanel({
4298
+ conf,
4299
+ setConf
4300
+ }) {
4301
+ const defaultValues = React.useMemo(() => {
4302
+ const {
4303
+ content = ""
4304
+ } = conf;
4305
+ return {
4306
+ content
4307
+ };
4308
+ }, [conf]);
4309
+ React.useEffect(() => {
4310
+ const configMalformed = !_.isEqual(conf, defaultValues);
4311
+ if (configMalformed) {
4312
+ setConf(defaultValues);
4313
+ }
4314
+ }, [conf, defaultValues]);
4315
+ const {
4316
+ control,
4317
+ handleSubmit,
4318
+ watch,
4319
+ getValues
4320
+ } = useForm({
4321
+ defaultValues
4322
+ });
4323
+ watch("content");
4324
+ const values = getValues();
4325
+ const changed = React.useMemo(() => {
4326
+ return !_.isEqual(values, conf);
4327
+ }, [values, conf]);
4328
+ return /* @__PURE__ */ jsx(Group, {
4329
+ direction: "column",
4330
+ mt: "md",
4331
+ spacing: "xs",
4332
+ grow: true,
4333
+ children: /* @__PURE__ */ jsxs("form", {
4334
+ onSubmit: handleSubmit(setConf),
4335
+ children: [/* @__PURE__ */ jsxs(Group, {
4336
+ position: "left",
4337
+ py: "md",
4338
+ pl: "md",
4339
+ sx: {
4340
+ borderBottom: "1px solid #eee",
4341
+ background: "#efefef"
4342
+ },
4343
+ children: [/* @__PURE__ */ jsx(Text, {
4344
+ children: "Content"
4345
+ }), /* @__PURE__ */ jsx(ActionIcon, {
4346
+ type: "submit",
4347
+ mr: 5,
4348
+ variant: "filled",
4349
+ color: "blue",
4350
+ disabled: !changed,
4351
+ children: /* @__PURE__ */ jsx(DeviceFloppy, {
4352
+ size: 20
4353
+ })
4354
+ })]
4355
+ }), /* @__PURE__ */ jsx(Controller, {
4356
+ name: "content",
4357
+ control,
4358
+ render: ({
4359
+ field
4360
+ }) => /* @__PURE__ */ jsx(RichTextEditor, __spreadValues({
4361
+ sx: {
4362
+ flex: 1
4363
+ }
4364
+ }, field))
4365
+ })]
4366
+ })
4367
+ });
4368
+ }
4369
+ function updateSchema(legacyConf) {
4370
+ if ("variables" in legacyConf) {
4371
+ return legacyConf;
4372
+ }
4373
+ const {
4374
+ align,
4375
+ size,
4376
+ weight,
4377
+ color: color2,
4378
+ content: {
4379
+ prefix = "",
4380
+ data_field = "value",
4381
+ formatter = {
4382
+ output: "number",
4383
+ mantissa: 0
4384
+ },
4385
+ postfix = ""
4386
+ } = {}
4387
+ } = legacyConf;
4388
+ return {
4389
+ align,
4390
+ template: `${prefix} \${value} ${postfix}`,
4391
+ variables: [
4392
+ {
4393
+ name: "value",
4394
+ data_field,
4395
+ aggregation: "none",
4396
+ formatter,
4397
+ color: color2,
4398
+ weight,
4399
+ size
4400
+ }
4401
+ ]
4402
+ };
4403
+ }
4404
+ const options = [{
4405
+ label: "None",
4406
+ value: "none"
4201
4407
  }, {
4202
- label: "500",
4203
- value: 25
4408
+ label: "Sum",
4409
+ value: "sum"
4204
4410
  }, {
4205
- label: "700",
4206
- value: 50
4411
+ label: "Mean",
4412
+ value: "mean"
4207
4413
  }, {
4208
- label: "semibold",
4209
- value: 75
4414
+ label: "Median",
4415
+ value: "median"
4210
4416
  }, {
4211
- label: "bold",
4212
- value: 100
4417
+ label: "Max",
4418
+ value: "max"
4419
+ }, {
4420
+ label: "Min",
4421
+ value: "min"
4213
4422
  }];
4214
- function _MantineFontWeightSlider({
4423
+ function _AggregationSelector({
4215
4424
  label,
4216
4425
  value,
4217
4426
  onChange
4218
4427
  }, ref) {
4219
- var _a, _b;
4220
- const [mark, setMark] = React.useState((_b = (_a = marks.find((m2) => m2.label === value)) == null ? void 0 : _a.value) != null ? _b : marks[0].value);
4221
- React.useEffect(() => {
4222
- const match = marks.find((s) => s.value === mark);
4223
- if (match) {
4224
- onChange(match.label);
4225
- }
4226
- }, [mark]);
4227
- return /* @__PURE__ */ jsxs(Group, {
4228
- direction: "column",
4229
- grow: true,
4230
- spacing: "xs",
4231
- mb: "lg",
4232
- children: [/* @__PURE__ */ jsx(Text, {
4233
- children: label
4234
- }), /* @__PURE__ */ jsx(Slider, {
4235
- label: null,
4236
- marks,
4237
- value: mark,
4238
- onChange: setMark,
4239
- step: 25,
4240
- placeholder: "Pick a font size",
4241
- ref
4242
- })]
4428
+ return /* @__PURE__ */ jsx(Select, {
4429
+ ref,
4430
+ label,
4431
+ data: options,
4432
+ value,
4433
+ onChange
4243
4434
  });
4244
4435
  }
4245
- const MantineFontWeightSlider = React.forwardRef(_MantineFontWeightSlider);
4246
- function _TextArrayInput({
4436
+ const AggregationSelector = React.forwardRef(_AggregationSelector);
4437
+ function _ColorArrayInput({
4247
4438
  label,
4248
4439
  value,
4249
4440
  onChange
@@ -4264,6 +4455,10 @@ function _TextArrayInput({
4264
4455
  const submit = () => {
4265
4456
  onChange(values.map((s) => s.toString()));
4266
4457
  };
4458
+ const theme = useMantineTheme();
4459
+ const swatches = React.useMemo(() => {
4460
+ return Object.entries(theme.colors).map(([_color, profile]) => profile[6]);
4461
+ }, [theme]);
4267
4462
  return /* @__PURE__ */ jsxs(Fragment, {
4268
4463
  children: [/* @__PURE__ */ jsxs(Group, {
4269
4464
  position: "left",
@@ -4281,15 +4476,15 @@ function _TextArrayInput({
4281
4476
  })
4282
4477
  })]
4283
4478
  }), /* @__PURE__ */ jsxs(Group, {
4284
- children: [values.map((v, i) => /* @__PURE__ */ jsx(TextInput, {
4479
+ children: [values.map((v, i) => /* @__PURE__ */ jsx(ColorInput, {
4285
4480
  value: v,
4286
- onChange: (event) => {
4287
- const newValue = event.currentTarget.value;
4481
+ onChange: (color2) => {
4288
4482
  setValues((s) => {
4289
- s.splice(i, 1, newValue);
4483
+ s.splice(i, 1, color2);
4290
4484
  return [...s];
4291
4485
  });
4292
4486
  },
4487
+ swatches,
4293
4488
  rightSection: /* @__PURE__ */ jsx(ActionIcon, {
4294
4489
  onClick: () => del(i),
4295
4490
  color: "red",
@@ -4311,8 +4506,58 @@ function _TextArrayInput({
4311
4506
  })]
4312
4507
  });
4313
4508
  }
4314
- const TextArrayInput = React.forwardRef(_TextArrayInput);
4315
- function _ColorArrayInput({
4509
+ const ColorArrayInput = React.forwardRef(_ColorArrayInput);
4510
+ const marks = [{
4511
+ label: "initial",
4512
+ value: 0
4513
+ }, {
4514
+ label: "500",
4515
+ value: 25
4516
+ }, {
4517
+ label: "700",
4518
+ value: 50
4519
+ }, {
4520
+ label: "semibold",
4521
+ value: 75
4522
+ }, {
4523
+ label: "bold",
4524
+ value: 100
4525
+ }];
4526
+ function _MantineFontWeightSlider({
4527
+ label,
4528
+ value,
4529
+ onChange
4530
+ }, ref) {
4531
+ var _a, _b;
4532
+ const [mark, setMark] = React.useState((_b = (_a = marks.find((m2) => m2.label === value)) == null ? void 0 : _a.value) != null ? _b : marks[0].value);
4533
+ React.useEffect(() => {
4534
+ const match = marks.find((s) => s.value === mark);
4535
+ if (match) {
4536
+ onChange(match.label);
4537
+ }
4538
+ }, [mark]);
4539
+ return /* @__PURE__ */ jsxs(Group, {
4540
+ direction: "column",
4541
+ grow: true,
4542
+ spacing: 0,
4543
+ mt: "sm",
4544
+ mb: "lg",
4545
+ children: [/* @__PURE__ */ jsx(Text, {
4546
+ size: "sm",
4547
+ children: label
4548
+ }), /* @__PURE__ */ jsx(Slider, {
4549
+ label: null,
4550
+ marks,
4551
+ value: mark,
4552
+ onChange: setMark,
4553
+ step: 25,
4554
+ placeholder: "Pick a font size",
4555
+ ref
4556
+ })]
4557
+ });
4558
+ }
4559
+ const MantineFontWeightSlider = React.forwardRef(_MantineFontWeightSlider);
4560
+ function _TextArrayInput({
4316
4561
  label,
4317
4562
  value,
4318
4563
  onChange
@@ -4333,10 +4578,6 @@ function _ColorArrayInput({
4333
4578
  const submit = () => {
4334
4579
  onChange(values.map((s) => s.toString()));
4335
4580
  };
4336
- const theme = useMantineTheme();
4337
- const swatches = React.useMemo(() => {
4338
- return Object.entries(theme.colors).map(([_color, profile]) => profile[6]);
4339
- }, [theme]);
4340
4581
  return /* @__PURE__ */ jsxs(Fragment, {
4341
4582
  children: [/* @__PURE__ */ jsxs(Group, {
4342
4583
  position: "left",
@@ -4354,15 +4595,15 @@ function _ColorArrayInput({
4354
4595
  })
4355
4596
  })]
4356
4597
  }), /* @__PURE__ */ jsxs(Group, {
4357
- children: [values.map((v, i) => /* @__PURE__ */ jsx(ColorInput, {
4598
+ children: [values.map((v, i) => /* @__PURE__ */ jsx(TextInput, {
4358
4599
  value: v,
4359
- onChange: (color2) => {
4600
+ onChange: (event) => {
4601
+ const newValue = event.currentTarget.value;
4360
4602
  setValues((s) => {
4361
- s.splice(i, 1, color2);
4603
+ s.splice(i, 1, newValue);
4362
4604
  return [...s];
4363
4605
  });
4364
4606
  },
4365
- swatches,
4366
4607
  rightSection: /* @__PURE__ */ jsx(ActionIcon, {
4367
4608
  onClick: () => del(i),
4368
4609
  color: "red",
@@ -4384,42 +4625,281 @@ function _ColorArrayInput({
4384
4625
  })]
4385
4626
  });
4386
4627
  }
4387
- const ColorArrayInput = React.forwardRef(_ColorArrayInput);
4388
- function VizStatsPanel({
4389
- conf,
4390
- setConf,
4628
+ const TextArrayInput = React.forwardRef(_TextArrayInput);
4629
+ const TemplateInput = React.forwardRef(function TemplateInput2(_a, ref) {
4630
+ var _b = _a, {
4631
+ value,
4632
+ onChange
4633
+ } = _b, rest = __objRest(_b, [
4634
+ "value",
4635
+ "onChange"
4636
+ ]);
4637
+ return /* @__PURE__ */ jsx(TextInput, __spreadValues({
4638
+ ref,
4639
+ value,
4640
+ onChange
4641
+ }, rest));
4642
+ });
4643
+ function TemplateVariableField({
4644
+ value,
4645
+ onChange,
4391
4646
  data
4392
4647
  }) {
4393
- const defaultValues = _.merge({}, {
4394
- align: "center",
4395
- size: "100px",
4648
+ const colorType = value.color.type;
4649
+ const handleChange = (path, newValue) => {
4650
+ const v = _.cloneDeep(value);
4651
+ _.set(v, path, newValue);
4652
+ onChange(v);
4653
+ };
4654
+ return /* @__PURE__ */ jsxs(Box, {
4655
+ px: "sm",
4656
+ py: "md",
4657
+ children: [/* @__PURE__ */ jsx(Text, {
4658
+ weight: "bold",
4659
+ pb: 0,
4660
+ children: value.name
4661
+ }), /* @__PURE__ */ jsx(Divider, {
4662
+ my: "xs",
4663
+ label: "Data",
4664
+ labelPosition: "center"
4665
+ }), /* @__PURE__ */ jsxs(Group, {
4666
+ direction: "row",
4667
+ grow: true,
4668
+ noWrap: true,
4669
+ children: [/* @__PURE__ */ jsx(TextInput, {
4670
+ label: "Name",
4671
+ required: true,
4672
+ value: value.name,
4673
+ onChange: (e) => handleChange("name", e.currentTarget.value)
4674
+ }), /* @__PURE__ */ jsx(DataFieldSelector, {
4675
+ label: "Data Field",
4676
+ required: true,
4677
+ data,
4678
+ value: value.data_field,
4679
+ onChange: (v) => handleChange("data_field", v)
4680
+ }), /* @__PURE__ */ jsx(AggregationSelector, {
4681
+ label: "Aggregation",
4682
+ value: value.aggregation,
4683
+ onChange: (v) => handleChange("aggregation", v)
4684
+ })]
4685
+ }), /* @__PURE__ */ jsx(NumbroFormatSelector, {
4686
+ value: value.formatter,
4687
+ onChange: (v) => handleChange("formatter", v)
4688
+ }), /* @__PURE__ */ jsx(Divider, {
4689
+ my: "xs",
4690
+ label: "Typography",
4691
+ labelPosition: "center"
4692
+ }), /* @__PURE__ */ jsx(Group, {
4693
+ direction: "column",
4694
+ grow: true,
4695
+ children: /* @__PURE__ */ jsx(TextInput, {
4696
+ label: "Font Size",
4697
+ placeholder: "10px, 1em, 1rem, 100%...",
4698
+ sx: {
4699
+ flex: 1
4700
+ },
4701
+ value: value.size,
4702
+ onChange: (e) => handleChange("size", e.currentTarget.value)
4703
+ })
4704
+ }), /* @__PURE__ */ jsx(Group, {
4705
+ position: "apart",
4706
+ grow: true,
4707
+ sx: {
4708
+ "> *": {
4709
+ flexGrow: 1,
4710
+ maxWidth: "100%"
4711
+ }
4712
+ },
4713
+ children: /* @__PURE__ */ jsx(MantineFontWeightSlider, {
4714
+ label: "Font Weight",
4715
+ value: value.weight,
4716
+ onChange: (v) => handleChange("weight", v)
4717
+ })
4718
+ }), /* @__PURE__ */ jsx(Divider, {
4719
+ my: "xs",
4720
+ label: "Style",
4721
+ labelPosition: "center"
4722
+ }), /* @__PURE__ */ jsxs(Group, {
4723
+ direction: "column",
4724
+ grow: true,
4725
+ children: [/* @__PURE__ */ jsx(Select, {
4726
+ label: "Color Type",
4727
+ data: [{
4728
+ label: "Static Color",
4729
+ value: "static"
4730
+ }, {
4731
+ label: "Continuous Color",
4732
+ value: "continuous"
4733
+ }],
4734
+ value: value.color.type,
4735
+ onChange: (v) => handleChange("color.type", v)
4736
+ }), colorType === "static" && /* @__PURE__ */ jsx(MantineColorSelector, {
4737
+ value: value.color.staticColor,
4738
+ onChange: (v) => handleChange("color.staticColor", v)
4739
+ }), colorType === "continuous" && /* @__PURE__ */ jsxs(Fragment, {
4740
+ children: [/* @__PURE__ */ jsx(TextArrayInput, {
4741
+ label: "Value Range",
4742
+ value: value.color.valueRange,
4743
+ onChange: (v) => handleChange("color.valueRange", v)
4744
+ }), /* @__PURE__ */ jsx(ColorArrayInput, {
4745
+ label: "Color Range",
4746
+ value: value.color.colorRange,
4747
+ onChange: (v) => handleChange("color.colorRange", v)
4748
+ })]
4749
+ })]
4750
+ })]
4751
+ });
4752
+ }
4753
+ function VariableField({
4754
+ control,
4755
+ index: index2,
4756
+ remove,
4757
+ data
4758
+ }) {
4759
+ return /* @__PURE__ */ jsxs(Group, {
4760
+ direction: "column",
4761
+ grow: true,
4762
+ my: "sm",
4763
+ p: 0,
4764
+ sx: {
4765
+ border: "1px solid #eee",
4766
+ borderTopColor: "#333",
4767
+ borderTopWidth: 2,
4768
+ position: "relative"
4769
+ },
4770
+ children: [/* @__PURE__ */ jsx(Controller, {
4771
+ name: `variables.${index2}`,
4772
+ control,
4773
+ render: ({
4774
+ field
4775
+ }) => /* @__PURE__ */ jsx(TemplateVariableField, __spreadValues({
4776
+ data
4777
+ }, field))
4778
+ }), /* @__PURE__ */ jsx(ActionIcon, {
4779
+ color: "red",
4780
+ variant: "hover",
4781
+ onClick: () => remove(index2),
4782
+ sx: {
4783
+ position: "absolute",
4784
+ top: 15,
4785
+ right: 5
4786
+ },
4787
+ children: /* @__PURE__ */ jsx(Trash, {
4788
+ size: 16
4789
+ })
4790
+ })]
4791
+ }, index2);
4792
+ }
4793
+ function VariablesField({
4794
+ control,
4795
+ watch,
4796
+ data
4797
+ }) {
4798
+ const {
4799
+ fields,
4800
+ append,
4801
+ remove
4802
+ } = useFieldArray({
4803
+ control,
4804
+ name: "variables"
4805
+ });
4806
+ const watchFieldArray = watch("variables");
4807
+ const controlledFields = fields.map((field, index2) => {
4808
+ return __spreadValues(__spreadValues({}, field), watchFieldArray[index2]);
4809
+ });
4810
+ const add = () => append({
4811
+ name: randomId(),
4812
+ size: "20px",
4396
4813
  weight: "bold",
4397
4814
  color: {
4398
4815
  type: "static",
4399
- staticColor: "red"
4816
+ staticColor: "blue"
4400
4817
  },
4401
- content: {
4402
- prefix: "",
4818
+ data_field: "",
4819
+ aggregation: "none",
4820
+ formatter: {
4821
+ output: "number",
4822
+ mantissa: 0
4823
+ }
4824
+ });
4825
+ return /* @__PURE__ */ jsxs(Group, {
4826
+ direction: "column",
4827
+ grow: true,
4828
+ children: [controlledFields.map((_variableItem, index2) => /* @__PURE__ */ jsx(VariableField, {
4829
+ control,
4830
+ index: index2,
4831
+ remove,
4832
+ data
4833
+ })), /* @__PURE__ */ jsx(Group, {
4834
+ position: "center",
4835
+ mt: "xs",
4836
+ children: /* @__PURE__ */ jsx(Button, {
4837
+ onClick: add,
4838
+ children: "Add a Variable"
4839
+ })
4840
+ })]
4841
+ });
4842
+ }
4843
+ function getInitialConf() {
4844
+ return {
4845
+ align: "center",
4846
+ template: "The variable ${value} is defined in Variables section",
4847
+ variables: [{
4848
+ name: "value",
4849
+ size: "20px",
4850
+ weight: "bold",
4851
+ color: {
4852
+ type: "static",
4853
+ staticColor: "blue"
4854
+ },
4403
4855
  data_field: "",
4856
+ aggregation: "none",
4404
4857
  formatter: {
4405
4858
  output: "number",
4406
4859
  mantissa: 0
4407
- },
4408
- postfix: ""
4860
+ }
4861
+ }]
4862
+ };
4863
+ }
4864
+ function VizStatsPanel({
4865
+ conf,
4866
+ setConf,
4867
+ data
4868
+ }) {
4869
+ const defaultValues = React.useMemo(() => {
4870
+ const {
4871
+ align,
4872
+ template = "",
4873
+ variables = []
4874
+ } = updateSchema(conf);
4875
+ if (!align) {
4876
+ return getInitialConf();
4409
4877
  }
4410
- }, conf);
4878
+ return {
4879
+ variables,
4880
+ template,
4881
+ align
4882
+ };
4883
+ }, [conf]);
4884
+ React.useEffect(() => {
4885
+ const configMalformed = !_.isEqual(conf, defaultValues);
4886
+ if (configMalformed) {
4887
+ setConf(defaultValues);
4888
+ }
4889
+ }, [conf, defaultValues]);
4411
4890
  const {
4412
4891
  control,
4413
4892
  handleSubmit,
4414
4893
  watch,
4415
- formState: {
4416
- isDirty
4417
- }
4894
+ getValues
4418
4895
  } = useForm({
4419
4896
  defaultValues
4420
4897
  });
4421
- const colorType = watch("color.type");
4422
- watch("color.valueField");
4898
+ watch(["variables", "template"]);
4899
+ const values = getValues();
4900
+ const changed = React.useMemo(() => {
4901
+ return !_.isEqual(values, conf);
4902
+ }, [values, conf]);
4423
4903
  return /* @__PURE__ */ jsx(Group, {
4424
4904
  direction: "column",
4425
4905
  mt: "md",
@@ -4444,166 +4924,32 @@ function VizStatsPanel({
4444
4924
  mr: 5,
4445
4925
  variant: "filled",
4446
4926
  color: "blue",
4447
- disabled: !isDirty,
4927
+ disabled: !changed,
4448
4928
  children: /* @__PURE__ */ jsx(DeviceFloppy, {
4449
4929
  size: 20
4450
4930
  })
4451
4931
  })]
4452
- }), /* @__PURE__ */ jsxs(Accordion, {
4453
- offsetIcon: false,
4454
- multiple: true,
4455
- initialState: {
4456
- 0: true,
4457
- 2: true
4458
- },
4459
- children: [/* @__PURE__ */ jsx(Accordion.Item, {
4460
- label: "Content",
4461
- children: /* @__PURE__ */ jsxs(Group, {
4462
- direction: "column",
4463
- grow: true,
4464
- children: [/* @__PURE__ */ jsxs(Group, {
4465
- direction: "row",
4466
- grow: true,
4467
- noWrap: true,
4468
- children: [/* @__PURE__ */ jsx(Controller, {
4469
- name: "content.prefix",
4470
- control,
4471
- render: ({
4472
- field
4473
- }) => /* @__PURE__ */ jsx(TextInput, __spreadValues({
4474
- label: "Prefix",
4475
- sx: {
4476
- flexGrow: 1
4477
- }
4478
- }, field))
4479
- }), /* @__PURE__ */ jsx(Controller, {
4480
- name: "content.data_field",
4481
- control,
4482
- render: ({
4483
- field
4484
- }) => /* @__PURE__ */ jsx(DataFieldSelector, __spreadValues({
4485
- label: "Data Field",
4486
- required: true,
4487
- data
4488
- }, field))
4489
- }), /* @__PURE__ */ jsx(Controller, {
4490
- name: "content.postfix",
4491
- control,
4492
- render: ({
4493
- field
4494
- }) => /* @__PURE__ */ jsx(TextInput, __spreadValues({
4495
- label: "Postfix",
4496
- sx: {
4497
- flexGrow: 1
4498
- }
4499
- }, field))
4500
- })]
4501
- }), /* @__PURE__ */ jsx(Controller, {
4502
- name: "content.formatter",
4503
- control,
4504
- render: ({
4505
- field
4506
- }) => /* @__PURE__ */ jsx(NumbroFormatSelector, __spreadValues({}, field))
4507
- })]
4508
- })
4509
- }), /* @__PURE__ */ jsxs(Accordion.Item, {
4510
- label: "Font",
4511
- children: [/* @__PURE__ */ jsx(Group, {
4512
- direction: "column",
4513
- grow: true,
4514
- children: /* @__PURE__ */ jsx(Controller, {
4515
- name: "size",
4516
- control,
4517
- render: ({
4518
- field
4519
- }) => /* @__PURE__ */ jsx(TextInput, __spreadValues({
4520
- label: "Font Size",
4521
- placeholder: "10px, 1em, 1rem, 100%...",
4522
- sx: {
4523
- flex: 1
4524
- }
4525
- }, field))
4526
- })
4527
- }), /* @__PURE__ */ jsx(Group, {
4528
- position: "apart",
4529
- grow: true,
4530
- sx: {
4531
- "> *": {
4532
- flexGrow: 1,
4533
- maxWidth: "100%"
4534
- }
4535
- },
4536
- children: /* @__PURE__ */ jsx(Controller, {
4537
- name: "weight",
4538
- control,
4539
- render: ({
4540
- field
4541
- }) => /* @__PURE__ */ jsx(MantineFontWeightSlider, __spreadValues({
4542
- label: "Font Weight"
4543
- }, field))
4544
- })
4545
- })]
4546
- }), /* @__PURE__ */ jsx(Accordion.Item, {
4547
- label: "Color",
4548
- children: /* @__PURE__ */ jsxs(Group, {
4549
- direction: "column",
4550
- grow: true,
4551
- children: [/* @__PURE__ */ jsx(Controller, {
4552
- name: "color.type",
4553
- control,
4554
- render: ({
4555
- field
4556
- }) => /* @__PURE__ */ jsx(Select, __spreadValues({
4557
- label: "Color Type",
4558
- data: [{
4559
- label: "Static Color",
4560
- value: "static"
4561
- }, {
4562
- label: "Continuous Color",
4563
- value: "continuous"
4564
- }]
4565
- }, field))
4566
- }), colorType === "static" && /* @__PURE__ */ jsx(Controller, {
4567
- name: "color.staticColor",
4568
- control,
4569
- render: ({
4570
- field
4571
- }) => /* @__PURE__ */ jsx(MantineColorSelector, __spreadValues({}, field))
4572
- }), colorType === "continuous" && /* @__PURE__ */ jsxs(Fragment, {
4573
- children: [/* @__PURE__ */ jsx(Controller, {
4574
- name: "color.valueField",
4575
- control,
4576
- defaultValue: "",
4577
- render: ({
4578
- field
4579
- }) => /* @__PURE__ */ jsx(TextInput, __spreadValues({
4580
- placeholder: "Calculate color with this field",
4581
- label: "Value Field",
4582
- required: true,
4583
- sx: {
4584
- flex: 1
4585
- }
4586
- }, field))
4587
- }), /* @__PURE__ */ jsx(Controller, {
4588
- name: "color.valueRange",
4589
- control,
4590
- render: ({
4591
- field
4592
- }) => /* @__PURE__ */ jsx(TextArrayInput, __spreadValues({
4593
- label: "Value Range"
4594
- }, field))
4595
- }), /* @__PURE__ */ jsx(Controller, {
4596
- name: "color.colorRange",
4597
- control,
4598
- render: ({
4599
- field
4600
- }) => /* @__PURE__ */ jsx(ColorArrayInput, __spreadValues({
4601
- label: "Color Range"
4602
- }, field))
4603
- })]
4604
- })]
4605
- })
4606
- })]
4932
+ }), /* @__PURE__ */ jsx(Controller, {
4933
+ name: "template",
4934
+ control,
4935
+ render: ({
4936
+ field
4937
+ }) => /* @__PURE__ */ jsx(TemplateInput, __spreadValues({
4938
+ label: "Template",
4939
+ py: "md",
4940
+ sx: {
4941
+ flexGrow: 1
4942
+ }
4943
+ }, field))
4944
+ }), /* @__PURE__ */ jsx(Text, {
4945
+ pb: "sm",
4946
+ pt: "md",
4947
+ size: "sm",
4948
+ children: "Variables"
4949
+ }), /* @__PURE__ */ jsx(VariablesField, {
4950
+ control,
4951
+ watch,
4952
+ data
4607
4953
  })]
4608
4954
  })
4609
4955
  });
@@ -4688,17 +5034,17 @@ function ValueTypeSelector({
4688
5034
  sx
4689
5035
  });
4690
5036
  }
4691
- function VizTablePanel(_e) {
4692
- var _f = _e, {
4693
- conf: _g
4694
- } = _f, _h = _g, {
5037
+ function VizTablePanel(_c) {
5038
+ var _d = _c, {
5039
+ conf: _e
5040
+ } = _d, _f = _e, {
4695
5041
  columns
4696
- } = _h, restConf = __objRest(_h, [
5042
+ } = _f, restConf = __objRest(_f, [
4697
5043
  "columns"
4698
5044
  ]), {
4699
5045
  setConf,
4700
5046
  data
4701
- } = _f;
5047
+ } = _d;
4702
5048
  const form = useForm$1({
4703
5049
  initialValues: __spreadValues({
4704
5050
  id_field: "id",
@@ -5044,6 +5390,10 @@ const types = [{
5044
5390
  value: "stats",
5045
5391
  label: "Stats",
5046
5392
  Panel: VizStatsPanel
5393
+ }, {
5394
+ value: "rich-text",
5395
+ label: "Rich Text",
5396
+ Panel: VizRichTextPanel
5047
5397
  }, {
5048
5398
  value: "table",
5049
5399
  label: "Table",
@@ -5722,7 +6072,7 @@ function SelectOrAddQuery({
5722
6072
  chooseDefault();
5723
6073
  }
5724
6074
  }, [id, queries, chooseDefault]);
5725
- const options = React.useMemo(() => {
6075
+ const options2 = React.useMemo(() => {
5726
6076
  return queries.map((d) => ({
5727
6077
  value: d.id,
5728
6078
  label: d.id
@@ -5749,7 +6099,7 @@ function SelectOrAddQuery({
5749
6099
  children: [/* @__PURE__ */ jsx(Text, {
5750
6100
  children: "Select a Query"
5751
6101
  }), /* @__PURE__ */ jsx(Select, {
5752
- data: options,
6102
+ data: options2,
5753
6103
  value: id,
5754
6104
  onChange: setID,
5755
6105
  allowDeselect: false,