├── .editorconfig ├── .github └── workflows │ ├── ci_build.yml │ └── ui_tests.yml ├── .gitignore ├── .prettierrc ├── .yarn ├── plugins │ └── @yarnpkg │ │ └── plugin-workspace-tools.cjs └── releases │ └── yarn-3.2.0.cjs ├── .yarnrc.yml ├── LICENSE ├── README.md ├── package.json ├── shared ├── codeEditor │ ├── codeEditor.module.scss │ ├── completions.ts │ ├── index.tsx │ ├── package.json │ ├── styles.d.ts │ ├── terminalCursor.ts │ ├── theme.ts │ └── tsconfig.json ├── common │ ├── branchGraph │ │ ├── branchGraph.module.scss │ │ ├── diff.ts │ │ ├── index.tsx │ │ ├── layout.ts │ │ └── utils.ts │ ├── components │ │ ├── dataGrid │ │ │ ├── dataGrid.module.scss │ │ │ ├── index.tsx │ │ │ ├── renderUtils.tsx │ │ │ ├── state.ts │ │ │ └── utils.ts │ │ ├── infoCards │ │ │ ├── getLatestInfo.ts │ │ │ ├── index.tsx │ │ │ └── infoCards.module.scss │ │ └── resultGrid │ │ │ ├── index.tsx │ │ │ ├── resultGrid.module.scss │ │ │ └── state.ts │ ├── decodeRawBuffer │ │ └── index.ts │ ├── fonts │ │ ├── edgedb-300.woff2 │ │ ├── edgedb-400.woff2 │ │ ├── edgedb-500.woff2 │ │ ├── edgedb-600.woff2 │ │ ├── edgedb-700.woff2 │ │ └── include.scss │ ├── hooks │ │ ├── globalDragCursor │ │ │ ├── globalDragCursor.module.scss │ │ │ └── index.tsx │ │ ├── useDragHandler.ts │ │ ├── useInitialValue.ts │ │ ├── useKeyboardShortcut.ts │ │ ├── useMobile.ts │ │ ├── useModal │ │ │ └── index.tsx │ │ ├── usePersistedState.ts │ │ ├── useResize.ts │ │ ├── useTheme │ │ │ ├── index.tsx │ │ │ └── mixin.scss │ │ └── useTooltips.tsx │ ├── index.ts │ ├── logo.tsx │ ├── mixins.scss │ ├── newui │ │ ├── button │ │ │ ├── button.module.scss │ │ │ └── index.tsx │ │ ├── checkbox │ │ │ ├── checkbox.module.scss │ │ │ └── index.tsx │ │ ├── copyButton │ │ │ ├── copyButton.module.scss │ │ │ └── index.tsx │ │ ├── fieldHeader │ │ │ ├── fieldHeader.module.scss │ │ │ └── index.tsx │ │ ├── horizontalCardList │ │ │ ├── horizontalCardList.module.scss │ │ │ └── index.tsx │ │ ├── iconToggle │ │ │ ├── iconToggle.module.scss │ │ │ └── index.tsx │ │ ├── icons │ │ │ ├── index.tsx │ │ │ └── other.tsx │ │ ├── index.ts │ │ ├── infoTooltip │ │ │ ├── index.tsx │ │ │ └── infoTooltip.module.scss │ │ ├── loadingSkeleton │ │ │ ├── index.tsx │ │ │ └── loadingSkeleton.module.scss │ │ ├── logo.module.scss │ │ ├── logo.tsx │ │ ├── logos │ │ │ └── index.tsx │ │ ├── modal │ │ │ ├── index.tsx │ │ │ └── modal.module.scss │ │ ├── panelTabs │ │ │ ├── index.tsx │ │ │ └── panelTabs.module.scss │ │ ├── select │ │ │ ├── index.tsx │ │ │ └── select.module.scss │ │ ├── textInput │ │ │ ├── index.tsx │ │ │ └── textInput.module.scss │ │ └── theme.module.scss │ ├── package.json │ ├── schemaData │ │ ├── index.ts │ │ ├── knownTypes.ts │ │ ├── queries.ts │ │ └── utils.ts │ ├── styles.d.ts │ ├── styles.scss │ ├── tsconfig.json │ ├── ui │ │ ├── button │ │ │ ├── button.module.scss │ │ │ └── index.tsx │ │ ├── codeBlock │ │ │ └── index.tsx │ │ ├── customScrollbar │ │ │ ├── customScrollbar.module.scss │ │ │ └── index.tsx │ │ ├── icons │ │ │ ├── index.tsx │ │ │ └── logo.tsx │ │ ├── mobile │ │ │ ├── button │ │ │ │ ├── button.module.scss │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── roundButton │ │ │ │ ├── index.tsx │ │ │ │ └── roundButton.module.scss │ │ ├── modal │ │ │ ├── index.tsx │ │ │ └── modal.module.scss │ │ ├── navtabs │ │ │ ├── interfaces.tsx │ │ │ ├── mobile.tsx │ │ │ └── mobileNavTabs.module.scss │ │ ├── select │ │ │ ├── index.tsx │ │ │ └── select.module.scss │ │ ├── spinner │ │ │ ├── index.tsx │ │ │ └── spinner.module.scss │ │ ├── splitView │ │ │ ├── index.tsx │ │ │ ├── model.ts │ │ │ └── splitView.module.scss │ │ ├── switch │ │ │ ├── index.tsx │ │ │ └── switch.module.scss │ │ ├── switcherButton │ │ │ ├── index.tsx │ │ │ └── switcherButton.module.scss │ │ ├── themeSwitcher │ │ │ ├── icons.tsx │ │ │ ├── index.tsx │ │ │ └── themeSwitcher.module.scss │ │ ├── toggleSwitch │ │ │ ├── index.tsx │ │ │ └── toggleSwitch.module.scss │ │ ├── tooltip │ │ │ ├── index.tsx │ │ │ └── tooltip.module.scss │ │ └── verticalTabBar │ │ │ ├── index.tsx │ │ │ └── verticalTabBar.module.scss │ └── utils │ │ ├── array.ts │ │ ├── assertNever.ts │ │ ├── classNames.ts │ │ ├── debug.ts │ │ ├── easing.ts │ │ ├── format.ts │ │ ├── fuzzysortHighlight.tsx │ │ ├── markString.tsx │ │ ├── moduleNames.ts │ │ └── relativeTime.tsx ├── inspector │ ├── buildItem.tsx │ ├── buildScalar.tsx │ ├── context.ts │ ├── index.tsx │ ├── inspector.module.scss │ ├── package.json │ ├── renderJsonResult.ts │ ├── state.ts │ ├── styles.d.ts │ └── tsconfig.json ├── lang-edgeql │ ├── debug.js │ ├── edgeql.ts │ ├── genMeta.js │ ├── lang.grammar │ ├── lang.js │ ├── lang.terms.js │ ├── meta.d.ts │ ├── meta.js │ ├── package.json │ └── tokens.js ├── schemaGraph │ ├── components │ │ ├── schemaGraph │ │ │ ├── SchemaLink.tsx │ │ │ ├── SchemaNode.tsx │ │ │ ├── SchemaNodeLinkProps.tsx │ │ │ ├── SchemaNodeObject.tsx │ │ │ ├── debug.module.scss │ │ │ ├── debug.tsx │ │ │ ├── index.tsx │ │ │ ├── interfaces.ts │ │ │ └── schemaGraph.module.scss │ │ └── schemaMinimap │ │ │ ├── index.tsx │ │ │ └── schemaMinimap.module.scss │ ├── core │ │ ├── aStar.ts │ │ ├── focusedLayout.ts │ │ ├── grid.ts │ │ ├── index.ts │ │ ├── interfaces.ts │ │ ├── linkLayout.ts │ │ ├── routeLinks.ts │ │ ├── utils.ts │ │ ├── webcolaLayout.ts │ │ └── worker │ │ │ ├── index.ts │ │ │ ├── interfaces.ts │ │ │ └── layout.worker.ts │ ├── index.ts │ ├── package.json │ └── state │ │ ├── graph.ts │ │ ├── index.ts │ │ ├── interfaces.ts │ │ ├── provider.ts │ │ ├── query.ts │ │ └── sidepanel.ts └── studio │ ├── components │ ├── dataEditor │ │ ├── dataEditor.module.scss │ │ ├── editor.tsx │ │ ├── icons.tsx │ │ ├── index.tsx │ │ ├── parsers.ts │ │ └── utils.ts │ ├── databasePage │ │ ├── databasePage.module.scss │ │ └── index.tsx │ ├── errorPage │ │ ├── errorPage.module.scss │ │ └── index.tsx │ ├── explainVis │ │ ├── codeEditorContexts.tsx │ │ ├── codeblock.tsx │ │ ├── explainVis.module.scss │ │ ├── index.tsx │ │ ├── state.ts │ │ └── treemapLayout.tsx │ ├── extendedViewers │ │ ├── hexViewer │ │ │ ├── hexViewer.module.scss │ │ │ ├── index.tsx │ │ │ └── state.ts │ │ ├── index.tsx │ │ ├── jsonViewer │ │ │ ├── index.tsx │ │ │ └── jsonViewer.module.scss │ │ ├── postgisViewer │ │ │ ├── controlPoint.png │ │ │ ├── editableGeom │ │ │ │ ├── convert.ts │ │ │ │ ├── curves.ts │ │ │ │ ├── geojsonTypes.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── icons.tsx │ │ │ ├── index.tsx │ │ │ ├── postgisViewer.module.scss │ │ │ ├── state.ts │ │ │ ├── styles │ │ │ │ ├── LICENSE.md │ │ │ │ ├── dark_layers.json │ │ │ │ ├── layers.ts │ │ │ │ └── light_layers.json │ │ │ ├── toolbar.tsx │ │ │ └── wktRenderers.tsx │ │ ├── shared.module.scss │ │ ├── shared.tsx │ │ └── textViewer │ │ │ ├── index.tsx │ │ │ └── textViewer.module.scss │ ├── headerNav │ │ ├── elements.tsx │ │ ├── headerNav.module.scss │ │ └── index.tsx │ ├── lazyTabs │ │ └── lazyTabs.module.scss │ ├── modals │ │ ├── createBranch.tsx │ │ └── modals.module.scss │ ├── objectTypeSelect │ │ └── index.tsx │ ├── sessionState │ │ ├── icons.tsx │ │ ├── index.tsx │ │ └── sessionState.module.scss │ └── visualQuerybuilder │ │ ├── index.tsx │ │ ├── queryBuilder.module.scss │ │ └── state.ts │ ├── hooks │ └── dbRoute.ts │ ├── icons │ ├── checkbox_checked.svg │ ├── docs.tsx │ └── index.tsx │ ├── idbStore │ └── index.ts │ ├── index.ts │ ├── package.json │ ├── state │ ├── connection.ts │ ├── database.ts │ ├── explainGraphSettings.ts │ ├── index.ts │ ├── instance.ts │ ├── sessionState.ts │ └── utils │ │ └── lru.ts │ ├── styles.d.ts │ ├── tabs │ ├── ai │ │ ├── ai.tsx │ │ ├── aiAdmin.module.scss │ │ ├── index.tsx │ │ ├── logos.tsx │ │ ├── playground.tsx │ │ ├── prompts.tsx │ │ ├── providers.tsx │ │ └── state │ │ │ ├── index.ts │ │ │ └── rag.ts │ ├── auth │ │ ├── auth.tsx │ │ ├── authAdmin.module.scss │ │ ├── colourUtils.ts │ │ ├── config.tsx │ │ ├── icons.tsx │ │ ├── index.tsx │ │ ├── loginUIPreview.tsx │ │ ├── loginuipreview.module.scss │ │ ├── providers.tsx │ │ ├── shared.tsx │ │ ├── smtp.tsx │ │ ├── state │ │ │ └── index.tsx │ │ └── webhooks.tsx │ ├── dashboard │ │ ├── databaseDashboard.module.scss │ │ ├── exampleSchema.mts │ │ └── index.tsx │ ├── dataview │ │ ├── dataInspector.module.scss │ │ ├── dataInspector.tsx │ │ ├── dataview.module.scss │ │ ├── editsModal.module.scss │ │ ├── fieldConfig.module.scss │ │ ├── fieldConfig.tsx │ │ ├── icons.tsx │ │ ├── index.tsx │ │ ├── reviewEditsModal.tsx │ │ └── state │ │ │ ├── edits.ts │ │ │ └── index.ts │ ├── graphql │ │ ├── graphql.module.scss │ │ ├── index.tsx │ │ ├── lang.ts │ │ └── state │ │ │ └── index.ts │ ├── perfStats │ │ ├── analyze.tsx │ │ ├── filters.tsx │ │ ├── index.tsx │ │ ├── perfStats.module.scss │ │ ├── state │ │ │ └── index.ts │ │ ├── statsChart.tsx │ │ ├── statsTable.tsx │ │ └── utils.tsx │ ├── queryEditor │ │ ├── history.tsx │ │ ├── index.tsx │ │ ├── paramEditor │ │ │ ├── index.tsx │ │ │ └── paramEditor.module.scss │ │ ├── queryeditor.module.scss │ │ └── state │ │ │ ├── extractQueryParameters.ts │ │ │ ├── index.ts │ │ │ ├── parameters.ts │ │ │ └── thumbnailGen.tsx │ ├── repl │ │ ├── commands.tsx │ │ ├── index.tsx │ │ ├── repl.module.scss │ │ └── state │ │ │ ├── commands.ts │ │ │ ├── index.ts │ │ │ ├── itemHeights.ts │ │ │ └── utils.ts │ └── schema │ │ ├── graphView.tsx │ │ ├── index.tsx │ │ ├── renderers │ │ ├── alias.tsx │ │ ├── annotation.tsx │ │ ├── constraint.tsx │ │ ├── extension.tsx │ │ ├── function.tsx │ │ ├── global.tsx │ │ ├── index.tsx │ │ ├── module.tsx │ │ ├── object.tsx │ │ ├── operator.tsx │ │ ├── pointer.tsx │ │ ├── scalar.tsx │ │ ├── schemaIndex.tsx │ │ └── utils.tsx │ │ ├── schema.module.scss │ │ ├── state │ │ ├── index.ts │ │ └── textView.ts │ │ ├── textView.module.scss │ │ └── textView.tsx │ ├── tsconfig.json │ └── utils │ ├── completer.ts │ ├── decodeRawBuffer.ts │ ├── extractErrorDetails.ts │ ├── formatDuration.ts │ ├── rewriteTypedesc.ts │ └── syntaxTree.ts ├── tsconfig.base.json ├── web ├── .gitignore ├── README.md ├── eslint.config.js ├── index.html ├── package.json ├── playwright.config.ts ├── playwright │ ├── _globalSetup.ts │ ├── _globalTeardown.ts │ ├── auth.json │ ├── fixtures.ts │ └── index.ts ├── public │ ├── android-chrome-192.png │ ├── android-chrome-512.png │ ├── app.webmanifest │ ├── apple-touch-icon.png │ ├── favicon-16.png │ ├── favicon-32.png │ └── robots.txt ├── src │ ├── app.module.scss │ ├── app.tsx │ ├── components │ │ ├── databasePage │ │ │ ├── databasePage.module.scss │ │ │ └── index.tsx │ │ ├── header │ │ │ ├── header.module.scss │ │ │ └── index.tsx │ │ ├── instancePage │ │ │ ├── index.tsx │ │ │ └── instancePage.module.scss │ │ ├── loginPage │ │ │ ├── index.tsx │ │ │ └── loginPage.module.scss │ │ └── main │ │ │ ├── index.tsx │ │ │ └── main.module.scss │ ├── fonts │ │ ├── edgedb-300.woff2 │ │ ├── edgedb-400.woff2 │ │ ├── edgedb-500.woff2 │ │ ├── edgedb-600.woff2 │ │ ├── edgedb-700.woff2 │ │ └── include.scss │ ├── index.css │ ├── main.tsx │ ├── mixins.scss │ ├── state │ │ ├── models │ │ │ └── app.ts │ │ ├── providers.ts │ │ └── store.ts │ ├── utils │ │ └── reportAccessibility.ts │ └── vite-env.d.ts ├── tests │ ├── clientSettings.spec.ts │ ├── dataViewer.spec.ts │ ├── queryEditor.spec.ts │ ├── repl.spec.ts │ ├── schema.spec.ts │ └── simple.spec.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | 7 | [*.{js,ts,json,tsx,yml,yaml,scss}] 8 | indent_size = 2 9 | indent_style = space 10 | charset = utf-8 11 | -------------------------------------------------------------------------------- /.github/workflows/ci_build.yml: -------------------------------------------------------------------------------- 1 | name: Test CI build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 50 19 | submodules: true 20 | 21 | - name: Set up Node 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: lts/* 25 | 26 | - name: Install dev deps 27 | working-directory: web 28 | run: | 29 | yarn 30 | 31 | - name: Lint and typecheck 32 | working-directory: web 33 | run: | 34 | yarn lint && yarn typecheck 35 | 36 | - name: Run build 37 | working-directory: web 38 | run: | 39 | yarn build 40 | -------------------------------------------------------------------------------- /.github/workflows/ui_tests.yml: -------------------------------------------------------------------------------- 1 | name: UI tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 1 16 | submodules: true 17 | 18 | - name: Set up Node 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: lts/* 22 | 23 | - name: Install Gel 24 | uses: geldata/setup-gel@v1 25 | with: 26 | server-version: nightly 27 | 28 | - name: Install dev deps 29 | working-directory: web 30 | run: | 31 | yarn 32 | 33 | - name: Install Playwright Browsers 34 | working-directory: web 35 | run: | 36 | yarn playwright install --with-deps chromium firefox webkit 37 | 38 | - name: Run tests 39 | working-directory: web 40 | run: | 41 | yarn test 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.vscode 4 | 5 | yarn-error.log 6 | 7 | /.yarn/* 8 | !/.yarn/releases 9 | !/.yarn/plugins 10 | 11 | .env*.local 12 | 13 | .env 14 | .vscode 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 79, 3 | "tabWidth": 2, 4 | "semi": true, 5 | "trailingComma": "es5", 6 | "bracketSpacing": false, 7 | "arrowParens": "always" 8 | } 9 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | plugins: 4 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 5 | spec: "@yarnpkg/plugin-workspace-tools" 6 | 7 | yarnPath: .yarn/releases/yarn-3.2.0.cjs 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gel UI 2 | 3 | This monorepo is the home of Gel UI and all related UI components 4 | that it shares with the Gel website and cloud. 5 | 6 | If you are just looking to use Gel UI: it already comes bundled with 7 | the Gel server, and opening it is as simple as running the command 8 | `gel ui` from your project directory. 9 | 10 | ## Contributing 11 | 12 | This repo is organised using yarn workspaces as follows: 13 | 14 | - `/web`: This is the main workspace of Gel UI that is bundled with the 15 | Gel server. Refer to <./web/readme.md> for instructions on how to build 16 | and develop Gel UI. 17 | 18 | - `/shared`: This directory contains all the shared components used by Gel 19 | UI and across the website and cloud. Each subdirectory is it's own 20 | workspace; the most notable being `studio`, which contains the REPL, schema 21 | viewer and data viewer/editor components. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "license": "Apache-2.0", 4 | "workspaces": { 5 | "packages": [ 6 | "web", 7 | "shared/*" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /shared/codeEditor/codeEditor.module.scss: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400&display=swap"); 2 | @import "../common/mixins.scss"; 3 | 4 | .codeEditor { 5 | display: contents; 6 | 7 | :global(.cm-content) { 8 | user-select: text; 9 | } 10 | 11 | &.terminalCursor { 12 | :global { 13 | .cm-cursorLayer { 14 | animation: none !important; 15 | z-index: -1 !important; 16 | } 17 | 18 | .cm-cursor { 19 | border: 0; 20 | margin: 0; 21 | width: 1ch; 22 | background: var(--cursor-color); 23 | } 24 | } 25 | } 26 | 27 | .terminalCursorMark { 28 | &, 29 | & * { 30 | color: var(--cursor-text-color) !important; 31 | } 32 | } 33 | 34 | .warningUnderline { 35 | background-repeat: repeat-x; 36 | background-position: bottom; 37 | background-image: url("data:image/svg+xml,%3Csvg width='6' height='3' viewBox='0 0 6 3' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0.82843C0.511845 0.82843 1.02369 1.02369 1.41421 1.41422L1.58579 1.58579C2.36683 2.36684 3.63316 2.36684 4.41421 1.58579L4.58579 1.41422C4.97631 1.02369 5.48816 0.828431 6 0.828431' stroke='%23d1a131'/%3E%3C/svg%3E%0A"); 38 | padding-bottom: 1px; 39 | } 40 | 41 | .errorUnderline { 42 | background-repeat: repeat-x; 43 | background-position: bottom; 44 | background-image: url("data:image/svg+xml,%3Csvg width='6' height='3' viewBox='0 0 6 3' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0.82843C0.511845 0.82843 1.02369 1.02369 1.41421 1.41422L1.58579 1.58579C2.36683 2.36684 3.63316 2.36684 4.41421 1.58579L4.58579 1.41422C4.97631 1.02369 5.48816 0.828431 6 0.828431' stroke='%23E91919'/%3E%3C/svg%3E%0A"); 45 | padding-bottom: 1px; 46 | } 47 | 48 | .errorLineHighlight { 49 | background: rgba(255, 0, 0, 0.15); 50 | 51 | &:global(.cm-activeLine) { 52 | background: rgba(255, 0, 0, 0.2); 53 | } 54 | } 55 | 56 | .explainContextMark { 57 | } 58 | 59 | .explainContextSnippet { 60 | background: #e1e1e1; 61 | border-radius: 4px; 62 | padding: 2px 1px; 63 | font-style: italic; 64 | 65 | &:before { 66 | content: " := "; 67 | } 68 | 69 | @include darkTheme { 70 | background: #3a3a3a; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /shared/codeEditor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@edgedb/code-editor", 3 | "private": true, 4 | "version": "0.0.0", 5 | "license": "UNLICENSED", 6 | "peerDependencies": { 7 | "react": "^18.0.0", 8 | "react-dom": "^18.0.0" 9 | }, 10 | "devDependencies": { 11 | "@codemirror/autocomplete": "^6.0.0", 12 | "@codemirror/commands": "^6.0.0", 13 | "@codemirror/language": "^6.0.0", 14 | "@codemirror/state": "^6.0.0", 15 | "@codemirror/view": "^6.0.0", 16 | "@replit/codemirror-indentation-markers": "^6.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /shared/codeEditor/styles.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.module.scss" { 2 | const classes: {[key: string]: string}; 3 | export default classes; 4 | } 5 | -------------------------------------------------------------------------------- /shared/codeEditor/terminalCursor.ts: -------------------------------------------------------------------------------- 1 | import {RangeSet} from "@codemirror/state"; 2 | import {Decoration, DecorationSet, ViewPlugin} from "@codemirror/view"; 3 | 4 | import styles from "./codeEditor.module.scss"; 5 | 6 | export function cursorPlugin() { 7 | let decorations: DecorationSet = RangeSet.empty; 8 | return ViewPlugin.define( 9 | () => ({ 10 | update: (update) => { 11 | const cursor = update.state.selection.main.head; 12 | decorations = update.view.hasFocus 13 | ? RangeSet.of( 14 | Decoration.mark({ 15 | class: styles.terminalCursorMark, 16 | }).range(cursor, cursor + 1) 17 | ) 18 | : RangeSet.empty; 19 | }, 20 | }), 21 | { 22 | decorations: () => decorations, 23 | } 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /shared/codeEditor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "strict": true, 5 | "downlevelIteration": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /shared/common/branchGraph/utils.ts: -------------------------------------------------------------------------------- 1 | export function tidyIndents(code: string) { 2 | return code 3 | .trim() 4 | .replace( 5 | /\n( {4})+/g, 6 | (match) => "\n" + match.slice(1, 1 + (match.length - 1) / 2) 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /shared/common/components/dataGrid/utils.ts: -------------------------------------------------------------------------------- 1 | export function calculateInitialColWidths( 2 | cols: {id: string; typename: string; isLink: boolean}[], 3 | gridWidth: number 4 | ): {[id: string]: number} { 5 | const colWidths: {[id: string]: number} = {}; 6 | const unsizedCols: string[] = []; 7 | let totalWidth = 0; 8 | for (const col of cols) { 9 | if (col.isLink) { 10 | colWidths[col.id] = 280; 11 | continue; 12 | } 13 | const width = sizedColTypes[col.typename]; 14 | if (width != null) { 15 | colWidths[col.id] = width; 16 | totalWidth += width; 17 | } else { 18 | unsizedCols.push(col.id); 19 | } 20 | } 21 | if (unsizedCols.length) { 22 | const width = Math.max( 23 | 200, 24 | Math.min(480, (gridWidth - totalWidth) / unsizedCols.length) 25 | ); 26 | for (const colId of unsizedCols) { 27 | colWidths[colId] = width; 28 | } 29 | } 30 | 31 | return colWidths; 32 | } 33 | 34 | const sizedColTypes: {[typename: string]: number} = { 35 | "std::uuid": 230, 36 | "std::int16": 100, 37 | "std::int32": 130, 38 | "std::int64": 200, 39 | "std::float32": 130, 40 | "std::float64": 200, 41 | "std::decimal": 200, 42 | "std::bigint": 200, 43 | "std::bool": 100, 44 | "std::datetime": 320, 45 | "cal::local_datetime": 280, 46 | "cal::local_date": 130, 47 | "cal::local_time": 170, 48 | "std::cal::local_datetime": 280, 49 | "std::cal::local_date": 130, 50 | "std::cal::local_time": 170, 51 | "std::duration": 200, 52 | "cal::relative_duration": 280, 53 | "cal::date_duration": 100, 54 | "std::cal::relative_duration": 280, 55 | "std::cal::date_duration": 100, 56 | "cfg::memory": 100, 57 | }; 58 | -------------------------------------------------------------------------------- /shared/common/components/infoCards/getLatestInfo.ts: -------------------------------------------------------------------------------- 1 | import useSWR from "swr"; 2 | import {z} from "zod"; 3 | 4 | const latestInfoUrl = 5 | (import.meta as any).env?.VITE_LATEST_INFO_URL || 6 | "https://www.geldata.com/latestInfo.json"; 7 | 8 | const latestInfoType = z.object({ 9 | latestBlogPost: z.object({ 10 | title: z.string(), 11 | url: z.string(), 12 | publishedTimestamp: z.number(), 13 | imageUrl: z.string(), 14 | imageBrightness: z.number(), 15 | }), 16 | latestUpdate: z.object({ 17 | title: z.string(), 18 | url: z.string(), 19 | }), 20 | latestEdgeDBVersion: z.string().transform((ver) => { 21 | const [major, minor] = ver.split(".").map((n) => parseInt(n, 10)); 22 | return {major, minor}; 23 | }), 24 | }); 25 | 26 | export type LatestInfo = z.infer; 27 | 28 | async function fetchLatestInfo() { 29 | const res = await fetch(latestInfoUrl); 30 | if (!res.ok) { 31 | throw new Error( 32 | `fetching latest info failed with error: ${res.status} ${res.statusText}` 33 | ); 34 | } 35 | try { 36 | return latestInfoType.parse(await res.json()); 37 | } catch (e) { 38 | console.error(e); 39 | throw e; 40 | } 41 | } 42 | 43 | export function useLatestInfo() { 44 | const {data} = useSWR("_latestInfo", fetchLatestInfo, { 45 | revalidateOnFocus: false, 46 | }); 47 | 48 | return data; 49 | } 50 | -------------------------------------------------------------------------------- /shared/common/components/resultGrid/resultGrid.module.scss: -------------------------------------------------------------------------------- 1 | @import "@edgedb/common/mixins.scss"; 2 | 3 | .header { 4 | position: relative; 5 | padding: 8px 12px; 6 | overflow: clip; 7 | font-size: 13px; 8 | white-space: nowrap; 9 | 10 | &.hasSubheaders { 11 | color: var(--secondary_text_color); 12 | border-bottom: 1px solid var(--panel_border); 13 | margin: 0 8px; 14 | padding: 8px 4px; 15 | 16 | .headerName { 17 | position: sticky; 18 | left: 12px; 19 | width: max-content; 20 | } 21 | } 22 | 23 | .typename { 24 | font-size: 11px; 25 | color: var(--tertiary_text_color); 26 | margin-top: 2px; 27 | } 28 | } 29 | 30 | .cellContent { 31 | height: 39px; 32 | line-height: 38px; 33 | padding: 0 12px; 34 | white-space: nowrap; 35 | overflow: hidden; 36 | 37 | &.stickyCell { 38 | position: sticky; 39 | top: var(--gridHeaderHeight); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /shared/common/decodeRawBuffer/index.ts: -------------------------------------------------------------------------------- 1 | import {_CodecsRegistry, _ReadBuffer, _ICodec, Options} from "gel"; 2 | import type {ProtocolVersion} from "gel/dist/ifaces"; 3 | 4 | export type EdgeDBSet = Array & {_codec: _ICodec}; 5 | 6 | export function decode( 7 | registry: InstanceType, 8 | outCodecBuf: Uint8Array, 9 | resultBuf: Uint8Array, 10 | options: Options, 11 | protocolVersion: ProtocolVersion = [0, 10] 12 | ): EdgeDBSet | null { 13 | let result: EdgeDBSet | null = null; 14 | 15 | if (outCodecBuf.length) { 16 | const codec = registry.buildCodec(outCodecBuf, protocolVersion); 17 | 18 | result = new Array() as EdgeDBSet; 19 | 20 | const buf = new _ReadBuffer(resultBuf); 21 | const codecReadBuf = _ReadBuffer.alloc(); 22 | while (buf.length > 0) { 23 | const msgType = buf.readUInt8(); 24 | const len = buf.readUInt32(); 25 | 26 | if (msgType !== 68 || len <= 4) { 27 | throw new Error("invalid data packet"); 28 | } 29 | 30 | buf.sliceInto(codecReadBuf, len - 4); 31 | codecReadBuf.discard(6); 32 | const val = codec.decode(codecReadBuf, options.makeCodecContext()); 33 | result.push(val); 34 | } 35 | 36 | result._codec = codec; 37 | } 38 | 39 | return result; 40 | } 41 | -------------------------------------------------------------------------------- /shared/common/fonts/edgedb-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geldata/gel-ui/f6c57edd80c7d05a1d6cac9ae1f02160e2b8eff5/shared/common/fonts/edgedb-300.woff2 -------------------------------------------------------------------------------- /shared/common/fonts/edgedb-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geldata/gel-ui/f6c57edd80c7d05a1d6cac9ae1f02160e2b8eff5/shared/common/fonts/edgedb-400.woff2 -------------------------------------------------------------------------------- /shared/common/fonts/edgedb-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geldata/gel-ui/f6c57edd80c7d05a1d6cac9ae1f02160e2b8eff5/shared/common/fonts/edgedb-500.woff2 -------------------------------------------------------------------------------- /shared/common/fonts/edgedb-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geldata/gel-ui/f6c57edd80c7d05a1d6cac9ae1f02160e2b8eff5/shared/common/fonts/edgedb-600.woff2 -------------------------------------------------------------------------------- /shared/common/fonts/edgedb-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geldata/gel-ui/f6c57edd80c7d05a1d6cac9ae1f02160e2b8eff5/shared/common/fonts/edgedb-700.woff2 -------------------------------------------------------------------------------- /shared/common/fonts/include.scss: -------------------------------------------------------------------------------- 1 | @mixin font($name, $fn, $weight, $italic: false) { 2 | @font-face { 3 | font-family: $name; 4 | font-weight: $weight; 5 | font-display: swap; 6 | 7 | @if $italic { 8 | font-style: italic; 9 | src: url("./#{$fn}-#{$weight}italic.woff2") format("woff2"); 10 | } @else { 11 | font-style: normal; 12 | src: url("./#{$fn}-#{$weight}.woff2") format("woff2"); 13 | } 14 | } 15 | } 16 | 17 | @each $weight in 300, 400, 500, 600, 700 { 18 | @include font("EdgeDB", "edgedb", $weight); 19 | } 20 | -------------------------------------------------------------------------------- /shared/common/hooks/globalDragCursor/globalDragCursor.module.scss: -------------------------------------------------------------------------------- 1 | .globalDragCursorOverlay { 2 | position: fixed; 3 | inset: 0; 4 | z-index: 999999; 5 | } 6 | -------------------------------------------------------------------------------- /shared/common/hooks/globalDragCursor/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createContext, 3 | PropsWithChildren, 4 | useState, 5 | useContext, 6 | CSSProperties, 7 | } from "react"; 8 | 9 | import styles from "./globalDragCursor.module.scss"; 10 | 11 | type Cursor = Exclude; 12 | 13 | const GlobalDragCursorContext = createContext< 14 | [Cursor | null, (val: Cursor | null) => void] | null 15 | >(null); 16 | 17 | export function GlobalDragCursorProvider({children}: PropsWithChildren<{}>) { 18 | const state = useState(null); 19 | 20 | return ( 21 | <> 22 | 23 | {children} 24 | 25 | {state[0] ? ( 26 |
30 | ) : null} 31 | 32 | ); 33 | } 34 | 35 | export function useGlobalDragCursor() { 36 | return useContext(GlobalDragCursorContext)!; 37 | } 38 | -------------------------------------------------------------------------------- /shared/common/hooks/useDragHandler.ts: -------------------------------------------------------------------------------- 1 | import {useCallback} from "react"; 2 | 3 | export interface Position { 4 | x: number; 5 | y: number; 6 | } 7 | 8 | export function useDragHandler( 9 | setup: () => { 10 | onStart: ( 11 | initialMousePos: Position, 12 | event: React.MouseEvent, 13 | customParams?: T 14 | ) => void; 15 | onMove: ( 16 | currentMousePos: Position, 17 | isFirstMove: boolean, 18 | customParams?: T 19 | ) => void; 20 | onEnd?: (didMove: boolean, customParams?: T) => void; 21 | }, 22 | deps: React.DependencyList = [] 23 | ) { 24 | return useCallback((event: React.MouseEvent, customParams?: T) => { 25 | const {onStart, onMove, onEnd} = setup(); 26 | 27 | onStart( 28 | { 29 | x: event.clientX, 30 | y: event.clientY, 31 | }, 32 | event, 33 | customParams 34 | ); 35 | 36 | let hasMoved = false; 37 | const mousemove = (e: MouseEvent) => { 38 | onMove( 39 | { 40 | x: e.clientX, 41 | y: e.clientY, 42 | }, 43 | !hasMoved, 44 | customParams 45 | ); 46 | hasMoved = true; 47 | }; 48 | 49 | window.addEventListener("mousemove", mousemove, {capture: true}); 50 | window.addEventListener( 51 | "mouseup", 52 | () => { 53 | window.removeEventListener("mousemove", mousemove, { 54 | capture: true, 55 | }); 56 | onEnd?.(hasMoved, customParams); 57 | }, 58 | {capture: true, once: true} 59 | ); 60 | }, deps); 61 | } 62 | -------------------------------------------------------------------------------- /shared/common/hooks/useInitialValue.ts: -------------------------------------------------------------------------------- 1 | import {useState} from "react"; 2 | 3 | export function useInitialValue(valueGetter: () => T): T { 4 | const [value] = useState(valueGetter); 5 | 6 | return value; 7 | } 8 | -------------------------------------------------------------------------------- /shared/common/hooks/useKeyboardShortcut.ts: -------------------------------------------------------------------------------- 1 | import {useEffect} from "react"; 2 | 3 | export function useKeyboardShortcut(key: string | null, callback: Function) { 4 | useEffect(() => { 5 | if (key == null) return; 6 | 7 | const handleKeyDown = (event: KeyboardEvent) => { 8 | const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; 9 | const isKeyPressed = event.key === key; 10 | 11 | if ( 12 | (isMac && event.metaKey && isKeyPressed) || 13 | (!isMac && event.ctrlKey && isKeyPressed) 14 | ) { 15 | event.preventDefault(); 16 | callback(); 17 | } 18 | }; 19 | 20 | window.addEventListener("keydown", handleKeyDown); 21 | 22 | return () => { 23 | window.removeEventListener("keydown", handleKeyDown); 24 | }; 25 | }, [key, callback]); 26 | } 27 | -------------------------------------------------------------------------------- /shared/common/hooks/useMobile.ts: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from "react"; 2 | 3 | const mediaQuery = window.matchMedia("(max-width: 767.5px)"); 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = useState(() => mediaQuery.matches); 7 | 8 | useEffect(() => { 9 | const listener = (ev: MediaQueryListEvent) => setIsMobile(ev.matches); 10 | 11 | mediaQuery.addEventListener("change", listener); 12 | 13 | return () => { 14 | mediaQuery.removeEventListener("change", listener); 15 | }; 16 | }, []); 17 | 18 | return isMobile; 19 | } 20 | -------------------------------------------------------------------------------- /shared/common/hooks/usePersistedState.ts: -------------------------------------------------------------------------------- 1 | import {useCallback, useState} from "react"; 2 | 3 | const stateCache: {[key: string]: any} = {}; 4 | 5 | export function usePersistedState( 6 | key: string, 7 | initialState: S | (() => S) 8 | ): [S, (value: S) => void] { 9 | const persisted: S | null = 10 | stateCache[key] ?? JSON.parse(localStorage.getItem(key) ?? "null"); 11 | 12 | const [state, _setState] = useState(persisted ?? initialState); 13 | 14 | const setState = useCallback( 15 | (value: S) => { 16 | stateCache[key] = value; 17 | localStorage.setItem(key, JSON.stringify(value)); 18 | _setState(value); 19 | }, 20 | [key, state] 21 | ); 22 | 23 | return [state, setState]; 24 | } 25 | -------------------------------------------------------------------------------- /shared/common/hooks/useResize.ts: -------------------------------------------------------------------------------- 1 | import {DependencyList, useLayoutEffect} from "react"; 2 | 3 | export function useResize( 4 | ref: React.RefObject | Element | null, 5 | onResize: (rect: DOMRect) => void, 6 | deps: DependencyList = [] 7 | ): void { 8 | useLayoutEffect(() => { 9 | const _ref = ref instanceof Element ? ref : ref?.current; 10 | if (_ref) { 11 | const resizeObserver = new ResizeObserver((entries) => { 12 | if (entries[0]) { 13 | onResize(entries[0].contentRect); 14 | } 15 | }); 16 | 17 | resizeObserver.observe(_ref); 18 | 19 | return () => { 20 | resizeObserver.disconnect(); 21 | }; 22 | } 23 | }, [ref, ...deps]); 24 | } 25 | -------------------------------------------------------------------------------- /shared/common/hooks/useTheme/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin darkTheme { 2 | :global(.dark-theme) & { 3 | @content; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /shared/common/hooks/useTooltips.tsx: -------------------------------------------------------------------------------- 1 | import {createContext, PropsWithChildren, useState, useContext} from "react"; 2 | 3 | const GlobalTooltipsContext = createContext< 4 | [boolean, React.Dispatch>] 5 | >(null!); 6 | 7 | export function GlobalTooltipsProvider({children}: PropsWithChildren<{}>) { 8 | const [showTooltips, setShowTooltips] = useState(true); 9 | 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | } 16 | 17 | export function useTooltips() { 18 | const context = useContext(GlobalTooltipsContext); 19 | 20 | if (!context) { 21 | throw new Error( 22 | "useTooltips must be used within a GlobalTooltipsProvider" 23 | ); 24 | } 25 | 26 | return context; 27 | } 28 | -------------------------------------------------------------------------------- /shared/common/index.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /shared/common/mixins.scss: -------------------------------------------------------------------------------- 1 | @use "@sass-fairy/url"; 2 | @import "./hooks/useTheme/mixin.scss"; 3 | 4 | @mixin hideScrollbar { 5 | -ms-overflow-style: none; /* IE and Edge */ 6 | scrollbar-width: none; 7 | 8 | &::-webkit-scrollbar { 9 | display: none; 10 | } 11 | } 12 | 13 | @mixin customScrollbar { 14 | &::-webkit-scrollbar, 15 | &::-webkit-scrollbar-corner { 16 | width: 12px; 17 | height: 12px; 18 | background: transparent; 19 | } 20 | 21 | &::-webkit-scrollbar-thumb { 22 | background: rgba(255, 255, 255, 0.2); 23 | border-radius: 8px; 24 | border: 3px solid transparent; 25 | background-clip: padding-box; 26 | } 27 | } 28 | 29 | @function dashedBorderBg($stroke, $width: 1, $radius: 8, $dash-array: "4,4") { 30 | @return url.svg( 31 | '' '' 32 | '' 33 | "" 34 | '" "" 37 | ); 38 | } 39 | 40 | $breakpoints: ( 41 | mobile: 768px, 42 | tablet: 1024px, 43 | ); 44 | 45 | @mixin breakpoint($breakpoint) { 46 | @if map-has-key($breakpoints, $breakpoint) { 47 | $breakpoint-value: map-get($breakpoints, $breakpoint); 48 | // Subtracts 0.5px to make breakpoint exclusive (width < x) instead of 49 | // inclusive (width <= x) 50 | // (Uses -0.5px instead of -1px due to incorrect behaviour on some 51 | // systems ie. win10 with fractional display scaling ¯\_(ツ)_/¯) 52 | @media (max-width: #{$breakpoint-value - 0.5px}) { 53 | @content; 54 | } 55 | } @else { 56 | @warn 'Invalid breakpoint: #{$breakpoint}.'; 57 | } 58 | } 59 | 60 | @mixin isMobile { 61 | @media (max-width: 767.5px) { 62 | @content; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /shared/common/newui/button/button.module.scss: -------------------------------------------------------------------------------- 1 | @import "@edgedb/common/mixins.scss"; 2 | 3 | .button, 4 | .linkButton { 5 | box-sizing: border-box; 6 | display: flex; 7 | flex-shrink: 0; 8 | height: 36px; 9 | padding: 0px 10px; 10 | justify-content: center; 11 | align-items: center; 12 | gap: 2px; 13 | border-radius: 6px; 14 | border: 1px solid #e6e6e6; 15 | background: #fff; 16 | box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.04); 17 | outline: 0; 18 | font-family: inherit; 19 | color: inherit; 20 | cursor: pointer; 21 | font-size: 14px; 22 | font-style: normal; 23 | font-weight: 500; 24 | line-height: 24px; 25 | letter-spacing: 0.1pt; 26 | 27 | & > span { 28 | padding: 0 6px; 29 | } 30 | 31 | & > svg { 32 | flex-shrink: 0; 33 | } 34 | 35 | .shortcut { 36 | opacity: 0.85; 37 | font-size: 13px; 38 | margin-left: 8px; 39 | } 40 | 41 | &.primary { 42 | // background: linear-gradient(180deg, #a565cd 0%, #9c56b4 100%); 43 | background: var(--buttonPrimaryBackground, #a565cd); 44 | box-shadow: 0px 2px 4px 0px 45 | color-mix( 46 | in srgb, 47 | var(--buttonPrimaryBackground, #a565cd) 25%, 48 | transparent 49 | ); 50 | border: 0; 51 | color: #fffffffa; 52 | } 53 | 54 | &.outline { 55 | background: none; 56 | border: 1px solid var(--Grey85, #d9d9d9); 57 | box-shadow: none; 58 | color: var(--secondary_text_color, #666); 59 | } 60 | 61 | &:disabled, 62 | &.disabled { 63 | opacity: 0.65; 64 | box-shadow: none; 65 | pointer-events: none; 66 | } 67 | 68 | @include darkTheme { 69 | border: 1px solid #4d4d4d; 70 | background: #363636; 71 | color: #ccc; 72 | box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.16); 73 | 74 | &.primary { 75 | background: var(--buttonPrimaryBackground, #6c4186); 76 | box-shadow: 0px 2px 4px 0px 77 | color-mix( 78 | in srgb, 79 | var(--buttonPrimaryBackground, #6c4186) 40%, 80 | transparent 81 | ); 82 | border: 0; 83 | color: #ffffffdb; 84 | } 85 | 86 | &.outline { 87 | background: none; 88 | box-shadow: none; 89 | } 90 | } 91 | } 92 | 93 | .linkButton { 94 | text-decoration: none; 95 | color: inherit; 96 | } 97 | -------------------------------------------------------------------------------- /shared/common/newui/checkbox/checkbox.module.scss: -------------------------------------------------------------------------------- 1 | @use "@sass-fairy/url"; 2 | @import "@edgedb/common/mixins.scss"; 3 | 4 | .checkbox { 5 | display: flex; 6 | height: 32px; 7 | align-items: center; 8 | gap: 6px; 9 | padding: 0 4px; 10 | color: var(--main_text_color, #4d4d4d); 11 | font-size: 14px; 12 | font-style: normal; 13 | font-weight: 500; 14 | line-height: 24px; 15 | cursor: pointer; 16 | 17 | .check { 18 | width: 20px; 19 | height: 20px; 20 | flex-shrink: 0; 21 | border-radius: 6px; 22 | border: 1px solid var(--Grey85, #d9d9d9); 23 | background: #fff; 24 | box-sizing: border-box; 25 | } 26 | 27 | input { 28 | display: none; 29 | 30 | &:checked + .check { 31 | border: 0; 32 | background-color: #a15ec0; 33 | background-image: url.svg( 34 | '' 35 | '' 36 | "" 37 | ); 38 | } 39 | } 40 | 41 | &.disabled { 42 | opacity: 0.5; 43 | pointer-events: none; 44 | } 45 | 46 | &.readonly { 47 | pointer-events: none; 48 | 49 | .check { 50 | opacity: 0.5; 51 | } 52 | } 53 | 54 | @include darkTheme { 55 | color: #ccc; 56 | 57 | .check { 58 | border-color: var(--Grey40, #666); 59 | background: #363636; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /shared/common/newui/checkbox/index.tsx: -------------------------------------------------------------------------------- 1 | import cn from "@edgedb/common/utils/classNames"; 2 | 3 | import styles from "./checkbox.module.scss"; 4 | 5 | export type CheckboxProps = { 6 | className?: string; 7 | label?: string | JSX.Element; 8 | checked: boolean; 9 | disabled?: boolean; 10 | readOnly?: Readonly; 11 | } & (Readonly extends true 12 | ? { 13 | onChange?: (checked: boolean) => void; 14 | } 15 | : { 16 | onChange: (checked: boolean) => void; 17 | }); 18 | 19 | export function Checkbox({ 20 | className, 21 | label, 22 | checked, 23 | onChange, 24 | disabled, 25 | readOnly, 26 | }: CheckboxProps) { 27 | return ( 28 |