diff --git a/package.json b/package.json index b30884a2..ad8cf3cc 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/assets/icons/ResizeBottomRight.tsx b/src/assets/icons/ResizeBottomRight.tsx new file mode 100644 index 00000000..83e967bc --- /dev/null +++ b/src/assets/icons/ResizeBottomRight.tsx @@ -0,0 +1,10 @@ +import SvgIcon, { SvgIconProps } from "@mui/material/SvgIcon"; +import { mdiResizeBottomRight } from "@mdi/js"; + +export default function ResizeBottomRight(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/components/CircularProgressOptical.tsx b/src/components/CircularProgressOptical.tsx new file mode 100644 index 00000000..be71800d --- /dev/null +++ b/src/components/CircularProgressOptical.tsx @@ -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 ( + + ); +} diff --git a/src/components/CodeEditor.tsx b/src/components/CodeEditor.tsx deleted file mode 100644 index 0c02862b..00000000 --- a/src/components/CodeEditor.tsx +++ /dev/null @@ -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>; - 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(); - - 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 ( -
- -
- ); -} diff --git a/src/components/CodeEditorHelper.tsx b/src/components/CodeEditor/CodeEditorHelper.tsx similarity index 100% rename from src/components/CodeEditorHelper.tsx rename to src/components/CodeEditor/CodeEditorHelper.tsx diff --git a/src/components/CodeEditor/extensions.d.ts b/src/components/CodeEditor/extensions.d.ts new file mode 100644 index 00000000..7f357e8d --- /dev/null +++ b/src/components/CodeEditor/extensions.d.ts @@ -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); + +// 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; + +type SendgridEmailBody = (context: ExtensionContext) => Promise; + +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; diff --git a/public/auth.d.ts b/src/components/CodeEditor/firebaseAuth.d.ts similarity index 100% rename from public/auth.d.ts rename to src/components/CodeEditor/firebaseAuth.d.ts diff --git a/public/storage.d.ts b/src/components/CodeEditor/firebaseStorage.d.ts similarity index 100% rename from public/storage.d.ts rename to src/components/CodeEditor/firebaseStorage.d.ts diff --git a/public/firestore.d.ts b/src/components/CodeEditor/firestore.d.ts similarity index 99% rename from public/firestore.d.ts rename to src/components/CodeEditor/firestore.d.ts index c840e33d..dc561cff 100644 --- a/public/firestore.d.ts +++ b/src/components/CodeEditor/firestore.d.ts @@ -844,9 +844,9 @@ declare namespace FirebaseFirestore { * `exists` property will always be true and `data()` will never return * 'undefined'. */ - export class QueryDocumentSnapshot extends DocumentSnapshot< - T - > { + export class QueryDocumentSnapshot< + T = DocumentData + > extends DocumentSnapshot { private constructor(); /** diff --git a/src/components/CodeEditor/github-dark-default.json b/src/components/CodeEditor/github-dark-default.json new file mode 100644 index 00000000..c4dd4815 --- /dev/null +++ b/src/components/CodeEditor/github-dark-default.json @@ -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": [] +} diff --git a/src/components/CodeEditor/github-light-default.json b/src/components/CodeEditor/github-light-default.json new file mode 100644 index 00000000..da31839d --- /dev/null +++ b/src/components/CodeEditor/github-light-default.json @@ -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": [] +} diff --git a/src/components/CodeEditor/index.tsx b/src/components/CodeEditor/index.tsx new file mode 100644 index 00000000..8598e74b --- /dev/null +++ b/src/components/CodeEditor/index.tsx @@ -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 { + value: string; + minHeight?: number; + disabled?: boolean; + error?: boolean; + containerProps?: Partial; + + 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 ( + + } + 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, + }} + /> + + + + ); +} diff --git a/src/components/CodeEditor/utils.d.ts b/src/components/CodeEditor/utils.d.ts new file mode 100644 index 00000000..38a02852 --- /dev/null +++ b/src/components/CodeEditor/utils.d.ts @@ -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 {} +} diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index f3e60c26..4b73a467 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -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 ( - - {this.state.errorMessage} - {this.state.errorMessage.startsWith("Loading chunk") ? ( + if (this.state.errorMessage.startsWith("Loading chunk")) + return ( + + Reload this page to get the latest update - ) : ( - - )} + + } + fullScreen + /> + ); + + return ( + + {this.state.errorMessage} + } fullScreen diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx index fbe4127e..2ed9402f 100644 --- a/src/components/Loading.tsx +++ b/src/components/Loading.tsx @@ -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 { message?: string; fullScreen?: boolean; @@ -33,7 +29,7 @@ export default function Loading({ ...props.style, }} > - + (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) { + {email} +
+ Project: {projectId} + + } primaryTypographyProps={{ variant: "subtitle1" }} /> diff --git a/src/components/RichTextEditor.tsx b/src/components/RichTextEditor.tsx index 051edd36..a0f4111a 100644 --- a/src/components/RichTextEditor.tsx +++ b/src/components/RichTextEditor.tsx @@ -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`, }, }, diff --git a/src/components/Settings/UserSettings/Personalization.tsx b/src/components/Settings/UserSettings/Personalization.tsx index b461d28f..1ae6e1f3 100644 --- a/src/components/Settings/UserSettings/Personalization.tsx +++ b/src/components/Settings/UserSettings/Personalization.tsx @@ -29,23 +29,6 @@ export default function Personalization({ return ( <> - { - updateSettings({ - theme: _merge(settings.theme, { - dark: { palette: { darker: e.target.checked } }, - }), - }); - }} - /> - } - label="Darker dark theme" - sx={{ my: -10 / 8 }} - /> - } label="Customize theme colors" - style={{ marginLeft: -11, marginBottom: -10 }} + style={{ marginLeft: -11, marginBottom: -10, marginTop: -10 }} /> diff --git a/src/components/Settings/UserSettings/Theme.tsx b/src/components/Settings/UserSettings/Theme.tsx index 999d8143..5d35f930 100644 --- a/src/components/Settings/UserSettings/Theme.tsx +++ b/src/components/Settings/UserSettings/Theme.tsx @@ -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 ( - - Theme + <> + + Theme - { - if (e.target.value === "system") { - setThemeOverridden(false); - } else { - setTheme(e.target.value as typeof theme); - setThemeOverridden(true); - } - }} - > - } - value="system" - label="Match system theme" - /> - } value="light" label="Light" /> - } value="dark" label="Dark" /> - - + { + if (e.target.value === "system") { + setThemeOverridden(false); + } else { + setTheme(e.target.value as typeof theme); + setThemeOverridden(true); + } + }} + > + } + value="system" + label="Match system theme" + /> + } value="light" label="Light" /> + } value="dark" label="Dark" /> + + + + + + { + updateSettings({ + theme: _merge(settings.theme, { + dark: { palette: { darker: e.target.checked } }, + }), + }); + }} + /> + } + label="Darker dark theme" + style={{ marginLeft: -11, marginBottom: -10, marginTop: 13 }} + /> + ); } diff --git a/src/components/Setup/SetupItem.tsx b/src/components/Setup/SetupItem.tsx index 43be2dd6..160f16f3 100644 --- a/src/components/Setup/SetupItem.tsx +++ b/src/components/Setup/SetupItem.tsx @@ -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" ? ( ) : status === "loading" ? ( - + ) : ( )} diff --git a/src/components/Setup/Step4Rules.tsx b/src/components/Setup/Step4Rules.tsx index 4ceecd06..52eaef83 100644 --- a/src/components/Setup/Step4Rules.tsx +++ b/src/components/Setup/Step4Rules.tsx @@ -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 ( <> @@ -201,15 +214,23 @@ export default function Step4Rules({ Please check the generated rules first. - - Set Firestore Rules - - + {" "} + + Set Firestore Rules + + + {rulesStatus !== "LOADING" && typeof rulesStatus === "string" && ( {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); diff --git a/src/components/SideDrawer/Form/index.tsx b/src/components/SideDrawer/Form/index.tsx index 7852882e..2f2fa487 100644 --- a/src/components/SideDrawer/Form/index.tsx +++ b/src/components/SideDrawer/Form/index.tsx @@ -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 (
diff --git a/src/components/SideDrawer/index.tsx b/src/components/SideDrawer/index.tsx index 0cd164b0..d2c331f8 100644 --- a/src/components/SideDrawer/index.tsx +++ b/src/components/SideDrawer/index.tsx @@ -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 }); diff --git a/src/components/SnackbarProgress.tsx b/src/components/SnackbarProgress.tsx index 28cf9cd9..2be7986c 100644 --- a/src/components/SnackbarProgress.tsx +++ b/src/components/SnackbarProgress.tsx @@ -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>; @@ -31,12 +33,11 @@ export default function SnackbarProgress({ {progress}/{target} - ); diff --git a/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx b/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx index 05b45960..93a65a3d 100644 --- a/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx +++ b/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx @@ -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" } }, + }, + }} > @@ -78,24 +67,21 @@ export default function DefaultValueInput({ primary="Null" secondary={ <> - Initialise as null. + Initialise as null. } - className={classes.typeSelectItem} /> @@ -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" && ( <> -
+ }> -
+ )} diff --git a/src/components/Table/ColumnMenu/FieldSettings/index.tsx b/src/components/Table/ColumnMenu/FieldSettings/index.tsx index 0012fd21..1146357c 100644 --- a/src/components/Table/ColumnMenu/FieldSettings/index.tsx +++ b/src/components/Table/ColumnMenu/FieldSettings/index.tsx @@ -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, })} )} diff --git a/src/components/Table/TableHeader/Extensions/Step3Conditions.tsx b/src/components/Table/TableHeader/Extensions/Step3Conditions.tsx index ad23b46b..f5df76c4 100644 --- a/src/components/Table/TableHeader/Extensions/Step3Conditions.tsx +++ b/src/components/Table/TableHeader/Extensions/Step3Conditions.tsx @@ -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 ( <> -
+ }> { + 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)} /> -
+ import("components/CodeEditor" /* webpackChunkName: "CodeEditor" */) +); + const additionalVariables = [ { key: "change", @@ -38,14 +43,14 @@ export default function Step4Body({ return ( <> -
+ }> { + 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)} /> -
+ , + startIcon: updating && , disabled: updating, }, secondary: { diff --git a/src/components/Table/TableHeader/TableLogs.tsx b/src/components/Table/TableHeader/TableLogs.tsx index 5e2764b0..4e5db568 100644 --- a/src/components/Table/TableHeader/TableLogs.tsx +++ b/src/components/Table/TableHeader/TableLogs.tsx @@ -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) { })}
{status === "BUILDING" && ( - @@ -371,7 +371,7 @@ function SnackLog({ log, onClose, onOpenPanel }) { })}
{status === "BUILDING" && ( - @@ -427,10 +427,9 @@ export default function TableLogs() { <> {latestStatus === "BUILDING" && ( - )} @@ -499,7 +498,7 @@ export default function TableLogs() { style={{ textAlign: "left" }} > {logEntry.status === "BUILDING" && ( - + )} {logEntry.status === "SUCCESS" && } {logEntry.status === "FAIL" && } diff --git a/src/components/Table/TableHeader/Webhooks/Step2Conditions.tsx b/src/components/Table/TableHeader/Webhooks/Step2Conditions.tsx index 41efaa76..3bde07af 100644 --- a/src/components/Table/TableHeader/Webhooks/Step2Conditions.tsx +++ b/src/components/Table/TableHeader/Webhooks/Step2Conditions.tsx @@ -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({ <>
{ + value={webhookObject.conditions} + minHeight={200} + onChange={(newValue) => { setWebhookObject({ ...webhookObject, - conditions: newValue, + conditions: newValue || "", }); }} onValidStatusUpdate={({ isValid }) => { diff --git a/src/components/Table/TableHeader/Webhooks/Step3Parser.tsx b/src/components/Table/TableHeader/Webhooks/Step3Parser.tsx index cbeb8dc4..7939dedb 100644 --- a/src/components/Table/TableHeader/Webhooks/Step3Parser.tsx +++ b/src/components/Table/TableHeader/Webhooks/Step3Parser.tsx @@ -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({ <>
{ + value={webhookObject.parser} + minHeight={400} + onChange={(newValue) => { setWebhookObject({ ...webhookObject, - parser: newValue, + parser: newValue || "", }); }} onValidStatusUpdate={({ isValid }) => { diff --git a/src/components/Table/TableHeader/Webhooks/WebhookList.tsx b/src/components/Table/TableHeader/Webhooks/WebhookList.tsx index 106011dd..fd8af180 100644 --- a/src/components/Table/TableHeader/Webhooks/WebhookList.tsx +++ b/src/components/Table/TableHeader/Webhooks/WebhookList.tsx @@ -90,7 +90,7 @@ export default function WebhookList({ onClick={handleAddButton} ref={addButtonRef} > - Add Webhook… + Add webhook… (null); const [webhookLogs, setWebhookLogs] = useState(); - 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 ( <> } /> diff --git a/src/components/Table/TableHeader/index.tsx b/src/components/Table/TableHeader/index.tsx index 7fd0bea6..1d56f31e 100644 --- a/src/components/Table/TableHeader/index.tsx +++ b/src/components/Table/TableHeader/index.tsx @@ -100,7 +100,6 @@ export default function TableHeader() { <> {/* Spacer */}
- {/* Spacer */}
{(hasDerivatives || hasExtensions) && } diff --git a/src/components/Table/editors/CodeEditor.tsx b/src/components/Table/editors/CodeEditor.tsx deleted file mode 100644 index dbe6e488..00000000 --- a/src/components/Table/editors/CodeEditor.tsx +++ /dev/null @@ -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(); - - 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); - - // 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; - - type SendgridEmailBody = (context: ExtensionContext) => Promise; - - 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 - `; - - 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 ( - <> -
- -
- - ); -} diff --git a/src/components/Wizards/ImportCsvWizard/Step1Columns.tsx b/src/components/Wizards/ImportCsvWizard/Step1Columns.tsx index 9b8bf433..5c658704 100644 --- a/src/components/Wizards/ImportCsvWizard/Step1Columns.tsx +++ b/src/components/Wizards/ImportCsvWizard/Step1Columns.tsx @@ -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) diff --git a/src/components/Wizards/ImportCsvWizard/Step3Preview.tsx b/src/components/Wizards/ImportCsvWizard/Step3Preview.tsx index b6303592..467fd13e 100644 --- a/src/components/Wizards/ImportCsvWizard/Step3Preview.tsx +++ b/src/components/Wizards/ImportCsvWizard/Step3Preview.tsx @@ -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) { diff --git a/src/components/fields/Action/ActionFab.tsx b/src/components/fields/Action/ActionFab.tsx index 949733f0..86dec038 100644 --- a/src/components/fields/Action/ActionFab.tsx +++ b/src/components/fields/Action/ActionFab.tsx @@ -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 ? ( - + ) : ( getStateIcon(actionState) )} diff --git a/src/components/fields/Action/Settings.tsx b/src/components/fields/Action/Settings.tsx index a7e374fd..35520fa9 100644 --- a/src/components/fields/Action/Settings.tsx +++ b/src/components/fields/Action/Settings.tsx @@ -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 }) => { action script }> { } else return `static ${param.name}:any`; }), "}", - ], + ].join("\n"), ]} - handleChange={handleChange("script")} + onChange={handleChange("script")} /> { Undo action script }> diff --git a/src/components/fields/Aggregate/Settings.tsx b/src/components/fields/Aggregate/Settings.tsx index 8d8895a8..d1002147 100644 --- a/src/components/fields/Aggregate/Settings.tsx +++ b/src/components/fields/Aggregate/Settings.tsx @@ -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 }) => { Aggergate script }> diff --git a/src/components/fields/Code/SideDrawerField.tsx b/src/components/fields/Code/SideDrawerField.tsx index 5a443701..65696b80 100644 --- a/src/components/fields/Code/SideDrawerField.tsx +++ b/src/components/fields/Code/SideDrawerField.tsx @@ -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 ( ( - + )} /> ); diff --git a/src/components/fields/ConnectService/InlineCell.tsx b/src/components/fields/ConnectService/InlineCell.tsx index 3f4d2453..955b9f5c 100644 --- a/src/components/fields/ConnectService/InlineCell.tsx +++ b/src/components/fields/ConnectService/InlineCell.tsx @@ -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 ) { const config = column.config ?? {}; - + const displayKey = config.titleKey ?? config.primaryKey; return ( showPopoverCell(true)} @@ -29,9 +30,9 @@ export const ConnectService = forwardRef(function ConnectService( > {Array.isArray(value) && - value.map((doc: any) => ( - - + value.map((snapshot) => ( + + ))} diff --git a/src/components/fields/ConnectTable/ConnectTableSelect.tsx b/src/components/fields/ConnectTable/ConnectTableSelect.tsx index 16327561..cbfc031b 100644 --- a/src/components/fields/ConnectTable/ConnectTableSelect.tsx +++ b/src/components/fields/ConnectTable/ConnectTableSelect.tsx @@ -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; diff --git a/src/components/fields/Date/BasicCell.tsx b/src/components/fields/Date/BasicCell.tsx index 96cf2ec0..385ef55e 100644 --- a/src/components/fields/Date/BasicCell.tsx +++ b/src/components/fields/Date/BasicCell.tsx @@ -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 ( {formatted} ); diff --git a/src/components/fields/DateTime/BasicCell.tsx b/src/components/fields/DateTime/BasicCell.tsx index 8d23fc5f..254c6867 100644 --- a/src/components/fields/DateTime/BasicCell.tsx +++ b/src/components/fields/DateTime/BasicCell.tsx @@ -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 ( {formatted} ); diff --git a/src/components/fields/Derivative/Settings.tsx b/src/components/fields/Derivative/Settings.tsx index 807829ab..14e75323 100644 --- a/src/components/fields/Derivative/Settings.tsx +++ b/src/components/fields/Derivative/Settings.tsx @@ -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 }) => { Derivative script }> - +
); -}; -export default Settings; +} diff --git a/src/components/fields/File/SideDrawerField.tsx b/src/components/fields/File/SideDrawerField.tsx index 5cea9319..547fd9b3 100644 --- a/src/components/fields/File/SideDrawerField.tsx +++ b/src/components/fields/File/SideDrawerField.tsx @@ -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({ } label={localFile} - deleteIcon={ - - } + deleteIcon={} /> )} diff --git a/src/components/fields/File/TableCell.tsx b/src/components/fields/File/TableCell.tsx index 9d609556..dd37cc9f 100644 --- a/src/components/fields/File/TableCell.tsx +++ b/src/components/fields/File/TableCell.tsx @@ -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_({ ) ) : (
-
diff --git a/src/components/fields/Image/SideDrawerField.tsx b/src/components/fields/Image/SideDrawerField.tsx index d5275205..8968ffef 100644 --- a/src/components/fields/Image/SideDrawerField.tsx +++ b/src/components/fields/Image/SideDrawerField.tsx @@ -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} > - -
diff --git a/src/components/fields/Json/Settings.tsx b/src/components/fields/Json/Settings.tsx index 4955ff44..d424c707 100644 --- a/src/components/fields/Json/Settings.tsx +++ b/src/components/fields/Json/Settings.tsx @@ -1,21 +1,25 @@ -import { Switch, FormControlLabel } from "@mui/material"; +import { Checkbox, FormControlLabel, FormHelperText } from "@mui/material"; const Settings = ({ config, handleChange }) => { return ( <> handleChange("isArray")(!Boolean(config.isArray))} name="isArray" /> } - label="Set as array" - sx={{ - alignItems: "center", - "& .MuiFormControlLabel-label": { mt: 0 }, - }} + label={ + <> + Default as array + + You can still set individual field values as a JSON object or + array using the code editor + + + } /> ); diff --git a/src/components/fields/Json/SideDrawerField.tsx b/src/components/fields/Json/SideDrawerField.tsx index 46c3f761..f3f6f084 100644 --- a/src/components/fields/Json/SideDrawerField.tsx +++ b/src/components/fields/Json/SideDrawerField.tsx @@ -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 ( { + const sanitizedValue = + value !== undefined && isValidJson(value) + ? value + : column.config?.isArray + ? [] + : {}; + const formattedJson = jsonFormat(sanitizedValue, { + type: "space", + char: " ", + size: 2, + }); + if (disabled) return (
- {value && - jsonFormat(value, { - type: "space", - char: " ", - size: 2, - })} + {value && formattedJson}
); @@ -55,46 +78,68 @@ export default function Json({ }; return ( -
- + -
+ > + + + + + +
+ +
+
+ + + { + 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 && ( + + Invalid JSON + + )} + + ); }} /> diff --git a/src/components/fields/types.ts b/src/components/fields/types.ts index 9f9932d3..853ea496 100644 --- a/src/components/fields/types.ts +++ b/src/components/fields/types.ts @@ -61,9 +61,7 @@ export interface ISideDrawerFieldProps { export interface ISettingsProps { handleChange: (key: string) => (value: any) => void; config: Record; - // TODO: WRITE TYPES - tables: any; - [key: string]: any; + fieldName: string; } // TODO: WRITE TYPES diff --git a/src/hooks/useTable/useTableData.tsx b/src/hooks/useTable/useTableData.tsx index 0ea6e34c..2b1a18f1 100644 --- a/src/hooks/useTable/useTableData.tsx +++ b/src/hooks/useTable/useTableData.tsx @@ -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 { diff --git a/src/pages/Settings/UserSettings.tsx b/src/pages/Settings/UserSettings.tsx index 96866fa9..40d94b59 100644 --- a/src/pages/Settings/UserSettings.tsx +++ b/src/pages/Settings/UserSettings.tsx @@ -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 }, ]; diff --git a/src/pages/Test.tsx b/src/pages/Test.tsx index 68adf056..5511cae0 100644 --- a/src/pages/Test.tsx +++ b/src/pages/Test.tsx @@ -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() { - + + {/* size 40 thickness 3.6 */} + + + + + + + + + + {/* size 40 thickness 3.6 */} + + + + + + + + diff --git a/src/theme/components.tsx b/src/theme/components.tsx index 644907bc..1f914289 100644 --- a/src/theme/components.tsx +++ b/src/theme/components.tsx @@ -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, diff --git a/src/utils/fns.ts b/src/utils/fns.ts index 936b2189..93efd586 100644 --- a/src/utils/fns.ts +++ b/src/utils/fns.ts @@ -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 diff --git a/types/custom.ts b/types/custom.ts index ae704ef5..63f9f2d0 100644 --- a/types/custom.ts +++ b/types/custom.ts @@ -6,3 +6,7 @@ declare module "*.mp4" { const content: any; export default content; } +declare module "!!raw-loader!*" { + const content: string; + export default content; +} diff --git a/yarn.lock b/yarn.lock index 5c7be15c..0a60e954 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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==