Merge branch 'develop' into feat/webhooks

* develop: (21 commits)
  generateBiggerId: remove console.log
  fix generateSmallerId algorithm (#567)
  fix merge
  fix inline cell of connect-service
  Fixes issue where the title field name is shown rather than the value
  Update reload screen copy (#530)
  Derivatives: don't list current field in listener field options
  potentially fix side drawer randomly scrolling
  JSON field: improve tree editor dark mode
  JSON: fix array support
  CodeEditor: use text field background color
  added a temp skip button for rules setting step
  JSON editor: add code editor mode (#509)
  CodeEditor: use TextField box-shadows & fix RichTextEditor box-shadows
  CodeEditor: fix onChange not being passed as prop
  consolidate CodeEditor to components/CodeEditor, allow resize, use github themes
  add CircularProgressOptical
  Import CSV: prevent import to ID field
  Import CSV: fix crash
  User Settings: move darker dark mode to theme
  ...
This commit is contained in:
Sidney Alcantara
2021-10-28 19:14:55 +11:00
63 changed files with 2006 additions and 902 deletions

View File

@@ -14,7 +14,7 @@
"@emotion/styled": "^11.3.0",
"@hookform/resolvers": "^2.8.1",
"@mdi/js": "^6.2.95",
"@monaco-editor/react": "^4.1.0",
"@monaco-editor/react": "^4.3.1",
"@mui/icons-material": "^5.0.0",
"@mui/lab": "^5.0.0-alpha.50",
"@mui/material": "^5.0.0",

View File

@@ -0,0 +1,10 @@
import SvgIcon, { SvgIconProps } from "@mui/material/SvgIcon";
import { mdiResizeBottomRight } from "@mdi/js";
export default function ResizeBottomRight(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d={mdiResizeBottomRight} />
</SvgIcon>
);
}

View File

@@ -0,0 +1,19 @@
import { CircularProgress, CircularProgressProps } from "@mui/material";
export default function CircularProgressOptical({
size = 40,
...props
}: CircularProgressProps & { size?: number }) {
const DEFAULT_SIZE = 40;
const DEFAULT_THICKNESS = 3.6;
const linearThickness = (DEFAULT_SIZE / size) * DEFAULT_THICKNESS;
const opticalRatio = 1 - (1 - size / DEFAULT_SIZE) / 2;
return (
<CircularProgress
{...props}
size={size}
thickness={linearThickness * opticalRatio}
/>
);
}

View File

@@ -1,114 +0,0 @@
import React, { useRef, useMemo, useState } from "react";
import clsx from "clsx";
import Editor, { useMonaco } from "@monaco-editor/react";
import { makeStyles, createStyles } from "@mui/styles";
import { useTheme } from "@mui/material";
import { useProjectContext } from "contexts/ProjectContext";
const useStyles = makeStyles((theme) =>
createStyles({
editorWrapper: { position: "relative" },
resizeIcon: {
position: "absolute",
bottom: 0,
right: 0,
color: theme.palette.text.disabled,
},
saveButton: {
marginTop: theme.spacing(1),
},
})
);
export interface ICodeEditorProps {
onChange: (value: string) => void;
value: string;
height?: number;
wrapperProps?: Partial<React.HTMLAttributes<HTMLDivElement>>;
disabled?: boolean;
editorOptions?: any;
}
export default function CodeEditor({
onChange,
value,
height = 400,
wrapperProps,
disabled,
editorOptions,
}: ICodeEditorProps) {
const theme = useTheme();
const [initialEditorValue] = useState(value ?? "");
const { tableState } = useProjectContext();
const classes = useStyles();
const monacoInstance = useMonaco();
const editorRef = useRef<any>();
function handleEditorDidMount(_, editor) {
editorRef.current = editor;
}
const themeTransformer = (theme: string) => {
switch (theme) {
case "dark":
return "vs-dark";
default:
return theme;
}
};
useMemo(async () => {
if (!monacoInstance) {
// useMonaco returns a monaco instance but initialisation is done asynchronously
// dont execute the logic until the instance is initialised
return;
}
try {
monacoInstance.languages.typescript.javascriptDefaults.setDiagnosticsOptions(
{
noSemanticValidation: true,
noSyntaxValidation: false,
}
);
// compiler options
monacoInstance.languages.typescript.javascriptDefaults.setCompilerOptions(
{
target: monacoInstance.languages.typescript.ScriptTarget.ES5,
allowNonTsExtensions: true,
}
);
} catch (error) {
console.error(
"An error occurred during initialization of Monaco: ",
error
);
}
}, [tableState?.columns]);
return (
<div
{...wrapperProps}
className={clsx(classes.editorWrapper, wrapperProps?.className)}
>
<Editor
theme={themeTransformer(theme.palette.mode)}
height={height}
onMount={handleEditorDidMount}
language="javascript"
value={initialEditorValue}
options={{
readOnly: disabled,
fontFamily: theme.typography.fontFamilyMono,
rulers: [80],
minimap: { enabled: false },
...editorOptions,
}}
onChange={onChange as any}
/>
</div>
);
}

View File

@@ -0,0 +1,97 @@
type Trigger = "create" | "update" | "delete";
type Triggers = Trigger[];
// function types that defines extension body and should run
type Condition =
| boolean
| ((data: ExtensionContext) => boolean | Promise<boolean>);
// the argument that the extension body takes in
type ExtensionContext = {
row: Row;
ref: FirebaseFirestore.DocumentReference;
storage: firebasestorage.Storage;
db: FirebaseFirestore.Firestore;
auth: adminauth.BaseAuth;
change: any;
triggerType: Triggers;
fieldTypes: any;
extensionConfig: {
label: string;
type: string;
triggers: Trigger[];
conditions: Condition;
requiredFields: string[];
extensionBody: any;
};
utilFns: any;
};
// extension body definition
type slackEmailBody = {
channels?: string[];
text?: string;
emails: string[];
blocks?: object[];
attachments?: any;
};
type slackChannelBody = {
channels: string[];
text?: string;
emails?: string[];
blocks?: object[];
attachments?: any;
};
type DocSyncBody = (context: ExtensionContext) => Promise<{
fieldsToSync: Fields;
row: Row;
targetPath: string;
}>;
type HistorySnapshotBody = (context: ExtensionContext) => Promise<{
trackedFields: Fields;
}>;
type AlgoliaIndexBody = (context: ExtensionContext) => Promise<{
fieldsToSync: Fields;
index: string;
row: Row;
objectID: string;
}>;
type MeiliIndexBody = (context: ExtensionContext) => Promise<{
fieldsToSync: Fields;
index: string;
row: Row;
objectID: string;
}>;
type BigqueryIndexBody = (context: ExtensionContext) => Promise<{
fieldsToSync: Fields;
index: string;
row: Row;
objectID: string;
}>;
type SlackMessageBody = (
context: ExtensionContext
) => Promise<slackEmailBody | slackChannelBody>;
type SendgridEmailBody = (context: ExtensionContext) => Promise<any>;
type ApiCallBody = (context: ExtensionContext) => Promise<{
body: string;
url: string;
method: string;
callback: any;
}>;
type TwilioMessageBody = (context: ExtensionContext) => Promise<{
body: string;
from: string;
to: string;
}>;
type TaskBody = (context: ExtensionContext) => Promise<any>;

View File

@@ -844,9 +844,9 @@ declare namespace FirebaseFirestore {
* `exists` property will always be true and `data()` will never return
* 'undefined'.
*/
export class QueryDocumentSnapshot<T = DocumentData> extends DocumentSnapshot<
T
> {
export class QueryDocumentSnapshot<
T = DocumentData
> extends DocumentSnapshot<T> {
private constructor();
/**

View File

@@ -0,0 +1,535 @@
{
"inherit": true,
"base": "vs-dark",
"colors": {
"focusBorder": "#1f6feb",
"foreground": "#c9d1d9",
"descriptionForeground": "#8b949e",
"errorForeground": "#f85149",
"textLink.foreground": "#58a6ff",
"textLink.activeForeground": "#58a6ff",
"textBlockQuote.background": "#010409",
"textBlockQuote.border": "#30363d",
"textCodeBlock.background": "#6e768166",
"textPreformat.foreground": "#8b949e",
"textSeparator.foreground": "#21262d",
"button.background": "#238636",
"button.foreground": "#ffffff",
"button.hoverBackground": "#2ea043",
"button.secondaryBackground": "#282e33",
"button.secondaryForeground": "#c9d1d9",
"button.secondaryHoverBackground": "#30363d",
"checkbox.background": "#161b22",
"checkbox.border": "#30363d",
"dropdown.background": "#161b22",
"dropdown.border": "#30363d",
"dropdown.foreground": "#c9d1d9",
"dropdown.listBackground": "#161b22",
"input.background": "#0d1117",
"input.border": "#30363d",
"input.foreground": "#c9d1d9",
"input.placeholderForeground": "#484f58",
"badge.foreground": "#f0f6fc",
"badge.background": "#1f6feb",
"progressBar.background": "#1f6feb",
"titleBar.activeForeground": "#8b949e",
"titleBar.activeBackground": "#0d1117",
"titleBar.inactiveForeground": "#8b949e",
"titleBar.inactiveBackground": "#010409",
"titleBar.border": "#30363d",
"activityBar.foreground": "#c9d1d9",
"activityBar.inactiveForeground": "#8b949e",
"activityBar.background": "#0d1117",
"activityBarBadge.foreground": "#f0f6fc",
"activityBarBadge.background": "#1f6feb",
"activityBar.activeBorder": "#f78166",
"activityBar.border": "#30363d",
"sideBar.foreground": "#c9d1d9",
"sideBar.background": "#010409",
"sideBar.border": "#30363d",
"sideBarTitle.foreground": "#c9d1d9",
"sideBarSectionHeader.foreground": "#c9d1d9",
"sideBarSectionHeader.background": "#010409",
"sideBarSectionHeader.border": "#30363d",
"list.hoverForeground": "#c9d1d9",
"list.inactiveSelectionForeground": "#c9d1d9",
"list.activeSelectionForeground": "#c9d1d9",
"list.hoverBackground": "#6e76811a",
"list.inactiveSelectionBackground": "#6e768166",
"list.activeSelectionBackground": "#6e768166",
"list.focusForeground": "#c9d1d9",
"list.focusBackground": "#388bfd26",
"list.inactiveFocusBackground": "#388bfd26",
"list.highlightForeground": "#58a6ff",
"tree.indentGuidesStroke": "#21262d",
"notificationCenterHeader.foreground": "#8b949e",
"notificationCenterHeader.background": "#161b22",
"notifications.foreground": "#c9d1d9",
"notifications.background": "#161b22",
"notifications.border": "#30363d",
"notificationsErrorIcon.foreground": "#f85149",
"notificationsWarningIcon.foreground": "#d29922",
"notificationsInfoIcon.foreground": "#58a6ff",
"pickerGroup.border": "#30363d",
"pickerGroup.foreground": "#8b949e",
"quickInput.background": "#161b22",
"quickInput.foreground": "#c9d1d9",
"statusBar.foreground": "#8b949e",
"statusBar.background": "#0d1117",
"statusBar.border": "#30363d",
"statusBar.noFolderBackground": "#0d1117",
"statusBar.debuggingBackground": "#da3633",
"statusBar.debuggingForeground": "#f0f6fc",
"statusBarItem.prominentBackground": "#161b22",
"editorGroupHeader.tabsBackground": "#010409",
"editorGroupHeader.tabsBorder": "#30363d",
"editorGroup.border": "#30363d",
"tab.activeForeground": "#c9d1d9",
"tab.inactiveForeground": "#8b949e",
"tab.inactiveBackground": "#010409",
"tab.activeBackground": "#0d1117",
"tab.hoverBackground": "#0d1117",
"tab.unfocusedHoverBackground": "#6e76811a",
"tab.border": "#30363d",
"tab.unfocusedActiveBorderTop": "#30363d",
"tab.activeBorder": "#0d1117",
"tab.unfocusedActiveBorder": "#0d1117",
"tab.activeBorderTop": "#f78166",
"breadcrumb.foreground": "#8b949e",
"breadcrumb.focusForeground": "#c9d1d9",
"breadcrumb.activeSelectionForeground": "#8b949e",
"breadcrumbPicker.background": "#161b22",
"editor.foreground": "#c9d1d9",
"editor.background": "#0d1117",
"editorWidget.background": "#161b22",
"editor.foldBackground": "#6e76811a",
"editor.lineHighlightBackground": "#6e76811a",
"editorLineNumber.foreground": "#8b949e",
"editorLineNumber.activeForeground": "#c9d1d9",
"editorIndentGuide.background": "#21262d",
"editorIndentGuide.activeBackground": "#30363d",
"editorWhitespace.foreground": "#484f58",
"editorCursor.foreground": "#58a6ff",
"editor.findMatchBackground": "#ffd33d44",
"editor.findMatchHighlightBackground": "#ffd33d22",
"editor.linkedEditingBackground": "#3392FF22",
"editor.inactiveSelectionBackground": "#3392FF22",
"editor.selectionBackground": "#3392FF44",
"editor.selectionHighlightBackground": "#17E5E633",
"editor.selectionHighlightBorder": "#17E5E600",
"editor.wordHighlightBackground": "#17E5E600",
"editor.wordHighlightStrongBackground": "#17E5E600",
"editor.wordHighlightBorder": "#17E5E699",
"editor.wordHighlightStrongBorder": "#17E5E666",
"editorBracketMatch.background": "#17E5E650",
"editorBracketMatch.border": "#17E5E600",
"editorGutter.modifiedBackground": "#bb800966",
"editorGutter.addedBackground": "#2ea04366",
"editorGutter.deletedBackground": "#f8514966",
"diffEditor.insertedTextBackground": "#2ea04326",
"diffEditor.removedTextBackground": "#f8514926",
"scrollbar.shadow": "#0008",
"scrollbarSlider.background": "#484F5833",
"scrollbarSlider.hoverBackground": "#484F5844",
"scrollbarSlider.activeBackground": "#484F5888",
"editorOverviewRuler.border": "#010409",
"panel.background": "#010409",
"panel.border": "#30363d",
"panelTitle.activeBorder": "#f78166",
"panelTitle.activeForeground": "#c9d1d9",
"panelTitle.inactiveForeground": "#8b949e",
"panelInput.border": "#30363d",
"terminal.foreground": "#8b949e",
"terminal.ansiBlack": "#484f58",
"terminal.ansiRed": "#ff7b72",
"terminal.ansiGreen": "#3fb950",
"terminal.ansiYellow": "#d29922",
"terminal.ansiBlue": "#58a6ff",
"terminal.ansiMagenta": "#bc8cff",
"terminal.ansiCyan": "#39c5cf",
"terminal.ansiWhite": "#b1bac4",
"terminal.ansiBrightBlack": "#6e7681",
"terminal.ansiBrightRed": "#ffa198",
"terminal.ansiBrightGreen": "#56d364",
"terminal.ansiBrightYellow": "#e3b341",
"terminal.ansiBrightBlue": "#79c0ff",
"terminal.ansiBrightMagenta": "#d2a8ff",
"terminal.ansiBrightCyan": "#56d4dd",
"terminal.ansiBrightWhite": "#f0f6fc",
"gitDecoration.addedResourceForeground": "#3fb950",
"gitDecoration.modifiedResourceForeground": "#d29922",
"gitDecoration.deletedResourceForeground": "#f85149",
"gitDecoration.untrackedResourceForeground": "#3fb950",
"gitDecoration.ignoredResourceForeground": "#484f58",
"gitDecoration.conflictingResourceForeground": "#db6d28",
"gitDecoration.submoduleResourceForeground": "#8b949e",
"debugToolBar.background": "#161b22",
"editor.stackFrameHighlightBackground": "#D2992225",
"editor.focusedStackFrameHighlightBackground": "#3FB95025",
"peekViewEditor.matchHighlightBackground": "#ffd33d33",
"peekViewResult.matchHighlightBackground": "#ffd33d33",
"peekViewEditor.background": "#0d111788",
"peekViewResult.background": "#0d1117",
"settings.headerForeground": "#8b949e",
"settings.modifiedItemIndicator": "#bb800966",
"welcomePage.buttonBackground": "#21262d",
"welcomePage.buttonHoverBackground": "#30363d"
},
"rules": [
{
"foreground": "#8b949e",
"token": "comment"
},
{
"foreground": "#8b949e",
"token": "punctuation.definition.comment"
},
{
"foreground": "#8b949e",
"token": "string.comment"
},
{
"foreground": "#79c0ff",
"token": "constant"
},
{
"foreground": "#79c0ff",
"token": "entity.name.constant"
},
{
"foreground": "#79c0ff",
"token": "variable.other.constant"
},
{
"foreground": "#79c0ff",
"token": "variable.language"
},
{
"foreground": "#79c0ff",
"token": "entity"
},
{
"foreground": "#ffa657",
"token": "entity.name"
},
{
"foreground": "#ffa657",
"token": "meta.export.default"
},
{
"foreground": "#ffa657",
"token": "meta.definition.variable"
},
{
"foreground": "#c9d1d9",
"token": "variable.parameter.function"
},
{
"foreground": "#c9d1d9",
"token": "meta.jsx.children"
},
{
"foreground": "#c9d1d9",
"token": "meta.block"
},
{
"foreground": "#c9d1d9",
"token": "meta.tag.attributes"
},
{
"foreground": "#c9d1d9",
"token": "entity.name.constant"
},
{
"foreground": "#c9d1d9",
"token": "meta.object.member"
},
{
"foreground": "#c9d1d9",
"token": "meta.embedded.expression"
},
{
"foreground": "#d2a8ff",
"token": "entity.name.function"
},
{
"foreground": "#7ee787",
"token": "entity.name.tag"
},
{
"foreground": "#7ee787",
"token": "support.class.component"
},
{
"foreground": "#ff7b72",
"token": "keyword"
},
{
"foreground": "#ff7b72",
"token": "storage"
},
{
"foreground": "#ff7b72",
"token": "storage.type"
},
{
"foreground": "#c9d1d9",
"token": "storage.modifier.package"
},
{
"foreground": "#c9d1d9",
"token": "storage.modifier.import"
},
{
"foreground": "#c9d1d9",
"token": "storage.type.java"
},
{
"foreground": "#a5d6ff",
"token": "string"
},
{
"foreground": "#a5d6ff",
"token": "punctuation.definition.string"
},
{
"foreground": "#a5d6ff",
"token": "string punctuation.section.embedded source"
},
{
"foreground": "#79c0ff",
"token": "support"
},
{
"foreground": "#79c0ff",
"token": "meta.property-name"
},
{
"foreground": "#ffa657",
"token": "variable"
},
{
"foreground": "#c9d1d9",
"token": "variable.other"
},
{
"fontStyle": "italic",
"foreground": "#ffa198",
"token": "invalid.broken"
},
{
"fontStyle": "italic",
"foreground": "#ffa198",
"token": "invalid.deprecated"
},
{
"fontStyle": "italic",
"foreground": "#ffa198",
"token": "invalid.illegal"
},
{
"fontStyle": "italic",
"foreground": "#ffa198",
"token": "invalid.unimplemented"
},
{
"fontStyle": "italic underline",
"background": "#ff7b72",
"foreground": "#0d1117",
"content": "^M",
"token": "carriage-return"
},
{
"foreground": "#ffa198",
"token": "message.error"
},
{
"foreground": "#c9d1d9",
"token": "string source"
},
{
"foreground": "#79c0ff",
"token": "string variable"
},
{
"foreground": "#a5d6ff",
"token": "source.regexp"
},
{
"foreground": "#a5d6ff",
"token": "string.regexp"
},
{
"foreground": "#a5d6ff",
"token": "string.regexp.character-class"
},
{
"foreground": "#a5d6ff",
"token": "string.regexp constant.character.escape"
},
{
"foreground": "#a5d6ff",
"token": "string.regexp source.ruby.embedded"
},
{
"foreground": "#a5d6ff",
"token": "string.regexp string.regexp.arbitrary-repitition"
},
{
"fontStyle": "bold",
"foreground": "#7ee787",
"token": "string.regexp constant.character.escape"
},
{
"foreground": "#79c0ff",
"token": "support.constant"
},
{
"foreground": "#79c0ff",
"token": "support.variable"
},
{
"foreground": "#79c0ff",
"token": "meta.module-reference"
},
{
"foreground": "#ffa657",
"token": "punctuation.definition.list.begin.markdown"
},
{
"fontStyle": "bold",
"foreground": "#79c0ff",
"token": "markup.heading"
},
{
"fontStyle": "bold",
"foreground": "#79c0ff",
"token": "markup.heading entity.name"
},
{
"foreground": "#7ee787",
"token": "markup.quote"
},
{
"fontStyle": "italic",
"foreground": "#c9d1d9",
"token": "markup.italic"
},
{
"fontStyle": "bold",
"foreground": "#c9d1d9",
"token": "markup.bold"
},
{
"foreground": "#79c0ff",
"token": "markup.raw"
},
{
"background": "#490202",
"foreground": "#ffa198",
"token": "markup.deleted"
},
{
"background": "#490202",
"foreground": "#ffa198",
"token": "meta.diff.header.from-file"
},
{
"background": "#490202",
"foreground": "#ffa198",
"token": "punctuation.definition.deleted"
},
{
"background": "#04260f",
"foreground": "#7ee787",
"token": "markup.inserted"
},
{
"background": "#04260f",
"foreground": "#7ee787",
"token": "meta.diff.header.to-file"
},
{
"background": "#04260f",
"foreground": "#7ee787",
"token": "punctuation.definition.inserted"
},
{
"background": "#5a1e02",
"foreground": "#ffa657",
"token": "markup.changed"
},
{
"background": "#5a1e02",
"foreground": "#ffa657",
"token": "punctuation.definition.changed"
},
{
"foreground": "#161b22",
"background": "#79c0ff",
"token": "markup.ignored"
},
{
"foreground": "#161b22",
"background": "#79c0ff",
"token": "markup.untracked"
},
{
"foreground": "#d2a8ff",
"fontStyle": "bold",
"token": "meta.diff.range"
},
{
"foreground": "#79c0ff",
"token": "meta.diff.header"
},
{
"fontStyle": "bold",
"foreground": "#79c0ff",
"token": "meta.separator"
},
{
"foreground": "#79c0ff",
"token": "meta.output"
},
{
"foreground": "#8b949e",
"token": "brackethighlighter.tag"
},
{
"foreground": "#8b949e",
"token": "brackethighlighter.curly"
},
{
"foreground": "#8b949e",
"token": "brackethighlighter.round"
},
{
"foreground": "#8b949e",
"token": "brackethighlighter.square"
},
{
"foreground": "#8b949e",
"token": "brackethighlighter.angle"
},
{
"foreground": "#8b949e",
"token": "brackethighlighter.quote"
},
{
"foreground": "#ffa198",
"token": "brackethighlighter.unmatched"
},
{
"foreground": "#a5d6ff",
"fontStyle": "underline",
"token": "constant.other.reference.link"
},
{
"foreground": "#a5d6ff",
"fontStyle": "underline",
"token": "string.other.link"
}
],
"encodedTokensColors": []
}

View File

@@ -0,0 +1,531 @@
{
"inherit": true,
"base": "vs",
"colors": {
"focusBorder": "#0969da",
"foreground": "#24292f",
"descriptionForeground": "#57606a",
"errorForeground": "#cf222e",
"textLink.foreground": "#0969da",
"textLink.activeForeground": "#0969da",
"textBlockQuote.background": "#f6f8fa",
"textBlockQuote.border": "#d0d7de",
"textCodeBlock.background": "#afb8c133",
"textPreformat.foreground": "#57606a",
"textSeparator.foreground": "#d8dee4",
"button.background": "#2da44e",
"button.foreground": "#ffffff",
"button.hoverBackground": "#2c974b",
"button.secondaryBackground": "#ebecf0",
"button.secondaryForeground": "#24292f",
"button.secondaryHoverBackground": "#f3f4f6",
"checkbox.background": "#f6f8fa",
"checkbox.border": "#d0d7de",
"dropdown.background": "#ffffff",
"dropdown.border": "#d0d7de",
"dropdown.foreground": "#24292f",
"dropdown.listBackground": "#ffffff",
"input.background": "#ffffff",
"input.border": "#d0d7de",
"input.foreground": "#24292f",
"input.placeholderForeground": "#6e7781",
"badge.foreground": "#ffffff",
"badge.background": "#0969da",
"progressBar.background": "#0969da",
"titleBar.activeForeground": "#57606a",
"titleBar.activeBackground": "#ffffff",
"titleBar.inactiveForeground": "#57606a",
"titleBar.inactiveBackground": "#f6f8fa",
"titleBar.border": "#d0d7de",
"activityBar.foreground": "#24292f",
"activityBar.inactiveForeground": "#57606a",
"activityBar.background": "#ffffff",
"activityBarBadge.foreground": "#ffffff",
"activityBarBadge.background": "#0969da",
"activityBar.activeBorder": "#fd8c73",
"activityBar.border": "#d0d7de",
"sideBar.foreground": "#24292f",
"sideBar.background": "#f6f8fa",
"sideBar.border": "#d0d7de",
"sideBarTitle.foreground": "#24292f",
"sideBarSectionHeader.foreground": "#24292f",
"sideBarSectionHeader.background": "#f6f8fa",
"sideBarSectionHeader.border": "#d0d7de",
"list.hoverForeground": "#24292f",
"list.inactiveSelectionForeground": "#24292f",
"list.activeSelectionForeground": "#24292f",
"list.hoverBackground": "#eaeef280",
"list.inactiveSelectionBackground": "#afb8c133",
"list.activeSelectionBackground": "#afb8c133",
"list.focusForeground": "#24292f",
"list.focusBackground": "#ddf4ff",
"list.inactiveFocusBackground": "#ddf4ff",
"list.highlightForeground": "#0969da",
"tree.indentGuidesStroke": "#d8dee4",
"notificationCenterHeader.foreground": "#57606a",
"notificationCenterHeader.background": "#f6f8fa",
"notifications.foreground": "#24292f",
"notifications.background": "#ffffff",
"notifications.border": "#d0d7de",
"notificationsErrorIcon.foreground": "#cf222e",
"notificationsWarningIcon.foreground": "#9a6700",
"notificationsInfoIcon.foreground": "#0969da",
"pickerGroup.border": "#d0d7de",
"pickerGroup.foreground": "#57606a",
"quickInput.background": "#ffffff",
"quickInput.foreground": "#24292f",
"statusBar.foreground": "#57606a",
"statusBar.background": "#ffffff",
"statusBar.border": "#d0d7de",
"statusBar.noFolderBackground": "#ffffff",
"statusBar.debuggingBackground": "#cf222e",
"statusBar.debuggingForeground": "#ffffff",
"statusBarItem.prominentBackground": "#f6f8fa",
"editorGroupHeader.tabsBackground": "#f6f8fa",
"editorGroupHeader.tabsBorder": "#d0d7de",
"editorGroup.border": "#d0d7de",
"tab.activeForeground": "#24292f",
"tab.inactiveForeground": "#57606a",
"tab.inactiveBackground": "#f6f8fa",
"tab.activeBackground": "#ffffff",
"tab.hoverBackground": "#ffffff",
"tab.unfocusedHoverBackground": "#eaeef280",
"tab.border": "#d0d7de",
"tab.unfocusedActiveBorderTop": "#d0d7de",
"tab.activeBorder": "#ffffff",
"tab.unfocusedActiveBorder": "#ffffff",
"tab.activeBorderTop": "#fd8c73",
"breadcrumb.foreground": "#57606a",
"breadcrumb.focusForeground": "#24292f",
"breadcrumb.activeSelectionForeground": "#57606a",
"breadcrumbPicker.background": "#ffffff",
"editor.foreground": "#24292f",
"editor.background": "#ffffff",
"editorWidget.background": "#ffffff",
"editor.foldBackground": "#6e77811a",
"editor.lineHighlightBackground": "#eaeef280",
"editorLineNumber.foreground": "#57606a",
"editorLineNumber.activeForeground": "#24292f",
"editorIndentGuide.background": "#d8dee4",
"editorIndentGuide.activeBackground": "#d0d7de",
"editorWhitespace.foreground": "#6e7781",
"editorCursor.foreground": "#0969da",
"editor.findMatchBackground": "#bf8700",
"editor.findMatchHighlightBackground": "#ffdf5d66",
"editor.linkedEditingBackground": "#0366d611",
"editor.inactiveSelectionBackground": "#0366d611",
"editor.selectionBackground": "#0366d625",
"editor.selectionHighlightBackground": "#34d05840",
"editor.selectionHighlightBorder": "#34d05800",
"editor.wordHighlightBackground": "#34d05800",
"editor.wordHighlightStrongBackground": "#34d05800",
"editor.wordHighlightBorder": "#24943e99",
"editor.wordHighlightStrongBorder": "#24943e50",
"editorBracketMatch.background": "#34d05840",
"editorBracketMatch.border": "#34d05800",
"editorGutter.modifiedBackground": "#d4a72c66",
"editorGutter.addedBackground": "#4ac26b66",
"editorGutter.deletedBackground": "#ff818266",
"diffEditor.insertedTextBackground": "#dafbe1",
"diffEditor.removedTextBackground": "#ffebe9",
"scrollbar.shadow": "#6a737d33",
"scrollbarSlider.background": "#959da533",
"scrollbarSlider.hoverBackground": "#959da544",
"scrollbarSlider.activeBackground": "#959da588",
"editorOverviewRuler.border": "#ffffff",
"panel.background": "#f6f8fa",
"panel.border": "#d0d7de",
"panelTitle.activeBorder": "#fd8c73",
"panelTitle.activeForeground": "#24292f",
"panelTitle.inactiveForeground": "#57606a",
"panelInput.border": "#d0d7de",
"terminal.foreground": "#57606a",
"terminal.ansiBlack": "#24292f",
"terminal.ansiRed": "#cf222e",
"terminal.ansiGreen": "#116329",
"terminal.ansiYellow": "#4d2d00",
"terminal.ansiBlue": "#0969da",
"terminal.ansiMagenta": "#8250df",
"terminal.ansiCyan": "#1b7c83",
"terminal.ansiWhite": "#6e7781",
"terminal.ansiBrightBlack": "#57606a",
"terminal.ansiBrightRed": "#a40e26",
"terminal.ansiBrightGreen": "#1a7f37",
"terminal.ansiBrightYellow": "#633c01",
"terminal.ansiBrightBlue": "#218bff",
"terminal.ansiBrightMagenta": "#a475f9",
"terminal.ansiBrightCyan": "#3192aa",
"terminal.ansiBrightWhite": "#8c959f",
"gitDecoration.addedResourceForeground": "#1a7f37",
"gitDecoration.modifiedResourceForeground": "#9a6700",
"gitDecoration.deletedResourceForeground": "#cf222e",
"gitDecoration.untrackedResourceForeground": "#1a7f37",
"gitDecoration.ignoredResourceForeground": "#6e7781",
"gitDecoration.conflictingResourceForeground": "#bc4c00",
"gitDecoration.submoduleResourceForeground": "#57606a",
"debugToolBar.background": "#ffffff",
"editor.stackFrameHighlightBackground": "#ffd33d33",
"editor.focusedStackFrameHighlightBackground": "#28a74525",
"settings.headerForeground": "#57606a",
"settings.modifiedItemIndicator": "#d4a72c66",
"welcomePage.buttonBackground": "#f6f8fa",
"welcomePage.buttonHoverBackground": "#f3f4f6"
},
"rules": [
{
"foreground": "#6e7781",
"token": "comment"
},
{
"foreground": "#6e7781",
"token": "punctuation.definition.comment"
},
{
"foreground": "#6e7781",
"token": "string.comment"
},
{
"foreground": "#0550ae",
"token": "constant"
},
{
"foreground": "#0550ae",
"token": "entity.name.constant"
},
{
"foreground": "#0550ae",
"token": "variable.other.constant"
},
{
"foreground": "#0550ae",
"token": "variable.language"
},
{
"foreground": "#0550ae",
"token": "entity"
},
{
"foreground": "#953800",
"token": "entity.name"
},
{
"foreground": "#953800",
"token": "meta.export.default"
},
{
"foreground": "#953800",
"token": "meta.definition.variable"
},
{
"foreground": "#24292f",
"token": "variable.parameter.function"
},
{
"foreground": "#24292f",
"token": "meta.jsx.children"
},
{
"foreground": "#24292f",
"token": "meta.block"
},
{
"foreground": "#24292f",
"token": "meta.tag.attributes"
},
{
"foreground": "#24292f",
"token": "entity.name.constant"
},
{
"foreground": "#24292f",
"token": "meta.object.member"
},
{
"foreground": "#24292f",
"token": "meta.embedded.expression"
},
{
"foreground": "#8250df",
"token": "entity.name.function"
},
{
"foreground": "#116329",
"token": "entity.name.tag"
},
{
"foreground": "#116329",
"token": "support.class.component"
},
{
"foreground": "#cf222e",
"token": "keyword"
},
{
"foreground": "#cf222e",
"token": "storage"
},
{
"foreground": "#cf222e",
"token": "storage.type"
},
{
"foreground": "#24292f",
"token": "storage.modifier.package"
},
{
"foreground": "#24292f",
"token": "storage.modifier.import"
},
{
"foreground": "#24292f",
"token": "storage.type.java"
},
{
"foreground": "#0a3069",
"token": "string"
},
{
"foreground": "#0a3069",
"token": "punctuation.definition.string"
},
{
"foreground": "#0a3069",
"token": "string punctuation.section.embedded source"
},
{
"foreground": "#0550ae",
"token": "support"
},
{
"foreground": "#0550ae",
"token": "meta.property-name"
},
{
"foreground": "#953800",
"token": "variable"
},
{
"foreground": "#24292f",
"token": "variable.other"
},
{
"fontStyle": "italic",
"foreground": "#82071e",
"token": "invalid.broken"
},
{
"fontStyle": "italic",
"foreground": "#82071e",
"token": "invalid.deprecated"
},
{
"fontStyle": "italic",
"foreground": "#82071e",
"token": "invalid.illegal"
},
{
"fontStyle": "italic",
"foreground": "#82071e",
"token": "invalid.unimplemented"
},
{
"fontStyle": "italic underline",
"background": "#cf222e",
"foreground": "#f6f8fa",
"content": "^M",
"token": "carriage-return"
},
{
"foreground": "#82071e",
"token": "message.error"
},
{
"foreground": "#24292f",
"token": "string source"
},
{
"foreground": "#0550ae",
"token": "string variable"
},
{
"foreground": "#0a3069",
"token": "source.regexp"
},
{
"foreground": "#0a3069",
"token": "string.regexp"
},
{
"foreground": "#0a3069",
"token": "string.regexp.character-class"
},
{
"foreground": "#0a3069",
"token": "string.regexp constant.character.escape"
},
{
"foreground": "#0a3069",
"token": "string.regexp source.ruby.embedded"
},
{
"foreground": "#0a3069",
"token": "string.regexp string.regexp.arbitrary-repitition"
},
{
"fontStyle": "bold",
"foreground": "#116329",
"token": "string.regexp constant.character.escape"
},
{
"foreground": "#0550ae",
"token": "support.constant"
},
{
"foreground": "#0550ae",
"token": "support.variable"
},
{
"foreground": "#0550ae",
"token": "meta.module-reference"
},
{
"foreground": "#953800",
"token": "punctuation.definition.list.begin.markdown"
},
{
"fontStyle": "bold",
"foreground": "#0550ae",
"token": "markup.heading"
},
{
"fontStyle": "bold",
"foreground": "#0550ae",
"token": "markup.heading entity.name"
},
{
"foreground": "#116329",
"token": "markup.quote"
},
{
"fontStyle": "italic",
"foreground": "#24292f",
"token": "markup.italic"
},
{
"fontStyle": "bold",
"foreground": "#24292f",
"token": "markup.bold"
},
{
"foreground": "#0550ae",
"token": "markup.raw"
},
{
"background": "#FFEBE9",
"foreground": "#82071e",
"token": "markup.deleted"
},
{
"background": "#FFEBE9",
"foreground": "#82071e",
"token": "meta.diff.header.from-file"
},
{
"background": "#FFEBE9",
"foreground": "#82071e",
"token": "punctuation.definition.deleted"
},
{
"background": "#dafbe1",
"foreground": "#116329",
"token": "markup.inserted"
},
{
"background": "#dafbe1",
"foreground": "#116329",
"token": "meta.diff.header.to-file"
},
{
"background": "#dafbe1",
"foreground": "#116329",
"token": "punctuation.definition.inserted"
},
{
"background": "#ffd8b5",
"foreground": "#953800",
"token": "markup.changed"
},
{
"background": "#ffd8b5",
"foreground": "#953800",
"token": "punctuation.definition.changed"
},
{
"foreground": "#eaeef2",
"background": "#0550ae",
"token": "markup.ignored"
},
{
"foreground": "#eaeef2",
"background": "#0550ae",
"token": "markup.untracked"
},
{
"foreground": "#8250df",
"fontStyle": "bold",
"token": "meta.diff.range"
},
{
"foreground": "#0550ae",
"token": "meta.diff.header"
},
{
"fontStyle": "bold",
"foreground": "#0550ae",
"token": "meta.separator"
},
{
"foreground": "#0550ae",
"token": "meta.output"
},
{
"foreground": "#57606a",
"token": "brackethighlighter.tag"
},
{
"foreground": "#57606a",
"token": "brackethighlighter.curly"
},
{
"foreground": "#57606a",
"token": "brackethighlighter.round"
},
{
"foreground": "#57606a",
"token": "brackethighlighter.square"
},
{
"foreground": "#57606a",
"token": "brackethighlighter.angle"
},
{
"foreground": "#57606a",
"token": "brackethighlighter.quote"
},
{
"foreground": "#82071e",
"token": "brackethighlighter.unmatched"
},
{
"foreground": "#0a3069",
"fontStyle": "underline",
"token": "constant.other.reference.link"
},
{
"foreground": "#0a3069",
"fontStyle": "underline",
"token": "string.other.link"
}
],
"encodedTokensColors": []
}

View File

@@ -0,0 +1,276 @@
import React, { useState, useEffect } from "react";
import Editor, { EditorProps, useMonaco } from "@monaco-editor/react";
import type { editor, languages } from "monaco-editor/esm/vs/editor/editor.api";
import githubLightTheme from "./github-light-default.json";
import githubDarkTheme from "./github-dark-default.json";
import { useTheme, Box, BoxProps } from "@mui/material";
import CircularProgressOptical from "components/CircularProgressOptical";
import ResizeBottomRightIcon from "assets/icons/ResizeBottomRight";
import { useProjectContext } from "contexts/ProjectContext";
import { getFieldProp } from "components/fields";
/* eslint-disable import/no-webpack-loader-syntax */
import firestoreDefs from "!!raw-loader!./firestore.d.ts";
import firebaseAuthDefs from "!!raw-loader!./firebaseAuth.d.ts";
import firebaseStorageDefs from "!!raw-loader!./firebaseStorage.d.ts";
import utilsDefs from "!!raw-loader!./utils.d.ts";
import extensionsDefs from "!!raw-loader!./extensions.d.ts";
export interface ICodeEditorProps extends Partial<EditorProps> {
value: string;
minHeight?: number;
disabled?: boolean;
error?: boolean;
containerProps?: Partial<BoxProps>;
extraLibs?: string[];
onValidate?: EditorProps["onValidate"];
onValidStatusUpdate?: (result: {
isValid: boolean;
markers: editor.IMarker[];
}) => void;
diagnosticsOptions?: languages.typescript.DiagnosticsOptions;
onUnmount?: () => void;
}
export default function CodeEditor({
value,
minHeight = 100,
disabled,
error,
containerProps,
extraLibs,
onValidate,
onValidStatusUpdate,
diagnosticsOptions,
onUnmount,
...props
}: ICodeEditorProps) {
const theme = useTheme();
const { tableState } = useProjectContext();
const [initialEditorValue] = useState(value ?? "");
const monaco = useMonaco();
useEffect(() => {
return () => {
onUnmount?.();
};
}, []);
const onValidate_: EditorProps["onValidate"] = (markers) => {
if (onValidStatusUpdate)
onValidStatusUpdate({ isValid: markers.length <= 0, markers });
else if (onValidate) onValidate(markers);
};
useEffect(() => {
if (!monaco) {
// useMonaco returns a monaco instance but initialisation is done asynchronously
// dont execute the logic until the instance is initialised
return;
}
setTimeout(() => {
try {
monaco.editor.defineTheme("github-light", githubLightTheme as any);
monaco.editor.defineTheme("github-dark", githubDarkTheme as any);
monaco.editor.setTheme("github-" + theme.palette.mode);
} catch (error) {
console.error("Could not set Monaco theme: ", error);
}
});
}, [monaco, theme.palette.mode]);
useEffect(() => {
if (!monaco) {
// useMonaco returns a monaco instance but initialisation is done asynchronously
// dont execute the logic until the instance is initialised
return;
}
try {
monaco.editor.defineTheme("github-light", githubLightTheme as any);
monaco.editor.defineTheme("github-dark", githubDarkTheme as any);
monaco.languages.typescript.javascriptDefaults.addExtraLib(firestoreDefs);
monaco.languages.typescript.javascriptDefaults.addExtraLib(
firebaseAuthDefs
);
monaco.languages.typescript.javascriptDefaults.addExtraLib(
firebaseStorageDefs
);
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions(
diagnosticsOptions ?? {
noSemanticValidation: true,
noSyntaxValidation: false,
}
);
// compiler options
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2020,
allowNonTsExtensions: true,
});
if (extraLibs) {
monaco.languages.typescript.javascriptDefaults.addExtraLib(
extraLibs.join("\n"),
"ts:filename/extraLibs.d.ts"
);
}
monaco.languages.typescript.javascriptDefaults.addExtraLib(
utilsDefs,
"ts:filename/utils.d.ts"
);
const rowDefinition =
Object.keys(tableState?.columns!)
.map((columnKey: string) => {
const column = tableState?.columns[columnKey];
return `static ${columnKey}: ${getFieldProp("type", column.type)}`;
})
.join(";\n") + ";";
const availableFields = Object.keys(tableState?.columns!)
.map((columnKey: string) => `"${columnKey}"`)
.join("|\n");
monaco.languages.typescript.javascriptDefaults.addExtraLib(
[
"/**",
" * extensions type configuration",
" */",
"// basic types that are used in all places",
`type Row = {${rowDefinition}};`,
`type Field = ${availableFields} | string | object;`,
`type Fields = Field[];`,
extensionsDefs,
].join("\n"),
"ts:filename/extensions.d.ts"
);
monaco.languages.typescript.javascriptDefaults.addExtraLib(
[
"declare var require: any;",
"declare var Buffer: any;",
"const ref: FirebaseFirestore.DocumentReference;",
"const storage: firebasestorage.Storage;",
"const db: FirebaseFirestore.Firestore;",
"const auth: adminauth.BaseAuth;",
"declare class row {",
" /**",
" * Returns the row fields",
" */",
rowDefinition,
"}",
].join("\n"),
"ts:filename/rowFields.d.ts"
);
} catch (error) {
console.error(
"An error occurred during initialization of Monaco: ",
error
);
}
}, [tableState?.columns, monaco, diagnosticsOptions, extraLibs]);
return (
<Box
sx={{
minWidth: 400,
minHeight,
height: minHeight,
borderRadius: 1,
resize: "vertical",
overflow: "hidden",
position: "relative",
backgroundColor: disabled ? "transparent" : theme.palette.action.input,
"&::after": {
content: '""',
position: "absolute",
top: 0,
left: 0,
bottom: 0,
right: 0,
pointerEvents: "none",
borderRadius: "inherit",
boxShadow: `0 -1px 0 0 ${theme.palette.text.disabled} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
transition: theme.transitions.create("box-shadow", {
duration: theme.transitions.duration.short,
}),
},
"&:hover::after": {
boxShadow: `0 -1px 0 0 ${theme.palette.text.primary} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
},
"&:focus-within::after": {
boxShadow: `0 -2px 0 0 ${theme.palette.primary.main} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
},
...(error
? {
"&::after, &:hover::after, &:focus-within::after": {
boxShadow: `0 -2px 0 0 ${theme.palette.error.main} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
},
}
: {}),
"& .editor": {
// Overwrite user-select: none that causes editor
// to not be focusable in Safari
userSelect: "auto",
height: "100%",
},
"& .monaco-editor, & .monaco-editor .margin, & .monaco-editor-background":
{
backgroundColor: "transparent",
},
...containerProps?.sx,
}}
>
<Editor
defaultLanguage="javascript"
value={initialEditorValue}
onValidate={onValidate_}
loading={<CircularProgressOptical size={20} sx={{ m: 2 }} />}
className="editor"
{...props}
options={{
readOnly: disabled,
fontFamily: theme.typography.fontFamilyMono,
rulers: [80],
minimap: { enabled: false },
lineNumbersMinChars: 4,
lineDecorationsWidth: 0,
automaticLayout: true,
fixedOverflowWidgets: true,
tabSize: 2,
...props.options,
}}
/>
<ResizeBottomRightIcon
aria-label="This code editor is resizable"
color="action"
sx={{
position: "absolute",
bottom: 1,
right: 1,
zIndex: 1,
}}
/>
</Box>
);
}

50
src/components/CodeEditor/utils.d.ts vendored Normal file
View File

@@ -0,0 +1,50 @@
/**
* utility functions
*/
declare namespace utilFns {
/**
* Sends out an email through sendGrid
*/
function sendEmail(msg: {
from: string;
templateId: string;
personalizations: { to: string; dynamic_template_data: any }[];
}): void {}
/**
* Gets the secret defined in Google Cloud Secret
*/
async function getSecret(name: string, v?: string): any {}
/**
* Async version of forEach
*/
async function asyncForEach(array: any[], callback: Function): void {}
/**
* Generate random ID from numbers and English characters including lowercase and uppercase
*/
function generateId(): string {}
/**
* Add an item to an array field
*/
function arrayUnion(val: string): void {}
/**
* Remove an item to an array field
*/
function arrayRemove(val: string): void {}
/**
* Increment a number field
*/
function increment(val: number): void {}
function hasRequiredFields(requiredFields: string[], data: any): boolean {}
function hasAnyRole(
authorizedRoles: string[],
context: functions.https.CallableContext
): boolean {}
}

View File

@@ -1,11 +1,13 @@
import React from "react";
import { Component } from "react";
import EmptyState, { IEmptyStateProps } from "./EmptyState";
import { Button } from "@mui/material";
import ReloadIcon from "@mui/icons-material/Refresh";
import InlineOpenInNewIcon from "components/InlineOpenInNewIcon";
import meta from "../../package.json";
class ErrorBoundary extends React.Component<
import meta from "@root/package.json";
class ErrorBoundary extends Component<
IEmptyStateProps & { render?: (errorMessage: string) => React.ReactNode }
> {
state = { hasError: false, errorMessage: "" };
@@ -25,13 +27,14 @@ class ErrorBoundary extends React.Component<
if (this.state.hasError) {
if (this.props.render) return this.props.render(this.state.errorMessage);
return (
<EmptyState
message="Something went wrong"
description={
<>
<span>{this.state.errorMessage}</span>
{this.state.errorMessage.startsWith("Loading chunk") ? (
if (this.state.errorMessage.startsWith("Loading chunk"))
return (
<EmptyState
Icon={ReloadIcon}
message="New update available"
description={
<>
<span>Reload this page to get the latest update</span>
<Button
variant="outlined"
color="secondary"
@@ -40,19 +43,28 @@ class ErrorBoundary extends React.Component<
>
Reload
</Button>
) : (
<Button
href={
meta.repository.url.replace(".git", "") +
"/issues/new/choose"
}
target="_blank"
rel="noopener noreferrer"
>
Report issue
<InlineOpenInNewIcon />
</Button>
)}
</>
}
fullScreen
/>
);
return (
<EmptyState
message="Something went wrong"
description={
<>
<span>{this.state.errorMessage}</span>
<Button
href={
meta.repository.url.replace(".git", "") + "/issues/new/choose"
}
target="_blank"
rel="noopener noreferrer"
>
Report issue
<InlineOpenInNewIcon />
</Button>
</>
}
fullScreen

View File

@@ -1,12 +1,8 @@
import { use100vh } from "react-div-100vh";
import {
Fade,
Stack,
StackProps,
CircularProgress,
Typography,
} from "@mui/material";
import { Fade, Stack, StackProps, Typography } from "@mui/material";
import CircularProgressOptical from "components/CircularProgressOptical";
interface ILoadingProps extends Partial<StackProps> {
message?: string;
fullScreen?: boolean;
@@ -33,7 +29,7 @@ export default function Loading({
...props.style,
}}
>
<CircularProgress />
<CircularProgressOptical />
<Typography
variant="subtitle1"
component="div"

View File

@@ -10,6 +10,7 @@ import {
ListItem,
ListItemAvatar,
ListItemText,
Typography,
ListItemSecondaryAction,
Divider,
Grow,
@@ -25,8 +26,14 @@ export default function UserMenu(props: IconButtonProps) {
const [open, setOpen] = useState(false);
const [themeSubMenu, setThemeSubMenu] = useState<EventTarget | null>(null);
const { userDoc, theme, themeOverridden, setTheme, setThemeOverridden } =
useAppContext();
const {
userDoc,
theme,
themeOverridden,
setTheme,
setThemeOverridden,
projectId,
} = useAppContext();
const displayName = userDoc?.state?.doc?.user?.displayName;
const avatarUrl = userDoc?.state?.doc?.user?.photoURL;
@@ -95,7 +102,13 @@ export default function UserMenu(props: IconButtonProps) {
</ListItemAvatar>
<ListItemText
primary={displayName}
secondary={email}
secondary={
<>
{email}
<br />
<Typography variant="caption">Project: {projectId}</Typography>
</>
}
primaryTypographyProps={{ variant: "subtitle1" }}
/>
</ListItem>

View File

@@ -44,15 +44,15 @@ const useStyles = makeStyles((theme) =>
border: "none",
backgroundColor: theme.palette.action.input,
boxShadow: `0 0 0 1px ${theme.palette.action.inputOutline} inset,
0 -1px 0 0 ${theme.palette.text.disabled} inset`,
boxShadow: `0 -1px 0 0 ${theme.palette.text.disabled} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
transition: theme.transitions.create("box-shadow", {
duration: theme.transitions.duration.short,
}),
"&:hover": {
boxShadow: `0 0 0 1px ${theme.palette.action.inputOutline} inset,
0 -1px 0 0 ${theme.palette.text.primary} inset`,
boxShadow: `0 -1px 0 0 ${theme.palette.text.primary} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
},
},
@@ -92,8 +92,8 @@ const useStyles = makeStyles((theme) =>
focus: {
"& .tox.tox-tinymce, & .tox.tox-tinymce:hover": {
boxShadow: `0 0 0 1px ${theme.palette.action.inputOutline} inset,
0 -2px 0 0 ${theme.palette.primary.main} inset`,
boxShadow: `0 -2px 0 0 ${theme.palette.primary.main} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
},
},

View File

@@ -29,23 +29,6 @@ export default function Personalization({
return (
<>
<FormControlLabel
control={
<Checkbox
checked={settings.theme?.dark?.palette?.darker}
onChange={(e) => {
updateSettings({
theme: _merge(settings.theme, {
dark: { palette: { darker: e.target.checked } },
}),
});
}}
/>
}
label="Darker dark theme"
sx={{ my: -10 / 8 }}
/>
<FormControlLabel
control={
<Checkbox
@@ -62,7 +45,7 @@ export default function Personalization({
/>
}
label="Customize theme colors"
style={{ marginLeft: -11, marginBottom: -10 }}
style={{ marginLeft: -11, marginBottom: -10, marginTop: -10 }}
/>
<Collapse in={customizedThemeColor} style={{ marginTop: 0 }}>

View File

@@ -1,39 +1,68 @@
import { IUserSettingsChildProps } from "pages/Settings/UserSettings";
import _merge from "lodash/merge";
import {
FormControl,
RadioGroup,
FormControlLabel,
Radio,
Divider,
Checkbox,
} from "@mui/material";
import { useAppContext } from "contexts/AppContext";
export default function Theme() {
export default function Theme({
settings,
updateSettings,
}: IUserSettingsChildProps) {
const { theme, themeOverridden, setTheme, setThemeOverridden } =
useAppContext();
return (
<FormControl component="fieldset" variant="standard" sx={{ my: -10 / 8 }}>
<legend style={{ fontSize: 0 }}>Theme</legend>
<>
<FormControl component="fieldset" variant="standard" sx={{ my: -10 / 8 }}>
<legend style={{ fontSize: 0 }}>Theme</legend>
<RadioGroup
value={themeOverridden ? theme : "system"}
onChange={(e) => {
if (e.target.value === "system") {
setThemeOverridden(false);
} else {
setTheme(e.target.value as typeof theme);
setThemeOverridden(true);
}
}}
>
<FormControlLabel
control={<Radio />}
value="system"
label="Match system theme"
/>
<FormControlLabel control={<Radio />} value="light" label="Light" />
<FormControlLabel control={<Radio />} value="dark" label="Dark" />
</RadioGroup>
</FormControl>
<RadioGroup
value={themeOverridden ? theme : "system"}
onChange={(e) => {
if (e.target.value === "system") {
setThemeOverridden(false);
} else {
setTheme(e.target.value as typeof theme);
setThemeOverridden(true);
}
}}
>
<FormControlLabel
control={<Radio />}
value="system"
label="Match system theme"
/>
<FormControlLabel control={<Radio />} value="light" label="Light" />
<FormControlLabel control={<Radio />} value="dark" label="Dark" />
</RadioGroup>
</FormControl>
<Divider />
<FormControlLabel
control={
<Checkbox
checked={settings.theme?.dark?.palette?.darker}
onChange={(e) => {
updateSettings({
theme: _merge(settings.theme, {
dark: { palette: { darker: e.target.checked } },
}),
});
}}
/>
}
label="Darker dark theme"
style={{ marginLeft: -11, marginBottom: -10, marginTop: 13 }}
/>
</>
);
}

View File

@@ -1,6 +1,7 @@
import { Stack, CircularProgress, Typography } from "@mui/material";
import { Stack, Typography } from "@mui/material";
import CheckIcon from "@mui/icons-material/Check";
import ArrowIcon from "@mui/icons-material/ArrowForward";
import CircularProgressOptical from "components/CircularProgressOptical";
export interface ISetupItemProps {
status: "complete" | "loading" | "incomplete";
@@ -25,12 +26,7 @@ export default function SetupItem({
{status === "complete" ? (
<CheckIcon aria-label="Item complete" color="action" />
) : status === "loading" ? (
<CircularProgress
id="progress"
size={20}
thickness={5}
sx={{ m: 0.25 }}
/>
<CircularProgressOptical id="progress" size={20} sx={{ m: 0.25 }} />
) : (
<ArrowIcon aria-label="Item" color="primary" />
)}

View File

@@ -21,6 +21,7 @@ import { CONFIG } from "config/dbPaths";
import { requiredRules, adminRules, utilFns } from "config/firestoreRules";
import { rowyRun } from "utils/rowyRun";
import { runRoutes } from "constants/runRoutes";
import { useConfirmation } from "components/ConfirmationDialog";
export default function Step4Rules({
rowyRunUrl,
@@ -28,6 +29,7 @@ export default function Step4Rules({
setCompletion,
}: ISetupStepBodyProps) {
const { projectId, getAuthToken } = useAppContext();
const { requestConfirmation } = useConfirmation();
const [hasRules, setHasRules] = useState(completion.rules);
const [adminRule, setAdminRule] = useState(true);
@@ -83,13 +85,11 @@ export default function Step4Rules({
body: { ruleset: newRules },
});
if (!res.success) throw new Error(res.message);
const isSuccessful = await checkRules(rowyRunUrl, authToken);
if (isSuccessful) {
setCompletion((c) => ({ ...c, rules: true }));
setHasRules(true);
}
setRulesStatus("IDLE");
} catch (e: any) {
console.error(e);
@@ -97,6 +97,19 @@ export default function Step4Rules({
}
};
const handleSkip = () => {
requestConfirmation({
title: "Skip rules",
body: "This might prevent you or other users in your project from accessing firestore data on Rowy",
confirm: "Skip",
cancel: "cancel",
handleConfirm: async () => {
setCompletion((c) => ({ ...c, rules: true }));
setHasRules(true);
},
});
};
return (
<>
<Typography variant="inherit">
@@ -201,15 +214,23 @@ export default function Step4Rules({
Please check the generated rules first.
</Typography>
<LoadingButton
variant="contained"
color="primary"
onClick={setRules}
loading={rulesStatus === "LOADING"}
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
Set Firestore Rules
</LoadingButton>
{" "}
<LoadingButton
variant="contained"
color="primary"
onClick={setRules}
loading={rulesStatus === "LOADING"}
>
Set Firestore Rules
</LoadingButton>
<Button onClick={handleSkip}>Skip</Button>
</div>
{rulesStatus !== "LOADING" && typeof rulesStatus === "string" && (
<Typography variant="caption" color="error">
{rulesStatus}
@@ -246,7 +267,6 @@ export const checkRules = async (
sanitizedRules.includes(
utilFns.replace(/\s{2,}/g, " ").replace(/\n/g, " ")
);
return hasRules;
} catch (e: any) {
console.error(e);

View File

@@ -51,8 +51,8 @@ export default function Form({ values }: IFormProps) {
const { control, reset, formState, getValues } = methods;
const { dirtyFields } = formState;
const column = sideDrawerRef?.current?.cell?.column;
useEffect(() => {
const column = sideDrawerRef?.current?.cell?.column;
if (!column) return;
const labelElem = document.getElementById(
@@ -65,7 +65,7 @@ export default function Form({ values }: IFormProps) {
if (labelElem) labelElem.scrollIntoView({ behavior: "smooth" });
if (fieldElem) fieldElem.focus({ preventScroll: true });
}, 200);
}, [sideDrawerRef?.current]);
}, [column]);
return (
<form>

View File

@@ -46,18 +46,10 @@ export default function SideDrawer() {
setCell!((cell) => ({ column: cell!.column, row }));
const idx = tableState?.columns[cell!.column]?.index;
console.log(
"selectCell",
{ rowIdx: cell!.row, idx },
dataGridRef?.current?.selectCell
);
dataGridRef?.current?.selectCell({ rowIdx: row, idx }, false);
};
const [urlDocState, dispatchUrlDoc] = useDoc({});
// useEffect(() => {
// if (urlDocState.doc) setOpen(true);
// }, [urlDocState]);
useEffect(() => {
setOpen(false);
@@ -83,7 +75,6 @@ export default function SideDrawer() {
tableState?.rows[cell.row].ref.path
)}`
);
// console.log(tableState?.tablePath, tableState?.rows[cell.row].id);
if (urlDocState.doc) {
urlDocState.unsubscribe();
dispatchUrlDoc({ path: "", doc: null });

View File

@@ -1,5 +1,7 @@
import { useState, Dispatch, SetStateAction, MutableRefObject } from "react";
import { Stack, CircularProgress } from "@mui/material";
import { Stack } from "@mui/material";
import CircularProgressOptical from "components/CircularProgressOptical";
export interface ISnackbarProgressRef {
setProgress: Dispatch<SetStateAction<number>>;
@@ -31,12 +33,11 @@ export default function SnackbarProgress({
{progress}/{target}
</span>
<CircularProgress
<CircularProgressOptical
value={(progress / target) * 100}
variant="determinate"
size={24}
color="inherit"
thickness={4}
/>
</Stack>
);

View File

@@ -1,35 +1,21 @@
import React from "react";
import { lazy, Suspense, createElement } from "react";
import { useForm } from "react-hook-form";
import { IMenuModalProps } from "..";
import { makeStyles, createStyles } from "@mui/styles";
import Checkbox from "@mui/material/Checkbox";
import FormControlLabel from "@mui/material/FormControlLabel";
import { Typography, TextField, MenuItem, ListItemText } from "@mui/material";
import Subheading from "../Subheading";
import { getFieldProp } from "components/fields";
import CodeEditorHelper from "components/CodeEditorHelper";
import CodeEditor from "components/Table/editors/CodeEditor";
import FieldSkeleton from "components/SideDrawer/Form/FieldSkeleton";
import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper";
import FormAutosave from "./FormAutosave";
import { FieldType } from "constants/fields";
import { WIKI_LINKS } from "constants/externalLinks";
import { name } from "@root/package.json";
const useStyles = makeStyles((theme) =>
createStyles({
typeSelect: { marginBottom: theme.spacing(1) },
typeSelectItem: { whiteSpace: "normal" },
codeEditorContainer: {
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
},
mono: {
fontFamily: theme.typography.fontFamilyMono,
},
})
const CodeEditor = lazy(
() => import("components/CodeEditor" /* webpackChunkName: "CodeEditor" */)
);
export interface IDefaultValueInputProps extends IMenuModalProps {
@@ -43,7 +29,6 @@ export default function DefaultValueInput({
fieldName,
...props
}: IDefaultValueInputProps) {
const classes = useStyles();
const _type =
type !== FieldType.derivative
? type
@@ -64,13 +49,17 @@ export default function DefaultValueInput({
value={config.defaultValue?.type ?? "undefined"}
onChange={(e) => handleChange("defaultValue.type")(e.target.value)}
fullWidth
className={classes.typeSelect}
sx={{ mb: 1 }}
SelectProps={{
MenuProps: {
sx: { "& .MuiListItemText-root": { whiteSpace: "normal" } },
},
}}
>
<MenuItem value="undefined">
<ListItemText
primary="Undefined"
secondary="No default value. The field will not appear in the rows corresponding Firestore document by default."
className={classes.typeSelectItem}
/>
</MenuItem>
<MenuItem value="null">
@@ -78,24 +67,21 @@ export default function DefaultValueInput({
primary="Null"
secondary={
<>
Initialise as <span className={classes.mono}>null</span>.
Initialise as <code>null</code>.
</>
}
className={classes.typeSelectItem}
/>
</MenuItem>
<MenuItem value="static">
<ListItemText
primary="Static"
secondary="Set a specific default value for all cells in this column."
className={classes.typeSelectItem}
/>
</MenuItem>
<MenuItem value="dynamic">
<ListItemText
primary={`Dynamic (Requires ${name} Cloud Functions)`}
secondary={`Write code to set the default value using this tables ${name} Cloud Function. Setup is required.`}
className={classes.typeSelectItem}
/>
</MenuItem>
</TextField>
@@ -135,7 +121,7 @@ export default function DefaultValueInput({
}
/>
{React.createElement(customFieldInput, {
{createElement(customFieldInput, {
column: { type, key: fieldName, config, ...props, ...config },
control,
docRef: {},
@@ -147,18 +133,12 @@ export default function DefaultValueInput({
{config.defaultValue?.type === "dynamic" && (
<>
<CodeEditorHelper docLink={WIKI_LINKS.howToDefaultValues} />
<div className={classes.codeEditorContainer}>
<Suspense fallback={<FieldSkeleton height={100} />}>
<CodeEditor
height={120}
script={config.defaultValue?.script}
handleChange={handleChange("defaultValue.script")}
editorOptions={{
minimap: {
enabled: false,
},
}}
value={config.defaultValue?.script}
onChange={handleChange("defaultValue.script")}
/>
</div>
</Suspense>
</>
)}
</>

View File

@@ -2,7 +2,7 @@ import { useState, Suspense, useMemo, createElement } from "react";
import _set from "lodash/set";
import { IMenuModalProps } from "..";
import { Typography, Divider, Stack } from "@mui/material";
import { Typography, Stack } from "@mui/material";
import Modal from "components/Modal";
import { getFieldProp } from "components/fields";
@@ -79,6 +79,7 @@ export default function FieldSettings(props: IMenuModalProps) {
{createElement(customFieldSettings, {
config: newConfig,
handleChange,
fieldName,
})}
</Stack>
)}

View File

@@ -1,11 +1,15 @@
import { lazy, Suspense } from "react";
import { IExtensionModalStepProps } from "./ExtensionModal";
import useStateRef from "react-usestateref";
import CodeEditor from "components/Table/editors/CodeEditor";
import CodeEditorHelper from "components/CodeEditorHelper";
import FieldSkeleton from "components/SideDrawer/Form/FieldSkeleton";
import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper";
import { WIKI_LINKS } from "constants/externalLinks";
const CodeEditor = lazy(
() => import("components/CodeEditor" /* webpackChunkName: "CodeEditor" */)
);
const additionalVariables = [
{
key: "change",
@@ -38,14 +42,14 @@ export default function Step3Conditions({
return (
<>
<div>
<Suspense fallback={<FieldSkeleton height={200} />}>
<CodeEditor
script={extensionObject.conditions}
height={200}
handleChange={(newValue) => {
value={extensionObject.conditions}
minHeight={200}
onChange={(newValue) => {
setExtensionObject({
...extensionObject,
conditions: newValue,
conditions: newValue || "",
});
}}
onValidStatusUpdate={({ isValid }) => {
@@ -60,7 +64,7 @@ export default function Step3Conditions({
onMount={() => setConditionEditorActive(true)}
onUnmount={() => setConditionEditorActive(false)}
/>
</div>
</Suspense>
<CodeEditorHelper
docLink={WIKI_LINKS.extensions}

View File

@@ -1,12 +1,17 @@
import { lazy, Suspense } from "react";
import { IExtensionModalStepProps } from "./ExtensionModal";
import _upperFirst from "lodash/upperFirst";
import useStateRef from "react-usestateref";
import CodeEditor from "components/Table/editors/CodeEditor";
import CodeEditorHelper from "components/CodeEditorHelper";
import FieldSkeleton from "components/SideDrawer/Form/FieldSkeleton";
import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper";
import { WIKI_LINKS } from "constants/externalLinks";
const CodeEditor = lazy(
() => import("components/CodeEditor" /* webpackChunkName: "CodeEditor" */)
);
const additionalVariables = [
{
key: "change",
@@ -38,14 +43,14 @@ export default function Step4Body({
return (
<>
<div>
<Suspense fallback={<FieldSkeleton height={200} />}>
<CodeEditor
script={extensionObject.extensionBody}
height={400}
handleChange={(newValue) => {
value={extensionObject.extensionBody}
minHeight={400}
onChange={(newValue) => {
setExtensionObject({
...extensionObject,
extensionBody: newValue,
extensionBody: newValue || "",
});
}}
onValidStatusUpdate={({ isValid }) => {
@@ -63,7 +68,7 @@ export default function Step4Body({
onMount={() => setBodyEditorActive(true)}
onUnmount={() => setBodyEditorActive(false)}
/>
</div>
</Suspense>
<CodeEditorHelper
docLink={

View File

@@ -6,7 +6,7 @@ import LoopIcon from "@mui/icons-material/Loop";
import { useProjectContext } from "contexts/ProjectContext";
import { db } from "../../../firebase";
import { isCollectionGroup } from "utils/fns";
import CircularProgress from "@mui/material/CircularProgress";
import CircularProgressOptical from "components/CircularProgressOptical";
import Modal from "components/Modal";
@@ -61,7 +61,7 @@ export default function ReExecute() {
primary: {
children: "Confirm",
onClick: handleConfirm,
startIcon: updating && <CircularProgress size={16} />,
startIcon: updating && <CircularProgressOptical size={16} />,
disabled: updating,
},
secondary: {

View File

@@ -12,7 +12,6 @@ import moment from "moment";
import {
Chip,
Stack,
CircularProgress,
Typography,
Box,
Tabs,
@@ -32,6 +31,7 @@ import CloseIcon from "@mui/icons-material/Close";
import TableHeaderButton from "./TableHeaderButton";
import Ansi from "ansi-to-react";
import EmptyState from "components/EmptyState";
import CircularProgressOptical from "components/CircularProgressOptical";
import PropTypes from "prop-types";
import routes from "constants/routes";
@@ -244,7 +244,7 @@ function LogPanel(props) {
})}
<div ref={liveStreamingRef} id="live-stream-target">
{status === "BUILDING" && (
<CircularProgress
<CircularProgressOptical
className={classes.logPanelProgress}
size={30}
/>
@@ -371,7 +371,7 @@ function SnackLog({ log, onClose, onOpenPanel }) {
})}
<div ref={liveStreamingRef} id="live-stream-target-snack">
{status === "BUILDING" && (
<CircularProgress
<CircularProgressOptical
className={classes.logPanelProgress}
size={30}
/>
@@ -427,10 +427,9 @@ export default function TableLogs() {
<>
<LogsIcon />
{latestStatus === "BUILDING" && (
<CircularProgress
<CircularProgressOptical
className={classes.toolbarStatusIcon}
size={12}
thickness={6}
style={{ padding: 1 }}
/>
)}
@@ -499,7 +498,7 @@ export default function TableLogs() {
style={{ textAlign: "left" }}
>
{logEntry.status === "BUILDING" && (
<CircularProgress size={24} />
<CircularProgressOptical size={24} />
)}
{logEntry.status === "SUCCESS" && <SuccessIcon />}
{logEntry.status === "FAIL" && <FailIcon />}

View File

@@ -1,8 +1,8 @@
import { IWebhookModalStepProps } from "./WebhookModal";
import useStateRef from "react-usestateref";
import CodeEditor from "components/Table/editors/CodeEditor";
import CodeEditorHelper from "components/CodeEditorHelper";
import CodeEditor from "components/CodeEditor";
import CodeEditorHelper from "components/CodeEditor/CodeEditorHelper";
import { WIKI_LINKS } from "constants/externalLinks";
@@ -26,12 +26,12 @@ export default function Step3Conditions({
<>
<div>
<CodeEditor
script={webhookObject.conditions}
height={200}
handleChange={(newValue) => {
value={webhookObject.conditions}
minHeight={200}
onChange={(newValue) => {
setWebhookObject({
...webhookObject,
conditions: newValue,
conditions: newValue || "",
});
}}
onValidStatusUpdate={({ isValid }) => {

View File

@@ -2,8 +2,8 @@ import { IWebhookModalStepProps } from "./WebhookModal";
import _upperFirst from "lodash/upperFirst";
import useStateRef from "react-usestateref";
import CodeEditor from "components/Table/editors/CodeEditor";
import CodeEditorHelper from "components/CodeEditorHelper";
import CodeEditor from "components/CodeEditor";
import CodeEditorHelper from "components/CodeEditor/CodeEditorHelper";
import { WIKI_LINKS } from "constants/externalLinks";
@@ -26,12 +26,12 @@ export default function Step4Body({
<>
<div>
<CodeEditor
script={webhookObject.parser}
height={400}
handleChange={(newValue) => {
value={webhookObject.parser}
minHeight={400}
onChange={(newValue) => {
setWebhookObject({
...webhookObject,
parser: newValue,
parser: newValue || "",
});
}}
onValidStatusUpdate={({ isValid }) => {

View File

@@ -90,7 +90,7 @@ export default function WebhookList({
onClick={handleAddButton}
ref={addButtonRef}
>
Add Webhook
Add webhook
</Button>
<Menu
anchorEl={anchorEl}

View File

@@ -48,7 +48,7 @@ export default function WebhookModal({
disableBackdropClick
disableEscapeKeyDown
fullWidth
title={`Webhook Logs: ${webhookObject.name}`}
title={`Webhook logs: ${webhookObject.name}`}
sx={{
"& .MuiPaper-root": {
maxWidth: 742 + 20,

View File

@@ -82,7 +82,7 @@ export default function WebhookModal({
disableBackdropClick
disableEscapeKeyDown
fullWidth
title={`${mode === "add" ? "Add" : "Update"} Webhook: ${
title={`${mode === "add" ? "Add" : "Update"} webhook: ${
webhookNames[webhookObject.type]
}`}
sx={{

View File

@@ -25,9 +25,9 @@ export default function Webhooks() {
const { requestConfirmation } = useConfirmation();
const { enqueueSnackbar } = useSnackbar();
const currentwebhooks = (tableState?.config.webhooks ?? []) as IWebhook[];
const currentWebhooks = (tableState?.config.webhooks ?? []) as IWebhook[];
const [localWebhooksObjects, setLocalWebhooksObjects] =
useState(currentwebhooks);
useState(currentWebhooks);
const [openWebhookList, setOpenWebhookList] = useState(false);
const [webhookModal, setWebhookModal] = useState<{
mode: "add" | "update";
@@ -36,7 +36,7 @@ export default function Webhooks() {
} | null>(null);
const [webhookLogs, setWebhookLogs] = useState<IWebhook | null>();
const edited = !_isEqual(currentwebhooks, localWebhooksObjects);
const edited = !_isEqual(currentWebhooks, localWebhooksObjects);
const tablePathTokens =
tableState?.tablePath?.split("/").filter(function (_, i) {
@@ -55,7 +55,7 @@ export default function Webhooks() {
body: "You will lose changes you have made to webhooks",
confirm: "Discard",
handleConfirm: () => {
setLocalWebhooksObjects(currentwebhooks);
setLocalWebhooksObjects(currentWebhooks);
setOpenWebhookList(false);
},
});
@@ -171,7 +171,7 @@ export default function Webhooks() {
return (
<>
<TableHeaderButton
title="Webhook"
title="Webhooks"
onClick={handleOpen}
icon={<WebhookIcon />}
/>

View File

@@ -100,7 +100,6 @@ export default function TableHeader() {
<>
{/* Spacer */} <div />
<Webhooks />
{/* Spacer */} <div />
<Extensions />
<TableLogs />
{(hasDerivatives || hasExtensions) && <ReExecute />}

View File

@@ -1,398 +0,0 @@
import { useRef, useMemo, useState } from "react";
import { makeStyles, createStyles } from "@mui/styles";
import { useTheme } from "@mui/material/styles";
import Editor, { useMonaco } from "@monaco-editor/react";
import { useProjectContext } from "contexts/ProjectContext";
import { FieldType } from "constants/fields";
import { useEffect } from "react";
const useStyles = makeStyles((theme) =>
createStyles({
editorWrapper: {
position: "relative",
minWidth: 400,
minHeight: 100,
height: "calc(100% - 50px)",
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
overflow: "hidden",
},
saveButton: {
marginTop: theme.spacing(1),
},
editor: {
// overwrite user-select: none that causes editor not focusable in Safari
userSelect: "auto",
},
})
);
export default function CodeEditor(props: any) {
const {
handleChange,
extraLibs,
height = 400,
script,
onValideStatusUpdate,
diagnosticsOptions,
onUnmount,
onMount,
} = props;
const theme = useTheme();
const monacoInstance = useMonaco();
const [initialEditorValue] = useState(script ?? "");
const { tableState } = useProjectContext();
const classes = useStyles();
const editorRef = useRef<any>();
useEffect(() => {
return () => {
onUnmount?.();
};
}, []);
function handleEditorDidMount(_, editor) {
editorRef.current = editor;
onMount?.();
}
const themeTransformer = (theme: string) => {
switch (theme) {
case "dark":
return "vs-dark";
default:
return theme;
}
};
useMemo(async () => {
if (!monacoInstance) {
// useMonaco returns a monaco instance but initialisation is done asynchronously
// dont execute the logic until the instance is initialised
return;
}
const firestoreDefsFile = await fetch(
`${process.env.PUBLIC_URL}/firestore.d.ts`
);
const firebaseAuthDefsFile = await fetch(
`${process.env.PUBLIC_URL}/auth.d.ts`
);
const firebaseStorageDefsFile = await fetch(
`${process.env.PUBLIC_URL}/storage.d.ts`
);
const firestoreDefs = await firestoreDefsFile.text();
const firebaseStorageDefs = await firebaseStorageDefsFile.text();
const firebaseAuthDefs = (await firebaseAuthDefsFile.text())
?.replace("export", "declare")
?.replace("admin.auth", "adminauth");
try {
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
firestoreDefs
);
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
firebaseAuthDefs
);
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
firebaseStorageDefs
);
monacoInstance.languages.typescript.javascriptDefaults.setDiagnosticsOptions(
diagnosticsOptions ?? {
noSemanticValidation: true,
noSyntaxValidation: false,
}
);
// compiler options
monacoInstance.languages.typescript.javascriptDefaults.setCompilerOptions(
{
target: monacoInstance.languages.typescript.ScriptTarget.ES2020,
allowNonTsExtensions: true,
}
);
if (extraLibs) {
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
extraLibs.join("\n"),
"ts:filename/extraLibs.d.ts"
);
}
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
[
" /**",
" * utility functions",
" */",
`
declare namespace utilFns {
/**
* Sends out an email through sendGrid
*/
function sendEmail(msg: {
from: string;
templateId: string;
personalizations: { to: string; dynamic_template_data: any }[];
}): void {}
/**
* Gets the secret defined in Google Cloud Secret
*/
async function getSecret(name: string, v?: string): any {}
/**
* Async version of forEach
*/
async function asyncForEach(array: any[], callback: Function): void {}
/**
* Generate random ID from numbers and English characters including lowercase and uppercase
*/
function generateId(): string {}
/**
* Add an item to an array field
*/
function arrayUnion(val: string): void {}
/**
* Remove an item to an array field
*/
function arrayRemove(val: string): void {}
/**
* Increment a number field
*/
function increment(val: number): void {}
function hasRequiredFields(requiredFields: string[], data: any): boolean {}
function hasAnyRole(
authorizedRoles: string[],
context: functions.https.CallableContext
): boolean {}
}
`,
].join("\n"),
"ts:filename/utils.d.ts"
);
const rowDefinition = [
...Object.keys(tableState?.columns!).map((columnKey: string) => {
const column = tableState?.columns[columnKey];
switch (column.type) {
case FieldType.shortText:
case FieldType.longText:
case FieldType.email:
case FieldType.phone:
case FieldType.code:
return `${columnKey}:string`;
case FieldType.singleSelect:
const typeString = [
...(column.config?.options?.map((opt) => `"${opt}"`) ?? []),
].join(" | ");
return `${columnKey}:${typeString}`;
case FieldType.multiSelect:
return `${columnKey}:string[]`;
case FieldType.checkbox:
return `${columnKey}:boolean`;
default:
return `${columnKey}:any`;
}
}),
].join(";\n");
const availableFields = Object.keys(tableState?.columns!)
.map((columnKey: string) => `"${columnKey}"`)
.join("|\n");
const extensionsDefinition = `
// basic types that are used in all places
type Row = {${rowDefinition}};
type Field = ${availableFields} | string | object;
type Fields = Field[];
type Trigger = "create" | "update" | "delete";
type Triggers = Trigger[];
// function types that defines extension body and should run
type Condition = boolean | ((data: ExtensionContext) => boolean | Promise<boolean>);
// the argument that the extension body takes in
type ExtensionContext = {
row: Row;
ref:FirebaseFirestore.DocumentReference;
storage:firebasestorage.Storage;
db:FirebaseFirestore.Firestore;
auth:adminauth.BaseAuth;
change: any;
triggerType: Triggers;
fieldTypes: any;
extensionConfig: {
label: string;
type: string;
triggers: Trigger[];
conditions: Condition;
requiredFields: string[];
extensionBody: any;
};
utilFns: any;
}
// extension body definition
type slackEmailBody = {
channels?: string[];
text?: string;
emails: string[];
blocks?: object[];
attachments?: any;
}
type slackChannelBody = {
channels: string[];
text?: string;
emails?: string[];
blocks?: object[];
attachments?: any;
}
type DocSyncBody = (context: ExtensionContext) => Promise<{
fieldsToSync: Fields;
row: Row;
targetPath: string;
}>
type HistorySnapshotBody = (context: ExtensionContext) => Promise<{
trackedFields: Fields;
}>
type AlgoliaIndexBody = (context: ExtensionContext) => Promise<{
fieldsToSync: Fields;
index: string;
row: Row;
objectID: string;
}>
type MeiliIndexBody = (context: ExtensionContext) => Promise<{
fieldsToSync: Fields;
index: string;
row: Row;
objectID: string;
}>
type BigqueryIndexBody = (context: ExtensionContext) => Promise<{
fieldsToSync: Fields;
index: string;
row: Row;
objectID: string;
}>
type SlackMessageBody = (context: ExtensionContext) => Promise<slackEmailBody | slackChannelBody>;
type SendgridEmailBody = (context: ExtensionContext) => Promise<any>;
type ApiCallBody = (context: ExtensionContext) => Promise<{
body: string;
url: string;
method: string;
callback: any;
}>
type TwilioMessageBody = (context: ExtensionContext) => Promise<{
body: string;
from: string;
to: string;
}>
type TaskBody = (context: ExtensionContext) => Promise<any>
`;
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
[
" /**",
" * extensions type configuration",
" */",
extensionsDefinition,
].join("\n"),
"ts:filename/extensions.d.ts"
);
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
[
" declare var require: any;",
" declare var Buffer: any;",
" const ref:FirebaseFirestore.DocumentReference",
" const storage:firebasestorage.Storage",
" const db:FirebaseFirestore.Firestore;",
" const auth:adminauth.BaseAuth;",
"declare class row {",
" /**",
" * Returns the row fields",
" */",
...Object.keys(tableState?.columns!).map((columnKey: string) => {
const column = tableState?.columns[columnKey];
switch (column.type) {
case FieldType.shortText:
case FieldType.longText:
case FieldType.email:
case FieldType.phone:
case FieldType.code:
return `static ${columnKey}:string`;
case FieldType.singleSelect:
const typeString = [
...(column.config?.options?.map((opt) => `"${opt}"`) ?? []),
// "string",
].join(" | ");
return `static ${columnKey}:${typeString}`;
case FieldType.multiSelect:
return `static ${columnKey}:string[]`;
case FieldType.checkbox:
return `static ${columnKey}:boolean`;
default:
return `static ${columnKey}:any`;
}
}),
"}",
].join("\n"),
"ts:filename/rowFields.d.ts"
);
} catch (error) {
console.error(
"An error occurred during initialization of Monaco: ",
error
);
}
}, [tableState?.columns, monacoInstance]);
function handleEditorValidation(markers) {
if (onValideStatusUpdate) {
onValideStatusUpdate({
isValid: markers.length <= 0,
});
}
}
return (
<>
<div className={classes.editorWrapper}>
<Editor
theme={themeTransformer(theme.palette.mode)}
onMount={handleEditorDidMount}
language="javascript"
height={height}
value={initialEditorValue}
onChange={handleChange}
onValidate={handleEditorValidation}
options={{
// readOnly: disabled,
fontFamily: theme.typography.fontFamilyMono,
rulers: [80],
minimap: { enabled: false },
// ...editorOptions,
}}
className={classes.editor}
/>
</div>
</>
);
}

View File

@@ -91,10 +91,12 @@ export default function Step1Columns({
const { tableState } = useProjectContext();
const tableColumns = _sortBy(Object.values(tableState?.columns ?? {}), [
"index",
]).map((column) => ({
label: column.name as string,
value: column.key as string,
}));
])
.filter((column) => column.type !== FieldType.id)
.map((column) => ({
label: column.name as string,
value: column.key as string,
}));
const [selectedFields, setSelectedFields] = useState(
config.pairs.map((pair) => pair.csvKey)

View File

@@ -1,4 +1,5 @@
import _find from "lodash/find";
import { parseJSON } from "date-fns";
import { makeStyles, createStyles } from "@mui/styles";
import { Grid } from "@mui/material";
@@ -8,6 +9,7 @@ import Column from "../Column";
import Cell from "../Cell";
import { useProjectContext } from "contexts/ProjectContext";
import { FieldType } from "constants/fields";
const useStyles = makeStyles((theme) =>
createStyles({
@@ -81,7 +83,11 @@ export default function Step4Preview({ csvData, config }: IStepProps) {
<Cell
key={csvKey + i}
field={columnKey}
value={row[columnKey]}
value={
type === FieldType.date || type === FieldType.dateTime
? parseJSON(row[columnKey])
: row[columnKey]
}
type={type}
name={name}
/>

View File

@@ -2,10 +2,11 @@ import { useState } from "react";
import _get from "lodash/get";
import { useSnackbar } from "notistack";
import { Fab, FabProps, CircularProgress } from "@mui/material";
import { Fab, FabProps } from "@mui/material";
import PlayIcon from "@mui/icons-material/PlayArrow";
import RefreshIcon from "@mui/icons-material/Refresh";
import UndoIcon from "@mui/icons-material/Undo";
import CircularProgressOptical from "components/CircularProgressOptical";
import { useProjectContext } from "contexts/ProjectContext";
import { functions } from "@src/firebase";
@@ -159,7 +160,7 @@ export default function ActionFab({
{...props}
>
{isRunning ? (
<CircularProgress color="secondary" size={16} thickness={5.6} />
<CircularProgressOptical color="secondary" size={16} />
) : (
getStateIcon(actionState)
)}

View File

@@ -11,10 +11,7 @@ import FieldSkeleton from "components/SideDrawer/Form/FieldSkeleton";
import { useProjectContext } from "contexts/ProjectContext";
const CodeEditor = lazy(
() =>
import(
"components/Table/editors/CodeEditor" /* webpackChunkName: "CodeEditor" */
)
() => import("components/CodeEditor" /* webpackChunkName: "CodeEditor" */)
);
const Settings = ({ config, handleChange }) => {
@@ -93,8 +90,8 @@ const Settings = ({ config, handleChange }) => {
<Typography variant="overline">action script</Typography>
<Suspense fallback={<FieldSkeleton height={300} />}>
<CodeEditor
height={300}
script={config.script}
minHeight={300}
value={config.script}
extraLibs={[
[
"declare class ref {",
@@ -121,9 +118,9 @@ const Settings = ({ config, handleChange }) => {
} else return `static ${param.name}:any`;
}),
"}",
],
].join("\n"),
]}
handleChange={handleChange("script")}
onChange={handleChange("script")}
/>
</Suspense>
<FormControlLabel
@@ -175,9 +172,9 @@ const Settings = ({ config, handleChange }) => {
<Typography variant="overline">Undo action script</Typography>
<Suspense fallback={<FieldSkeleton height={300} />}>
<CodeEditor
height={300}
script={config["undo.script"]}
handleChange={handleChange("undo.script")}
minHeight={300}
value={config["undo.script"]}
onChange={handleChange("undo.script")}
/>
</Suspense>
</>

View File

@@ -6,10 +6,7 @@ import { FieldType } from "constants/fields";
import FieldsDropdown from "components/Table/ColumnMenu/FieldsDropdown";
import { useProjectContext } from "contexts/ProjectContext";
const CodeEditor = lazy(
() =>
import(
"components/Table/editors/CodeEditor" /* webpackChunkName: "CodeEditor" */
)
() => import("components/CodeEditor" /* webpackChunkName: "CodeEditor" */)
);
const Settings = ({ config, handleChange }) => {
@@ -29,7 +26,7 @@ const Settings = ({ config, handleChange }) => {
<Typography variant="overline">Aggergate script</Typography>
<Suspense fallback={<FieldSkeleton height={200} />}>
<CodeEditor
script={
value={
config.script ??
`//triggerType: create | update | delete\n//aggregateState: the subtable accumulator stored in the cell of this column\n//snapshot: the triggered document snapshot of the the subcollection\n//incrementor: short for firebase.firestore.FieldValue.increment(n);\n//This script needs to return the new aggregateState cell value.
switch (triggerType){
@@ -45,13 +42,13 @@ switch (triggerType){
}
extraLibs={[
` /**
* increaments firestore field value
* increments firestore field value
*/",
function incrementor(value:number):number {
}`,
]}
handleChange={handleChange("script")}
onChange={handleChange("script")}
/>
</Suspense>

View File

@@ -2,46 +2,18 @@ import { Controller } from "react-hook-form";
import { ISideDrawerFieldProps } from "../types";
import CodeEditor from "components/CodeEditor";
import { makeStyles, createStyles } from "@mui/styles";
const useStyles = makeStyles((theme) =>
createStyles({
wrapper: {
border: "1px solid",
borderColor:
theme.palette.mode === "light"
? "rgba(0, 0, 0, 0.09)"
: "rgba(255, 255, 255, 0.09)",
borderRadius: theme.shape.borderRadius,
overflow: "hidden",
},
})
);
export default function Code({
control,
column,
disabled,
}: ISideDrawerFieldProps) {
const classes = useStyles();
return (
<Controller
control={control}
name={column.key}
render={({ field: { onChange, value } }) => (
<CodeEditor
disabled={disabled}
value={value}
onChange={onChange}
wrapperProps={{ className: classes.wrapper }}
editorOptions={{
minimap: {
enabled: false,
},
}}
/>
<CodeEditor disabled={disabled} value={value} onChange={onChange} />
)}
/>
);

View File

@@ -5,13 +5,14 @@ import { ButtonBase, Grid, Chip } from "@mui/material";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import ChipList from "components/Table/formatters/ChipList";
import { get } from "lodash";
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)}
@@ -29,9 +30,9 @@ export const ConnectService = forwardRef(function ConnectService(
>
<ChipList>
{Array.isArray(value) &&
value.map((doc: any) => (
<Grid item key={doc.primaryKey}>
<Chip label={config.titleKey} size="small" />
value.map((snapshot) => (
<Grid item key={get(snapshot, config.primaryKey)}>
<Chip label={get(snapshot, displayKey)} size="small" />
</Grid>
))}
</ChipList>

View File

@@ -16,8 +16,10 @@ import { useProjectContext } from "@src/contexts/ProjectContext";
import { runRoutes } from "@src/constants/runRoutes";
import { WIKI_LINKS } from "constants/externalLinks";
const useAlgoliaSearchKeys = createPersistedState("_ROWY_algolia-search-keys");
const useAlgoliaAppId = createPersistedState("_ROWY_algolia-app-id");
const useAlgoliaSearchKeys = createPersistedState(
"__ROWY__ALGOLIA_SEARCH_KEYS"
);
const useAlgoliaAppId = createPersistedState("__ROWY__ALGOLIA_APP_ID");
export type ConnectTableValue = {
docPath: string;

View File

@@ -1,4 +1,6 @@
import { IBasicCellProps } from "../types";
import _isFunction from "lodash/isFunction";
import _isDate from "lodash/isDate";
import { format } from "date-fns";
import { DATE_FORMAT } from "constants/dates";
@@ -6,9 +8,12 @@ export default function Date_({
value,
format: formatProp,
}: IBasicCellProps & { format?: string }) {
if (!!value && "toDate" in value) {
if ((!!value && _isFunction(value.toDate)) || _isDate(value)) {
try {
const formatted = format(value.toDate(), formatProp || DATE_FORMAT);
const formatted = format(
_isDate(value) ? value : value.toDate(),
formatProp || DATE_FORMAT
);
return (
<span style={{ fontVariantNumeric: "tabular-nums" }}>{formatted}</span>
);

View File

@@ -1,4 +1,6 @@
import { IBasicCellProps } from "../types";
import _isFunction from "lodash/isFunction";
import _isDate from "lodash/isDate";
import { format } from "date-fns";
import { DATE_TIME_FORMAT } from "constants/dates";
@@ -6,9 +8,12 @@ export default function DateTime({
value,
format: formatProp,
}: IBasicCellProps & { format?: string }) {
if (!!value && "toDate" in value) {
if ((!!value && _isFunction(value.toDate)) || _isDate(value)) {
try {
const formatted = format(value.toDate(), formatProp || DATE_TIME_FORMAT);
const formatted = format(
_isDate(value) ? value : value.toDate(),
formatProp || DATE_TIME_FORMAT
);
return (
<span style={{ fontVariantNumeric: "tabular-nums" }}>{formatted}</span>
);

View File

@@ -1,25 +1,29 @@
import { lazy, Suspense } from "react";
import { ISettingsProps } from "../types";
import { Grid, InputLabel } from "@mui/material";
import MultiSelect from "@rowy/multiselect";
import FieldSkeleton from "components/SideDrawer/Form/FieldSkeleton";
import { FieldType } from "constants/fields";
import FieldsDropdown from "components/Table/ColumnMenu/FieldsDropdown";
import { useProjectContext } from "contexts/ProjectContext";
import CodeEditorHelper from "components/CodeEditorHelper";
import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper";
import { FieldType } from "constants/fields";
import { useProjectContext } from "contexts/ProjectContext";
import { WIKI_LINKS } from "constants/externalLinks";
const CodeEditor = lazy(
() =>
import(
"components/Table/editors/CodeEditor" /* webpackChunkName: "CodeEditor" */
)
() => import("components/CodeEditor" /* webpackChunkName: "CodeEditor" */)
);
const Settings = ({ config, handleChange }) => {
export default function Settings({
config,
handleChange,
fieldName,
}: ISettingsProps) {
const { tableState } = useProjectContext();
if (!tableState?.columns) return <></>;
const columnOptions = Object.values(tableState.columns)
.filter((column) => column.fieldName !== fieldName)
.filter((column) => column.type !== FieldType.subTable)
.map((c) => ({ label: c.name, value: c.key }));
@@ -63,13 +67,9 @@ const Settings = ({ config, handleChange }) => {
<InputLabel>Derivative script</InputLabel>
<CodeEditorHelper docLink={WIKI_LINKS.fieldTypesDerivative} />
<Suspense fallback={<FieldSkeleton height={200} />}>
<CodeEditor
script={config.script}
handleChange={handleChange("script")}
/>
<CodeEditor value={config.script} onChange={handleChange("script")} />
</Suspense>
</div>
</>
);
};
export default Settings;
}

View File

@@ -15,12 +15,12 @@ import {
Grid,
Tooltip,
Chip,
CircularProgress,
} from "@mui/material";
import UploadIcon from "assets/icons/Upload";
import { FileIcon } from ".";
import Confirmation from "components/Confirmation";
import CircularProgressOptical from "components/CircularProgressOptical";
import { DATE_TIME_FORMAT } from "constants/dates";
import { useFieldStyles } from "components/SideDrawer/Form/utils";
@@ -148,9 +148,7 @@ function ControlledFileUploader({
<Chip
icon={<FileIcon />}
label={localFile}
deleteIcon={
<CircularProgress size={20} thickness={4.5} color="inherit" />
}
deleteIcon={<CircularProgressOptical size={20} color="inherit" />}
/>
</Grid>
)}

View File

@@ -6,17 +6,10 @@ import _findIndex from "lodash/findIndex";
import { format } from "date-fns";
import { makeStyles, createStyles } from "@mui/styles";
import {
alpha,
Stack,
Grid,
Tooltip,
Chip,
IconButton,
CircularProgress,
} from "@mui/material";
import { alpha, Stack, Grid, Tooltip, Chip, IconButton } from "@mui/material";
import UploadIcon from "assets/icons/Upload";
import ChipList from "components/Table/formatters/ChipList";
import CircularProgressOptical from "components/CircularProgressOptical";
import { useConfirmation } from "components/ConfirmationDialog";
import useUploader, { FileValue } from "hooks/useTable/useUploader";
@@ -184,11 +177,10 @@ export default function File_({
)
) : (
<div style={{ padding: 4 }}>
<CircularProgress
<CircularProgressOptical
size={24}
variant={progress === 0 ? "indeterminate" : "determinate"}
value={progress}
thickness={4}
style={{ display: "block" }}
/>
</div>

View File

@@ -7,14 +7,7 @@ import { useDropzone } from "react-dropzone";
import useUploader from "hooks/useTable/useUploader";
import { makeStyles, createStyles } from "@mui/styles";
import {
alpha,
ButtonBase,
Typography,
Grid,
CircularProgress,
Tooltip,
} from "@mui/material";
import { alpha, ButtonBase, Typography, Grid, Tooltip } from "@mui/material";
import AddIcon from "@mui/icons-material/AddAPhotoOutlined";
import DeleteIcon from "@mui/icons-material/DeleteOutlined";
@@ -22,6 +15,7 @@ import OpenIcon from "@mui/icons-material/OpenInNewOutlined";
import { IMAGE_MIME_TYPES } from ".";
import Thumbnail from "components/Thumbnail";
import CircularProgressOptical from "components/CircularProgressOptical";
import { useConfirmation } from "components/ConfirmationDialog";
import { useProjectContext } from "contexts/ProjectContext";
@@ -247,7 +241,7 @@ function ControlledImageUploader({
alignItems="center"
className={classes.overlay}
>
<CircularProgress
<CircularProgressOptical
color="inherit"
size={48}
variant={progress === 0 ? "indeterminate" : "determinate"}

View File

@@ -12,13 +12,13 @@ import {
Grid,
IconButton,
ButtonBase,
CircularProgress,
Tooltip,
} from "@mui/material";
import AddIcon from "@mui/icons-material/AddAPhotoOutlined";
import DeleteIcon from "@mui/icons-material/DeleteOutlined";
import OpenIcon from "@mui/icons-material/OpenInNewOutlined";
import CircularProgressOptical from "components/CircularProgressOptical";
import { useConfirmation } from "components/ConfirmationDialog";
import useUploader, { FileValue } from "hooks/useTable/useUploader";
import { IMAGE_MIME_TYPES } from "./index";
@@ -272,11 +272,10 @@ export default function Image_({
)
) : (
<div style={{ padding: 4 }}>
<CircularProgress
<CircularProgressOptical
size={24}
variant={progress === 0 ? "indeterminate" : "determinate"}
value={progress}
thickness={4}
style={{ display: "block" }}
/>
</div>

View File

@@ -1,21 +1,25 @@
import { Switch, FormControlLabel } from "@mui/material";
import { Checkbox, FormControlLabel, FormHelperText } from "@mui/material";
const Settings = ({ config, handleChange }) => {
return (
<>
<FormControlLabel
control={
<Switch
<Checkbox
checked={config.isArray}
onChange={() => handleChange("isArray")(!Boolean(config.isArray))}
name="isArray"
/>
}
label="Set as array"
sx={{
alignItems: "center",
"& .MuiFormControlLabel-label": { mt: 0 },
}}
label={
<>
Default as array
<FormHelperText>
You can still set individual field values as a JSON object or
array using the code editor
</FormHelperText>
</>
}
/>
</>
);

View File

@@ -1,12 +1,20 @@
import { useState } from "react";
import { Controller } from "react-hook-form";
import createPersistedState from "use-persisted-state";
import { ISideDrawerFieldProps } from "../types";
import ReactJson from "react-json-view";
import jsonFormat from "json-format";
import CodeEditor from "components/CodeEditor";
import { useTheme } from "@mui/material";
import { useTheme, Tab, FormHelperText } from "@mui/material";
import TabContext from "@mui/lab/TabContext";
import TabList from "@mui/lab/TabList";
import TabPanel from "@mui/lab/TabPanel";
import { useFieldStyles } from "components/SideDrawer/Form/utils";
const useJsonEditor = createPersistedState("__ROWY__JSON_EDITOR");
const isValidJson = (val: any) => {
try {
if (typeof val === "string") JSON.parse(val);
@@ -25,11 +33,31 @@ export default function Json({
const fieldClasses = useFieldStyles();
const theme = useTheme();
const [editor, setEditor] = useJsonEditor<"tree" | "code">("tree");
const [codeValid, setCodeValid] = useState(true);
const changeEditor = (_, newValue) => {
setEditor(newValue);
setCodeValid(true);
};
return (
<Controller
control={control}
name={column.key}
render={({ field: { onChange, value } }) => {
const sanitizedValue =
value !== undefined && isValidJson(value)
? value
: column.config?.isArray
? []
: {};
const formattedJson = jsonFormat(sanitizedValue, {
type: "space",
char: " ",
size: 2,
});
if (disabled)
return (
<div
@@ -41,12 +69,7 @@ export default function Json({
wordBreak: "break-word",
}}
>
{value &&
jsonFormat(value, {
type: "space",
char: " ",
size: 2,
})}
{value && formattedJson}
</div>
);
@@ -55,46 +78,68 @@ export default function Json({
};
return (
<div
className={fieldClasses.root}
style={{ overflowX: "auto", ...theme.typography.caption }}
>
<ReactJson
src={
value !== undefined && isValidJson(value)
? value
: column.config?.isArray
? []
: {}
}
onEdit={handleEdit}
onAdd={handleEdit}
onDelete={handleEdit}
theme={{
base00: "rgba(0, 0, 0, 0)",
base01: theme.palette.background.default,
base02: theme.palette.divider,
base03: "#93a1a1",
base04: theme.palette.text.disabled,
base05: theme.palette.text.secondary,
base06: "#073642",
base07: theme.palette.text.primary,
base08: "#d33682",
base09: "#cb4b16",
base0A: "#dc322f",
base0B: "#859900",
base0C: "#6c71c4",
base0D: theme.palette.text.secondary,
base0E: "#2aa198",
base0F: "#268bd2",
<TabContext value={editor}>
<TabList
onChange={changeEditor}
aria-label="JSON editor"
variant="standard"
sx={{
minHeight: 32,
mt: -32 / 8,
"& .MuiTabs-flexContainer": { justifyContent: "flex-end" },
"& .MuiTab-root": { minHeight: 32, py: 0 },
}}
iconStyle="triangle"
style={{
fontFamily: theme.typography.fontFamilyMono,
backgroundColor: "transparent",
}}
/>
</div>
>
<Tab label="Tree" value="tree" />
<Tab label="Code" value="code" />
</TabList>
<TabPanel value="tree" sx={{ p: 0 }}>
<div
className={fieldClasses.root}
style={{ overflowX: "auto", ...theme.typography.caption }}
>
<ReactJson
src={sanitizedValue}
onEdit={handleEdit}
onAdd={handleEdit}
onDelete={handleEdit}
theme={
theme.palette.mode === "dark" ? "monokai" : "rjv-default"
}
iconStyle="triangle"
style={{
fontFamily: theme.typography.fontFamilyMono,
backgroundColor: "transparent",
minHeight: 100 - 4 - 4,
}}
/>
</div>
</TabPanel>
<TabPanel value="code" sx={{ p: 0 }}>
<CodeEditor
defaultLanguage="json"
value={formattedJson || "{\n \n}"}
onChange={(v) => {
try {
if (v) onChange(JSON.parse(v));
} catch (e) {
console.log(`Failed to parse JSON: ${e}`);
setCodeValid(false);
}
}}
onValidStatusUpdate={({ isValid }) => setCodeValid(isValid)}
error={!codeValid}
/>
{!codeValid && (
<FormHelperText error variant="filled">
Invalid JSON
</FormHelperText>
)}
</TabPanel>
</TabContext>
);
}}
/>

View File

@@ -61,9 +61,7 @@ export interface ISideDrawerFieldProps {
export interface ISettingsProps {
handleChange: (key: string) => (value: any) => void;
config: Record<string, any>;
// TODO: WRITE TYPES
tables: any;
[key: string]: any;
fieldName: string;
}
// TODO: WRITE TYPES

View File

@@ -270,7 +270,7 @@ const useTableData = () => {
: [];
const { path } = tableState;
const newId = generateSmallerId(rows[0]?.id ?? "zzzzzzzzzzzzzzzzzzzzzzzz");
const newId = generateSmallerId(rows[0]?.id ?? "zzzzzzzzzzzzzzzzzzzz");
if (missingRequiredFields.length === 0) {
try {

View File

@@ -48,7 +48,7 @@ export default function UserSettingsPage() {
const sections = [
{ title: "Account", Component: Account, props: childProps },
{ title: "Theme", Component: Theme },
{ title: "Theme", Component: Theme, props: childProps },
{ title: "Personalization", Component: Personalization, props: childProps },
];

View File

@@ -43,6 +43,7 @@ import { useConfirmation } from "components/ConfirmationDialog";
import SnackbarProgress, {
ISnackbarProgressRef,
} from "components/SnackbarProgress";
import CircularProgressOptical from "components/CircularProgressOptical";
const typographyVariants = [
"h1",
@@ -913,7 +914,26 @@ export default function TestView() {
</Button>
</Stack>
<CircularProgress />
<Stack spacing={1} direction="row" alignItems="flex-end">
{/* size 40 thickness 3.6 */}
<CircularProgress />
<CircularProgress size={30} thickness={4.2} />
<CircularProgress size={24} thickness={4.8} />
<CircularProgress size={20} thickness={5.4} />
<CircularProgress size={16} thickness={6.3} />
<CircularProgress size={12} thickness={7.8} />
</Stack>
<Stack spacing={1} direction="row" alignItems="flex-end">
{/* size 40 thickness 3.6 */}
<CircularProgressOptical />
<CircularProgressOptical size={30} />
<CircularProgressOptical size={24} />
<CircularProgressOptical size={20} />
<CircularProgressOptical size={16} />
<CircularProgressOptical size={12} />
</Stack>
<LinearProgress />
</Stack>
</Container>

View File

@@ -256,23 +256,23 @@ export const components = (theme: Theme): ThemeOptions => {
backgroundColor: theme.palette.action.input,
},
boxShadow: `0 0 0 1px ${theme.palette.action.inputOutline} inset,
0 -1px 0 0 ${theme.palette.text.disabled} inset`,
boxShadow: `0 -1px 0 0 ${theme.palette.text.disabled} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
transition: theme.transitions.create("box-shadow", {
duration: theme.transitions.duration.short,
}),
"&:hover": {
boxShadow: `0 0 0 1px ${theme.palette.action.inputOutline} inset,
0 -1px 0 0 ${theme.palette.text.primary} inset`,
boxShadow: `0 -1px 0 0 ${theme.palette.text.primary} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
},
"&.Mui-focused, &.Mui-focused:hover": {
boxShadow: `0 0 0 1px ${theme.palette.action.inputOutline} inset,
0 -2px 0 0 ${theme.palette.primary.main} inset`,
boxShadow: `0 -2px 0 0 ${theme.palette.primary.main} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
},
"&.Mui-error, &.Mui-error:hover": {
boxShadow: `0 0 0 1px ${theme.palette.action.inputOutline} inset,
0 -2px 0 0 ${theme.palette.error.main} inset`,
boxShadow: `0 -2px 0 0 ${theme.palette.error.main} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
},
borderRadius: theme.shape.borderRadius,
@@ -368,7 +368,7 @@ export const components = (theme: Theme): ThemeOptions => {
root: {
width: `calc(100% - ${theme.spacing(1)})`,
margin: theme.spacing(0, 0.5),
padding: theme.spacing(0.5, 0.75, 0.5, 1.5),
padding: theme.spacing(0.5, 1.5),
minHeight: 32,
borderRadius: theme.shape.borderRadius,

View File

@@ -68,29 +68,56 @@ export const isCollectionGroup = () => {
const pathName = window.location.pathname.split("/")[1];
return pathName === "tableGroup";
};
var characters =
const characters =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
export function makeId(length) {
var result = "";
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
export const makeId = (length: number = 20) => {
let result = "";
const charactersLength = characters.length;
for (var i = 0; i < length; i++)
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
};
export const generateSmallerId = (id: string) => {
const indexOfFirstChar = characters.indexOf(id[0]);
if (indexOfFirstChar !== 0)
return characters[indexOfFirstChar - 1] + makeId(id.length - 1);
else return id[0] + generateSmallerId(id.substr(1, id.length - 1));
const generated = id.split("");
for (let i = generated.length - 1; i >= 0; i--) {
const charIndex = characters.indexOf(id[i]);
if (charIndex > 0) {
generated[i] = characters[charIndex - 1];
break;
} else if (i > 0) {
continue;
} else {
generated.push(characters[characters.length - 1]);
}
}
// Ensure we don't get 00...0, then the next ID would be 00...0z,
// which would appear as the second row
if (generated.every((char) => char === characters[0]))
generated.push(characters[characters.length - 1]);
return generated.join("");
};
export const generateBiggerId = (id: string) => {
const indexOfFirstChar = characters.indexOf(id[0]);
if (indexOfFirstChar !== 61)
return characters[indexOfFirstChar + 1] + makeId(id.length - 1);
else return id[0] + generateBiggerId(id.substr(1, id.length - 1));
const generated = id.split("");
for (let i = generated.length - 1; i >= 0; i--) {
const charIndex = characters.indexOf(id[i]);
console.log(i, id[i], charIndex);
if (charIndex < characters.length - 1) {
generated[i] = characters[charIndex + 1];
break;
} else if (i > 0) {
continue;
} else {
generated.push(characters[0]);
}
}
return generated.join("");
};
// Gets sub-table ID in $1

View File

@@ -6,3 +6,7 @@ declare module "*.mp4" {
const content: any;
export default content;
}
declare module "!!raw-loader!*" {
const content: string;
export default content;
}

View File

@@ -2374,7 +2374,7 @@
dependencies:
state-local "^1.0.6"
"@monaco-editor/react@^4.1.0":
"@monaco-editor/react@^4.3.1":
version "4.3.1"
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.3.1.tgz#d65bcbf174c39b6d4e7fec43d0cddda82b70a12a"
integrity sha512-f+0BK1PP/W5I50hHHmwf11+Ea92E5H1VZXs+wvKplWUWOfyMa1VVwqkJrXjRvbcqHL+XdIGYWhWNdi4McEvnZg==