@bluemarble/bi 0.7.0 → 0.8.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.
- package/dist/{DashboardDevtools-D0pzwSgn.js → DashboardDevtools-wk4vMfhy.js} +1130 -383
- package/dist/DashboardDevtools-wk4vMfhy.js.map +1 -0
- package/dist/{ModelDictionary-Cw4sa8tW.js → ModelDictionary-DZiYK1mV.js} +56 -3
- package/dist/ModelDictionary-DZiYK1mV.js.map +1 -0
- package/dist/client/index.d.ts +16 -4
- package/dist/client/index.js +4 -2
- package/dist/client/index.js.map +1 -1
- package/dist/{formatRules-DxA9vMvz.d.ts → formatRules-DXMHY9tU.d.ts} +14 -2
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +16 -0
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/dist/DashboardDevtools-D0pzwSgn.js.map +0 -1
- package/dist/ModelDictionary-Cw4sa8tW.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { c as VALUE_FORMATS, f as validateModel, g as parseColumnRef, h as modelEdges, m as measureHome, o as AGGREGATIONS, s as AGGREGATION_LABEL, t as canonicalJson, u as isAggregate, v as referencedTables } from "./codegen-LoQzFAle.js";
|
|
2
2
|
import { n as diffRows } from "./compare-DloZhg1T.js";
|
|
3
3
|
import { C as DEFAULT_SCALE_MIN_COLOR, D as UNARY_OPERATORS, E as RULE_OPERATOR_LABEL, O as VALUE_RULE_OPERATORS, S as COLUMN_VERTICAL_ALIGNS, T as RULE_OPERATORS, a as FILTER_FIELDS, b as COLOR_SCALE_TARGETS, c as chartColumns, f as filterReferences, g as percentColumn, h as layoutModelWarnings, i as CARD_FIELDS, l as chartOfType, n as CANVAS_PRESETS, o as cardsGap, r as CARDS_FIELDS, t as BUILTIN_CHART_TYPES, u as chartReferences, v as tableColumns, w as NUMERIC_OPERATORS, x as COLUMN_ALIGNS, y as validateLayout } from "./layout-BS3nO6OT.js";
|
|
4
|
-
import { C as parseLayoutItemKey,
|
|
4
|
+
import { C as useDashboard, D as parseLayoutItemKey, E as layoutItemKey, T as LAYOUT_ITEM_ATTRIBUTE, g as SERIES_COLORS, n as INSPECT_ATTRIBUTE, s as formatMeasure, t as ModelDictionary, w as errorMessage } from "./ModelDictionary-DZiYK1mV.js";
|
|
5
5
|
import { Alert, Autocomplete, Box, Button, Checkbox, Chip, CircularProgress, Collapse, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControlLabel, IconButton, List, ListItemButton, ListSubheader, Menu, MenuItem, MenuList, Paper, Popover, Popper, Portal, Switch, Tab, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TableSortLabel, Tabs, TextField, ThemeProvider, ToggleButton, ToggleButtonGroup, Tooltip, Typography, createTheme } from "@mui/material";
|
|
6
6
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
7
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -3237,7 +3237,7 @@ function ColumnOptions({ kind, name, model, label, onLabel, format, onFormat, fo
|
|
|
3237
3237
|
value: formatter,
|
|
3238
3238
|
options: formatters.map((option) => ({ name: option })),
|
|
3239
3239
|
onChange: onFormatter
|
|
3240
|
-
}), "
|
|
3240
|
+
}), "Built-in (to_month_name, to_date, ...) e os passados ao <DashboardCharts formatters={{ ... }}> da página"),
|
|
3241
3241
|
format !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3242
3242
|
/* @__PURE__ */ jsxs(Box, {
|
|
3243
3243
|
sx: {
|
|
@@ -3821,11 +3821,490 @@ function freeName(prefix, taken) {
|
|
|
3821
3821
|
for (let index = 2;; index++) if (!taken.includes(`${prefix}${index}`)) return `${prefix}${index}`;
|
|
3822
3822
|
}
|
|
3823
3823
|
//#endregion
|
|
3824
|
+
//#region src/devtools/QueryBuilder.tsx
|
|
3825
|
+
const RESERVED = new Set("select from where group order by limit key keys index table desc asc and or not in is null like as on join case when then else end interval range rows".split(" "));
|
|
3826
|
+
const quote = (name) => /^[A-Za-z_]\w*$/.test(name) && !RESERVED.has(name.toLowerCase()) ? name : `\`${name}\``;
|
|
3827
|
+
const unquote = (name) => name.trim().replace(/^`(.*)`$/, "$1");
|
|
3828
|
+
function buildSelect(table, columns) {
|
|
3829
|
+
return `SELECT\n${columns.length ? columns.map((column) => ` ${quote(column)}`).join(",\n") : " *"}\nFROM ${quote(table)}`;
|
|
3830
|
+
}
|
|
3831
|
+
/** Tabela e colunas de um `SELECT a, b FROM t` (sem WHERE, JOIN etc.); senão `null`. */
|
|
3832
|
+
function parseSelect(sql) {
|
|
3833
|
+
const match = /^\s*SELECT\s+([\s\S]+?)\s+FROM\s+(`[^`]+`|\w+)\s*;?\s*$/i.exec(sql);
|
|
3834
|
+
if (!match) return null;
|
|
3835
|
+
const columns = match[1].trim() === "*" ? [] : match[1].split(",").map(unquote);
|
|
3836
|
+
if (columns.some((column) => !/^[^\s`().]+$/.test(column))) return null;
|
|
3837
|
+
return {
|
|
3838
|
+
table: unquote(match[2]),
|
|
3839
|
+
columns
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
function QueryBuilder({ open, onClose, tables, sql, onApply }) {
|
|
3843
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
3844
|
+
open,
|
|
3845
|
+
onClose,
|
|
3846
|
+
maxWidth: "md",
|
|
3847
|
+
fullWidth: true,
|
|
3848
|
+
children: open && /* @__PURE__ */ jsx(Builder, {
|
|
3849
|
+
tables,
|
|
3850
|
+
sql,
|
|
3851
|
+
onClose,
|
|
3852
|
+
onApply
|
|
3853
|
+
})
|
|
3854
|
+
});
|
|
3855
|
+
}
|
|
3856
|
+
function Builder({ tables, sql, onClose, onApply }) {
|
|
3857
|
+
const [initial] = useState(() => {
|
|
3858
|
+
const parsed = parseSelect(sql);
|
|
3859
|
+
const table = parsed && tables.find((item) => item.name === parsed.table);
|
|
3860
|
+
if (!parsed || !table) return {
|
|
3861
|
+
table: null,
|
|
3862
|
+
columns: []
|
|
3863
|
+
};
|
|
3864
|
+
const known = new Set(table.columns.map((column) => column.name));
|
|
3865
|
+
return {
|
|
3866
|
+
table,
|
|
3867
|
+
columns: parsed.columns.filter((column) => known.has(column))
|
|
3868
|
+
};
|
|
3869
|
+
});
|
|
3870
|
+
const [table, setTable] = useState(initial.table);
|
|
3871
|
+
const [selected, setSelected] = useState(initial.columns);
|
|
3872
|
+
const [search, setSearch] = useState("");
|
|
3873
|
+
const visible = useMemo(() => {
|
|
3874
|
+
const lower = search.trim().toLowerCase();
|
|
3875
|
+
return (table?.columns ?? []).filter((column) => column.name.toLowerCase().includes(lower));
|
|
3876
|
+
}, [table, search]);
|
|
3877
|
+
const ordered = (table?.columns ?? []).map((column) => column.name).filter((name) => selected.includes(name));
|
|
3878
|
+
const result = table ? buildSelect(table.name, ordered) : "";
|
|
3879
|
+
const allVisible = visible.length > 0 && visible.every((column) => selected.includes(column.name));
|
|
3880
|
+
const toggle = (name) => setSelected((current) => current.includes(name) ? current.filter((item) => item !== name) : [...current, name]);
|
|
3881
|
+
const toggleVisible = () => {
|
|
3882
|
+
const names = visible.map((column) => column.name);
|
|
3883
|
+
setSelected((current) => allVisible ? current.filter((name) => !names.includes(name)) : [.../* @__PURE__ */ new Set([...current, ...names])]);
|
|
3884
|
+
};
|
|
3885
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3886
|
+
/* @__PURE__ */ jsx(DialogTitle, {
|
|
3887
|
+
sx: { fontSize: 14 },
|
|
3888
|
+
children: "Criar query"
|
|
3889
|
+
}),
|
|
3890
|
+
/* @__PURE__ */ jsxs(DialogContent, {
|
|
3891
|
+
sx: {
|
|
3892
|
+
display: "flex",
|
|
3893
|
+
flexDirection: "column",
|
|
3894
|
+
gap: 1.5
|
|
3895
|
+
},
|
|
3896
|
+
children: [
|
|
3897
|
+
/* @__PURE__ */ jsx(Autocomplete, {
|
|
3898
|
+
size: "small",
|
|
3899
|
+
options: tables,
|
|
3900
|
+
value: table,
|
|
3901
|
+
onChange: (_, value) => {
|
|
3902
|
+
setTable(value);
|
|
3903
|
+
setSelected([]);
|
|
3904
|
+
setSearch("");
|
|
3905
|
+
},
|
|
3906
|
+
getOptionLabel: (option) => option.name,
|
|
3907
|
+
isOptionEqualToValue: (option, value) => option.name === value.name,
|
|
3908
|
+
noOptionsText: tables.length ? "Nenhuma tabela" : "Carregando as tabelas do banco…",
|
|
3909
|
+
renderOption: ({ key, ...props }, option) => /* @__PURE__ */ jsxs(Box, {
|
|
3910
|
+
component: "li",
|
|
3911
|
+
...props,
|
|
3912
|
+
sx: {
|
|
3913
|
+
gap: 2,
|
|
3914
|
+
fontFamily: MONO,
|
|
3915
|
+
fontSize: 12
|
|
3916
|
+
},
|
|
3917
|
+
children: [option.name, /* @__PURE__ */ jsxs(Box, {
|
|
3918
|
+
component: "span",
|
|
3919
|
+
sx: {
|
|
3920
|
+
ml: "auto",
|
|
3921
|
+
color: "text.secondary",
|
|
3922
|
+
fontSize: 11
|
|
3923
|
+
},
|
|
3924
|
+
children: [
|
|
3925
|
+
option.type,
|
|
3926
|
+
" · ",
|
|
3927
|
+
option.columns.length,
|
|
3928
|
+
" colunas"
|
|
3929
|
+
]
|
|
3930
|
+
})]
|
|
3931
|
+
}, key),
|
|
3932
|
+
renderInput: (params) => /* @__PURE__ */ jsx(TextField, {
|
|
3933
|
+
...params,
|
|
3934
|
+
label: "tabela",
|
|
3935
|
+
sx: { mt: 1 }
|
|
3936
|
+
})
|
|
3937
|
+
}),
|
|
3938
|
+
table && /* @__PURE__ */ jsxs(Box, {
|
|
3939
|
+
sx: {
|
|
3940
|
+
display: "flex",
|
|
3941
|
+
flexDirection: "column",
|
|
3942
|
+
gap: .5
|
|
3943
|
+
},
|
|
3944
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
3945
|
+
sx: {
|
|
3946
|
+
display: "flex",
|
|
3947
|
+
alignItems: "center",
|
|
3948
|
+
gap: 1
|
|
3949
|
+
},
|
|
3950
|
+
children: [
|
|
3951
|
+
/* @__PURE__ */ jsx(FormControlLabel, {
|
|
3952
|
+
control: /* @__PURE__ */ jsx(Checkbox, {
|
|
3953
|
+
size: "small",
|
|
3954
|
+
checked: allVisible,
|
|
3955
|
+
indeterminate: !allVisible && visible.some((c) => selected.includes(c.name)),
|
|
3956
|
+
onChange: toggleVisible
|
|
3957
|
+
}),
|
|
3958
|
+
label: /* @__PURE__ */ jsx(Typography, {
|
|
3959
|
+
sx: { fontSize: 12 },
|
|
3960
|
+
children: "Todas"
|
|
3961
|
+
})
|
|
3962
|
+
}),
|
|
3963
|
+
/* @__PURE__ */ jsxs(Typography, {
|
|
3964
|
+
sx: {
|
|
3965
|
+
fontSize: 12,
|
|
3966
|
+
color: "text.secondary"
|
|
3967
|
+
},
|
|
3968
|
+
children: [
|
|
3969
|
+
selected.length,
|
|
3970
|
+
" de ",
|
|
3971
|
+
table.columns.length,
|
|
3972
|
+
" marcadas",
|
|
3973
|
+
!selected.length && " (nenhuma = *)"
|
|
3974
|
+
]
|
|
3975
|
+
}),
|
|
3976
|
+
/* @__PURE__ */ jsx(TextField, {
|
|
3977
|
+
size: "small",
|
|
3978
|
+
placeholder: "filtrar colunas",
|
|
3979
|
+
value: search,
|
|
3980
|
+
onChange: (event) => setSearch(event.target.value),
|
|
3981
|
+
sx: {
|
|
3982
|
+
ml: "auto",
|
|
3983
|
+
width: 200
|
|
3984
|
+
}
|
|
3985
|
+
})
|
|
3986
|
+
]
|
|
3987
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
3988
|
+
sx: {
|
|
3989
|
+
maxHeight: 280,
|
|
3990
|
+
overflow: "auto",
|
|
3991
|
+
border: 1,
|
|
3992
|
+
borderColor: "divider",
|
|
3993
|
+
borderRadius: 1,
|
|
3994
|
+
px: 1,
|
|
3995
|
+
display: "grid",
|
|
3996
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))"
|
|
3997
|
+
},
|
|
3998
|
+
children: visible.map((column) => /* @__PURE__ */ jsx(FormControlLabel, {
|
|
3999
|
+
control: /* @__PURE__ */ jsx(Checkbox, {
|
|
4000
|
+
size: "small",
|
|
4001
|
+
checked: selected.includes(column.name),
|
|
4002
|
+
onChange: () => toggle(column.name)
|
|
4003
|
+
}),
|
|
4004
|
+
label: /* @__PURE__ */ jsxs(Box, {
|
|
4005
|
+
component: "span",
|
|
4006
|
+
sx: {
|
|
4007
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
4008
|
+
fontSize: 12
|
|
4009
|
+
},
|
|
4010
|
+
children: [
|
|
4011
|
+
column.name,
|
|
4012
|
+
" ",
|
|
4013
|
+
/* @__PURE__ */ jsx(Box, {
|
|
4014
|
+
component: "span",
|
|
4015
|
+
sx: {
|
|
4016
|
+
color: "text.secondary",
|
|
4017
|
+
fontSize: 11
|
|
4018
|
+
},
|
|
4019
|
+
children: column.type
|
|
4020
|
+
})
|
|
4021
|
+
]
|
|
4022
|
+
}),
|
|
4023
|
+
sx: {
|
|
4024
|
+
m: 0,
|
|
4025
|
+
minWidth: 0
|
|
4026
|
+
}
|
|
4027
|
+
}, column.name))
|
|
4028
|
+
})]
|
|
4029
|
+
}),
|
|
4030
|
+
result && /* @__PURE__ */ jsx(Box, {
|
|
4031
|
+
component: "pre",
|
|
4032
|
+
sx: {
|
|
4033
|
+
m: 0,
|
|
4034
|
+
p: 1,
|
|
4035
|
+
border: 1,
|
|
4036
|
+
borderColor: "divider",
|
|
4037
|
+
borderRadius: 1,
|
|
4038
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
4039
|
+
fontSize: 12,
|
|
4040
|
+
maxHeight: 200,
|
|
4041
|
+
overflow: "auto"
|
|
4042
|
+
},
|
|
4043
|
+
children: /* @__PURE__ */ jsx(HighlightedSql, { code: result })
|
|
4044
|
+
})
|
|
4045
|
+
]
|
|
4046
|
+
}),
|
|
4047
|
+
/* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
|
|
4048
|
+
onClick: onClose,
|
|
4049
|
+
children: "Cancelar"
|
|
4050
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
4051
|
+
variant: "contained",
|
|
4052
|
+
disabled: !table,
|
|
4053
|
+
onClick: () => {
|
|
4054
|
+
onApply(result);
|
|
4055
|
+
onClose();
|
|
4056
|
+
},
|
|
4057
|
+
title: "Substitui o SQL atual",
|
|
4058
|
+
children: "Usar no SQL"
|
|
4059
|
+
})] })
|
|
4060
|
+
] });
|
|
4061
|
+
}
|
|
4062
|
+
//#endregion
|
|
4063
|
+
//#region src/devtools/queryTemplates.ts
|
|
4064
|
+
/**
|
|
4065
|
+
* `WITH` da origem: a tabela do banco entra pelo nome; o SQL de uma tabela do modelo vira o CTE
|
|
4066
|
+
* `origem`, antes dos CTEs do template.
|
|
4067
|
+
*/
|
|
4068
|
+
function withSource(source) {
|
|
4069
|
+
if (source.kind === "database") return {
|
|
4070
|
+
ctes: [],
|
|
4071
|
+
from: quote(source.table)
|
|
4072
|
+
};
|
|
4073
|
+
return {
|
|
4074
|
+
ctes: [`origem AS (\n${source.sql.trim().replace(/;+\s*$/, "").replace(/^/gm, " ")}\n)`],
|
|
4075
|
+
from: "origem"
|
|
4076
|
+
};
|
|
4077
|
+
}
|
|
4078
|
+
/**
|
|
4079
|
+
* Um dia por linha, do menor ao maior valor da coluna, com ano e mês. É a tabela "Calendário" do
|
|
4080
|
+
* Power BI: relacione a coluna de data da fato com `dia`.
|
|
4081
|
+
*
|
|
4082
|
+
* Não é um `WITH RECURSIVE` de um dia por iteração: o MySQL (`cte_max_recursion_depth`) e o MariaDB
|
|
4083
|
+
* (`max_recursive_iterations`) param em 1000 iterações, e o MariaDB corta em silêncio — o calendário
|
|
4084
|
+
* acabaria ~2,7 anos depois do início. A recursão só gera 0–999 e o dia sai de `mil * 1000 + n`.
|
|
4085
|
+
*/
|
|
4086
|
+
function calendarSql(source, column) {
|
|
4087
|
+
const { ctes, from } = withSource(source);
|
|
4088
|
+
const col = quote(column);
|
|
4089
|
+
return [
|
|
4090
|
+
`WITH RECURSIVE ${[
|
|
4091
|
+
...ctes,
|
|
4092
|
+
[
|
|
4093
|
+
"limites AS (",
|
|
4094
|
+
` SELECT DATE(MIN(${col})) AS inicio, DATE(MAX(${col})) AS fim`,
|
|
4095
|
+
` FROM ${from}`,
|
|
4096
|
+
")"
|
|
4097
|
+
],
|
|
4098
|
+
[
|
|
4099
|
+
"numeros AS (",
|
|
4100
|
+
" SELECT 0 AS n",
|
|
4101
|
+
" UNION ALL",
|
|
4102
|
+
" SELECT n + 1 FROM numeros WHERE n < 999",
|
|
4103
|
+
")"
|
|
4104
|
+
],
|
|
4105
|
+
[
|
|
4106
|
+
"dias AS (",
|
|
4107
|
+
" SELECT inicio + INTERVAL (mil.n * 1000 + numeros.n) DAY AS dia",
|
|
4108
|
+
" FROM limites, numeros mil, numeros",
|
|
4109
|
+
" WHERE mil.n <= DATEDIFF(fim, inicio) DIV 1000",
|
|
4110
|
+
" AND mil.n * 1000 + numeros.n <= DATEDIFF(fim, inicio)",
|
|
4111
|
+
")"
|
|
4112
|
+
]
|
|
4113
|
+
].map((part) => Array.isArray(part) ? part.join("\n") : part).join(",\n")}`,
|
|
4114
|
+
"SELECT",
|
|
4115
|
+
" dia,",
|
|
4116
|
+
" YEAR(dia) AS ano,",
|
|
4117
|
+
" MONTH(dia) AS mes",
|
|
4118
|
+
"FROM dias"
|
|
4119
|
+
].join("\n");
|
|
4120
|
+
}
|
|
4121
|
+
const QUERY_TEMPLATES = [{
|
|
4122
|
+
id: "calendar",
|
|
4123
|
+
label: "Calendário",
|
|
4124
|
+
description: "Um dia por linha (com ano e mês) entre a menor e a maior data da coluna. Relacione a data da fato com `dia`.",
|
|
4125
|
+
columnLabel: "coluna de data",
|
|
4126
|
+
preferredTypes: /date|time/i,
|
|
4127
|
+
build: calendarSql
|
|
4128
|
+
}];
|
|
4129
|
+
//#endregion
|
|
4130
|
+
//#region src/devtools/QueryTemplates.tsx
|
|
4131
|
+
function QueryTemplates({ open, onClose, ...props }) {
|
|
4132
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
4133
|
+
open,
|
|
4134
|
+
onClose,
|
|
4135
|
+
maxWidth: "md",
|
|
4136
|
+
fullWidth: true,
|
|
4137
|
+
children: open && /* @__PURE__ */ jsx(Templates, {
|
|
4138
|
+
onClose,
|
|
4139
|
+
...props
|
|
4140
|
+
})
|
|
4141
|
+
});
|
|
4142
|
+
}
|
|
4143
|
+
function Templates({ tables, modelTables, onClose, onApply }) {
|
|
4144
|
+
const [templateId, setTemplateId] = useState(QUERY_TEMPLATES[0].id);
|
|
4145
|
+
const [source, setSource] = useState(null);
|
|
4146
|
+
const [column, setColumn] = useState(null);
|
|
4147
|
+
const template = QUERY_TEMPLATES.find((item) => item.id === templateId) ?? QUERY_TEMPLATES[0];
|
|
4148
|
+
const sources = useMemo(() => [...Object.entries(modelTables).map(([name, table]) => ({
|
|
4149
|
+
key: `model:${name}`,
|
|
4150
|
+
name,
|
|
4151
|
+
group: "Tabelas do modelo",
|
|
4152
|
+
columns: table.columns.map((column) => ({ name: column })),
|
|
4153
|
+
source: {
|
|
4154
|
+
kind: "model",
|
|
4155
|
+
sql: table.sql
|
|
4156
|
+
}
|
|
4157
|
+
})), ...tables.map((table) => ({
|
|
4158
|
+
key: `database:${table.name}`,
|
|
4159
|
+
name: table.name,
|
|
4160
|
+
group: "Tabelas do banco",
|
|
4161
|
+
columns: table.columns,
|
|
4162
|
+
source: {
|
|
4163
|
+
kind: "database",
|
|
4164
|
+
table: table.name
|
|
4165
|
+
}
|
|
4166
|
+
}))], [tables, modelTables]);
|
|
4167
|
+
const columns = useMemo(() => {
|
|
4168
|
+
const list = source?.columns ?? [];
|
|
4169
|
+
const preferred = (type) => !!type && !!template.preferredTypes?.test(type);
|
|
4170
|
+
return [...list.filter((c) => preferred(c.type)), ...list.filter((c) => !preferred(c.type))];
|
|
4171
|
+
}, [source, template]);
|
|
4172
|
+
const result = source && column ? template.build(source.source, column) : "";
|
|
4173
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4174
|
+
/* @__PURE__ */ jsx(DialogTitle, {
|
|
4175
|
+
sx: { fontSize: 14 },
|
|
4176
|
+
children: "Importar template"
|
|
4177
|
+
}),
|
|
4178
|
+
/* @__PURE__ */ jsxs(DialogContent, {
|
|
4179
|
+
sx: {
|
|
4180
|
+
display: "flex",
|
|
4181
|
+
flexDirection: "column",
|
|
4182
|
+
gap: 1.5
|
|
4183
|
+
},
|
|
4184
|
+
children: [
|
|
4185
|
+
/* @__PURE__ */ jsx(TextField, {
|
|
4186
|
+
select: true,
|
|
4187
|
+
size: "small",
|
|
4188
|
+
label: "template",
|
|
4189
|
+
value: template.id,
|
|
4190
|
+
onChange: (event) => setTemplateId(event.target.value),
|
|
4191
|
+
helperText: template.description,
|
|
4192
|
+
sx: { mt: 1 },
|
|
4193
|
+
children: QUERY_TEMPLATES.map((item) => /* @__PURE__ */ jsx(MenuItem, {
|
|
4194
|
+
value: item.id,
|
|
4195
|
+
children: item.label
|
|
4196
|
+
}, item.id))
|
|
4197
|
+
}),
|
|
4198
|
+
/* @__PURE__ */ jsx(Autocomplete, {
|
|
4199
|
+
size: "small",
|
|
4200
|
+
options: sources,
|
|
4201
|
+
value: source,
|
|
4202
|
+
onChange: (_, value) => {
|
|
4203
|
+
setSource(value);
|
|
4204
|
+
setColumn(null);
|
|
4205
|
+
},
|
|
4206
|
+
groupBy: (option) => option.group,
|
|
4207
|
+
getOptionLabel: (option) => option.name,
|
|
4208
|
+
isOptionEqualToValue: (option, value) => option.key === value.key,
|
|
4209
|
+
noOptionsText: "Nenhuma tabela",
|
|
4210
|
+
renderOption: ({ key, ...props }, option) => /* @__PURE__ */ jsxs(Box, {
|
|
4211
|
+
component: "li",
|
|
4212
|
+
...props,
|
|
4213
|
+
sx: {
|
|
4214
|
+
gap: 2,
|
|
4215
|
+
fontFamily: MONO,
|
|
4216
|
+
fontSize: 12
|
|
4217
|
+
},
|
|
4218
|
+
children: [option.name, /* @__PURE__ */ jsxs(Box, {
|
|
4219
|
+
component: "span",
|
|
4220
|
+
sx: {
|
|
4221
|
+
ml: "auto",
|
|
4222
|
+
color: "text.secondary",
|
|
4223
|
+
fontSize: 11
|
|
4224
|
+
},
|
|
4225
|
+
children: [option.columns.length, " colunas"]
|
|
4226
|
+
})]
|
|
4227
|
+
}, key),
|
|
4228
|
+
renderInput: (params) => /* @__PURE__ */ jsx(TextField, {
|
|
4229
|
+
...params,
|
|
4230
|
+
label: "tabela"
|
|
4231
|
+
})
|
|
4232
|
+
}),
|
|
4233
|
+
source && /* @__PURE__ */ jsx(Autocomplete, {
|
|
4234
|
+
size: "small",
|
|
4235
|
+
options: columns.map((item) => item.name),
|
|
4236
|
+
value: column,
|
|
4237
|
+
onChange: (_, value) => setColumn(value),
|
|
4238
|
+
noOptionsText: source.source.kind === "model" ? "Sem colunas: valide o SQL dessa tabela" : "Nenhuma coluna",
|
|
4239
|
+
renderOption: ({ key, ...props }, name) => /* @__PURE__ */ jsxs(Box, {
|
|
4240
|
+
component: "li",
|
|
4241
|
+
...props,
|
|
4242
|
+
sx: {
|
|
4243
|
+
gap: 2,
|
|
4244
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
4245
|
+
fontSize: 12
|
|
4246
|
+
},
|
|
4247
|
+
children: [name, /* @__PURE__ */ jsx(Box, {
|
|
4248
|
+
component: "span",
|
|
4249
|
+
sx: {
|
|
4250
|
+
ml: "auto",
|
|
4251
|
+
color: "text.secondary",
|
|
4252
|
+
fontSize: 11
|
|
4253
|
+
},
|
|
4254
|
+
children: columns.find((item) => item.name === name)?.type
|
|
4255
|
+
})]
|
|
4256
|
+
}, key),
|
|
4257
|
+
renderInput: (params) => /* @__PURE__ */ jsx(TextField, {
|
|
4258
|
+
...params,
|
|
4259
|
+
label: template.columnLabel
|
|
4260
|
+
})
|
|
4261
|
+
}),
|
|
4262
|
+
result ? /* @__PURE__ */ jsx(Box, {
|
|
4263
|
+
component: "pre",
|
|
4264
|
+
sx: {
|
|
4265
|
+
m: 0,
|
|
4266
|
+
p: 1,
|
|
4267
|
+
border: 1,
|
|
4268
|
+
borderColor: "divider",
|
|
4269
|
+
borderRadius: 1,
|
|
4270
|
+
fontFamily: MONO,
|
|
4271
|
+
fontSize: 12,
|
|
4272
|
+
maxHeight: 280,
|
|
4273
|
+
overflow: "auto"
|
|
4274
|
+
},
|
|
4275
|
+
children: /* @__PURE__ */ jsx(HighlightedSql, { code: result })
|
|
4276
|
+
}) : /* @__PURE__ */ jsx(Typography, {
|
|
4277
|
+
sx: {
|
|
4278
|
+
fontSize: 12,
|
|
4279
|
+
color: "text.secondary"
|
|
4280
|
+
},
|
|
4281
|
+
children: "Escolha a tabela e a coluna para ver o SQL."
|
|
4282
|
+
})
|
|
4283
|
+
]
|
|
4284
|
+
}),
|
|
4285
|
+
/* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
|
|
4286
|
+
onClick: onClose,
|
|
4287
|
+
children: "Cancelar"
|
|
4288
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
4289
|
+
variant: "contained",
|
|
4290
|
+
disabled: !result,
|
|
4291
|
+
onClick: () => {
|
|
4292
|
+
onApply(result);
|
|
4293
|
+
onClose();
|
|
4294
|
+
},
|
|
4295
|
+
title: "Substitui o SQL atual",
|
|
4296
|
+
children: "Usar no SQL"
|
|
4297
|
+
})] })
|
|
4298
|
+
] });
|
|
4299
|
+
}
|
|
4300
|
+
//#endregion
|
|
3824
4301
|
//#region src/devtools/SqlEditor.tsx
|
|
3825
4302
|
/**
|
|
3826
4303
|
* Campo de SQL com destaque de sintaxe e autocomplete de tabelas e colunas. As colunas vêm do
|
|
3827
4304
|
* `information_schema` (tabelas/views do banco) e das colunas de cada tabela do modelo (lidas do banco).
|
|
3828
|
-
* O ⤢ abre o mesmo SQL num editor grande
|
|
4305
|
+
* O ⤢ abre o mesmo SQL num editor grande; com `queryTools`, ele traz "Criar query" (escolher tabela e
|
|
4306
|
+
* colunas do banco), "Importar template" (calendário etc.) e "Validar SQL" (executa no banco e
|
|
4307
|
+
* mostra uma amostra).
|
|
3829
4308
|
*
|
|
3830
4309
|
* O destaque é um `<pre>` colorido por baixo do `<textarea>` de texto transparente: mesma fonte,
|
|
3831
4310
|
* mesmo recuo e mesma quebra de linha, com a rolagem sincronizada. O cursor, a seleção e o
|
|
@@ -3901,12 +4380,36 @@ function suggest(sql, caret, completions) {
|
|
|
3901
4380
|
items
|
|
3902
4381
|
} : null;
|
|
3903
4382
|
}
|
|
3904
|
-
function SqlEditor({ value, onChange, label, placeholder = "SQL", minRows, maxRows = 6, error, warning, expandable = true }) {
|
|
4383
|
+
function SqlEditor({ value, onChange, label, placeholder = "SQL", minRows, maxRows = 6, error, warning, expandable = true, queryTools = false }) {
|
|
3905
4384
|
const completions = useContext(SqlCompletionsContext);
|
|
3906
4385
|
const anchor = useRef(null);
|
|
3907
4386
|
const input = useRef(null);
|
|
3908
4387
|
const highlight = useRef(null);
|
|
3909
4388
|
const [expanded, setExpanded] = useState(false);
|
|
4389
|
+
const [building, setBuilding] = useState(false);
|
|
4390
|
+
const [importing, setImporting] = useState(false);
|
|
4391
|
+
const [test, setTest] = useState(null);
|
|
4392
|
+
const [testing, setTesting] = useState(false);
|
|
4393
|
+
const currentTest = test?.sql === value ? test : null;
|
|
4394
|
+
const testSql = completions.testSql;
|
|
4395
|
+
const runTest = async () => {
|
|
4396
|
+
if (!testSql) return;
|
|
4397
|
+
const sql = value;
|
|
4398
|
+
setTesting(true);
|
|
4399
|
+
try {
|
|
4400
|
+
setTest({
|
|
4401
|
+
sql,
|
|
4402
|
+
result: await testSql(sql)
|
|
4403
|
+
});
|
|
4404
|
+
} catch (error) {
|
|
4405
|
+
setTest({
|
|
4406
|
+
sql,
|
|
4407
|
+
error: errorMessage(error)
|
|
4408
|
+
});
|
|
4409
|
+
} finally {
|
|
4410
|
+
setTesting(false);
|
|
4411
|
+
}
|
|
4412
|
+
};
|
|
3910
4413
|
const [state, setState] = useState(null);
|
|
3911
4414
|
const accept = (item) => {
|
|
3912
4415
|
const element = input.current;
|
|
@@ -4046,25 +4549,90 @@ function SqlEditor({ value, onChange, label, placeholder = "SQL", minRows, maxRo
|
|
|
4046
4549
|
sx: { fontSize: 14 },
|
|
4047
4550
|
children: label ?? "SQL"
|
|
4048
4551
|
}),
|
|
4049
|
-
/* @__PURE__ */
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4552
|
+
/* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
4553
|
+
/* @__PURE__ */ jsx(Box, {
|
|
4554
|
+
sx: { pt: 1 },
|
|
4555
|
+
children: /* @__PURE__ */ jsx(SqlEditor, {
|
|
4556
|
+
value,
|
|
4557
|
+
onChange,
|
|
4558
|
+
placeholder,
|
|
4559
|
+
minRows: 20,
|
|
4560
|
+
maxRows: 36,
|
|
4561
|
+
error,
|
|
4562
|
+
warning,
|
|
4563
|
+
expandable: false
|
|
4564
|
+
})
|
|
4565
|
+
}),
|
|
4566
|
+
currentTest?.error && /* @__PURE__ */ jsx(Alert, {
|
|
4567
|
+
severity: "error",
|
|
4568
|
+
sx: {
|
|
4569
|
+
mt: 1,
|
|
4570
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
4571
|
+
fontSize: 12
|
|
4572
|
+
},
|
|
4573
|
+
children: currentTest.error
|
|
4574
|
+
}),
|
|
4575
|
+
currentTest?.result && /* @__PURE__ */ jsxs(Box, {
|
|
4576
|
+
sx: {
|
|
4577
|
+
mt: 1,
|
|
4578
|
+
display: "flex",
|
|
4579
|
+
flexDirection: "column",
|
|
4580
|
+
gap: 1
|
|
4581
|
+
},
|
|
4582
|
+
children: [/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Chip, {
|
|
4583
|
+
color: "success",
|
|
4584
|
+
size: "small",
|
|
4585
|
+
label: `SQL válido · ${currentTest.result.columns.length} colunas · ${currentTest.result.durationMs} ms`
|
|
4586
|
+
}) }), /* @__PURE__ */ jsx(ResultTable, {
|
|
4587
|
+
rows: currentTest.result.rows,
|
|
4588
|
+
maxHeight: 240
|
|
4589
|
+
})]
|
|
4060
4590
|
})
|
|
4061
|
-
|
|
4062
|
-
/* @__PURE__ */
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4591
|
+
] }),
|
|
4592
|
+
/* @__PURE__ */ jsxs(DialogActions, { children: [
|
|
4593
|
+
queryTools && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4594
|
+
/* @__PURE__ */ jsx(Button, {
|
|
4595
|
+
onClick: () => setBuilding(true),
|
|
4596
|
+
title: "Escolher a tabela e as colunas",
|
|
4597
|
+
children: "Criar query"
|
|
4598
|
+
}),
|
|
4599
|
+
/* @__PURE__ */ jsx(Button, {
|
|
4600
|
+
onClick: () => setImporting(true),
|
|
4601
|
+
title: "Calendário e outras queries prontas",
|
|
4602
|
+
children: "Importar template"
|
|
4603
|
+
}),
|
|
4604
|
+
testSql && /* @__PURE__ */ jsx(Button, {
|
|
4605
|
+
disabled: !value.trim() || testing,
|
|
4606
|
+
onClick: runTest,
|
|
4607
|
+
title: "Executa o SQL no banco e mostra as primeiras linhas",
|
|
4608
|
+
children: testing ? "Validando…" : "Validar SQL"
|
|
4609
|
+
})
|
|
4610
|
+
] }),
|
|
4611
|
+
/* @__PURE__ */ jsx(Box, { sx: { flex: 1 } }),
|
|
4612
|
+
/* @__PURE__ */ jsx(Button, {
|
|
4613
|
+
onClick: () => setExpanded(false),
|
|
4614
|
+
children: "Fechar"
|
|
4615
|
+
})
|
|
4616
|
+
] })
|
|
4066
4617
|
]
|
|
4067
4618
|
}),
|
|
4619
|
+
queryTools && /* @__PURE__ */ jsx(QueryBuilder, {
|
|
4620
|
+
open: building,
|
|
4621
|
+
onClose: () => setBuilding(false),
|
|
4622
|
+
tables: completions.tables,
|
|
4623
|
+
sql: value,
|
|
4624
|
+
onApply: onChange
|
|
4625
|
+
}),
|
|
4626
|
+
queryTools && /* @__PURE__ */ jsx(QueryTemplates, {
|
|
4627
|
+
open: importing,
|
|
4628
|
+
onClose: () => setImporting(false),
|
|
4629
|
+
tables: completions.tables,
|
|
4630
|
+
modelTables: Object.fromEntries(Object.entries(completions.modelSql ?? {}).map(([name, sql]) => [name, {
|
|
4631
|
+
sql,
|
|
4632
|
+
columns: completions.modelColumns[name] ?? []
|
|
4633
|
+
}])),
|
|
4634
|
+
onApply: onChange
|
|
4635
|
+
}),
|
|
4068
4636
|
/* @__PURE__ */ jsx(Popper, {
|
|
4069
4637
|
open: !!state,
|
|
4070
4638
|
anchorEl: anchor.current,
|
|
@@ -4524,6 +5092,28 @@ function DimensionSource({ row, editor, columns, error, warning, onPatch }) {
|
|
|
4524
5092
|
}
|
|
4525
5093
|
//#endregion
|
|
4526
5094
|
//#region src/devtools/ModelDiagram.tsx
|
|
5095
|
+
const TARGET_NAME = {
|
|
5096
|
+
table: "tabela",
|
|
5097
|
+
dimension: "dimensão",
|
|
5098
|
+
measure: "medida"
|
|
5099
|
+
};
|
|
5100
|
+
/** Põe uma medida calculada (fórmula) nova, com nome livre, no fim; devolve o `uid` dela. */
|
|
5101
|
+
function addFormulaTo(setEditor) {
|
|
5102
|
+
const uid = newUid();
|
|
5103
|
+
setEditor((current) => {
|
|
5104
|
+
const taken = [...current.dimensions, ...current.measures].map((item) => item.name);
|
|
5105
|
+
const measure = emptyMeasure({
|
|
5106
|
+
uid,
|
|
5107
|
+
type: "formula",
|
|
5108
|
+
name: freeName("novaMedida", taken)
|
|
5109
|
+
});
|
|
5110
|
+
return {
|
|
5111
|
+
...current,
|
|
5112
|
+
measures: [...current.measures, measure]
|
|
5113
|
+
};
|
|
5114
|
+
});
|
|
5115
|
+
return uid;
|
|
5116
|
+
}
|
|
4527
5117
|
const CARD_WIDTH = 230;
|
|
4528
5118
|
const HEADER = 34;
|
|
4529
5119
|
const ROW = 22;
|
|
@@ -4690,6 +5280,12 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
4690
5280
|
});
|
|
4691
5281
|
const [selection, setSelection] = useState(null);
|
|
4692
5282
|
const [panel, setPanel] = useState(loadPanel);
|
|
5283
|
+
const [renaming, setRenaming] = useState(null);
|
|
5284
|
+
const [menu, setMenu] = useState(null);
|
|
5285
|
+
const closeMenu = () => setMenu((current) => current && {
|
|
5286
|
+
...current,
|
|
5287
|
+
open: false
|
|
5288
|
+
});
|
|
4693
5289
|
useEffect(() => {
|
|
4694
5290
|
try {
|
|
4695
5291
|
localStorage.setItem(PANEL_KEY, JSON.stringify(panel));
|
|
@@ -4810,15 +5406,16 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
4810
5406
|
const rect = canvas.current?.getBoundingClientRect();
|
|
4811
5407
|
return toWorld((rect?.left ?? 0) + (rect?.width ?? 600) / 2 - CARD_WIDTH / 2, (rect?.top ?? 0) + (rect?.height ?? 400) / 3);
|
|
4812
5408
|
};
|
|
4813
|
-
|
|
5409
|
+
/** `at`: canto do card (o ponto do botão direito); sem ele, perto do centro da tela. */
|
|
5410
|
+
const addTable = (at) => {
|
|
4814
5411
|
const uid = newUid();
|
|
4815
5412
|
const name = freeName("novaTabela", editor.tables.map((table) => table.name));
|
|
4816
|
-
const
|
|
5413
|
+
const position = at ?? visibleCenter();
|
|
4817
5414
|
setEditor((current) => ({
|
|
4818
5415
|
...current,
|
|
4819
5416
|
positions: {
|
|
4820
5417
|
...current.positions,
|
|
4821
|
-
[uid]:
|
|
5418
|
+
[uid]: position
|
|
4822
5419
|
},
|
|
4823
5420
|
tables: [...current.tables, {
|
|
4824
5421
|
uid,
|
|
@@ -4834,6 +5431,20 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
4834
5431
|
uid
|
|
4835
5432
|
});
|
|
4836
5433
|
};
|
|
5434
|
+
const organize = () => {
|
|
5435
|
+
const layout = autoLayout(nodes, sizes);
|
|
5436
|
+
setPositions((current) => Object.fromEntries(Object.entries(layout).map(([id, point]) => [id, {
|
|
5437
|
+
...current[id],
|
|
5438
|
+
...point
|
|
5439
|
+
}])));
|
|
5440
|
+
fitToScreen(layout);
|
|
5441
|
+
};
|
|
5442
|
+
const addFormula = () => {
|
|
5443
|
+
setSelection({
|
|
5444
|
+
kind: "measure",
|
|
5445
|
+
uid: addFormulaTo(setEditor)
|
|
5446
|
+
});
|
|
5447
|
+
};
|
|
4837
5448
|
const removeTable = (uid) => {
|
|
4838
5449
|
setEditor((current) => ({
|
|
4839
5450
|
...current,
|
|
@@ -4842,6 +5453,19 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
4842
5453
|
}));
|
|
4843
5454
|
setSelection(null);
|
|
4844
5455
|
};
|
|
5456
|
+
const remove = (target) => {
|
|
5457
|
+
if (target.kind === "table") return removeTable(target.uid);
|
|
5458
|
+
const list = {
|
|
5459
|
+
relationship: "relationships",
|
|
5460
|
+
dimension: "dimensions",
|
|
5461
|
+
measure: "measures"
|
|
5462
|
+
}[target.kind];
|
|
5463
|
+
setEditor((current) => ({
|
|
5464
|
+
...current,
|
|
5465
|
+
[list]: current[list].filter((row) => row.uid !== target.uid)
|
|
5466
|
+
}));
|
|
5467
|
+
setSelection((current) => current?.uid === target.uid ? null : current);
|
|
5468
|
+
};
|
|
4845
5469
|
const commitRename = (uid, name) => setEditor((current) => renameTable(current, uid, name));
|
|
4846
5470
|
const connect = (from, fromColumn, to, toColumn) => {
|
|
4847
5471
|
if (from.uid === to.uid) return;
|
|
@@ -4902,20 +5526,7 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
4902
5526
|
});
|
|
4903
5527
|
};
|
|
4904
5528
|
const removeSelection = () => {
|
|
4905
|
-
if (
|
|
4906
|
-
if (selection.kind === "table") removeTable(selection.uid);
|
|
4907
|
-
else {
|
|
4908
|
-
const list = {
|
|
4909
|
-
relationship: "relationships",
|
|
4910
|
-
dimension: "dimensions",
|
|
4911
|
-
measure: "measures"
|
|
4912
|
-
}[selection.kind];
|
|
4913
|
-
setEditor((current) => ({
|
|
4914
|
-
...current,
|
|
4915
|
-
[list]: current[list].filter((row) => row.uid !== selection.uid)
|
|
4916
|
-
}));
|
|
4917
|
-
setSelection(null);
|
|
4918
|
-
}
|
|
5529
|
+
if (selection) remove(selection);
|
|
4919
5530
|
};
|
|
4920
5531
|
const startPan = (event) => {
|
|
4921
5532
|
if (event.button !== 0) return;
|
|
@@ -5103,305 +5714,392 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
5103
5714
|
height: "100%",
|
|
5104
5715
|
minHeight: 0
|
|
5105
5716
|
},
|
|
5106
|
-
children: [
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
}
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5717
|
+
children: [
|
|
5718
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
5719
|
+
ref: canvas,
|
|
5720
|
+
tabIndex: -1,
|
|
5721
|
+
onMouseDown: startPan,
|
|
5722
|
+
onMouseDownCapture: () => canvas.current?.focus({ preventScroll: true }),
|
|
5723
|
+
onKeyDown: (event) => {
|
|
5724
|
+
if ((event.key === "Delete" || event.key === "Backspace") && event.target === canvas.current) removeSelection();
|
|
5725
|
+
},
|
|
5726
|
+
onWheel,
|
|
5727
|
+
onContextMenu: (event) => {
|
|
5728
|
+
event.preventDefault();
|
|
5729
|
+
setMenu({
|
|
5730
|
+
open: true,
|
|
5731
|
+
x: event.clientX,
|
|
5732
|
+
y: event.clientY,
|
|
5733
|
+
target: null
|
|
5734
|
+
});
|
|
5735
|
+
},
|
|
5736
|
+
sx: {
|
|
5737
|
+
outline: "none",
|
|
5738
|
+
flex: 1,
|
|
5739
|
+
position: "relative",
|
|
5740
|
+
overflow: "hidden",
|
|
5741
|
+
cursor: "grab",
|
|
5742
|
+
bgcolor: "#15171b",
|
|
5743
|
+
backgroundImage: "radial-gradient(rgba(255,255,255,.07) 1px, transparent 1px)",
|
|
5744
|
+
backgroundSize: `${20 * view.zoom}px ${20 * view.zoom}px`,
|
|
5745
|
+
backgroundPosition: `${view.x}px ${view.y}px`
|
|
5746
|
+
},
|
|
5747
|
+
children: [
|
|
5748
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
5749
|
+
sx: {
|
|
5139
5750
|
position: "absolute",
|
|
5140
|
-
|
|
5141
|
-
|
|
5751
|
+
left: 0,
|
|
5752
|
+
top: 0,
|
|
5753
|
+
transformOrigin: "0 0",
|
|
5754
|
+
transform: `translate(${view.x}px, ${view.y}px) scale(${view.zoom})`
|
|
5142
5755
|
},
|
|
5143
|
-
"
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
fill: "none",
|
|
5151
|
-
stroke: "#F58220",
|
|
5152
|
-
strokeWidth: 2,
|
|
5153
|
-
strokeDasharray: "5 4"
|
|
5154
|
-
})]
|
|
5155
|
-
}), nodes.map((node) => /* @__PURE__ */ jsx(TableCard, {
|
|
5156
|
-
node,
|
|
5157
|
-
position: placed(node),
|
|
5158
|
-
size: sizeOf(node),
|
|
5159
|
-
collapsed: !!collapsed[node.id],
|
|
5160
|
-
selected: !!isSelected(node),
|
|
5161
|
-
linking: !!linking && linking.node.id !== node.id,
|
|
5162
|
-
selectedField: selection?.kind === "dimension" || selection?.kind === "measure" ? selection.uid : null,
|
|
5163
|
-
onToggle: () => setCollapsed((current) => ({
|
|
5164
|
-
...current,
|
|
5165
|
-
[node.id]: !current[node.id]
|
|
5166
|
-
})),
|
|
5167
|
-
onScroll: (top) => setScrolls((current) => ({
|
|
5168
|
-
...current,
|
|
5169
|
-
[node.id]: top
|
|
5170
|
-
})),
|
|
5171
|
-
onHeaderDown: (event) => startMove(event, node),
|
|
5172
|
-
onResizeDown: (event, edges) => startResize(event, node, edges),
|
|
5173
|
-
onResizeReset: () => resetSize(node),
|
|
5174
|
-
onColumnDown: (event, column) => startLink(event, node, column),
|
|
5175
|
-
onFieldClick: (item) => setSelection({
|
|
5176
|
-
kind: item.kind,
|
|
5177
|
-
uid: item.uid
|
|
5178
|
-
}),
|
|
5179
|
-
onAddField: (kind, column) => addField(kind, node.name, column)
|
|
5180
|
-
}, node.id))]
|
|
5181
|
-
}),
|
|
5182
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
5183
|
-
onMouseDown: (event) => event.stopPropagation(),
|
|
5184
|
-
sx: {
|
|
5185
|
-
position: "absolute",
|
|
5186
|
-
top: 8,
|
|
5187
|
-
left: 8,
|
|
5188
|
-
right: 8,
|
|
5189
|
-
display: "flex",
|
|
5190
|
-
gap: .75,
|
|
5191
|
-
flexWrap: "wrap",
|
|
5192
|
-
alignItems: "center",
|
|
5193
|
-
pointerEvents: "none",
|
|
5194
|
-
"& > *": { pointerEvents: "auto" }
|
|
5195
|
-
},
|
|
5196
|
-
children: [
|
|
5197
|
-
/* @__PURE__ */ jsx(Button, {
|
|
5198
|
-
size: "small",
|
|
5199
|
-
variant: "outlined",
|
|
5200
|
-
onClick: addTable,
|
|
5201
|
-
children: "+ Tabela"
|
|
5202
|
-
}),
|
|
5203
|
-
/* @__PURE__ */ jsx(Button, {
|
|
5204
|
-
size: "small",
|
|
5205
|
-
onClick: onLoadColumns,
|
|
5206
|
-
disabled: loadingColumns,
|
|
5207
|
-
title: "Lê uma linha de cada tabela para listar as colunas",
|
|
5208
|
-
sx: { bgcolor: "#1b1e23" },
|
|
5209
|
-
children: loadingColumns ? "Lendo colunas…" : "Recarregar colunas"
|
|
5210
|
-
}),
|
|
5211
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
5212
|
-
sx: {
|
|
5213
|
-
ml: "auto",
|
|
5214
|
-
display: "flex",
|
|
5215
|
-
gap: .5,
|
|
5216
|
-
alignItems: "center"
|
|
5756
|
+
children: [/* @__PURE__ */ jsxs("svg", {
|
|
5757
|
+
width: 1,
|
|
5758
|
+
height: 1,
|
|
5759
|
+
style: {
|
|
5760
|
+
position: "absolute",
|
|
5761
|
+
overflow: "visible",
|
|
5762
|
+
pointerEvents: "none"
|
|
5217
5763
|
},
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5764
|
+
"aria-hidden": true,
|
|
5765
|
+
children: [lines.map((line) => /* @__PURE__ */ jsx(RelationshipLine, {
|
|
5766
|
+
line,
|
|
5767
|
+
hovered: hoveredEdge === line.id,
|
|
5768
|
+
onHover: (hovered) => setHoveredEdge(hovered ? line.id : null)
|
|
5769
|
+
}, line.id)), linkPreview && /* @__PURE__ */ jsx("path", {
|
|
5770
|
+
d: linkPreview,
|
|
5771
|
+
fill: "none",
|
|
5772
|
+
stroke: "#F58220",
|
|
5773
|
+
strokeWidth: 2,
|
|
5774
|
+
strokeDasharray: "5 4"
|
|
5775
|
+
})]
|
|
5776
|
+
}), nodes.map((node) => /* @__PURE__ */ jsx(TableCard, {
|
|
5777
|
+
node,
|
|
5778
|
+
position: placed(node),
|
|
5779
|
+
size: sizeOf(node),
|
|
5780
|
+
collapsed: !!collapsed[node.id],
|
|
5781
|
+
selected: !!isSelected(node),
|
|
5782
|
+
linking: !!linking && linking.node.id !== node.id,
|
|
5783
|
+
selectedField: selection?.kind === "dimension" || selection?.kind === "measure" ? selection.uid : null,
|
|
5784
|
+
onToggle: () => setCollapsed((current) => ({
|
|
5785
|
+
...current,
|
|
5786
|
+
[node.id]: !current[node.id]
|
|
5787
|
+
})),
|
|
5788
|
+
onScroll: (top) => setScrolls((current) => ({
|
|
5789
|
+
...current,
|
|
5790
|
+
[node.id]: top
|
|
5791
|
+
})),
|
|
5792
|
+
onHeaderDown: (event) => startMove(event, node),
|
|
5793
|
+
onResizeDown: (event, edges) => startResize(event, node, edges),
|
|
5794
|
+
onResizeReset: () => resetSize(node),
|
|
5795
|
+
onColumnDown: (event, column) => startLink(event, node, column),
|
|
5796
|
+
onFieldClick: (item) => setSelection({
|
|
5797
|
+
kind: item.kind,
|
|
5798
|
+
uid: item.uid
|
|
5799
|
+
}),
|
|
5800
|
+
onAddField: (kind, column) => addField(kind, node.name, column),
|
|
5801
|
+
renaming: renaming === node.uid,
|
|
5802
|
+
onRenameStart: () => setRenaming(node.uid),
|
|
5803
|
+
onRename: (name) => {
|
|
5804
|
+
setRenaming(null);
|
|
5805
|
+
if (name !== null && name !== node.name) commitRename(node.uid, name);
|
|
5806
|
+
},
|
|
5807
|
+
onContextMenu: (event, target) => {
|
|
5808
|
+
event.preventDefault();
|
|
5809
|
+
event.stopPropagation();
|
|
5810
|
+
setMenu({
|
|
5811
|
+
open: true,
|
|
5812
|
+
x: event.clientX,
|
|
5813
|
+
y: event.clientY,
|
|
5814
|
+
target
|
|
5815
|
+
});
|
|
5816
|
+
}
|
|
5817
|
+
}, node.id))]
|
|
5818
|
+
}),
|
|
5819
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
5820
|
+
onMouseDown: (event) => event.stopPropagation(),
|
|
5821
|
+
onContextMenu: (event) => event.stopPropagation(),
|
|
5822
|
+
sx: {
|
|
5823
|
+
position: "absolute",
|
|
5824
|
+
top: 8,
|
|
5825
|
+
left: 8,
|
|
5826
|
+
right: 8,
|
|
5827
|
+
display: "flex",
|
|
5828
|
+
gap: .75,
|
|
5829
|
+
flexWrap: "wrap",
|
|
5830
|
+
alignItems: "center",
|
|
5831
|
+
pointerEvents: "none",
|
|
5832
|
+
"& > *": { pointerEvents: "auto" }
|
|
5833
|
+
},
|
|
5834
|
+
children: [
|
|
5835
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5836
|
+
size: "small",
|
|
5837
|
+
variant: "outlined",
|
|
5838
|
+
onClick: () => addTable(),
|
|
5839
|
+
children: "+ Tabela"
|
|
5840
|
+
}),
|
|
5841
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5842
|
+
size: "small",
|
|
5843
|
+
onClick: onLoadColumns,
|
|
5844
|
+
disabled: loadingColumns,
|
|
5845
|
+
title: "Lê uma linha de cada tabela para listar as colunas",
|
|
5846
|
+
sx: { bgcolor: "#1b1e23" },
|
|
5847
|
+
children: loadingColumns ? "Lendo colunas…" : "Recarregar colunas"
|
|
5848
|
+
}),
|
|
5849
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
5850
|
+
sx: {
|
|
5851
|
+
ml: "auto",
|
|
5852
|
+
display: "flex",
|
|
5853
|
+
gap: .5,
|
|
5854
|
+
alignItems: "center"
|
|
5855
|
+
},
|
|
5856
|
+
children: [
|
|
5857
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5858
|
+
size: "small",
|
|
5859
|
+
onClick: organize,
|
|
5860
|
+
title: "Dimensões em cima, fatos embaixo (troca as posições salvas)",
|
|
5861
|
+
sx: { bgcolor: "#1b1e23" },
|
|
5862
|
+
children: "Organizar"
|
|
5863
|
+
}),
|
|
5864
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5865
|
+
size: "small",
|
|
5866
|
+
onClick: () => fitToScreen(),
|
|
5867
|
+
sx: { bgcolor: "#1b1e23" },
|
|
5868
|
+
children: "Ajustar à tela"
|
|
5869
|
+
}),
|
|
5870
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
5871
|
+
size: "small",
|
|
5872
|
+
"aria-label": "Diminuir zoom",
|
|
5873
|
+
onClick: () => setView((current) => ({
|
|
5874
|
+
...current,
|
|
5875
|
+
zoom: Math.max(.3, current.zoom - .1)
|
|
5876
|
+
})),
|
|
5877
|
+
children: "−"
|
|
5878
|
+
}),
|
|
5879
|
+
/* @__PURE__ */ jsxs(Typography, {
|
|
5880
|
+
sx: {
|
|
5881
|
+
fontSize: 11,
|
|
5882
|
+
fontFamily: MONO,
|
|
5883
|
+
width: 36,
|
|
5884
|
+
textAlign: "center"
|
|
5885
|
+
},
|
|
5886
|
+
children: [Math.round(view.zoom * 100), "%"]
|
|
5887
|
+
}),
|
|
5888
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
5889
|
+
size: "small",
|
|
5890
|
+
"aria-label": "Aumentar zoom",
|
|
5891
|
+
onClick: () => setView((current) => ({
|
|
5892
|
+
...current,
|
|
5893
|
+
zoom: Math.min(2, current.zoom + .1)
|
|
5894
|
+
})),
|
|
5895
|
+
children: "+"
|
|
5896
|
+
})
|
|
5897
|
+
]
|
|
5898
|
+
})
|
|
5899
|
+
]
|
|
5900
|
+
}),
|
|
5901
|
+
!nodes.length && /* @__PURE__ */ jsx(Box, {
|
|
5902
|
+
sx: {
|
|
5903
|
+
position: "absolute",
|
|
5904
|
+
inset: 0,
|
|
5905
|
+
display: "flex",
|
|
5906
|
+
alignItems: "center",
|
|
5907
|
+
justifyContent: "center",
|
|
5908
|
+
pointerEvents: "none",
|
|
5909
|
+
color: "text.secondary",
|
|
5910
|
+
fontSize: 13,
|
|
5911
|
+
textAlign: "center",
|
|
5912
|
+
px: 4
|
|
5913
|
+
},
|
|
5914
|
+
children: "Modelo vazio. Adicione as tabelas dimensão (cadastros: navio, calendário…) e fato (lançamentos: pesagens, custos…) e arraste a coluna da fato até a chave da dimensão para relacioná-las."
|
|
5915
|
+
}),
|
|
5916
|
+
!!nodes.length && /* @__PURE__ */ jsx(Typography, {
|
|
5917
|
+
sx: {
|
|
5918
|
+
position: "absolute",
|
|
5919
|
+
left: 10,
|
|
5920
|
+
bottom: 6,
|
|
5921
|
+
fontSize: 11,
|
|
5922
|
+
color: "text.disabled",
|
|
5923
|
+
pointerEvents: "none"
|
|
5924
|
+
},
|
|
5925
|
+
children: "Arraste uma coluna até a coluna de outra tabela para relacionar · +◇/+Σ na coluna cria dimensão/medida · Duplo clique no nome renomeia · Botão direito: editar/remover (no fundo: nova tabela/medida, recarregar, organizar) · Delete remove · Ctrl+roda: zoom"
|
|
5926
|
+
})
|
|
5927
|
+
]
|
|
5928
|
+
}),
|
|
5929
|
+
/* @__PURE__ */ jsx(Menu, {
|
|
5930
|
+
open: !!menu?.open,
|
|
5931
|
+
onClose: closeMenu,
|
|
5932
|
+
anchorReference: "anchorPosition",
|
|
5933
|
+
anchorPosition: menu ? {
|
|
5934
|
+
top: menu.y,
|
|
5935
|
+
left: menu.x
|
|
5936
|
+
} : void 0,
|
|
5937
|
+
transitionDuration: {
|
|
5938
|
+
enter: 90,
|
|
5939
|
+
exit: 60
|
|
5940
|
+
},
|
|
5941
|
+
onContextMenu: (event) => event.preventDefault(),
|
|
5942
|
+
children: menu?.target ? [/* @__PURE__ */ jsxs(MenuItem, {
|
|
5943
|
+
dense: true,
|
|
5944
|
+
onClick: () => {
|
|
5945
|
+
setSelection(menu.target);
|
|
5946
|
+
closeMenu();
|
|
5282
5947
|
},
|
|
5283
|
-
children: "
|
|
5284
|
-
}),
|
|
5285
|
-
|
|
5286
|
-
sx: {
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
fontSize: 11,
|
|
5291
|
-
color: "text.disabled",
|
|
5292
|
-
pointerEvents: "none"
|
|
5948
|
+
children: ["Editar ", TARGET_NAME[menu.target.kind]]
|
|
5949
|
+
}, "edit"), /* @__PURE__ */ jsxs(MenuItem, {
|
|
5950
|
+
dense: true,
|
|
5951
|
+
sx: { color: "error.main" },
|
|
5952
|
+
onClick: () => {
|
|
5953
|
+
if (menu.target) remove(menu.target);
|
|
5954
|
+
closeMenu();
|
|
5293
5955
|
},
|
|
5294
|
-
children: "
|
|
5295
|
-
})
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
"
|
|
5321
|
-
|
|
5322
|
-
|
|
5956
|
+
children: ["Remover ", TARGET_NAME[menu.target.kind]]
|
|
5957
|
+
}, "remove")] : [
|
|
5958
|
+
/* @__PURE__ */ jsx(MenuItem, {
|
|
5959
|
+
dense: true,
|
|
5960
|
+
onClick: () => {
|
|
5961
|
+
if (menu) addTable(toWorld(menu.x, menu.y));
|
|
5962
|
+
closeMenu();
|
|
5963
|
+
},
|
|
5964
|
+
children: "Nova Tabela"
|
|
5965
|
+
}, "table"),
|
|
5966
|
+
/* @__PURE__ */ jsx(MenuItem, {
|
|
5967
|
+
dense: true,
|
|
5968
|
+
onClick: () => {
|
|
5969
|
+
addFormula();
|
|
5970
|
+
closeMenu();
|
|
5971
|
+
},
|
|
5972
|
+
children: "Nova Medida Calculada"
|
|
5973
|
+
}, "formula"),
|
|
5974
|
+
/* @__PURE__ */ jsx(Divider, {}, "divider"),
|
|
5975
|
+
/* @__PURE__ */ jsx(MenuItem, {
|
|
5976
|
+
dense: true,
|
|
5977
|
+
disabled: loadingColumns,
|
|
5978
|
+
onClick: () => {
|
|
5979
|
+
onLoadColumns();
|
|
5980
|
+
closeMenu();
|
|
5981
|
+
},
|
|
5982
|
+
children: "Recarregar colunas"
|
|
5983
|
+
}, "columns"),
|
|
5984
|
+
/* @__PURE__ */ jsx(MenuItem, {
|
|
5985
|
+
dense: true,
|
|
5986
|
+
onClick: () => {
|
|
5987
|
+
organize();
|
|
5988
|
+
closeMenu();
|
|
5989
|
+
},
|
|
5990
|
+
children: "Organizar"
|
|
5991
|
+
}, "organize")
|
|
5992
|
+
]
|
|
5993
|
+
}),
|
|
5994
|
+
panel.open ? /* @__PURE__ */ jsxs(Box, {
|
|
5995
|
+
sx: {
|
|
5996
|
+
width: panel.width,
|
|
5997
|
+
flexShrink: 0,
|
|
5998
|
+
borderLeft: 1,
|
|
5999
|
+
borderColor: "divider",
|
|
6000
|
+
position: "relative",
|
|
6001
|
+
display: "flex",
|
|
6002
|
+
flexDirection: "column",
|
|
6003
|
+
minHeight: 0
|
|
6004
|
+
},
|
|
6005
|
+
children: [
|
|
6006
|
+
/* @__PURE__ */ jsx(Box, {
|
|
6007
|
+
onMouseDown: startPanelResize,
|
|
6008
|
+
title: "Arraste para redimensionar",
|
|
6009
|
+
sx: {
|
|
6010
|
+
position: "absolute",
|
|
6011
|
+
left: -3,
|
|
6012
|
+
top: 0,
|
|
6013
|
+
bottom: 0,
|
|
6014
|
+
width: 6,
|
|
6015
|
+
cursor: "ew-resize",
|
|
6016
|
+
zIndex: 2,
|
|
6017
|
+
"&:hover": {
|
|
6018
|
+
bgcolor: "primary.main",
|
|
6019
|
+
opacity: .4
|
|
6020
|
+
}
|
|
5323
6021
|
}
|
|
5324
|
-
}
|
|
5325
|
-
|
|
5326
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
5327
|
-
sx: {
|
|
5328
|
-
display: "flex",
|
|
5329
|
-
alignItems: "center",
|
|
5330
|
-
px: 1.5,
|
|
5331
|
-
py: .5,
|
|
5332
|
-
borderBottom: 1,
|
|
5333
|
-
borderColor: "divider"
|
|
5334
|
-
},
|
|
5335
|
-
children: [/* @__PURE__ */ jsx(Typography, {
|
|
6022
|
+
}),
|
|
6023
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
5336
6024
|
sx: {
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
6025
|
+
display: "flex",
|
|
6026
|
+
alignItems: "center",
|
|
6027
|
+
px: 1.5,
|
|
6028
|
+
py: .5,
|
|
6029
|
+
borderBottom: 1,
|
|
6030
|
+
borderColor: "divider"
|
|
5341
6031
|
},
|
|
5342
|
-
children:
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
})
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
6032
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
6033
|
+
sx: {
|
|
6034
|
+
fontSize: 12,
|
|
6035
|
+
fontWeight: 700,
|
|
6036
|
+
color: "text.secondary",
|
|
6037
|
+
flex: 1
|
|
6038
|
+
},
|
|
6039
|
+
children: "Propriedades"
|
|
6040
|
+
}), /* @__PURE__ */ jsx(IconButton, {
|
|
6041
|
+
size: "small",
|
|
6042
|
+
"aria-label": "Recolher propriedades",
|
|
6043
|
+
title: "Recolher",
|
|
6044
|
+
onClick: () => setPanel((current) => ({
|
|
6045
|
+
...current,
|
|
6046
|
+
open: false
|
|
6047
|
+
})),
|
|
6048
|
+
children: "⟩"
|
|
6049
|
+
})]
|
|
6050
|
+
}),
|
|
6051
|
+
/* @__PURE__ */ jsx(Box, {
|
|
6052
|
+
sx: {
|
|
6053
|
+
overflow: "auto",
|
|
6054
|
+
flex: 1,
|
|
6055
|
+
p: 1.5
|
|
6056
|
+
},
|
|
6057
|
+
children: /* @__PURE__ */ jsx(Properties, {
|
|
6058
|
+
editor,
|
|
6059
|
+
selection,
|
|
6060
|
+
columns,
|
|
6061
|
+
errors,
|
|
6062
|
+
warnings,
|
|
6063
|
+
usage,
|
|
6064
|
+
onSelect: setSelection,
|
|
6065
|
+
onTry,
|
|
6066
|
+
setEditor,
|
|
6067
|
+
patchTable,
|
|
6068
|
+
patchRelationship,
|
|
6069
|
+
patchDimension,
|
|
6070
|
+
patchMeasure,
|
|
6071
|
+
removeTable,
|
|
6072
|
+
commitRename,
|
|
6073
|
+
addField,
|
|
6074
|
+
unplaced
|
|
6075
|
+
})
|
|
5378
6076
|
})
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
6077
|
+
]
|
|
6078
|
+
}) : /* @__PURE__ */ jsx(Box, {
|
|
6079
|
+
component: "button",
|
|
6080
|
+
type: "button",
|
|
6081
|
+
title: "Mostrar as propriedades",
|
|
6082
|
+
onClick: () => setPanel((current) => ({
|
|
6083
|
+
...current,
|
|
6084
|
+
open: true
|
|
6085
|
+
})),
|
|
6086
|
+
sx: {
|
|
6087
|
+
width: 28,
|
|
6088
|
+
flexShrink: 0,
|
|
6089
|
+
border: 0,
|
|
6090
|
+
borderLeft: 1,
|
|
6091
|
+
borderColor: "divider",
|
|
6092
|
+
bgcolor: "background.paper",
|
|
6093
|
+
color: "text.secondary",
|
|
6094
|
+
cursor: "pointer",
|
|
6095
|
+
writingMode: "vertical-rl",
|
|
6096
|
+
fontSize: 12,
|
|
6097
|
+
py: 1,
|
|
6098
|
+
"&:hover": { color: "primary.main" }
|
|
6099
|
+
},
|
|
6100
|
+
children: "⟨ Propriedades"
|
|
6101
|
+
})
|
|
6102
|
+
]
|
|
5405
6103
|
});
|
|
5406
6104
|
}
|
|
5407
6105
|
/** Curva com tangentes horizontais (saída e chegada pelas laterais dos cards). */
|
|
@@ -5470,11 +6168,15 @@ function RelationshipLine({ line, hovered, onHover }) {
|
|
|
5470
6168
|
]
|
|
5471
6169
|
});
|
|
5472
6170
|
}
|
|
5473
|
-
function TableCard({ node, position, size, collapsed, selected, linking, selectedField, onToggle, onScroll, onHeaderDown, onResizeDown, onResizeReset, onColumnDown, onFieldClick, onAddField }) {
|
|
6171
|
+
function TableCard({ node, position, size, collapsed, selected, linking, selectedField, onToggle, onScroll, onHeaderDown, onResizeDown, onResizeReset, onColumnDown, onFieldClick, onAddField, renaming, onRenameStart, onRename, onContextMenu }) {
|
|
5474
6172
|
const style = ROLE_STYLE[node.role];
|
|
5475
6173
|
const columnActions = ["dimension", "measure"];
|
|
5476
6174
|
return /* @__PURE__ */ jsxs(Box, {
|
|
5477
6175
|
onMouseDown: (event) => event.stopPropagation(),
|
|
6176
|
+
onContextMenu: (event) => onContextMenu(event, {
|
|
6177
|
+
kind: "table",
|
|
6178
|
+
uid: node.uid
|
|
6179
|
+
}),
|
|
5478
6180
|
sx: {
|
|
5479
6181
|
position: "absolute",
|
|
5480
6182
|
left: position.x,
|
|
@@ -5515,7 +6217,12 @@ function TableCard({ node, position, size, collapsed, selected, linking, selecte
|
|
|
5515
6217
|
},
|
|
5516
6218
|
children: "▦"
|
|
5517
6219
|
}),
|
|
5518
|
-
/* @__PURE__ */ jsx(
|
|
6220
|
+
renaming ? /* @__PURE__ */ jsx(TableNameInput, {
|
|
6221
|
+
value: node.name,
|
|
6222
|
+
onDone: onRename
|
|
6223
|
+
}) : /* @__PURE__ */ jsx(Typography, {
|
|
6224
|
+
onDoubleClick: onRenameStart,
|
|
6225
|
+
title: "Duplo clique para renomear",
|
|
5519
6226
|
sx: {
|
|
5520
6227
|
fontWeight: 700,
|
|
5521
6228
|
fontSize: 13,
|
|
@@ -5643,6 +6350,10 @@ function TableCard({ node, position, size, collapsed, selected, linking, selecte
|
|
|
5643
6350
|
}, `c:${item.name}`) : /* @__PURE__ */ jsxs(Box, {
|
|
5644
6351
|
onMouseDown: (event) => event.stopPropagation(),
|
|
5645
6352
|
onClick: () => onFieldClick(item),
|
|
6353
|
+
onContextMenu: (event) => onContextMenu(event, {
|
|
6354
|
+
kind: item.kind,
|
|
6355
|
+
uid: item.uid
|
|
6356
|
+
}),
|
|
5646
6357
|
sx: {
|
|
5647
6358
|
height: ROW,
|
|
5648
6359
|
display: "flex",
|
|
@@ -5738,23 +6449,60 @@ const resizeHandleSx = {
|
|
|
5738
6449
|
position: "absolute",
|
|
5739
6450
|
zIndex: 1
|
|
5740
6451
|
};
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
6452
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
6453
|
+
/** Nome da tabela editado no cabeçalho do card: Enter/sair confirma, Esc cancela. */
|
|
6454
|
+
function TableNameInput({ value, onDone }) {
|
|
6455
|
+
const [text, setText] = useState(value);
|
|
6456
|
+
const valid = IDENTIFIER.test(text);
|
|
6457
|
+
const done = useRef(false);
|
|
6458
|
+
const finish = (name) => {
|
|
6459
|
+
if (done.current) return;
|
|
6460
|
+
done.current = true;
|
|
6461
|
+
onDone(name);
|
|
6462
|
+
};
|
|
6463
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
6464
|
+
component: "input",
|
|
6465
|
+
autoFocus: true,
|
|
6466
|
+
value: text,
|
|
6467
|
+
"aria-label": "Nome da tabela",
|
|
6468
|
+
onFocus: (event) => event.currentTarget.select(),
|
|
6469
|
+
onChange: (event) => setText(event.target.value),
|
|
6470
|
+
onMouseDown: (event) => event.stopPropagation(),
|
|
6471
|
+
onKeyDown: (event) => {
|
|
6472
|
+
event.stopPropagation();
|
|
6473
|
+
if (event.key === "Enter") finish(valid ? text : null);
|
|
6474
|
+
if (event.key === "Escape") finish(null);
|
|
5748
6475
|
},
|
|
5749
|
-
|
|
5750
|
-
|
|
6476
|
+
onBlur: () => finish(valid ? text : null),
|
|
6477
|
+
sx: {
|
|
6478
|
+
flex: 1,
|
|
6479
|
+
minWidth: 0,
|
|
6480
|
+
font: "inherit",
|
|
6481
|
+
fontSize: 13,
|
|
6482
|
+
fontWeight: 700,
|
|
6483
|
+
color: "text.primary",
|
|
6484
|
+
bgcolor: "#1b1e23",
|
|
6485
|
+
border: "1px solid",
|
|
6486
|
+
borderColor: valid ? "primary.main" : "error.main",
|
|
6487
|
+
borderRadius: .5,
|
|
6488
|
+
px: .5,
|
|
6489
|
+
outline: "none"
|
|
6490
|
+
}
|
|
6491
|
+
});
|
|
6492
|
+
}
|
|
6493
|
+
/** Bloco do painel; com `collapseKey`, o título recolhe/expande (lembrado no navegador). */
|
|
6494
|
+
function PropertyBlock({ title, collapseKey, children }) {
|
|
6495
|
+
return /* @__PURE__ */ jsx(Section, {
|
|
6496
|
+
title,
|
|
6497
|
+
collapseKey,
|
|
6498
|
+
children: /* @__PURE__ */ jsx(Box, {
|
|
5751
6499
|
sx: {
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
6500
|
+
display: "flex",
|
|
6501
|
+
flexDirection: "column",
|
|
6502
|
+
gap: 1
|
|
5755
6503
|
},
|
|
5756
|
-
children
|
|
5757
|
-
})
|
|
6504
|
+
children
|
|
6505
|
+
})
|
|
5758
6506
|
});
|
|
5759
6507
|
}
|
|
5760
6508
|
function ColumnSelect({ label, value, options, onChange }) {
|
|
@@ -5818,29 +6566,23 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
5818
6566
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5819
6567
|
/* @__PURE__ */ jsxs(PropertyBlock, {
|
|
5820
6568
|
title: "Tabela",
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
}),
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
minRows: 3,
|
|
5835
|
-
maxRows: 12,
|
|
5836
|
-
value: table.sql,
|
|
5837
|
-
error: errors[`table:${table.name}`],
|
|
5838
|
-
onChange: (sql) => patchTable(table.uid, { sql })
|
|
5839
|
-
})
|
|
5840
|
-
]
|
|
6569
|
+
collapseKey: "diagram:table",
|
|
6570
|
+
children: [/* @__PURE__ */ jsx(RenameField, {
|
|
6571
|
+
value: table.name,
|
|
6572
|
+
onCommit: (name) => commitRename(table.uid, name)
|
|
6573
|
+
}, table.uid), /* @__PURE__ */ jsx(SqlEditor, {
|
|
6574
|
+
label: "sql",
|
|
6575
|
+
minRows: 3,
|
|
6576
|
+
maxRows: 12,
|
|
6577
|
+
value: table.sql,
|
|
6578
|
+
error: errors[`table:${table.name}`],
|
|
6579
|
+
onChange: (sql) => patchTable(table.uid, { sql }),
|
|
6580
|
+
queryTools: true
|
|
6581
|
+
})]
|
|
5841
6582
|
}),
|
|
5842
6583
|
/* @__PURE__ */ jsx(PropertyBlock, {
|
|
5843
6584
|
title: "Dimensões (marque as colunas)",
|
|
6585
|
+
collapseKey: "diagram:dimensions",
|
|
5844
6586
|
children: /* @__PURE__ */ jsx(DimensionPicker, {
|
|
5845
6587
|
editor,
|
|
5846
6588
|
setEditor,
|
|
@@ -5854,6 +6596,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
5854
6596
|
}),
|
|
5855
6597
|
/* @__PURE__ */ jsx(PropertyBlock, {
|
|
5856
6598
|
title: "Dicionário",
|
|
6599
|
+
collapseKey: "diagram:dictionary",
|
|
5857
6600
|
children: /* @__PURE__ */ jsx(DictionaryFields, {
|
|
5858
6601
|
row: table,
|
|
5859
6602
|
onPatch: (changes) => patchTable(table.uid, changes)
|
|
@@ -6040,6 +6783,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6040
6783
|
}),
|
|
6041
6784
|
/* @__PURE__ */ jsx(PropertyBlock, {
|
|
6042
6785
|
title: "Dicionário",
|
|
6786
|
+
collapseKey: "diagram:dictionary",
|
|
6043
6787
|
children: /* @__PURE__ */ jsx(DictionaryFields, {
|
|
6044
6788
|
row,
|
|
6045
6789
|
onPatch: (changes) => patchDimension(row.uid, changes)
|
|
@@ -6092,6 +6836,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6092
6836
|
}),
|
|
6093
6837
|
/* @__PURE__ */ jsx(PropertyBlock, {
|
|
6094
6838
|
title: "Dicionário",
|
|
6839
|
+
collapseKey: "diagram:dictionary",
|
|
6095
6840
|
children: /* @__PURE__ */ jsx(DictionaryFields, {
|
|
6096
6841
|
row,
|
|
6097
6842
|
onPatch: (changes) => patchMeasure(row.uid, changes)
|
|
@@ -6118,6 +6863,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6118
6863
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6119
6864
|
/* @__PURE__ */ jsx(PropertyBlock, {
|
|
6120
6865
|
title: "Modelo",
|
|
6866
|
+
collapseKey: "diagram:model",
|
|
6121
6867
|
children: /* @__PURE__ */ jsx(Typography, {
|
|
6122
6868
|
sx: {
|
|
6123
6869
|
fontSize: 12,
|
|
@@ -6128,6 +6874,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6128
6874
|
}),
|
|
6129
6875
|
/* @__PURE__ */ jsxs(PropertyBlock, {
|
|
6130
6876
|
title: "Relacionamentos",
|
|
6877
|
+
collapseKey: "diagram:relationships",
|
|
6131
6878
|
children: [!editor.relationships.length && /* @__PURE__ */ jsx(Typography, {
|
|
6132
6879
|
sx: {
|
|
6133
6880
|
fontSize: 12,
|
|
@@ -6196,6 +6943,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6196
6943
|
}),
|
|
6197
6944
|
/* @__PURE__ */ jsxs(PropertyBlock, {
|
|
6198
6945
|
title: "Medidas calculadas",
|
|
6946
|
+
collapseKey: "diagram:formulas",
|
|
6199
6947
|
children: [formulas.map((row) => /* @__PURE__ */ jsxs(Button, {
|
|
6200
6948
|
size: "small",
|
|
6201
6949
|
onClick: () => onSelect({
|
|
@@ -6213,22 +6961,10 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6213
6961
|
]
|
|
6214
6962
|
}, row.uid)), /* @__PURE__ */ jsx(Button, {
|
|
6215
6963
|
size: "small",
|
|
6216
|
-
onClick: () => {
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
...current,
|
|
6221
|
-
measures: [...current.measures, emptyMeasure({
|
|
6222
|
-
uid,
|
|
6223
|
-
type: "formula",
|
|
6224
|
-
name: freeName("novaMedida", taken)
|
|
6225
|
-
})]
|
|
6226
|
-
}));
|
|
6227
|
-
onSelect({
|
|
6228
|
-
kind: "measure",
|
|
6229
|
-
uid
|
|
6230
|
-
});
|
|
6231
|
-
},
|
|
6964
|
+
onClick: () => onSelect({
|
|
6965
|
+
kind: "measure",
|
|
6966
|
+
uid: addFormulaTo(setEditor)
|
|
6967
|
+
}),
|
|
6232
6968
|
children: "+ Medida calculada"
|
|
6233
6969
|
})]
|
|
6234
6970
|
}),
|
|
@@ -6251,7 +6987,7 @@ function RenameField({ value, onCommit }) {
|
|
|
6251
6987
|
value: text,
|
|
6252
6988
|
onChange: setText,
|
|
6253
6989
|
onBlur: () => {
|
|
6254
|
-
if (
|
|
6990
|
+
if (IDENTIFIER.test(text)) onCommit(text);
|
|
6255
6991
|
else setText(value);
|
|
6256
6992
|
}
|
|
6257
6993
|
});
|
|
@@ -6429,11 +7165,22 @@ function ModelPanel({ devtools, original, capabilities, tables, onTry, onSaved }
|
|
|
6429
7165
|
});
|
|
6430
7166
|
const completions = useMemo(() => ({
|
|
6431
7167
|
tables,
|
|
6432
|
-
modelColumns: Object.fromEntries(editor.tables.map((row) => [row.name, columns?.tables[row.name] ?? []]))
|
|
7168
|
+
modelColumns: Object.fromEntries(editor.tables.map((row) => [row.name, columns?.tables[row.name] ?? []])),
|
|
7169
|
+
modelSql: Object.fromEntries(editor.tables.map((row) => [row.name, row.sql])),
|
|
7170
|
+
testSql: async (sql) => {
|
|
7171
|
+
const result = await fetcher({
|
|
7172
|
+
model: modelName,
|
|
7173
|
+
devtools: { testSql: sql }
|
|
7174
|
+
});
|
|
7175
|
+
if (!result.devtools?.sqlTest) throw new Error("O servidor não devolveu o teste do SQL");
|
|
7176
|
+
return result.devtools.sqlTest;
|
|
7177
|
+
}
|
|
6433
7178
|
}), [
|
|
6434
7179
|
tables,
|
|
6435
7180
|
columns,
|
|
6436
|
-
editor.tables
|
|
7181
|
+
editor.tables,
|
|
7182
|
+
fetcher,
|
|
7183
|
+
modelName
|
|
6437
7184
|
]);
|
|
6438
7185
|
const validationErrors = validation ? Object.entries(validation.errors) : [];
|
|
6439
7186
|
return /* @__PURE__ */ jsxs(SqlCompletionsContext.Provider, {
|
|
@@ -8582,4 +9329,4 @@ function ToggleButton$1({ draft, loading, problems, inspecting, onOpen }) {
|
|
|
8582
9329
|
//#endregion
|
|
8583
9330
|
export { DashboardDevtools as default };
|
|
8584
9331
|
|
|
8585
|
-
//# sourceMappingURL=DashboardDevtools-
|
|
9332
|
+
//# sourceMappingURL=DashboardDevtools-wk4vMfhy.js.map
|