mirror of
https://github.com/rowyio/rowy.git
synced 2026-07-11 04:52:08 +02:00
Merge branch 'rc' of https://github.com/rowyio/rowy into rc
This commit is contained in:
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -23,6 +23,9 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Rowy Run version**
|
||||
If applicable, share the Rowy Run version from your project settings.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "rowy",
|
||||
"version": "2.4.0",
|
||||
"version": "2.5.0",
|
||||
"homepage": "https://rowy.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -15,10 +15,10 @@
|
||||
"@hookform/resolvers": "^2.8.5",
|
||||
"@mdi/js": "^6.5.95",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@mui/icons-material": "^5.4.4",
|
||||
"@mui/lab": "^5.0.0-alpha.71",
|
||||
"@mui/material": "^5.4.4",
|
||||
"@mui/styles": "^5.4.4",
|
||||
"@mui/icons-material": "^5.5.1",
|
||||
"@mui/lab": "^5.0.0-alpha.73",
|
||||
"@mui/material": "^5.5.1",
|
||||
"@mui/styles": "^5.5.1",
|
||||
"@rowy/form-builder": "^0.5.3",
|
||||
"@rowy/multiselect": "^0.2.3",
|
||||
"@tinymce/tinymce-react": "^3.12.6",
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function CodeEditor({
|
||||
extraLibs,
|
||||
diagnosticsOptions,
|
||||
onUnmount,
|
||||
|
||||
defaultLanguage = "javascript",
|
||||
...props
|
||||
}: ICodeEditorProps) {
|
||||
const theme = useTheme();
|
||||
@@ -77,7 +77,7 @@ export default function CodeEditor({
|
||||
style={fullScreen ? { height: "100%" } : {}}
|
||||
>
|
||||
<Editor
|
||||
defaultLanguage="javascript"
|
||||
defaultLanguage={defaultLanguage}
|
||||
value={initialEditorValue}
|
||||
loading={<CircularProgressOptical size={20} sx={{ m: 2 }} />}
|
||||
className="editor"
|
||||
|
||||
5
src/components/CodeEditor/rowy.d.ts
vendored
5
src/components/CodeEditor/rowy.d.ts
vendored
@@ -48,7 +48,10 @@ interface Rowy {
|
||||
/**
|
||||
* Get an existing secret from the secret manager.
|
||||
*/
|
||||
get: (name: SecretNames, version?: string) => Promise<string | undefined>;
|
||||
get: (
|
||||
name: SecretNames,
|
||||
version?: string
|
||||
) => Promise<string | any | undefined>;
|
||||
};
|
||||
/**
|
||||
* Gives access to the Cloud Storage.
|
||||
|
||||
@@ -21,6 +21,7 @@ export default function Confirmation({
|
||||
confirm,
|
||||
confirmationCommand,
|
||||
handleConfirm,
|
||||
handleCancel,
|
||||
confirmColor,
|
||||
open,
|
||||
handleClose,
|
||||
@@ -60,7 +61,14 @@ export default function Confirmation({
|
||||
|
||||
<DialogActions>
|
||||
{!hideCancel && (
|
||||
<Button onClick={handleClose}>{cancel ?? "Cancel"}</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (handleCancel) handleCancel();
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
{cancel ?? "Cancel"}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
||||
@@ -8,6 +8,7 @@ export type confirmationProps =
|
||||
confirm?: string | JSX.Element;
|
||||
confirmationCommand?: string;
|
||||
handleConfirm: () => void;
|
||||
handleCancel?: () => void;
|
||||
open?: Boolean;
|
||||
confirmColor?: string;
|
||||
}
|
||||
|
||||
110
src/components/Modal/FullScreenModal.tsx
Normal file
110
src/components/Modal/FullScreenModal.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { ReactNode, useState } from "react";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogProps,
|
||||
Slide,
|
||||
IconButton,
|
||||
Container,
|
||||
} from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
|
||||
import ScrollableDialogContent, {
|
||||
IScrollableDialogContentProps,
|
||||
} from "./ScrollableDialogContent";
|
||||
|
||||
export interface IFullScreenModalProps
|
||||
extends Partial<Omit<DialogProps, "title">> {
|
||||
onClose: (setOpen: React.Dispatch<React.SetStateAction<boolean>>) => void;
|
||||
disableBackdropClick?: boolean;
|
||||
disableEscapeKeyDown?: boolean;
|
||||
|
||||
"aria-labelledby": DialogProps["aria-labelledby"];
|
||||
header?: ReactNode;
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
|
||||
hideCloseButton?: boolean;
|
||||
ScrollableDialogContentProps?: Partial<IScrollableDialogContentProps>;
|
||||
}
|
||||
|
||||
export default function FullScreenModal({
|
||||
onClose,
|
||||
disableBackdropClick,
|
||||
disableEscapeKeyDown,
|
||||
header,
|
||||
children,
|
||||
footer,
|
||||
hideCloseButton,
|
||||
ScrollableDialogContentProps,
|
||||
...props
|
||||
}: IFullScreenModalProps) {
|
||||
const [open, setOpen] = useState(true);
|
||||
const handleClose: NonNullable<DialogProps["onClose"]> = (_, reason) => {
|
||||
if (
|
||||
(disableBackdropClick && reason === "backdropClick") ||
|
||||
(disableEscapeKeyDown && reason === "escapeKeyDown")
|
||||
) {
|
||||
setEmphasizeCloseButton(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
setEmphasizeCloseButton(false);
|
||||
setTimeout(() => onClose(setOpen), 300);
|
||||
};
|
||||
|
||||
const [emphasizeCloseButton, setEmphasizeCloseButton] = useState(false);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
TransitionComponent={Slide}
|
||||
TransitionProps={{ direction: "up" } as any}
|
||||
{...props}
|
||||
>
|
||||
<Container
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
pt: { xs: "var(--dialog-spacing)", xl: 6 },
|
||||
}}
|
||||
>
|
||||
{!hideCloseButton && (
|
||||
<IconButton
|
||||
onClick={handleClose as any}
|
||||
aria-label="Close"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: (theme) => theme.spacing(1),
|
||||
right: (theme) => theme.spacing(1),
|
||||
|
||||
bgcolor: emphasizeCloseButton ? "error.main" : undefined,
|
||||
color: emphasizeCloseButton ? "error.contrastText" : undefined,
|
||||
"&:hover": emphasizeCloseButton
|
||||
? { bgcolor: "error.dark" }
|
||||
: undefined,
|
||||
}}
|
||||
className="dialog-close"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
{header}
|
||||
|
||||
<ScrollableDialogContent
|
||||
style={{ padding: 0 }}
|
||||
{...ScrollableDialogContentProps}
|
||||
>
|
||||
{children}
|
||||
</ScrollableDialogContent>
|
||||
|
||||
{footer}
|
||||
</Container>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,9 @@ const components = {
|
||||
a: (props) => <Link color="inherit" {...props} />,
|
||||
p: Typography,
|
||||
// eslint-disable-next-line jsx-a11y/alt-text
|
||||
img: (props) => <img style={{ maxWidth: "100%" }} {...props} />,
|
||||
img: (props) => (
|
||||
<img style={{ maxWidth: "100%", borderRadius: 4 }} {...props} />
|
||||
),
|
||||
};
|
||||
|
||||
const restrictionPresets = {
|
||||
@@ -31,7 +33,7 @@ export default function RenderedMarkdown({
|
||||
unwrapDisallowed
|
||||
linkTarget="_blank"
|
||||
remarkPlugins={remarkPlugins}
|
||||
components={components}
|
||||
components={{ ...components, ...props.components }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { WIKI_LINKS } from "@src/constants/externalLinks";
|
||||
import { useProjectContext } from "@src/contexts/ProjectContext";
|
||||
|
||||
export default function RowyRunModal() {
|
||||
const { userClaims } = useAppContext();
|
||||
const { userRoles } = useAppContext();
|
||||
const { settings } = useProjectContext();
|
||||
|
||||
const [state, setState] = useAtom(rowyRunModalAtom);
|
||||
@@ -79,12 +79,12 @@ export default function RowyRunModal() {
|
||||
size="large"
|
||||
onClick={handleClose}
|
||||
style={{ display: "flex" }}
|
||||
disabled={!userClaims?.roles.includes("ADMIN")}
|
||||
disabled={!userRoles.includes("ADMIN")}
|
||||
>
|
||||
Set up Rowy Run
|
||||
</Button>
|
||||
|
||||
{!userClaims?.roles.includes("ADMIN") && (
|
||||
{!userRoles.includes("ADMIN") && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
textAlign="center"
|
||||
|
||||
@@ -38,13 +38,10 @@ export default function SideDrawer() {
|
||||
|
||||
const handleNavigate = (direction: "up" | "down") => () => {
|
||||
if (!tableState?.rows) return;
|
||||
|
||||
let row = cell!.row;
|
||||
if (direction === "up" && row > 0) row -= 1;
|
||||
if (direction === "down" && row < tableState.rows.length - 1) row += 1;
|
||||
|
||||
setCell!((cell) => ({ column: cell!.column, row }));
|
||||
|
||||
const idx = tableState?.columns[cell!.column]?.index;
|
||||
dataGridRef?.current?.selectCell({ rowIdx: row, idx }, false);
|
||||
};
|
||||
@@ -68,13 +65,6 @@ export default function SideDrawer() {
|
||||
|
||||
useEffect(() => {
|
||||
if (cell && tableState?.rows[cell.row]) {
|
||||
window.history.pushState(
|
||||
"",
|
||||
`${tableState?.config.id}`,
|
||||
`${window.location.pathname}?rowRef=${encodeURIComponent(
|
||||
tableState?.rows[cell.row].ref.path
|
||||
)}`
|
||||
);
|
||||
if (urlDocState.doc) {
|
||||
urlDocState.unsubscribe();
|
||||
dispatchUrlDoc({ path: "", doc: null });
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getFieldProp } from "@src/components/fields";
|
||||
import MenuContents from "./MenuContent";
|
||||
import DuplicateIcon from "@src/assets/icons/CopyCells";
|
||||
import DeleteIcon from "@mui/icons-material/DeleteOutlined";
|
||||
import LinkIcon from "@mui/icons-material/Link";
|
||||
|
||||
import { useProjectContext } from "@src/contexts/ProjectContext";
|
||||
import { useContextMenuAtom } from "@src/atoms/ContextMenu";
|
||||
@@ -47,8 +48,18 @@ export default function ContextMenu() {
|
||||
}
|
||||
|
||||
const row = tableState?.rows[selectedCell!.rowIndex];
|
||||
if (userRoles.includes("ADMIN") && row) {
|
||||
if (row) {
|
||||
const rowActions = [
|
||||
{
|
||||
label: "Copy link to row",
|
||||
icon: <LinkIcon />,
|
||||
onClick: () => {
|
||||
const rowRef = encodeURIComponent(row.ref.path);
|
||||
navigator.clipboard.writeText(
|
||||
window.location.href + `?rowRef=${rowRef}`
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Duplicate row",
|
||||
icon: <DuplicateIcon />,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
import { IconButton, Menu, MenuItem, DialogContentText } from "@mui/material";
|
||||
import DeleteIcon from "@mui/icons-material/DeleteOutlined";
|
||||
@@ -27,14 +28,21 @@ export default function DeleteMenu({ clearDialog, data }: IDeleteMenuProps) {
|
||||
const handleClose = () => setAnchorEl(null);
|
||||
|
||||
const history = useHistory();
|
||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
|
||||
|
||||
const handleResetStructure = async () => {
|
||||
const snack = enqueueSnackbar("Resetting columns…", { persist: true });
|
||||
|
||||
const schemaDocRef = db.doc(`${TABLE_SCHEMAS}/${data!.id}`);
|
||||
await schemaDocRef.update({ columns: {} });
|
||||
|
||||
clearDialog();
|
||||
closeSnackbar(snack);
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
const snack = enqueueSnackbar("Deleting table…", { persist: true });
|
||||
|
||||
const tablesDocRef = db.doc(SETTINGS);
|
||||
const tableData = (await tablesDocRef.get()).data();
|
||||
const updatedTables = tableData?.tables.filter(
|
||||
@@ -49,8 +57,10 @@ export default function DeleteMenu({ clearDialog, data }: IDeleteMenuProps) {
|
||||
)
|
||||
.doc(data?.id)
|
||||
.delete();
|
||||
|
||||
await analytics.logEvent("delete_table");
|
||||
clearDialog();
|
||||
closeSnackbar(snack);
|
||||
history.push(routes.home);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,33 +1,22 @@
|
||||
import { useEffect } from "react";
|
||||
import { useWatch } from "react-hook-form";
|
||||
import _camelCase from "lodash/camelCase";
|
||||
import {
|
||||
ShortTextComponent,
|
||||
IShortTextComponentProps,
|
||||
} from "@rowy/form-builder";
|
||||
|
||||
import { TextField, TextFieldProps } from "@mui/material";
|
||||
import { IFieldComponentProps, FieldAssistiveText } from "@rowy/form-builder";
|
||||
|
||||
export interface ICamelCaseIdProps
|
||||
extends IFieldComponentProps,
|
||||
Omit<
|
||||
TextFieldProps,
|
||||
"variant" | "name" | "label" | "onBlur" | "onChange" | "value" | "ref"
|
||||
> {
|
||||
export interface ITableIdProps extends IShortTextComponentProps {
|
||||
watchedField?: string;
|
||||
}
|
||||
|
||||
export default function CamelCaseId({
|
||||
field: { onChange, onBlur, value, ref },
|
||||
export default function TableId({ watchedField, ...props }: ITableIdProps) {
|
||||
const {
|
||||
field: { onChange },
|
||||
useFormMethods: { control },
|
||||
disabled,
|
||||
} = props;
|
||||
|
||||
name,
|
||||
useFormMethods: { control },
|
||||
|
||||
errorMessage,
|
||||
assistiveText,
|
||||
|
||||
disabled,
|
||||
|
||||
watchedField,
|
||||
...props
|
||||
}: ICamelCaseIdProps) {
|
||||
const watchedValue = useWatch({ control, name: watchedField } as any);
|
||||
useEffect(() => {
|
||||
if (!disabled && typeof watchedValue === "string" && !!watchedValue)
|
||||
@@ -35,37 +24,9 @@ export default function CamelCaseId({
|
||||
}, [watchedValue, disabled]);
|
||||
|
||||
return (
|
||||
<TextField
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
value={value}
|
||||
fullWidth
|
||||
error={!!errorMessage}
|
||||
helperText={
|
||||
(errorMessage || assistiveText) && (
|
||||
<>
|
||||
{errorMessage}
|
||||
|
||||
<FieldAssistiveText style={{ margin: 0 }} disabled={!!disabled}>
|
||||
{assistiveText}
|
||||
</FieldAssistiveText>
|
||||
</>
|
||||
)
|
||||
}
|
||||
FormHelperTextProps={{ component: "div" } as any}
|
||||
name={name}
|
||||
id={`field-${name}`}
|
||||
sx={{ "& .MuiInputBase-input": { fontFamily: "mono" } }}
|
||||
<ShortTextComponent
|
||||
{...props}
|
||||
disabled={disabled}
|
||||
inputProps={{
|
||||
required: false,
|
||||
// https://github.com/react-hook-form/react-hook-form/issues/4485
|
||||
disabled: false,
|
||||
readOnly: disabled,
|
||||
style: disabled ? { cursor: "default" } : undefined,
|
||||
}}
|
||||
inputRef={ref}
|
||||
sx={{ "& .MuiInputBase-input": { fontFamily: "mono" } }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
27
src/components/TableSettings/TableName.tsx
Normal file
27
src/components/TableSettings/TableName.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useEffect } from "react";
|
||||
import { useWatch } from "react-hook-form";
|
||||
import _startCase from "lodash/startCase";
|
||||
import {
|
||||
ShortTextComponent,
|
||||
IShortTextComponentProps,
|
||||
} from "@rowy/form-builder";
|
||||
|
||||
export interface ITableNameProps extends IShortTextComponentProps {
|
||||
watchedField?: string;
|
||||
}
|
||||
|
||||
export default function TableName({ watchedField, ...props }: ITableNameProps) {
|
||||
const {
|
||||
field: { onChange },
|
||||
useFormMethods: { control },
|
||||
disabled,
|
||||
} = props;
|
||||
|
||||
const watchedValue = useWatch({ control, name: watchedField } as any);
|
||||
useEffect(() => {
|
||||
if (!disabled && typeof watchedValue === "string" && !!watchedValue)
|
||||
onChange(_startCase(watchedValue));
|
||||
}, [watchedValue, disabled]);
|
||||
|
||||
return <ShortTextComponent {...props} />;
|
||||
}
|
||||
@@ -194,10 +194,11 @@ export const tableSettings = (
|
||||
// Step 2: Display
|
||||
{
|
||||
step: "display",
|
||||
type: FieldType.shortText,
|
||||
type: "tableName",
|
||||
name: "name",
|
||||
label: "Table name",
|
||||
required: true,
|
||||
watchedField: "collection",
|
||||
assistiveText: "User-facing name for this table",
|
||||
autoFocus: true,
|
||||
gridCols: { xs: 12, sm: 6 },
|
||||
|
||||
@@ -2,12 +2,14 @@ import useSWR from "swr";
|
||||
import _find from "lodash/find";
|
||||
import _sortBy from "lodash/sortBy";
|
||||
import _get from "lodash/get";
|
||||
import _isEmpty from "lodash/isEmpty";
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
import { DialogContentText, Stack, Typography } from "@mui/material";
|
||||
|
||||
import { FormDialog, FormFields } from "@rowy/form-builder";
|
||||
import { tableSettings } from "./form";
|
||||
import TableName from "./TableName";
|
||||
import TableId from "./TableId";
|
||||
import SuggestedRules from "./SuggestedRules";
|
||||
import SteppedAccordion from "@src/components/SteppedAccordion";
|
||||
@@ -25,12 +27,31 @@ import {
|
||||
TABLE_GROUP_SCHEMAS,
|
||||
TABLE_SCHEMAS,
|
||||
} from "@src/config/dbPaths";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
export enum TableSettingsDialogModes {
|
||||
create,
|
||||
update,
|
||||
}
|
||||
export interface ICreateTableDialogProps {
|
||||
const customComponents = {
|
||||
tableName: {
|
||||
component: TableName,
|
||||
defaultValue: "",
|
||||
validation: [["string"]],
|
||||
},
|
||||
tableId: {
|
||||
component: TableId,
|
||||
defaultValue: "",
|
||||
validation: [["string"]],
|
||||
},
|
||||
suggestedRules: {
|
||||
component: SuggestedRules,
|
||||
defaultValue: "",
|
||||
validation: [["string"]],
|
||||
},
|
||||
};
|
||||
|
||||
export interface ITableSettingsProps {
|
||||
mode: TableSettingsDialogModes | null;
|
||||
clearDialog: () => void;
|
||||
data: Table | null;
|
||||
@@ -40,7 +61,7 @@ export default function TableSettings({
|
||||
mode,
|
||||
clearDialog,
|
||||
data,
|
||||
}: ICreateTableDialogProps) {
|
||||
}: ITableSettingsProps) {
|
||||
const { settingsActions, roles, tables, rowyRun } = useProjectContext();
|
||||
const sectionNames = Array.from(
|
||||
new Set((tables ?? []).map((t) => t.section))
|
||||
@@ -49,12 +70,17 @@ export default function TableSettings({
|
||||
const router = useRouter();
|
||||
const { requestConfirmation } = useConfirmation();
|
||||
const snackLogContext = useSnackLogContext();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
|
||||
|
||||
const { data: collections } = useSWR(
|
||||
"firebaseCollections",
|
||||
() => rowyRun?.({ route: runRoutes.listCollections }),
|
||||
{ revalidateIfStale: false, dedupingInterval: 60_000 }
|
||||
{
|
||||
revalidateOnMount: true,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
dedupingInterval: 60_000 * 60,
|
||||
}
|
||||
);
|
||||
|
||||
const open = mode !== null;
|
||||
@@ -64,11 +90,12 @@ export default function TableSettings({
|
||||
const handleSubmit = async (v) => {
|
||||
const { _suggestedRules, ...values } = v;
|
||||
const data = { ...values };
|
||||
|
||||
if (values.schemaSource)
|
||||
data.schemaSource = _find(tables, { id: values.schemaSource });
|
||||
|
||||
const hasExtensions = Boolean(_get(data, "_schema.extensionObjects"));
|
||||
const hasWebhooks = Boolean(_get(data, "_schema.webhooks"));
|
||||
const hasExtensions = !_isEmpty(_get(data, "_schema.extensionObjects"));
|
||||
const hasWebhooks = !_isEmpty(_get(data, "_schema.webhooks"));
|
||||
const deployExtensionsWebhooks = (onComplete?: () => void) => {
|
||||
if (rowyRun && (hasExtensions || hasWebhooks)) {
|
||||
requestConfirmation({
|
||||
@@ -124,6 +151,31 @@ export default function TableSettings({
|
||||
|
||||
if (onComplete) onComplete();
|
||||
},
|
||||
handleCancel: async () => {
|
||||
let _schema: Record<string, any> = {};
|
||||
if (hasExtensions) {
|
||||
_schema.extensionObjects = _get(
|
||||
data,
|
||||
"_schema.extensionObjects"
|
||||
)!.map((x) => ({
|
||||
...x,
|
||||
active: false,
|
||||
}));
|
||||
}
|
||||
if (hasWebhooks) {
|
||||
_schema.webhooks = _get(data, "_schema.webhooks")!.map((x) => ({
|
||||
...x,
|
||||
active: false,
|
||||
}));
|
||||
}
|
||||
|
||||
await settingsActions?.updateTable({
|
||||
id: data.id,
|
||||
tableType: data.tableType,
|
||||
_schema,
|
||||
});
|
||||
if (onComplete) onComplete();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (onComplete) onComplete();
|
||||
@@ -135,7 +187,11 @@ export default function TableSettings({
|
||||
deployExtensionsWebhooks();
|
||||
clearDialog();
|
||||
analytics.logEvent("update_table", { type: values.tableType });
|
||||
enqueueSnackbar("Updated table");
|
||||
} else {
|
||||
const creatingSnackbar = enqueueSnackbar("Creating table…", {
|
||||
persist: true,
|
||||
});
|
||||
await settingsActions?.createTable(data);
|
||||
await analytics.logEvent("create_table", { type: values.tableType });
|
||||
deployExtensionsWebhooks(() => {
|
||||
@@ -149,6 +205,7 @@ export default function TableSettings({
|
||||
router.history.push(values.id);
|
||||
}
|
||||
clearDialog();
|
||||
closeSnackbar(creatingSnackbar);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -168,18 +225,6 @@ export default function TableSettings({
|
||||
),
|
||||
Array.isArray(collections) ? collections.filter((x) => x !== CONFIG) : null
|
||||
);
|
||||
const customComponents = {
|
||||
tableId: {
|
||||
component: TableId,
|
||||
defaultValue: "",
|
||||
validation: [["string"]],
|
||||
},
|
||||
suggestedRules: {
|
||||
component: SuggestedRules,
|
||||
defaultValue: "",
|
||||
validation: [["string"]],
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<FormDialog
|
||||
@@ -203,6 +248,13 @@ export default function TableSettings({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Controller
|
||||
control={formFieldsProps.control}
|
||||
name="_schema"
|
||||
defaultValue={{}}
|
||||
render={() => <></>}
|
||||
/>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
|
||||
@@ -14,11 +14,7 @@ import { useConfirmation } from "@src/components/ConfirmationDialog";
|
||||
import { useActionParams } from "./FormDialog/Context";
|
||||
import { runRoutes } from "@src/constants/runRoutes";
|
||||
|
||||
const replacer = (data: any) => (m: string, key: string) => {
|
||||
const objKey = key.split(":")[0];
|
||||
const defaultValue = key.split(":")[1] || "";
|
||||
return _get(data, objKey, defaultValue);
|
||||
};
|
||||
import { replacer } from "@src/utils/fns";
|
||||
|
||||
const getStateIcon = (actionState, config) => {
|
||||
switch (actionState) {
|
||||
|
||||
@@ -76,7 +76,7 @@ const Settings = ({ config, onChange }) => {
|
||||
|
||||
const scriptExtraLibs = [
|
||||
[
|
||||
"declare class ActionParams {",
|
||||
"declare interface actionParams {",
|
||||
" /**",
|
||||
" * actionParams are provided by dialog popup form",
|
||||
" */",
|
||||
|
||||
2
src/components/fields/Action/action.d.ts
vendored
2
src/components/fields/Action/action.d.ts
vendored
@@ -13,7 +13,7 @@ type ActionContext = {
|
||||
storage: firebasestorage.Storage;
|
||||
db: FirebaseFirestore.Firestore;
|
||||
auth: firebaseauth.BaseAuth;
|
||||
actionParams: ActionParams;
|
||||
actionParams: actionParams;
|
||||
user: ActionUser;
|
||||
};
|
||||
|
||||
|
||||
38
src/components/fields/Code/Settings.tsx
Normal file
38
src/components/fields/Code/Settings.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
|
||||
const languages = [
|
||||
"javascript",
|
||||
"typescript",
|
||||
"json",
|
||||
"html",
|
||||
"css",
|
||||
"scss",
|
||||
"shell",
|
||||
"yaml",
|
||||
"xml",
|
||||
"ruby",
|
||||
"python",
|
||||
"php",
|
||||
"markdown",
|
||||
"rust",
|
||||
"csharp",
|
||||
"cpp",
|
||||
"c",
|
||||
"java",
|
||||
"go",
|
||||
"plaintext",
|
||||
];
|
||||
export default function Settings({ config, onChange }) {
|
||||
return (
|
||||
<MultiSelect
|
||||
searchable
|
||||
multiple={false}
|
||||
options={languages}
|
||||
value={config.language ?? "javascript"}
|
||||
onChange={(value) => {
|
||||
onChange("language")(value);
|
||||
}}
|
||||
label="Language"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -13,7 +13,12 @@ export default function Code({
|
||||
control={control}
|
||||
name={column.key}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<CodeEditor disabled={disabled} value={value} onChange={onChange} />
|
||||
<CodeEditor
|
||||
defaultLanguage={column.config?.language}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,10 @@ import CodeIcon from "@mui/icons-material/Code";
|
||||
import BasicCell from "./BasicCell";
|
||||
import withSideDrawerEditor from "@src/components/Table/editors/withSideDrawerEditor";
|
||||
|
||||
const Settings = lazy(
|
||||
() => import("./Settings" /* webpackChunkName: "Settings-ConnectService" */)
|
||||
);
|
||||
|
||||
const SideDrawerField = lazy(
|
||||
() =>
|
||||
import("./SideDrawerField" /* webpackChunkName: "SideDrawerField-Code" */)
|
||||
@@ -23,5 +27,6 @@ export const config: IFieldConfig = {
|
||||
TableCell: withBasicCell(BasicCell),
|
||||
TableEditor: withSideDrawerEditor(BasicCell),
|
||||
SideDrawerField,
|
||||
settings: Settings,
|
||||
};
|
||||
export default config;
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
import { IPopoverCellProps } from "../types";
|
||||
import { ColorPicker, toColor } from "react-color-palette";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import "react-color-palette/lib/css/styles.css";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Color({ value, onSubmit }: IPopoverCellProps) {
|
||||
const handleChangeComplete = (color: any) => onSubmit(color);
|
||||
|
||||
const [localValue, setLocalValue] = useState(value);
|
||||
const [handleChangeComplete] = useDebouncedCallback((color) => {
|
||||
onSubmit(color);
|
||||
}, 400);
|
||||
useEffect(() => {
|
||||
handleChangeComplete(localValue);
|
||||
}, [localValue]);
|
||||
return (
|
||||
<ColorPicker
|
||||
width={240}
|
||||
height={180}
|
||||
color={value?.hex ? value : toColor("hex", "#fff")}
|
||||
onChange={handleChangeComplete}
|
||||
color={localValue?.hex ? localValue : toColor("hex", "#fff")}
|
||||
onChange={setLocalValue}
|
||||
alpha
|
||||
/>
|
||||
);
|
||||
|
||||
57
src/components/fields/Connector/InlineCell.tsx
Normal file
57
src/components/fields/Connector/InlineCell.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { forwardRef } from "react";
|
||||
import { IPopoverInlineCellProps } from "../types";
|
||||
|
||||
import { ButtonBase, Grid, Chip } from "@mui/material";
|
||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
||||
|
||||
import ChipList from "@src/components/Table/formatters/ChipList";
|
||||
import { get } from "lodash";
|
||||
import { getLabel } from "./utils";
|
||||
|
||||
export const ConnectService = forwardRef(function ConnectService(
|
||||
{ value, showPopoverCell, disabled, column }: IPopoverInlineCellProps,
|
||||
ref: React.Ref<any>
|
||||
) {
|
||||
const config = column.config ?? {};
|
||||
const displayKey = config.titleKey ?? config.primaryKey;
|
||||
return (
|
||||
<ButtonBase
|
||||
onClick={() => showPopoverCell(true)}
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
className="cell-collapse-padding"
|
||||
sx={{
|
||||
height: "100%",
|
||||
font: "inherit",
|
||||
color: "inherit !important",
|
||||
letterSpacing: "inherit",
|
||||
textAlign: "inherit",
|
||||
justifyContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
<ChipList>
|
||||
{Array.isArray(value) &&
|
||||
value.map((item) => (
|
||||
<Grid item key={get(item, config.id)}>
|
||||
<Chip label={getLabel(config, item)} size="small" />
|
||||
</Grid>
|
||||
))}
|
||||
</ChipList>
|
||||
|
||||
{!disabled && (
|
||||
<ArrowDropDownIcon
|
||||
className="row-hover-iconButton"
|
||||
sx={{
|
||||
flexShrink: 0,
|
||||
mr: 0.5,
|
||||
borderRadius: 1,
|
||||
p: (32 - 24) / 2 / 8,
|
||||
boxSizing: "content-box",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ButtonBase>
|
||||
);
|
||||
});
|
||||
|
||||
export default ConnectService;
|
||||
35
src/components/fields/Connector/PopoverCell.tsx
Normal file
35
src/components/fields/Connector/PopoverCell.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { IPopoverCellProps } from "../types";
|
||||
|
||||
import ConnectServiceSelect from "./Select";
|
||||
|
||||
export default function ConnectService({
|
||||
value,
|
||||
onSubmit,
|
||||
column,
|
||||
parentRef,
|
||||
showPopoverCell,
|
||||
disabled,
|
||||
docRef,
|
||||
}: IPopoverCellProps) {
|
||||
return (
|
||||
<ConnectServiceSelect
|
||||
value={value}
|
||||
onChange={onSubmit}
|
||||
column={column}
|
||||
disabled={disabled}
|
||||
docRef={docRef}
|
||||
TextFieldProps={{
|
||||
style: { display: "none" },
|
||||
SelectProps: {
|
||||
open: true,
|
||||
MenuProps: {
|
||||
anchorEl: parentRef,
|
||||
anchorOrigin: { vertical: "bottom", horizontal: "left" },
|
||||
transformOrigin: { vertical: "top", horizontal: "left" },
|
||||
},
|
||||
onClose: () => showPopoverCell(false),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
213
src/components/fields/Connector/Select/PopupContents.tsx
Normal file
213
src/components/fields/Connector/Select/PopupContents.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import _get from "lodash/get";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
List,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography,
|
||||
Radio,
|
||||
} from "@mui/material";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
|
||||
import { IConnectorSelectProps } from ".";
|
||||
import useStyles from "./styles";
|
||||
import Loading from "@src/components/Loading";
|
||||
import { useProjectContext } from "@src/contexts/ProjectContext";
|
||||
import { replacer } from "@src/utils/fns";
|
||||
import { getLabel } from "../utils";
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
export interface IPopupContentsProps
|
||||
extends Omit<IConnectorSelectProps, "className" | "TextFieldProps"> {}
|
||||
|
||||
// TODO: Implement infinite scroll here
|
||||
export default function PopupContents({
|
||||
value = [],
|
||||
onChange,
|
||||
column,
|
||||
docRef,
|
||||
}: IPopupContentsProps) {
|
||||
const { rowyRun, tableState } = useProjectContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
// const url = config.url ;
|
||||
const { config } = column;
|
||||
const elementId = config.elementId;
|
||||
const multiple = Boolean(config.multiple);
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
// Webservice search query
|
||||
const [query, setQuery] = useState("");
|
||||
// Webservice response
|
||||
const [response, setResponse] = useState<any | null>(null);
|
||||
const [hits, setHits] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(response);
|
||||
if (response?.success === false) {
|
||||
enqueueSnackbar(response.message, { variant: "error" });
|
||||
} else if (Array.isArray(response?.hits)) {
|
||||
setHits(response.hits);
|
||||
} else {
|
||||
setHits([]);
|
||||
//enqueueSnackbar("response is not any array", { variant: "error" });
|
||||
}
|
||||
}, [response]);
|
||||
const [search] = useDebouncedCallback(
|
||||
async (query: string) => {
|
||||
const resp = await rowyRun!({
|
||||
route: { method: "POST", path: "/connector" },
|
||||
body: {
|
||||
columnKey: column.key,
|
||||
query: query,
|
||||
schemaDocPath: tableState?.config.tableConfig.path,
|
||||
rowDocPath: docRef.path,
|
||||
},
|
||||
});
|
||||
setResponse(resp);
|
||||
},
|
||||
1000,
|
||||
{ leading: true }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
search(query);
|
||||
}, [query]);
|
||||
|
||||
if (!response) return <Loading />;
|
||||
|
||||
const select = (hit: any) => () => {
|
||||
if (multiple) onChange([...value, hit]);
|
||||
else onChange([hit]);
|
||||
};
|
||||
const deselect = (hit: any) => () => {
|
||||
if (multiple)
|
||||
onChange(value.filter((v) => v[elementId] !== hit[elementId]));
|
||||
else onChange([]);
|
||||
};
|
||||
|
||||
const selectedValues = value?.map((item) => _get(item, elementId));
|
||||
|
||||
const clearSelection = () => onChange([]);
|
||||
|
||||
return (
|
||||
<Grid container direction="column" className={classes.grid}>
|
||||
<Grid item className={classes.searchRow}>
|
||||
<TextField
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
fullWidth
|
||||
variant="filled"
|
||||
margin="dense"
|
||||
label="Search items"
|
||||
className={classes.noMargins}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs className={classes.listRow}>
|
||||
<List className={classes.list}>
|
||||
{hits.map((hit) => {
|
||||
const isSelected = selectedValues.some((v) => v === hit[elementId]);
|
||||
console.log({
|
||||
isSelected,
|
||||
selectedValues,
|
||||
elementId,
|
||||
});
|
||||
return (
|
||||
<React.Fragment key={_get(hit, elementId)}>
|
||||
<MenuItem
|
||||
dense
|
||||
onClick={isSelected ? deselect(hit) : select(hit)}
|
||||
disabled={
|
||||
!isSelected && multiple && value.length >= config.max
|
||||
}
|
||||
>
|
||||
<ListItemIcon className={classes.checkboxContainer}>
|
||||
{multiple ? (
|
||||
<Checkbox
|
||||
edge="start"
|
||||
checked={isSelected}
|
||||
tabIndex={-1}
|
||||
color="secondary"
|
||||
className={classes.checkbox}
|
||||
disableRipple
|
||||
inputProps={{
|
||||
"aria-labelledby": `label-${_get(hit, elementId)}`,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Radio
|
||||
edge="start"
|
||||
checked={isSelected}
|
||||
tabIndex={-1}
|
||||
color="secondary"
|
||||
className={classes.checkbox}
|
||||
disableRipple
|
||||
inputProps={{
|
||||
"aria-labelledby": `label-${_get(hit, elementId)}`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
id={`label-${_get(hit, elementId)}`}
|
||||
primary={getLabel(config, hit)}
|
||||
/>
|
||||
</MenuItem>
|
||||
<Divider className={classes.divider} />
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</Grid>
|
||||
|
||||
{multiple && (
|
||||
<Grid item className={clsx(classes.footerRow, classes.selectedRow)}>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography
|
||||
variant="button"
|
||||
color="textSecondary"
|
||||
className={classes.selectedNum}
|
||||
>
|
||||
{value?.length} of {hits?.length}
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
disabled={!value || value.length === 0}
|
||||
onClick={clearSelection}
|
||||
color="primary"
|
||||
className={classes.selectAllButton}
|
||||
>
|
||||
Clear selection
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
72
src/components/fields/Connector/Select/index.tsx
Normal file
72
src/components/fields/Connector/Select/index.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Suspense } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { TextField, TextFieldProps } from "@mui/material";
|
||||
|
||||
import useStyles from "./styles";
|
||||
import Loading from "@src/components/Loading";
|
||||
import ErrorBoundary from "@src/components/ErrorBoundary";
|
||||
import PopupContents from "./PopupContents";
|
||||
|
||||
export type ServiceValue = {
|
||||
value: string;
|
||||
[prop: string]: any;
|
||||
};
|
||||
|
||||
export interface IConnectorSelectProps {
|
||||
value: ServiceValue[];
|
||||
onChange: (value: ServiceValue[]) => void;
|
||||
column: any;
|
||||
editable?: boolean;
|
||||
/** Optional style overrides for root MUI `TextField` component */
|
||||
className?: string;
|
||||
/** Override any props of the root MUI `TextField` component */
|
||||
TextFieldProps?: Partial<TextFieldProps>;
|
||||
docRef: firebase.default.firestore.DocumentReference;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function ConnectorSelect({
|
||||
value = [],
|
||||
className,
|
||||
TextFieldProps = {},
|
||||
disabled,
|
||||
...props
|
||||
}: IConnectorSelectProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const sanitisedValue = Array.isArray(value) ? value : [];
|
||||
return (
|
||||
<TextField
|
||||
label=""
|
||||
hiddenLabel
|
||||
variant={"filled" as any}
|
||||
select
|
||||
value={sanitisedValue}
|
||||
className={clsx(classes.root, className)}
|
||||
{...TextFieldProps}
|
||||
SelectProps={{
|
||||
renderValue: (value) => `${(value as any[]).length} selected`,
|
||||
displayEmpty: true,
|
||||
classes: { select: classes.selectRoot },
|
||||
...TextFieldProps.SelectProps,
|
||||
// Must have this set to prevent MUI transforming `value`
|
||||
// prop for this component to a comma-separated string
|
||||
MenuProps: {
|
||||
classes: { paper: classes.paper, list: classes.menuChild },
|
||||
MenuListProps: { disablePadding: true },
|
||||
anchorOrigin: { vertical: "bottom", horizontal: "center" },
|
||||
transformOrigin: { vertical: "top", horizontal: "center" },
|
||||
...TextFieldProps.SelectProps?.MenuProps,
|
||||
},
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<ErrorBoundary>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<PopupContents value={sanitisedValue} {...props} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</TextField>
|
||||
);
|
||||
}
|
||||
83
src/components/fields/Connector/Select/styles.ts
Normal file
83
src/components/fields/Connector/Select/styles.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { makeStyles, createStyles } from "@mui/styles";
|
||||
|
||||
export const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
root: { minWidth: 200 },
|
||||
selectRoot: { paddingRight: theme.spacing(4) },
|
||||
|
||||
paper: { overflow: "hidden", maxHeight: "calc(100% - 48px)" },
|
||||
menuChild: {
|
||||
padding: `0 ${theme.spacing(2)}`,
|
||||
minWidth: 340,
|
||||
// Need to set fixed height here so popup is positioned correctly
|
||||
height: 340,
|
||||
},
|
||||
|
||||
grid: { outline: 0 },
|
||||
|
||||
noMargins: { margin: 0 },
|
||||
|
||||
searchRow: { marginTop: theme.spacing(2) },
|
||||
|
||||
listRow: {
|
||||
background: `${theme.palette.background.paper} no-repeat`,
|
||||
position: "relative",
|
||||
margin: theme.spacing(0, -2),
|
||||
maxWidth: `calc(100% + ${theme.spacing(4)})`,
|
||||
|
||||
"&::before, &::after": {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 9,
|
||||
|
||||
display: "block",
|
||||
height: 16,
|
||||
|
||||
background: `linear-gradient(to bottom, ${theme.palette.background.paper}, rgba(255, 255, 255, 0))`,
|
||||
},
|
||||
|
||||
"&::after": {
|
||||
top: "auto",
|
||||
bottom: 0,
|
||||
background: `linear-gradient(to top, ${theme.palette.background.paper}, rgba(255, 255, 255, 0))`,
|
||||
},
|
||||
},
|
||||
list: () => {
|
||||
let maxHeightDeductions = 0;
|
||||
maxHeightDeductions -= 64; // search box
|
||||
maxHeightDeductions -= 48; // multiple
|
||||
maxHeightDeductions += 8; // footer padding
|
||||
|
||||
return {
|
||||
padding: theme.spacing(2, 0),
|
||||
overflowY: "auto" as "auto",
|
||||
// height: `calc(340px - ${-maxHeightDeductions}px)`,
|
||||
height: 340 + maxHeightDeductions,
|
||||
};
|
||||
},
|
||||
|
||||
checkboxContainer: { minWidth: theme.spacing(36 / 8) },
|
||||
checkbox: {
|
||||
padding: theme.spacing(6 / 8, 9 / 8),
|
||||
"&:hover": { background: "transparent" },
|
||||
},
|
||||
|
||||
divider: { margin: theme.spacing(0, 2, 0, 6.5) },
|
||||
|
||||
footerRow: { marginBottom: theme.spacing(2) },
|
||||
selectedRow: {
|
||||
"$listRow + &": { marginTop: -theme.spacing(1) },
|
||||
"$footerRow + &": { marginTop: -theme.spacing(2) },
|
||||
|
||||
marginBottom: 0,
|
||||
"& > div": { height: 48 },
|
||||
},
|
||||
selectAllButton: { marginRight: -theme.spacing(1) },
|
||||
selectedNum: { fontFeatureSettings: '"tnum"' },
|
||||
})
|
||||
);
|
||||
|
||||
export default useStyles;
|
||||
164
src/components/fields/Connector/Settings.tsx
Normal file
164
src/components/fields/Connector/Settings.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
import { lazy, Suspense, useState } from "react";
|
||||
import _get from "lodash/get";
|
||||
import stringify from "json-stable-stringify-without-jsonify";
|
||||
|
||||
import {
|
||||
Stepper,
|
||||
Step,
|
||||
StepButton,
|
||||
StepContent,
|
||||
Stack,
|
||||
Grid,
|
||||
Switch,
|
||||
TextField,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormControlLabel,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
Typography,
|
||||
InputLabel,
|
||||
Link,
|
||||
Checkbox,
|
||||
FormHelperText,
|
||||
Fab,
|
||||
} from "@mui/material";
|
||||
|
||||
import SteppedAccordion from "@src/components/SteppedAccordion";
|
||||
import FieldSkeleton from "@src/components/SideDrawer/Form/FieldSkeleton";
|
||||
import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper";
|
||||
import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import connectorDefs from "!!raw-loader!./connector.d.ts";
|
||||
|
||||
import { useProjectContext } from "@src/contexts/ProjectContext";
|
||||
import { WIKI_LINKS } from "@src/constants/externalLinks";
|
||||
import { useAppContext } from "@src/contexts/AppContext";
|
||||
import { baseFunction } from "./utils";
|
||||
|
||||
//import typeDefs from "!!raw-loader!./types.d.ts";
|
||||
const CodeEditor = lazy(
|
||||
() =>
|
||||
import("@src/components/CodeEditor" /* webpackChunkName: "CodeEditor" */)
|
||||
);
|
||||
|
||||
// external service requirement
|
||||
// Web service URL : url
|
||||
// Results key path :resultsKey
|
||||
// Primary Key : primaryKey
|
||||
// Title Key : titleKey
|
||||
|
||||
// rowy managed service
|
||||
// function that takes query & user then returns a list of objects with an array of objects
|
||||
// Primary Key : primaryKey
|
||||
// label Key : labelKey
|
||||
// single select or multiselect
|
||||
const diagnosticsOptions = {
|
||||
noSemanticValidation: false,
|
||||
noSyntaxValidation: false,
|
||||
noSuggestionDiagnostics: true,
|
||||
};
|
||||
|
||||
export default function Settings({ config, onChange }) {
|
||||
const { projectId } = useAppContext();
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Typography variant="subtitle2">Connector Function</Typography>
|
||||
<Suspense fallback={<FieldSkeleton height={200} />}>
|
||||
<CodeEditor
|
||||
minHeight={200}
|
||||
value={config.connectorFn ?? baseFunction}
|
||||
onChange={onChange("connectorFn")}
|
||||
diagnosticsOptions={diagnosticsOptions}
|
||||
extraLibs={[connectorDefs]}
|
||||
/>
|
||||
</Suspense>
|
||||
<CodeEditorHelper
|
||||
docLink={WIKI_LINKS.fieldTypesConnector + "#examples"}
|
||||
additionalVariables={[]}
|
||||
/>
|
||||
</div>
|
||||
<FormControl>
|
||||
{/* <InputLabel variant="filled">Primary Key</InputLabel> */}
|
||||
<TextField
|
||||
id="elementId"
|
||||
label="ID"
|
||||
placeholder="id"
|
||||
name="id"
|
||||
value={config.elementId}
|
||||
fullWidth
|
||||
onChange={(e) => onChange("elementId")(e.target.value)}
|
||||
helperText={
|
||||
<>
|
||||
The key that will be used to uniquely identify the selected
|
||||
option.{" "}
|
||||
<Link
|
||||
href={WIKI_LINKS.fieldTypesConnector + "#api"}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn more
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
{/* <InputLabel variant="filled">Title Key</InputLabel> */}
|
||||
<TextField
|
||||
id="labelFormatter"
|
||||
label="Label Formatter"
|
||||
placeholder="{{id}} - {{name}}"
|
||||
name="labelFormatter"
|
||||
value={config.labelFormatter}
|
||||
fullWidth
|
||||
onChange={(e) => onChange("labelFormatter")(e.target.value)}
|
||||
helperText={
|
||||
<>
|
||||
The field key or template that will be used to display the
|
||||
selected option.{" "}
|
||||
<Link
|
||||
href={WIKI_LINKS.fieldTypesConnector + "#api"}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn more
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Grid container>
|
||||
<InputLabel variant="filled">
|
||||
Allow for multiple item selection
|
||||
</InputLabel>
|
||||
<Switch
|
||||
checked={config.multiple}
|
||||
onChange={(e) => onChange("multiple")(e.target.checked)}
|
||||
/>
|
||||
</Grid>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
{config.multiple && (
|
||||
<>
|
||||
<TextField
|
||||
type="number"
|
||||
id="max"
|
||||
label="Maximum"
|
||||
placeholder="Maximum"
|
||||
name="max"
|
||||
value={config.max}
|
||||
fullWidth
|
||||
onChange={(e) => onChange("max")(e.target.value)}
|
||||
helperText="The maximum number of items that can be selected, or 0 for no limit."
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
}
|
||||
74
src/components/fields/Connector/SideDrawerField.tsx
Normal file
74
src/components/fields/Connector/SideDrawerField.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Controller } from "react-hook-form";
|
||||
import { ISideDrawerFieldProps } from "../types";
|
||||
import { get } from "lodash";
|
||||
|
||||
import { useTheme, Grid, Chip } from "@mui/material";
|
||||
|
||||
import ConnectServiceSelect from "./Select";
|
||||
import { getLabel } from "./utils";
|
||||
|
||||
export default function ConnectService({
|
||||
column,
|
||||
control,
|
||||
disabled,
|
||||
docRef,
|
||||
}: ISideDrawerFieldProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
const config = column.config ?? {};
|
||||
const displayKey = config.titleKey ?? config.primaryKey;
|
||||
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={column.key}
|
||||
render={({ field: { onChange, onBlur, value } }) => {
|
||||
const handleDelete = (id: any) => () => {
|
||||
// if (multiple)
|
||||
onChange(value.filter((v) => get(v, config.elementId) !== id));
|
||||
// else form.setFieldValue(field.name, []);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{!disabled && (
|
||||
<ConnectServiceSelect
|
||||
column={column}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
docRef={docRef}
|
||||
TextFieldProps={{
|
||||
label: "",
|
||||
hiddenLabel: true,
|
||||
fullWidth: true,
|
||||
onBlur,
|
||||
SelectProps: {
|
||||
renderValue: () => `${value?.length ?? 0} selected`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{Array.isArray(value) && (
|
||||
<Grid container spacing={0.5} style={{ marginTop: 2 }}>
|
||||
{value.map((item) => {
|
||||
const key = get(item, config.elementId);
|
||||
console.log(key, item);
|
||||
return (
|
||||
<Grid item key={key}>
|
||||
<Chip
|
||||
component="li"
|
||||
label={getLabel(config, item)}
|
||||
onDelete={disabled ? undefined : handleDelete(key)}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
22
src/components/fields/Connector/connector.d.ts
vendored
Normal file
22
src/components/fields/Connector/connector.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
type ConnectorUser = {
|
||||
timestamp: Date;
|
||||
displayName: string;
|
||||
email: string;
|
||||
uid: string;
|
||||
emailVerified: boolean;
|
||||
photoURL: string;
|
||||
roles: string[];
|
||||
};
|
||||
type ConnectorContext = {
|
||||
row: Row;
|
||||
ref: FirebaseFirestore.DocumentReference;
|
||||
storage: firebasestorage.Storage;
|
||||
db: FirebaseFirestore.Firestore;
|
||||
auth: firebaseauth.BaseAuth;
|
||||
query: string;
|
||||
user: ConnectorUser;
|
||||
};
|
||||
type ConnectorResult = any[];
|
||||
type Connector = (
|
||||
context: ConnectorContext
|
||||
) => Promise<ConnectorResult> | ActionResult;
|
||||
42
src/components/fields/Connector/index.tsx
Normal file
42
src/components/fields/Connector/index.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "@src/components/fields/types";
|
||||
import withPopoverCell from "../_withTableCell/withPopoverCell";
|
||||
import ConnectorIcon from "@mui/icons-material/Cable";
|
||||
import BasicCell from "../_BasicCell/BasicCellNull";
|
||||
import InlineCell from "./InlineCell";
|
||||
import NullEditor from "@src/components/Table/editors/NullEditor";
|
||||
|
||||
const PopoverCell = lazy(
|
||||
() =>
|
||||
import("./PopoverCell" /* webpackChunkName: "PopoverCell-ConnectService" */)
|
||||
);
|
||||
const SideDrawerField = lazy(
|
||||
() =>
|
||||
import(
|
||||
"./SideDrawerField" /* webpackChunkName: "SideDrawerField-ConnectService" */
|
||||
)
|
||||
);
|
||||
const Settings = lazy(
|
||||
() => import("./Settings" /* webpackChunkName: "Settings-ConnectService" */)
|
||||
);
|
||||
|
||||
export const config: IFieldConfig = {
|
||||
type: FieldType.connector,
|
||||
name: "Connector",
|
||||
group: "Connection",
|
||||
dataType: "any",
|
||||
initialValue: "",
|
||||
initializable: true,
|
||||
icon: <ConnectorIcon />,
|
||||
description:
|
||||
"Connects to any table or API to fetch a list of results based on a text query or row data.",
|
||||
TableCell: withPopoverCell(BasicCell, InlineCell, PopoverCell, {
|
||||
anchorOrigin: { horizontal: "left", vertical: "bottom" },
|
||||
transparent: true,
|
||||
}),
|
||||
TableEditor: NullEditor as any,
|
||||
SideDrawerField,
|
||||
requireConfiguration: true,
|
||||
settings: Settings,
|
||||
};
|
||||
export default config;
|
||||
5
src/components/fields/Connector/types.d.ts
vendored
Normal file
5
src/components/fields/Connector/types.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
type ConnectService = (request: {
|
||||
query: string;
|
||||
row: any;
|
||||
user: any;
|
||||
}) => Promise<any[]>;
|
||||
21
src/components/fields/Connector/utils.ts
Normal file
21
src/components/fields/Connector/utils.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { replacer } from "@src/utils/fns";
|
||||
import _get from "lodash/get";
|
||||
export const sanitiseValue = (value: any) => {
|
||||
if (value === undefined || value === null || value === "") return [];
|
||||
else return value as string[];
|
||||
};
|
||||
|
||||
export const baseFunction = `const connectorFn: Connector = async ({query, row, user}) => {
|
||||
// TODO: Implement your service function here
|
||||
return [];
|
||||
};`;
|
||||
|
||||
export const getLabel = (config, row) => {
|
||||
if (!config.labelFormatter) {
|
||||
return `⚠️ needs configuration`;
|
||||
} else if (config.labelFormatter.includes("{{")) {
|
||||
return config.labelFormatter.replace(/\{\{(.*?)\}\}/g, replacer(row));
|
||||
} else {
|
||||
return _get(row, config.labelFormatter);
|
||||
}
|
||||
};
|
||||
@@ -30,6 +30,7 @@ const useStyles = makeStyles((theme) =>
|
||||
root: {
|
||||
padding: theme.spacing(0, 0.5, 0, 1),
|
||||
outline: "none",
|
||||
height: "100%",
|
||||
},
|
||||
dragActive: {
|
||||
backgroundColor: alpha(
|
||||
|
||||
@@ -39,6 +39,7 @@ import UpdatedAt from "./UpdatedAt";
|
||||
import User from "./User";
|
||||
import Id from "./Id";
|
||||
import Status from "./Status";
|
||||
import Connector from "./Connector";
|
||||
import { TableColumn } from "../Table";
|
||||
|
||||
// Export field configs in order for FieldsDropdown
|
||||
@@ -68,6 +69,7 @@ export const FIELDS: IFieldConfig[] = [
|
||||
Image_,
|
||||
File_,
|
||||
// CONNECTION
|
||||
Connector,
|
||||
SubTable,
|
||||
ConnectTable,
|
||||
ConnectService,
|
||||
|
||||
@@ -36,6 +36,7 @@ const WIKI_PATHS = {
|
||||
fieldTypesSupportedFields: "/field-types/supported-fields",
|
||||
fieldTypesDerivative: "/field-types/derivative",
|
||||
fieldTypesConnectTable: "/field-types/connect-table",
|
||||
fieldTypesConnector: "/field-types/connector",
|
||||
fieldTypesConnectService: "/field-types/connect-service",
|
||||
fieldTypesAction: "/field-types/action",
|
||||
fieldTypesAdd: "/field-types/add",
|
||||
|
||||
@@ -26,6 +26,7 @@ export enum FieldType {
|
||||
file = "FILE",
|
||||
// CONNECTION
|
||||
subTable = "SUB_TABLE",
|
||||
connector = "CONNECTOR",
|
||||
connectTable = "DOCUMENT_SELECT",
|
||||
connectService = "SERVICE_SELECT",
|
||||
// CODE
|
||||
|
||||
@@ -81,15 +81,18 @@ export const AppProvider: React.FC = ({ children }) => {
|
||||
};
|
||||
useEffect(() => {
|
||||
auth.onAuthStateChanged((auth) => {
|
||||
setCurrentUser(auth);
|
||||
if (auth)
|
||||
if (auth) {
|
||||
auth.getIdTokenResult(true).then((results) => {
|
||||
setCurrentUser(auth);
|
||||
setAuthToken(results.token);
|
||||
setUserRoles(
|
||||
Array.isArray(results.claims.roles) ? results.claims.roles : []
|
||||
);
|
||||
setUserClaims(results.claims);
|
||||
});
|
||||
} else {
|
||||
setCurrentUser(auth);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -6,9 +6,6 @@ import _find from "lodash/find";
|
||||
import firebase from "firebase/app";
|
||||
import { compare } from "compare-versions";
|
||||
|
||||
import { Button } from "@mui/material";
|
||||
import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
|
||||
|
||||
import useTable, { TableActions, TableState } from "@src/hooks/useTable";
|
||||
import useSettings from "@src/hooks/useSettings";
|
||||
import { useAppContext } from "./AppContext";
|
||||
@@ -123,6 +120,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { tableState, tableActions } = useTable();
|
||||
const [tables, setTables] = useState<IProjectContext["tables"]>();
|
||||
|
||||
const [settings, settingsActions] = useSettings();
|
||||
const table = _find(tables, (table) => table.id === tableState.config.id);
|
||||
|
||||
@@ -166,7 +164,12 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
|
||||
() =>
|
||||
Array.isArray(tables)
|
||||
? Array.from(
|
||||
new Set(tables.reduce((a, c) => [...a, ...c.roles], ["ADMIN"]))
|
||||
new Set(
|
||||
tables.reduce(
|
||||
(a, c) => [...a, ...c.roles],
|
||||
["ADMIN", "EDITOR", "VIEWER"]
|
||||
)
|
||||
)
|
||||
)
|
||||
: [],
|
||||
[tables]
|
||||
@@ -373,8 +376,8 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
|
||||
const { service, ...rest } = args;
|
||||
const authToken = await getAuthToken();
|
||||
const serviceUrl = service
|
||||
? settings.doc.services[service]
|
||||
: settings.doc.rowyRunUrl;
|
||||
? settings.doc?.services[service]
|
||||
: settings.doc?.rowyRunUrl;
|
||||
|
||||
if (serviceUrl) {
|
||||
return rowyRun({
|
||||
|
||||
@@ -310,14 +310,19 @@ const useTableData = () => {
|
||||
|
||||
let ref = db.collection(path).doc();
|
||||
if (typeof id === "string") ref = db.collection(path).doc(id);
|
||||
else if (id?.type === "smaller")
|
||||
ref = db
|
||||
.collection(path)
|
||||
.doc(
|
||||
decrementId(
|
||||
rows.find((r) => !r._rowy_outOfOrder)?.id ?? "zzzzzzzzzzzzzzzzzzzz"
|
||||
)
|
||||
);
|
||||
else if (id?.type === "smaller") {
|
||||
let prevId =
|
||||
rows.find((r) => !r._rowy_outOfOrder)?.id ?? "zzzzzzzzzzzzzzzzzzzz";
|
||||
if (
|
||||
tableState.orderBy?.length !== 0 ||
|
||||
tableState.filters?.length !== 0
|
||||
) {
|
||||
const query = await db.collection(tableState.path).limit(1).get();
|
||||
prevId = query.empty ? "zzzzzzzzzzzzzzzzzzzz" : query.docs[0].id;
|
||||
}
|
||||
ref = db.collection(path).doc(decrementId(prevId));
|
||||
}
|
||||
|
||||
const newId = ref.id;
|
||||
|
||||
const missingRequiredFields = requiredFields
|
||||
|
||||
@@ -78,10 +78,10 @@ export default function HomePage() {
|
||||
data: null,
|
||||
});
|
||||
|
||||
const handleCreateTable = () =>
|
||||
const handleCreateTable = (data?: null | (Table & { tableType: string })) =>
|
||||
setSettingsDialogState({
|
||||
mode: TableSettingsDialogModes.create,
|
||||
data: null,
|
||||
data: data || null,
|
||||
});
|
||||
|
||||
const [settingsDocState] = useDoc(
|
||||
@@ -107,7 +107,7 @@ export default function HomePage() {
|
||||
<Fab
|
||||
color="secondary"
|
||||
aria-label="Create table"
|
||||
onClick={handleCreateTable}
|
||||
onClick={() => handleCreateTable()}
|
||||
sx={{
|
||||
zIndex: "speedDial",
|
||||
position: "fixed",
|
||||
|
||||
@@ -195,3 +195,9 @@ export const isTargetInsideBox = (target, box) => {
|
||||
const boxRect = box.getBoundingClientRect();
|
||||
return targetRect.y < boxRect.y + boxRect.height;
|
||||
};
|
||||
|
||||
export const replacer = (data: any) => (m: string, key: string) => {
|
||||
const objKey = key.split(":")[0];
|
||||
const defaultValue = key.split(":")[1] || "";
|
||||
return _get(data, objKey, defaultValue);
|
||||
};
|
||||
|
||||
100
yarn.lock
100
yarn.lock
@@ -2429,38 +2429,38 @@
|
||||
"@monaco-editor/loader" "^1.2.0"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
"@mui/base@5.0.0-alpha.70":
|
||||
version "5.0.0-alpha.70"
|
||||
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.70.tgz#e280ee3b69d86034f2cff445161747940129d576"
|
||||
integrity sha512-8UZWhz1JYuQnPkAbC37cl4aL1JyNWZ08wDXlp57W7fYQp5xFpBP/7p56AcWg2qG9CNJP0IlFg2Wp4md1v2l4iA==
|
||||
"@mui/base@5.0.0-alpha.72":
|
||||
version "5.0.0-alpha.72"
|
||||
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.72.tgz#551d64402ee5065cf81fd1388a3e7ab8c426fe3e"
|
||||
integrity sha512-WCAooa9eqbsC68LhyKtDBRumH4hV1eRZ0A3SDKFHSwYG9fCOdsFv/H1dIYRJM0rwD45bMnuDiG3Qmx7YsTiptw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.17.2"
|
||||
"@emotion/is-prop-valid" "^1.1.2"
|
||||
"@mui/utils" "^5.4.4"
|
||||
"@popperjs/core" "^2.4.4"
|
||||
"@popperjs/core" "^2.11.3"
|
||||
clsx "^1.1.1"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^17.0.2"
|
||||
|
||||
"@mui/icons-material@^5.4.4":
|
||||
version "5.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.4.4.tgz#0dc7b4e68cbbdfc675f09f0763be1100aad910af"
|
||||
integrity sha512-7zoRpjO8vsd+bPvXq6rtXu0V8Saj70X09dtTQogZmxQKabrYW3g7+Yym7SCRA7IYVF3ysz2AvdQrGD1P/sGepg==
|
||||
"@mui/icons-material@^5.5.1":
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.5.1.tgz#848a57972617411370775980cbc6990588d4aafb"
|
||||
integrity sha512-40f68p5+Yhq3dCn3QYHqQt5RETPyR3AkDw+fma8PtcjqvZ+d+jF84kFmT6NqwA3he7TlwluEtkyAmPzUE4uPdA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.17.2"
|
||||
|
||||
"@mui/lab@^5.0.0-alpha.71":
|
||||
version "5.0.0-alpha.71"
|
||||
resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.71.tgz#6be1e648b63316dd628c0179b418adfd4c0a5c22"
|
||||
integrity sha512-ScGfSsiYa2XZl+TYEgDFoCv1DoXoWNQwyJBbDlapacEw10wGmY6sgMkCjsPhpuabgC5FVOaV5k30OxG7cZKXJQ==
|
||||
"@mui/lab@^5.0.0-alpha.73":
|
||||
version "5.0.0-alpha.73"
|
||||
resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.73.tgz#f1b81682be155d87d492f64202ada8b7fe83be08"
|
||||
integrity sha512-10Uj0Atc7gBTXKX4VV38P6RdqTQrJZxcl3HeEcytIO1S3NAGfc7gZ3Hdpnhtj5U8kcRJZZPH9LtrBbMZzxU/1A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.17.2"
|
||||
"@date-io/date-fns" "^2.13.1"
|
||||
"@date-io/dayjs" "^2.13.1"
|
||||
"@date-io/luxon" "^2.13.1"
|
||||
"@date-io/moment" "^2.13.1"
|
||||
"@mui/base" "5.0.0-alpha.70"
|
||||
"@mui/system" "^5.4.4"
|
||||
"@mui/base" "5.0.0-alpha.72"
|
||||
"@mui/system" "^5.5.1"
|
||||
"@mui/utils" "^5.4.4"
|
||||
clsx "^1.1.1"
|
||||
prop-types "^15.7.2"
|
||||
@@ -2468,19 +2468,19 @@
|
||||
react-transition-group "^4.4.2"
|
||||
rifm "^0.12.1"
|
||||
|
||||
"@mui/material@^5.4.4":
|
||||
version "5.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.4.4.tgz#2652a07085bf107da590007286336640f605055e"
|
||||
integrity sha512-VDJC7GzO1HTFqfMe2zwvaW/sRhABBJXFkKEv5gO3uXx7x9fdwJHQr4udU7NWZCUdOcx9Y0h3BsAILLefYq+WPw==
|
||||
"@mui/material@^5.5.1":
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.5.1.tgz#9ca89a8b32afd59c843a5bc0332b0786cf9bf1d0"
|
||||
integrity sha512-bJSYgymgSZ7btPTNnWFrr2EmGoVQc4A/0WLfP/ESY2dxnhnbFDwt7twiOKmJp3u84YXriEDt5v9EZQLf7A+y0Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.17.2"
|
||||
"@mui/base" "5.0.0-alpha.70"
|
||||
"@mui/system" "^5.4.4"
|
||||
"@mui/types" "^7.1.2"
|
||||
"@mui/base" "5.0.0-alpha.72"
|
||||
"@mui/system" "^5.5.1"
|
||||
"@mui/types" "^7.1.3"
|
||||
"@mui/utils" "^5.4.4"
|
||||
"@types/react-transition-group" "^4.4.4"
|
||||
clsx "^1.1.1"
|
||||
csstype "^3.0.10"
|
||||
csstype "^3.0.11"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^17.0.2"
|
||||
@@ -2504,18 +2504,18 @@
|
||||
"@emotion/cache" "^11.7.1"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
"@mui/styles@^5.4.4":
|
||||
version "5.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@mui/styles/-/styles-5.4.4.tgz#e6a18f3528a494d782fd80e7feb01bd75a3570dd"
|
||||
integrity sha512-w+9VIC1+JiPfF7osomX1j+aX7yyNNw8BnMYo6niK+zbwIxSYX/wcq4Jh7rlt6FSiaKL4Qi1uf7MPlNAhIxXq3g==
|
||||
"@mui/styles@^5.5.1":
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@mui/styles/-/styles-5.5.1.tgz#cfd2b6dbdb4b2cb0e989568bb9cc45f5d7346d2a"
|
||||
integrity sha512-mxwfjwTwPE+r7/U4Nn/QKPzJ2cIqmRuK3xu44Us613D5jqPeB/ftOsAy0OpCYAwpkUxmQIrRQiilQ8zE+f4rBQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.17.2"
|
||||
"@emotion/hash" "^0.8.0"
|
||||
"@mui/private-theming" "^5.4.4"
|
||||
"@mui/types" "^7.1.2"
|
||||
"@mui/types" "^7.1.3"
|
||||
"@mui/utils" "^5.4.4"
|
||||
clsx "^1.1.1"
|
||||
csstype "^3.0.10"
|
||||
csstype "^3.0.11"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
jss "^10.8.2"
|
||||
jss-plugin-camel-case "^10.8.2"
|
||||
@@ -2527,24 +2527,24 @@
|
||||
jss-plugin-vendor-prefixer "^10.8.2"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
"@mui/system@^5.4.4":
|
||||
version "5.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.4.4.tgz#cd5d3d35c75594abd88708e715b5e39a8874ff51"
|
||||
integrity sha512-Zjbztq2o/VRuRRCWjG44juRrPKYLQMqtQpMHmMttGu5BnvK6PAPW3WOY0r1JCAwLhbd8Kug9nyhGQYKETjo+tQ==
|
||||
"@mui/system@^5.5.1":
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.5.1.tgz#1f2b2a8c5542db6176e3b5a8ed12aea602cdeb81"
|
||||
integrity sha512-2hynI4hN8304hOCT8sc4knJviwUUYJ7XK3mXwQ0nagVGOPnWSOad/nYADm7K0vdlCeUXLIbDbe7oNN3Kaiu2kA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.17.2"
|
||||
"@mui/private-theming" "^5.4.4"
|
||||
"@mui/styled-engine" "^5.4.4"
|
||||
"@mui/types" "^7.1.2"
|
||||
"@mui/types" "^7.1.3"
|
||||
"@mui/utils" "^5.4.4"
|
||||
clsx "^1.1.1"
|
||||
csstype "^3.0.10"
|
||||
csstype "^3.0.11"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
"@mui/types@^7.1.2":
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.2.tgz#4f3678ae77a7a3efab73b6e040469cc6df2144ac"
|
||||
integrity sha512-SD7O1nVzqG+ckQpFjDhXPZjRceB8HQFHEvdLLrPhlJy4lLbwEBbxK74Tj4t6Jgk0fTvLJisuwOutrtYe9P/xBQ==
|
||||
"@mui/types@^7.1.3":
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.3.tgz#d7636f3046110bcccc63e6acfd100e2ad9ca712a"
|
||||
integrity sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA==
|
||||
|
||||
"@mui/utils@^5.4.4":
|
||||
version "5.4.4"
|
||||
@@ -2620,10 +2620,10 @@
|
||||
schema-utils "^2.6.5"
|
||||
source-map "^0.7.3"
|
||||
|
||||
"@popperjs/core@^2.4.4":
|
||||
version "2.10.2"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590"
|
||||
integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==
|
||||
"@popperjs/core@^2.11.3":
|
||||
version "2.11.4"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.4.tgz#d8c7b8db9226d2d7664553a0741ad7d0397ee503"
|
||||
integrity sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==
|
||||
|
||||
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||
version "1.1.2"
|
||||
@@ -6252,10 +6252,10 @@ cssstyle@^2.3.0:
|
||||
dependencies:
|
||||
cssom "~0.3.6"
|
||||
|
||||
csstype@^3.0.10:
|
||||
version "3.0.10"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
|
||||
integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==
|
||||
csstype@^3.0.11:
|
||||
version "3.0.11"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33"
|
||||
integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==
|
||||
|
||||
csstype@^3.0.2:
|
||||
version "3.0.9"
|
||||
@@ -11864,9 +11864,9 @@ minimatch@3.0.4, minimatch@^3.0.4:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
minipass-collect@^1.0.2:
|
||||
version "1.0.2"
|
||||
|
||||
Reference in New Issue
Block a user