diff --git a/.erb/configs/webpack.config.main.prod.ts b/.erb/configs/webpack.config.main.prod.ts index d8ab5cd..214ccf9 100644 --- a/.erb/configs/webpack.config.main.prod.ts +++ b/.erb/configs/webpack.config.main.prod.ts @@ -56,7 +56,6 @@ const configuration: webpack.Configuration = { */ new webpack.EnvironmentPlugin({ NODE_ENV: 'production', - DEBUG_PROD: false, START_MINIMIZED: false, }), diff --git a/.eslintrc.js b/.eslintrc.js index 7e8be13..f1a5ad7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,14 +4,31 @@ module.exports = { // A temporary hack related to IDE not resolving correct package.json 'import/no-extraneous-dependencies': 'off', 'import/no-unresolved': 'error', + 'import/no-cycle': 'off', + 'import/prefer-default-export': 'off', // Since React 17 and typescript 4.1 you can safely disable the rule 'react/react-in-jsx-scope': 'off', 'react/jsx-props-no-spreading': 'off', - 'import/no-cycle': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', 'react/require-default-props': [2, { functions: 'defaultArguments' }], - 'import/prefer-default-export': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/naming-convention': [ + 'error', + { + format: null, + selector: 'variable', + leadingUnderscore: 'allow', + }, + ], + 'prettier/prettier': 'off', }, parserOptions: { ecmaVersion: 2020, diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 05e3961..1b9e7bd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,6 +20,8 @@ jobs: node-version: 14 - name: Install Packages run: npm install + - name: Add Rollbar + run: echo export default "'${{ secrets.rollbar_token }}'" > ./src/renderer/lib/rollbarAccessToken.ts - name: Build and Test run: npm run build && npm test - name: Lint diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..857a75d --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,9 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +while read local_ref local_sha remote_ref remote_sha +do + npx @codiga/cli git-push-hook --remote-sha $remote_sha --local-sha $local_sha +done + +exit 0 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 2344832..3599f2c 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -26,6 +26,17 @@ To set up the `APPLE_ID` and `APPLE_ID_PASS` variables: - `APPLE_ID` is your Apple account - `APPLE_ID_PASS` is a password generated on the [Apple ID page](https://appleid.apple.com/account/manage) in App-Specific Passwords +## Uploading to the Microsoft Store + +Detailed step-by-step instructions can be found in this [blog post](https://www.codiga.io/blog/submit-electron-app-to-microsoft-store/). + +- Download the `.exe` file from [our latest releases](https://github.com/codiga/code-snippets-manager/releases/latest) +- Download the [MSIX Packaging Tool](https://apps.microsoft.com/store/detail/msix-packaging-tool/9N5LW3JBCXKF) +- Update the "Package Information" per the blog post instructions above +- Create your new `.msix` file +- Upload your new file and make any necessary changes to the submission +- Submit your new submission + ## Contact If you have any question, please ask on our [Slack channel](https://join.slack.com/t/codigahq/shared_invite/zt-9hvmfwie-9BUVFwZDwvpIGlkHv2mzYQ) diff --git a/README.md b/README.md index d1995ca..e4382fc 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,213 @@ -# Codiga - Electron App +[Datadog acquired Codiga](https://www.codiga.io/blog/codiga-joins-datadog/). This repository is no longer active. + +![Codiga Code Snippet Manager](img/codiga-banner.jpg) + +# Codiga Code Snippets Manager +### The easiest solution to search, manage and view your code snippets for Windows, MacOS and Linux. + +[![Slack](https://img.shields.io/badge/Slack-@codigahq.svg?logo=slack)](https://join.slack.com/t/codigahq/shared_invite/zt-9hvmfwie-9BUVFwZDwvpIGlkHv2mzYQ) +[![Twitter](https://img.shields.io/badge/Twitter-getcodiga-blue?logo=twitter&logoColor=blue&color=blue)](https://twitter.com/getcodiga) +[![Code Grade](https://api.codiga.io/project/34505/status/svg)](https://app.codiga.io/hub/project/34505/code-snippets-manager) +[![Code Quality](https://api.codiga.io/project/34505/score/svg)](https://app.codiga.io/hub/project/34505/code-snippets-manager) + +--- + +| -------------------- Contents ----------------------- | +| --------------------------------------------------------------------------------------- | +| ![Windows](img/ico-win.png) - [Install Codiga Code Snippets Manager for Windows](#win) | +| - Download Code Snippets Manager from GitHub | +| - Generate an API token | +| - Login the Code Snippets Manager | +| ![Mac](img/ico-mac.png) - [Install Codiga Code Snippets Manager for MacOS](#mac) | +| - Download Code Snippets Manager from GitHub | +| - Generate an API token | +| - Login the Code Snippets Manager | +| ![Linux](img/ico-linux.png) - [Install Codiga Code Snippets Manager for Linux](#lin) | +| - Download Code Snippets Manager from Snapcraft.io | +| - Download Code Snippets Manager from GitHub | +| - Generate an API token | +| - Login the Code Snippets Manager | + +--- + +## Install Codiga Code Snippets Manager for Windows + +### Download Code Snippets Manager from GitHub + +**Step 1** + +In your browser, go to https://github.com/codiga/code-snippets-manager/releases/latest + +![Codiga Code Snippet Manager](img/codiga-01.jpg) + +**Step 2** + +Scroll down to `Codiga-Setup-.exe` and click to download + +![Codiga Code Snippet Manager](img/codiga-02.jpg) + +### Generate an API token + +**Step 1** + +In your browser, go to https://app.codiga.io/api-tokens and click on the **Create Token** button on the top right + +![Codiga Code Snippet Manager](img/codiga-03.jpg) + +**Step 2** + +Type a description name for your API token and click Create Token + +![Codiga Code Snippet Manager](img/codiga-04.jpg) + +**Step 3** + +Copy your new API key. **Note:** This token **will not** be displayed again. + +![Codiga Code Snippet Manager](img/codiga-05.jpg) + +### Login the Code Snippets Manager + +**Step 1** + +In your computer, double click to open the downloaded file `Codiga-Setup-.exe` to install the Code Snippets Manager. After installed, open the app and click on Login in the top right. + +![Codiga Code Snippet Manager](img/codiga-06.jpg) + +**Step 2** + +In the modal, add your Codiga API Token and click on Login. + +![Codiga Code Snippet Manager](img/codiga-07.jpg) + +--- + +## Codiga Code Snippets Manager for MacOS + +### Download Code Snippets Manager from GitHub + +**Step 1** + +In your browser, go to https://github.com/codiga/code-snippets-manager/releases/latest + +![Codiga Code Snippet Manager](img/codiga-01.jpg) + +**Step 2** + +a. If your Mac is Intel based, click on `Codiga-Setup-.dmg` to download + +![Codiga Code Snippet Manager](img/codiga-08.jpg) + +b. If your Mac is a M1 or M2, click on `Codiga-Setup--arm64.dmg` to download + +![Codiga Code Snippet Manager](img/codiga-09.jpg) + +### Generate an API token + +**Step 1** + +In your browser, go to https://app.codiga.io/api-tokens and click on the **Create Token** button on the top right + +![Codiga Code Snippet Manager](img/codiga-03.jpg) + +**Step 2** + +Type a description name for your API token and click Create Token + +![Codiga Code Snippet Manager](img/codiga-04.jpg) + +**Step 3** + +Copy your new API key. **Note:** This token **will not** be displayed again. + +![Codiga Code Snippet Manager](img/codiga-05.jpg) + +### Login the Code Snippets Manager + +**Step 1** + +In your computer, double click to open the downloaded file `Codiga-Setup-.dmg` or `Codiga-Setup--arm64.dmg` to install the Code Snippets Manager. After installed, open the app and click on Login in the top right. + +![Codiga Code Snippet Manager](img/codiga-06.jpg) + +**Step 2** + +In the modal, add your Codiga API Token and click on Login. + +![Codiga Code Snippet Manager](img/codiga-07.jpg) + +--- + +## Codiga Code Snippets Manager for Linux + +### Download Code Snippets Manager from Snapcraft.io + +**Step 1** + +In your browser, go to https://snapcraft.io and search for Codiga, or click the badge below [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/codiga) +![Codiga Code Snippet Manager](img/codiga-10.jpg) + +**Step 2** + +Choose your Linux distribution to get detailed installation instructions. If yours is not shown, get more details on the [installing snapd documentation.](https://snapcraft.io/docs/installing-snapd) -This is the Codiga Electron App. Search, manage, view your code snippets within this app. +![Codiga Code Snippet Manager](img/codiga-11.jpg) -## Installation +### Download Code Snippets Manager from GitHub -Installation instructions coming soon. +**Step 1** -## Getting Started +In your browser, go to https://github.com/codiga/code-snippets-manager/releases/latest -- Clone the repo -- Install the packages -- `yarn start` +![Codiga Code Snippet Manager](img/codiga-01.jpg) + +**Step 2** + +Scroll down and click on `Codiga-Setup-.AppImage` to download + +![Codiga Code Snippet Manager](img/codiga-12.jpg) + +### Generate an API token + +**Step 1** + +In your browser, go to https://app.codiga.io/api-tokens and click on the **Create Token** button on the top right + +![Codiga Code Snippet Manager](img/codiga-03.jpg) + +**Step 2** + +Type a description name for your API token and click Create Token + +![Codiga Code Snippet Manager](img/codiga-04.jpg) + +**Step 3** + +Copy your new API key. **Note:** This token **will not** be displayed again. + +![Codiga Code Snippet Manager](img/codiga-05.jpg) + +### Login the Code Snippets Manager + +**Step 1** + +In your computer, double click to open the downloaded file `Codiga-Setup-0.0.3.AppImage` to install the Code Snippets Manager. After installed, open the app and click on Login in the top right. + +![Codiga Code Snippet Manager](img/codiga-06.jpg) + +**Step 2** + +In the modal, add your Codiga API Token and click on Login. + +![Codiga Code Snippet Manager](img/codiga-07.jpg) + +--- ## Acknowledgments This app was built using the great [Electron React Boilerplate](https://github.com/electron-react-boilerplate/electron-react-boilerplate). + +Codiga – All rights reserved 2022. diff --git a/assets/icon.icns b/assets/icon.icns index 33d14a3..0ec58da 100644 Binary files a/assets/icon.icns and b/assets/icon.icns differ diff --git a/assets/icon.ico b/assets/icon.ico index 111184b..6e4c777 100644 Binary files a/assets/icon.ico and b/assets/icon.ico differ diff --git a/codiga.yml b/codiga.yml new file mode 100644 index 0000000..ca0f09b --- /dev/null +++ b/codiga.yml @@ -0,0 +1,4 @@ +rulesets: + - jsx-a11y + - jsx-react + - react-best-practices \ No newline at end of file diff --git a/img/codiga-01.jpg b/img/codiga-01.jpg new file mode 100644 index 0000000..5242f12 Binary files /dev/null and b/img/codiga-01.jpg differ diff --git a/img/codiga-02.jpg b/img/codiga-02.jpg new file mode 100644 index 0000000..fabd41f Binary files /dev/null and b/img/codiga-02.jpg differ diff --git a/img/codiga-03.jpg b/img/codiga-03.jpg new file mode 100644 index 0000000..283905d Binary files /dev/null and b/img/codiga-03.jpg differ diff --git a/img/codiga-04.jpg b/img/codiga-04.jpg new file mode 100644 index 0000000..3520567 Binary files /dev/null and b/img/codiga-04.jpg differ diff --git a/img/codiga-05.jpg b/img/codiga-05.jpg new file mode 100644 index 0000000..896f1d9 Binary files /dev/null and b/img/codiga-05.jpg differ diff --git a/img/codiga-06.jpg b/img/codiga-06.jpg new file mode 100644 index 0000000..1753663 Binary files /dev/null and b/img/codiga-06.jpg differ diff --git a/img/codiga-07.jpg b/img/codiga-07.jpg new file mode 100644 index 0000000..0fad884 Binary files /dev/null and b/img/codiga-07.jpg differ diff --git a/img/codiga-08.jpg b/img/codiga-08.jpg new file mode 100644 index 0000000..5bb57f3 Binary files /dev/null and b/img/codiga-08.jpg differ diff --git a/img/codiga-09.jpg b/img/codiga-09.jpg new file mode 100644 index 0000000..c2c2268 Binary files /dev/null and b/img/codiga-09.jpg differ diff --git a/img/codiga-10.jpg b/img/codiga-10.jpg new file mode 100644 index 0000000..669c25f Binary files /dev/null and b/img/codiga-10.jpg differ diff --git a/img/codiga-11.jpg b/img/codiga-11.jpg new file mode 100644 index 0000000..6c4d0e5 Binary files /dev/null and b/img/codiga-11.jpg differ diff --git a/img/codiga-12.jpg b/img/codiga-12.jpg new file mode 100644 index 0000000..82d41a8 Binary files /dev/null and b/img/codiga-12.jpg differ diff --git a/img/codiga-banner.jpg b/img/codiga-banner.jpg new file mode 100644 index 0000000..680792b Binary files /dev/null and b/img/codiga-banner.jpg differ diff --git a/img/ico-linux.png b/img/ico-linux.png new file mode 100644 index 0000000..f2652e8 Binary files /dev/null and b/img/ico-linux.png differ diff --git a/img/ico-mac.png b/img/ico-mac.png new file mode 100644 index 0000000..c6a5ab0 Binary files /dev/null and b/img/ico-mac.png differ diff --git a/img/ico-win.png b/img/ico-win.png new file mode 100644 index 0000000..ce1f78f Binary files /dev/null and b/img/ico-win.png differ diff --git a/package-lock.json b/package-lock.json index b63e702..9381c5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "version": "0.0.3", + "version": "0.0.18", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1112,10 +1112,10 @@ "@chakra-ui/utils": "1.10.4" } }, - "@codiga/codiga-components": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@codiga/codiga-components/-/codiga-components-1.0.5.tgz", - "integrity": "sha512-khsatjVGvnZk/aSk2HiAsfWJTsbZZS8rWmGLnwv4A14e+upPInJ/KfDeJiGMkyYziE7bJq0e1SBfOlbdlWzGIA==" + "@codiga/components": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@codiga/components/-/components-1.3.0.tgz", + "integrity": "sha512-/qTPXcUVWkY30gSIw+wM8RLOTz8Bsyk+2B1CKJKXMAQtUAq4VOYgKkbZIdnlQOqnDIAV/mkSyFocK/UopeLtIw==" }, "@cspotcode/source-map-support": { "version": "0.8.1", @@ -2081,6 +2081,14 @@ "tslib": "^2.1.0" } }, + "@rollbar/react": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@rollbar/react/-/react-0.11.1.tgz", + "integrity": "sha512-QwI8wPjX1xc/AuX39TSJx/tEtjmf8macRqYgX+R/uRh7Y3+4ilZX9OMwLg/4Je8+NN+9y7PFNKkQa9adn58d/g==", + "requires": { + "tiny-invariant": "^1.1.0" + } + }, "@sinclair/typebox": { "version": "0.24.28", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz", @@ -3658,8 +3666,7 @@ "async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, "async-exit-hook": { "version": "2.0.1", @@ -3929,11 +3936,6 @@ "dev": true, "optional": true }, - "bootstrap": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", - "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==" - }, "boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -4368,11 +4370,6 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, - "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, "clean-css": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", @@ -4726,6 +4723,11 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, + "console-polyfill": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/console-polyfill/-/console-polyfill-0.3.0.tgz", + "integrity": "sha512-w+JSDZS7XML43Xnwo2x5O5vxB0ID7T5BdqDtyqT6uiCAX2kZAgcWxNaGqT97tZfSHzfOcvrfsDAodKcJ3UvnXQ==" + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -5159,6 +5161,15 @@ "ms": "2.1.2" } }, + "decache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/decache/-/decache-3.1.0.tgz", + "integrity": "sha512-p7D6wJ5EJFFq1CcF2lu1XeqKFLBob8jRQGNAvFLTsV3CbSKBl3VtliAVlUIGz2i9H6kEFnI2Amaft5ZopIG2Fw==", + "optional": true, + "requires": { + "find": "^0.2.4" + } + }, "decimal.js": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", @@ -5195,7 +5206,8 @@ "deepmerge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true }, "default-gateway": { "version": "6.0.3", @@ -5464,15 +5476,6 @@ "utila": "~0.4" } }, - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, "dom-serializer": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", @@ -6234,7 +6237,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.7.tgz", "integrity": "sha512-chLOW0ZGRf4s8raLrDxa5sdkvPec5YdvwbFnqJme4rk0rFajP8mPtrDL1+I+CwrQDCjswDA5sREX7jYQDQs9vA==", - "dev": true, "requires": { "stackframe": "^1.1.1" } @@ -7466,6 +7468,15 @@ } } }, + "find": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/find/-/find-0.2.9.tgz", + "integrity": "sha512-7a4/LCiInB9xYMnAUEjLilL9FKclwbwK7VlXw+h5jMvT2TDFeYFCHM24O1XdnC/on/hx8mxVO3FTQkyHZnOghQ==", + "optional": true, + "requires": { + "traverse-chain": "~0.1.0" + } + }, "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -7528,15 +7539,6 @@ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true }, - "frameless-titlebar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/frameless-titlebar/-/frameless-titlebar-2.1.4.tgz", - "integrity": "sha512-Wz7ZPOLpZNIQg5YxxI2lP6JVrGAolxKN/X85lTOFNpSEe5iJWAKMpYUviuFtc24/Ei8/rBRGkItgdhIFwer7lQ==", - "requires": { - "classnames": "^2.2.6", - "deepmerge": "^4.2.2" - } - }, "framer-motion": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-6.5.1.tgz", @@ -8644,6 +8646,11 @@ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, + "is_js": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/is_js/-/is_js-0.9.0.tgz", + "integrity": "sha512-8Y5EHSH+TonfUHX2g3pMJljdbGavg55q4jmHzghJCdqYDbdNROC8uw/YFQwIRCRqRJT1EY3pJefz+kglw+o7sg==" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -9591,9 +9598,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true, - "optional": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { "version": "2.2.1", @@ -11801,15 +11806,6 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, - "react-popper": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", - "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", - "requires": { - "react-fast-compare": "^3.0.1", - "warning": "^4.0.2" - } - }, "react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -11900,30 +11896,6 @@ } } }, - "react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - }, - "reactstrap": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-9.1.3.tgz", - "integrity": "sha512-1bYd6JxdnSn5nvvGaimFIAm1EZx79fCJp490/07Mh9B/lwG3/U3hAVOeRKYtYhtaZPgnAIkwxX9sc6G5J9OKuA==", - "requires": { - "@babel/runtime": "^7.12.5", - "@popperjs/core": "^2.6.0", - "classnames": "^2.2.3", - "prop-types": "^15.5.8", - "react-popper": "^2.2.4", - "react-transition-group": "^4.4.2" - } - }, "read-config-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz", @@ -12121,6 +12093,14 @@ "strip-ansi": "^6.0.1" } }, + "request-ip": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-2.0.2.tgz", + "integrity": "sha512-Y6LxqTmxLKKDk2I5tU2sxoCSKAnWJ42jmGqixNrH+oYoAyncpal7fFF5gqJ2bbgkRmb9qYNxdD6KFHfLS4dKBA==", + "requires": { + "is_js": "^0.9.0" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12243,6 +12223,33 @@ "sprintf-js": "^1.1.2" } }, + "rollbar": { + "version": "2.25.1", + "resolved": "https://registry.npmjs.org/rollbar/-/rollbar-2.25.1.tgz", + "integrity": "sha512-xDUZqktjtnI/Vv530C5lQKPR8SR8tdlAs0/OeiyBNdfqnL/ngpTHmmrYhgFn4rpEXcs+OzYp3jLquaozBlaCgg==", + "requires": { + "async": "~3.2.3", + "console-polyfill": "0.3.0", + "decache": "^3.0.5", + "error-stack-parser": "^2.0.4", + "json-stringify-safe": "~5.0.0", + "lru-cache": "~2.2.1", + "request-ip": "~2.0.1", + "source-map": "^0.5.7" + }, + "dependencies": { + "lru-cache": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", + "integrity": "sha512-Q5pAgXs+WEAfoEdw2qKQhNFFhMoFMTYqRVKKUMnzuiR7oKFHS7fWo848cPcTKw+4j/IdN17NyzdhVKgabFV0EA==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + } + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -12829,8 +12836,7 @@ "stackframe": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz", - "integrity": "sha512-h88QkzREN/hy8eRdyNhhsO7RSJ5oyTqxxmmn0dzBIMUclZsjpfmrsg81vp8mjjAs2vAZ72nyWxRUwSwmh0e4xg==", - "dev": true + "integrity": "sha512-h88QkzREN/hy8eRdyNhhsO7RSJ5oyTqxxmmn0dzBIMUclZsjpfmrsg81vp8mjjAs2vAZ72nyWxRUwSwmh0e4xg==" }, "stat-mode": { "version": "1.0.0", @@ -13304,6 +13310,12 @@ "punycode": "^2.1.1" } }, + "traverse-chain": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", + "integrity": "sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg==", + "optional": true + }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", diff --git a/package.json b/package.json index 342123c..73f11d4 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "coding-assistant", "codiga" ], - "version": "0.0.3", + "version": "0.0.18", "homepage": "https://www.codiga.io/", "bugs": { "url": "https://github.com/codiga/code-snippets-manager/issues" @@ -100,19 +100,18 @@ "@apollo/client": "^3.6.9", "@chakra-ui/icons": "^1.1.7", "@chakra-ui/react": "^1.8.8", - "@codiga/codiga-components": "^1.0.5", + "@codiga/components": "^1.3.0", "@electron/remote": "^2.0.8", "@emotion/react": "^11.10.0", "@emotion/styled": "^11.10.0", + "@rollbar/react": "^0.11.1", "apollo-link-debounce": "^3.0.0", - "bootstrap": "^5.1.3", "buffer": "^6.0.3", "cross-fetch": "^3.1.5", "electron-debug": "^3.2.0", "electron-log": "^4.4.8", "electron-store": "^8.1.0", "electron-updater": "^5.2.1", - "frameless-titlebar": "^2.1.4", "framer-motion": "^6.5.1", "graphql": "^16.6.0", "path-browserify": "^1.0.1", @@ -121,7 +120,7 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.34.2", "react-router-dom": "^6.3.0", - "reactstrap": "^9.1.3", + "rollbar": "^2.25.1", "url": "^0.11.0" }, "devDependencies": { diff --git a/release/app/package-lock.json b/release/app/package-lock.json index 7f65806..5c47d8b 100644 --- a/release/app/package-lock.json +++ b/release/app/package-lock.json @@ -1,6 +1,6 @@ { "name": "codiga", - "version": "0.0.3", + "version": "0.0.18", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/release/app/package.json b/release/app/package.json index 377a4bc..fd219e3 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -1,6 +1,6 @@ { "name": "codiga", - "version": "0.0.3", + "version": "0.0.18", "description": "Codiga Code Snippets Manager", "license": "MIT", "author": { diff --git a/src/main/main.ts b/src/main/main.ts index 4bb408a..ba28e33 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -31,6 +31,10 @@ ipcMain.on('ipc-example', async (event, arg) => { event.reply('ipc-example', msgTemplate('pong')); }); +ipcMain.on('app-version', async (event) => { + event.reply('app-version', app.getVersion()); +}); + if (process.env.NODE_ENV === 'production') { const sourceMapSupport = require('source-map-support'); sourceMapSupport.install(); @@ -46,7 +50,7 @@ if (isDebug) { const installExtensions = async () => { const installer = require('electron-devtools-installer'); const forceDownload = !!process.env.UPGRADE_EXTENSIONS; - const extensions = ['REACT_DEVELOPER_TOOLS']; + const extensions = ['REACT_DEVELOPER_TOOLS', 'APOLLO_DEVELOPER_TOOLS']; return installer .default( @@ -75,8 +79,8 @@ const createWindow = async () => { frame: false, width: 1024, height: 728, - minWidth: 600, - minHeight: 300, + minWidth: 900, + minHeight: 600, icon: getAssetPath('icon.png'), webPreferences: { sandbox: false, @@ -123,6 +127,9 @@ const createWindow = async () => { * Add event listeners... */ +// used for win32 desktop notifications - replaces `electron.app.${APP_NAME}` with Codiga. +app.setAppUserModelId('Codiga'); + app.on('window-all-closed', () => { // Respect the OSX convention of having the application in memory even // after all windows have been closed @@ -140,7 +147,11 @@ app mainWindow?.minimize(); }); ipcMain.on('maximizeApp', () => { - mainWindow?.maximize(); + if (mainWindow?.isMaximized()) { + mainWindow?.unmaximize(); + } else { + mainWindow?.maximize(); + } }); ipcMain.on('closeApp', () => { mainWindow?.close(); diff --git a/src/main/preload.ts b/src/main/preload.ts index 149f127..6c4609e 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -2,11 +2,13 @@ import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; export type Channels = | 'ipc-example' + | 'app-version' | 'minimizeApp' | 'maximizeApp' | 'closeApp'; contextBridge.exposeInMainWorld('electron', { + isMac: process.platform === 'darwin', ipcRenderer: { sendMessage(channel: Channels, args: unknown[]) { ipcRenderer.send(channel, args); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 2b915d2..e947a55 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,7 +1,8 @@ import { ApolloProvider } from '@apollo/client'; -import { ChakraProvider } from '@chakra-ui/react'; -import { theme } from '@codiga/codiga-components'; +import { ChakraProvider, ColorModeScript } from '@chakra-ui/react'; +import { theme } from '@codiga/components'; import { MemoryRouter as Router, Routes, Route } from 'react-router-dom'; +import { Provider as RollbarProvider, ErrorBoundary } from '@rollbar/react'; // PAGES import Home from './pages/Home'; @@ -11,6 +12,8 @@ import MyCookbooks from './pages/MyCookbooks'; import MySnippets from './pages/MySnippets'; import TeamCookbooks from './pages/TeamCookbooks'; import TeamSnippets from './pages/TeamSnippets'; +import ViewSnippet from './pages/ViewSnippet'; +import ViewCookbookSnippets from './pages/ViewCookbookSnippets'; // STYLES import './styles/reboot.css'; @@ -19,6 +22,7 @@ import './styles/app.css'; // OTHER import client from './graphql/client'; +import { rollbarConfig } from './lib/rollbar'; import Layout from './components/Layout'; import Filters from './components/Filters/Filters'; import { UserProvider } from './components/UserContext'; @@ -27,37 +31,59 @@ import { FiltersProvider } from './components/FiltersContext'; export default function App() { return ( - - - - - - + + + + + + + - + + + + }> + } /> + } /> + } + /> + } + /> + } + /> + } + /> + } + /> + - - } /> - - } /> - } - /> - } /> - } - /> - } /> - } /> - + } + /> + } + /> + + + - - - - - - + + + + + + ); } diff --git a/src/renderer/components/AboutApp/AboutApp.tsx b/src/renderer/components/AboutApp/AboutApp.tsx new file mode 100644 index 0000000..9cca87e --- /dev/null +++ b/src/renderer/components/AboutApp/AboutApp.tsx @@ -0,0 +1,101 @@ +import { useEffect, useState } from 'react'; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + Text, + Link, + IconButton, + useDisclosure, + Image, +} from '@chakra-ui/react'; +import { QuestionMarkCircleIcon } from '@codiga/components'; +import CodigaLogo from '../Layout/CodigaIcon.png'; + +export default function AboutApp() { + const { isOpen, onOpen, onClose } = useDisclosure(); + const [appVersion, setAppVersion] = useState('0.0.0'); + + useEffect(() => { + // eslint-disable-next-line no-alert + window.electron?.ipcRenderer.once('app-version', (arg) => { + setAppVersion(arg as string); + }); + window.electron?.ipcRenderer.sendMessage('app-version', ['']); + }, []); + + return ( + <> + } + _focus={{ + boxShadow: 'none', + }} + /> + + + + + + + + + + + + + + + + + Codiga Code Snippets Manager + + + + + + Version {appVersion} + + + + Copyright © 2022 Codiga + + + + + ); +} diff --git a/src/renderer/components/AboutApp/index.tsx b/src/renderer/components/AboutApp/index.tsx new file mode 100644 index 0000000..5e7503e --- /dev/null +++ b/src/renderer/components/AboutApp/index.tsx @@ -0,0 +1 @@ +export { default } from './AboutApp'; diff --git a/src/renderer/components/AvatarAndName/AvatarAndName.tsx b/src/renderer/components/AvatarAndName/AvatarAndName.tsx new file mode 100644 index 0000000..74db1a0 --- /dev/null +++ b/src/renderer/components/AvatarAndName/AvatarAndName.tsx @@ -0,0 +1,30 @@ +import { Flex, Text } from '@chakra-ui/react'; +import { Avatar } from '@codiga/components'; +import { PublicUser } from '../../types/userTypes'; +import { getAvatarUrl } from '../../utils/userUtils'; +import UserLink from '../UserLink'; + +type AvatarAndNameProps = { + owner?: PublicUser; +}; + +export default function AvatarAndName({ owner = {} }: AvatarAndNameProps) { + return ( + + + + + + + ); +} diff --git a/src/renderer/components/AvatarAndName/index.tsx b/src/renderer/components/AvatarAndName/index.tsx new file mode 100644 index 0000000..ed59e15 --- /dev/null +++ b/src/renderer/components/AvatarAndName/index.tsx @@ -0,0 +1 @@ +export { default } from './AvatarAndName'; diff --git a/src/renderer/components/BackButton/BackButton.tsx b/src/renderer/components/BackButton/BackButton.tsx new file mode 100644 index 0000000..51f77be --- /dev/null +++ b/src/renderer/components/BackButton/BackButton.tsx @@ -0,0 +1,22 @@ +import { IconButton } from '@chakra-ui/react'; +import { ChevronLeftIcon } from '@codiga/components'; +import { useNavigate } from 'react-router-dom'; + +export default function BackButton() { + const navigate = useNavigate(); + + return ( + navigate(-1)} + h="28px" + minW="28px" + fontSize="12px" + icon={} + aria-label="go back" + _focus={{ + boxShadow: 'none', + }} + /> + ); +} diff --git a/src/renderer/components/BackButton/index.tsx b/src/renderer/components/BackButton/index.tsx new file mode 100644 index 0000000..27d1ce8 --- /dev/null +++ b/src/renderer/components/BackButton/index.tsx @@ -0,0 +1 @@ +export { default } from './BackButton'; diff --git a/src/renderer/components/Code/Code.tsx b/src/renderer/components/Code/Code.tsx new file mode 100644 index 0000000..2c1f96e --- /dev/null +++ b/src/renderer/components/Code/Code.tsx @@ -0,0 +1,223 @@ +import { useEffect } from 'react'; +import { + Flex, + LinkBox, + IconButton, + useClipboard, + useColorModeValue, + useToken, + Tooltip, + Text, + Link, + Menu, + MenuButton, + Portal, + MenuList, + MenuItem, +} from '@chakra-ui/react'; +import { + BubbleIcon, + Code as CodigaCode, + CodeContent, + CopyIcon, + PencilIcon, + useToast, +} from '@codiga/components'; + +import { getSnippetUrl } from '../../utils/urlUtils'; +import useCodeView, { CodeViewsType } from '../../hooks/useCodeView'; +import { APP_URL } from '../../lib/config'; +import { AssistantRecipeWithStats } from '../../types/assistantTypes'; +import { decodeIndent } from '../../utils/codeUtils'; +import CodeViewToggler from './CodeViewToggler'; +import { useUser } from '../UserContext'; + +type CodeProps = { + recipe: AssistantRecipeWithStats; +}; + +export default function Code({ recipe }: CodeProps) { + const toast = useToast(); + const { id: userId } = useUser(); + + const [codeView, setCodeView] = useCodeView('preview'); + + const neutral100 = useToken('colors', 'neutral.100'); + const bg = useColorModeValue('white', neutral100); + + const code = + codeView === 'preview' + ? decodeIndent(recipe?.presentableFormat) + : decodeIndent(recipe?.code); + const imports = recipe?.imports?.join('\n'); + const codeForCopy = imports ? `${imports}\n${code}` : code; + + const { hasCopied, onCopy } = useClipboard(codeForCopy); + + useEffect(() => { + if (hasCopied) { + toast({ status: 'success', description: 'Snippet copied' }); + } + }, [hasCopied, toast]); + + const commentsCount = Number(recipe.commentsCount); + const lines = code.split('\n').length; + const lineMaxDigits = lines.toString().length; + const minWidth = lineMaxDigits < 3 ? '2.7em' : `${lineMaxDigits}.25em`; + + return ( + + + setCodeView(value as CodeViewsType), + }} + /> + + + } + onClick={onCopy} + aria-label="Copy Snippet" + /> + + + + + + + {commentsCount} + + + } + aria-label="Comment on Snippet" + /> + + + {userId && recipe.owner && userId === recipe.owner.id && ( + + + ••• + + + + + + Edit Snippet + + + + + + )} + + + + span:first-child > .linenumber:first-child': + { + paddingTop: '0.5em !important', + }, + 'code[class*="language-"] .linenumber': { + border: '0 !important', + background: 'transparent !important', + fontStyle: 'normal !important', + }, + }} + > + + {code} + + + + + ); +} diff --git a/src/renderer/components/Code/CodeLoading.tsx b/src/renderer/components/Code/CodeLoading.tsx new file mode 100644 index 0000000..3e3e014 --- /dev/null +++ b/src/renderer/components/Code/CodeLoading.tsx @@ -0,0 +1,54 @@ +import { Flex, Skeleton, VStack } from '@chakra-ui/react'; + +export default function CodeLoading() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/renderer/components/SearchResults/CodeViewToggler.tsx b/src/renderer/components/Code/CodeViewToggler.tsx similarity index 95% rename from src/renderer/components/SearchResults/CodeViewToggler.tsx rename to src/renderer/components/Code/CodeViewToggler.tsx index 8da342d..fb77805 100644 --- a/src/renderer/components/SearchResults/CodeViewToggler.tsx +++ b/src/renderer/components/Code/CodeViewToggler.tsx @@ -47,8 +47,13 @@ const RadioButton = ({ color="neutral.100" bg="neutral.0" tabIndex={0} + pos="relative" + _active={{ + zIndex: 2, + }} _checked={{ bg: 'neutral.50', + zIndex: 1, }} _focus={{ boxShadow: 'outline', diff --git a/src/renderer/components/CodeCount/CodeCount.tsx b/src/renderer/components/CodeCount/CodeCount.tsx new file mode 100644 index 0000000..dca4ee9 --- /dev/null +++ b/src/renderer/components/CodeCount/CodeCount.tsx @@ -0,0 +1,17 @@ +import { Flex, Text } from '@chakra-ui/react'; +import { CodeIcon } from '@codiga/components'; + +type CodeCountProps = { + count?: number; +}; + +export default function CodeCount({ count = 0 }: CodeCountProps) { + return ( + + + + {count} + + + ); +} diff --git a/src/renderer/components/CodeCount/index.tsx b/src/renderer/components/CodeCount/index.tsx new file mode 100644 index 0000000..04e688d --- /dev/null +++ b/src/renderer/components/CodeCount/index.tsx @@ -0,0 +1 @@ +export { default } from './CodeCount'; diff --git a/src/renderer/components/CookbookTable/CookbookTable.tsx b/src/renderer/components/CookbookTable/CookbookTable.tsx index fc9f5b0..88d32ba 100644 --- a/src/renderer/components/CookbookTable/CookbookTable.tsx +++ b/src/renderer/components/CookbookTable/CookbookTable.tsx @@ -9,22 +9,19 @@ import { Td as ChakraTd, TableCellProps, Link, + LinkBox, + LinkOverlay, } from '@chakra-ui/react'; -import { - LockIcon, - Avatar, - UsersIcon, - CodeIcon, - Logos, -} from '@codiga/codiga-components'; +import { UsersIcon, Logos } from '@codiga/components'; +import { Link as RouterLink } from 'react-router-dom'; -import { getCookbookUrl, getGroupUrl } from '../../utils/urlUtils'; -import { getAvatarUrl } from '../../utils/userUtils'; +import { getGroupUrl } from '../../utils/urlUtils'; import { AssistantCookbook } from '../../types/assistantTypes'; -import { PageTypes } from '../../types/pageTypes'; import FavoriteCookbook from '../Favorite/FavoriteCookbook'; -import UserLink from '../UserLink'; -import VotesCurrent from '../VotesCurrent'; +import PrivacyAndVotes from '../PrivacyAndVotes'; +import FormattedDate from '../FormattedDate'; +import AvatarAndName from '../AvatarAndName'; +import CodeCount from '../CodeCount'; const Td = (props: TableCellProps) => ( ( type CookbookTableProps = { cookbooks: AssistantCookbook[]; - page: PageTypes; }; -export default function CookbookTable({ cookbooks, page }: CookbookTableProps) { +export default function CookbookTable({ cookbooks }: CookbookTableProps) { return ( - - + + {cookbooks.map((cookbook) => { return ( - - {cookbook.name} - + + {cookbook.groups && cookbook.groups.length > 0 && ( )} + + + + - + ); })} diff --git a/src/renderer/components/CookbookTable/CookbookTableEmpty.tsx b/src/renderer/components/CookbookTable/CookbookTableEmpty.tsx index 6e51b68..087fce8 100644 --- a/src/renderer/components/CookbookTable/CookbookTableEmpty.tsx +++ b/src/renderer/components/CookbookTable/CookbookTableEmpty.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; export default function CookbookTableEmpty() { diff --git a/src/renderer/components/CookbookTable/CookbookTableEmptyFiltered.tsx b/src/renderer/components/CookbookTable/CookbookTableEmptyFiltered.tsx index e716163..c97c383 100644 --- a/src/renderer/components/CookbookTable/CookbookTableEmptyFiltered.tsx +++ b/src/renderer/components/CookbookTable/CookbookTableEmptyFiltered.tsx @@ -1,9 +1,9 @@ import { Button, Flex, Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; import { useFilters } from '../FiltersContext'; -export default function CookbookTableEmptyFiltereed() { +export default function CookbookTableEmptyFiltered() { const { resetAllFilters } = useFilters(); return ( diff --git a/src/renderer/components/CookbookTable/CookbookTableError.tsx b/src/renderer/components/CookbookTable/CookbookTableError.tsx index 9e5cec8..e5a6e73 100644 --- a/src/renderer/components/CookbookTable/CookbookTableError.tsx +++ b/src/renderer/components/CookbookTable/CookbookTableError.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; export default function CookbookTableError() { diff --git a/src/renderer/components/CookbookTable/CookbookTableLoading.tsx b/src/renderer/components/CookbookTable/CookbookTableLoading.tsx index 881e517..fac7879 100644 --- a/src/renderer/components/CookbookTable/CookbookTableLoading.tsx +++ b/src/renderer/components/CookbookTable/CookbookTableLoading.tsx @@ -15,15 +15,22 @@ const Td = (props: TableCellProps) => ( export default function CookbookTableLoading() { return ( - - + +
@@ -100,6 +99,7 @@ export default function CookbookTable({ cookbooks, page }: CookbookTableProps) { href={`${getGroupUrl( cookbook.groups[0].id! )}/cookbooks`} + _focus={{ boxShadow: 'none' }} > {cookbook.groups[0].name} @@ -107,56 +107,31 @@ export default function CookbookTable({ cookbooks, page }: CookbookTableProps) { - - - - - - + - - - - {cookbook.isPublic ? 'Public' : 'Private'} - - - + - - - {new Date(cookbook.creationTimestampMs!).toDateString()} - - + - - - - {cookbook?.recipesCount} - - +
{[1, 2, 3, 4, 5, 6, 7].map((num, i) => ( - diff --git a/src/renderer/components/PrivacyAndVotes/PrivacyAndVotes.tsx b/src/renderer/components/PrivacyAndVotes/PrivacyAndVotes.tsx new file mode 100644 index 0000000..a97f218 --- /dev/null +++ b/src/renderer/components/PrivacyAndVotes/PrivacyAndVotes.tsx @@ -0,0 +1,31 @@ +import { Flex, Text } from '@chakra-ui/react'; +import { LockIcon } from '@codiga/components'; +import VotesCurrent from '../VotesCurrent'; + +type PrivacyAndNotesProps = { + isPublic?: boolean; + upvotes?: number; + downvotes?: number; +}; + +export default function PrivacyAndVotes({ + isPublic = true, + upvotes = 0, + downvotes = 0, +}: PrivacyAndNotesProps) { + return ( + + + + {isPublic ? 'Public' : 'Private'} + + + + ); +} diff --git a/src/renderer/components/PrivacyAndVotes/index.tsx b/src/renderer/components/PrivacyAndVotes/index.tsx new file mode 100644 index 0000000..be5fa1a --- /dev/null +++ b/src/renderer/components/PrivacyAndVotes/index.tsx @@ -0,0 +1 @@ +export { default } from './PrivacyAndVotes'; diff --git a/src/renderer/components/SearchResults/SearchResults.tsx b/src/renderer/components/SearchResults/SearchResults.tsx deleted file mode 100644 index ed64747..0000000 --- a/src/renderer/components/SearchResults/SearchResults.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Box, Flex } from '@chakra-ui/react'; -import { useState } from 'react'; -import { - AssistantRecipeWithStats, - RecipeSummary, -} from 'renderer/types/assistantTypes'; -import SearchResultsCode from './SearchResultsCode'; -import SearchResultsList from './SearchResultsList'; -import SearchResultsListItem from './SearchResultsListItem'; - -type SearchResultsProps = { - results: AssistantRecipeWithStats[]; -}; - -export default function SearchResults({ results }: SearchResultsProps) { - const [snippetInFocus, setSnippetInFocus] = useState(results[0] || {}); - - const changeSnippetInFocus = (recipe: RecipeSummary) => { - setSnippetInFocus(recipe); - }; - - return ( - - - {results.map((result) => ( - - ))} - - - - {results[0] ? : null} - - - - ); -} diff --git a/src/renderer/components/SearchResults/SearchResultsCode.tsx b/src/renderer/components/SearchResults/SearchResultsCode.tsx deleted file mode 100644 index fcfaea0..0000000 --- a/src/renderer/components/SearchResults/SearchResultsCode.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { - Flex, - LinkBox, - IconButton, - useClipboard, - useColorModeValue, - useToken, - Tooltip, - Text, - Link, -} from '@chakra-ui/react'; -import { - BubbleIcon, - Code, - CodeContent, - CopyIcon, - useToast, -} from '@codiga/codiga-components'; -import { useEffect } from 'react'; -import useCodeView, { CodeViewsType } from '../../hooks/useCodeView'; -import { APP_URL } from '../../lib/config'; -import { AssistantRecipeWithStats } from '../../types/assistantTypes'; -import { decodeIndent } from '../../utils/codeUtils'; -import CodeViewToggler from './CodeViewToggler'; - -type SearchResultsCodeProps = { - recipe: AssistantRecipeWithStats; -}; - -export default function SearchResultsCode({ recipe }: SearchResultsCodeProps) { - const toast = useToast(); - const [codeView, setCodeView] = useCodeView('preview'); - - const neutral100 = useToken('colors', 'neutral.100'); - const bg = useColorModeValue('white', neutral100); - - const code = - codeView === 'preview' - ? decodeIndent(recipe?.presentableFormat) - : decodeIndent(recipe?.code); - const imports = recipe?.imports?.join('\n'); - const codeForCopy = imports ? `${imports}\n${code}` : code; - - const { hasCopied, onCopy } = useClipboard(codeForCopy); - - useEffect(() => { - if (hasCopied) { - toast({ status: 'success', description: 'Snippet copied' }); - } - }, [hasCopied, toast]); - - const commentsCount = Number(recipe.commentsCount); - const lines = code.split('\n').length; - const lineMaxDigits = lines.toString().length; - const minWidth = lineMaxDigits < 3 ? '2.7em' : `${lineMaxDigits}.25em`; - - return ( - - span:first-child > .linenumber:first-child': - { - paddingTop: '0.5em !important', - }, - 'code[class*="language-"] .linenumber': { - border: '0 !important', - background: 'transparent !important', - fontStyle: 'normal !important', - }, - }} - > - - setCodeView(value as CodeViewsType), - }} - /> - - - } - onClick={onCopy} - aria-label="Copy Snippet" - /> - - - - - - - {commentsCount} - - - } - aria-label="Comment on Snippet" - /> - - - - - {code} - - - - ); -} diff --git a/src/renderer/components/SearchResults/SearchResultsCodeLoading.tsx b/src/renderer/components/SearchResults/SearchResultsCodeLoading.tsx deleted file mode 100644 index bff221c..0000000 --- a/src/renderer/components/SearchResults/SearchResultsCodeLoading.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Skeleton, VStack } from '@chakra-ui/react'; - -export default function SearchResultsCodeLoading() { - return ( - - - - - - - - - - - - - - - - - - ); -} diff --git a/src/renderer/components/SearchResults/SearchResultsEmpty.tsx b/src/renderer/components/SearchResults/SearchResultsEmpty.tsx index ae83941..673caff 100644 --- a/src/renderer/components/SearchResults/SearchResultsEmpty.tsx +++ b/src/renderer/components/SearchResults/SearchResultsEmpty.tsx @@ -1,5 +1,5 @@ import { Button, Flex, Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; import { useFilters } from '../FiltersContext'; diff --git a/src/renderer/components/SearchResults/SearchResultsError.tsx b/src/renderer/components/SearchResults/SearchResultsError.tsx index df38f00..b9832e9 100644 --- a/src/renderer/components/SearchResults/SearchResultsError.tsx +++ b/src/renderer/components/SearchResults/SearchResultsError.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; export default function SearchResultsError() { diff --git a/src/renderer/components/SearchResults/SearchResultsLoading.tsx b/src/renderer/components/SearchResults/SearchResultsLoading.tsx deleted file mode 100644 index e264224..0000000 --- a/src/renderer/components/SearchResults/SearchResultsLoading.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import SearchResultsCodeLoading from './SearchResultsCodeLoading'; -import SearchResultsList from './SearchResultsList'; -import SearchResultsListItemLoading from './SearchResultsListItemLoading'; - -export default function SearchResultsLoading() { - return ( - - - {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((num) => ( - - ))} - - - - - - ); -} diff --git a/src/renderer/components/SearchResults/index.tsx b/src/renderer/components/SearchResults/index.tsx deleted file mode 100644 index 43ac09d..0000000 --- a/src/renderer/components/SearchResults/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SearchResults'; diff --git a/src/renderer/components/SnippetResults/SnippetResults.tsx b/src/renderer/components/SnippetResults/SnippetResults.tsx new file mode 100644 index 0000000..40eeb71 --- /dev/null +++ b/src/renderer/components/SnippetResults/SnippetResults.tsx @@ -0,0 +1,41 @@ +import { Flex } from '@chakra-ui/react'; +import useUrlQuery from '../../hooks/useUrlQuery'; +import { AssistantRecipeWithStats } from '../../types/assistantTypes'; +import Code from '../Code/Code'; +import SnippetResultsList from './SnippetResultsList'; +import SnippetResultsListItem from './SnippetResultsListItem'; + +type SnippetResultsProps = { + results: AssistantRecipeWithStats[]; +}; + +export default function SnippetResults({ results }: SnippetResultsProps) { + const query = useUrlQuery(); + const currentSnippetId = query.get('currentSnippetId'); + + // if we have a snippet id in the url, search for that first + let currentSnippet = currentSnippetId + ? results.find((recipe) => String(recipe.id) === currentSnippetId) + : undefined; + + // if we can't find the snippet from the url or it wasn't given, use the first result + if (!currentSnippet) { + currentSnippet = results[0] || {}; + } + + return ( + + + {results.map((result) => ( + + ))} + + + {currentSnippet?.id ? : null} + + ); +} diff --git a/src/renderer/components/SearchResults/SearchResultsList.tsx b/src/renderer/components/SnippetResults/SnippetResultsList.tsx similarity index 56% rename from src/renderer/components/SearchResults/SearchResultsList.tsx rename to src/renderer/components/SnippetResults/SnippetResultsList.tsx index dc1eb71..dd2a1db 100644 --- a/src/renderer/components/SearchResults/SearchResultsList.tsx +++ b/src/renderer/components/SnippetResults/SnippetResultsList.tsx @@ -1,13 +1,13 @@ import { ReactNode } from 'react'; import { Flex } from '@chakra-ui/react'; -type SearchResultsListProps = { +type SnippetResultsListProps = { children: ReactNode; }; -export default function SearchResultsList({ +export default function SnippetResultsList({ children, -}: SearchResultsListProps) { +}: SnippetResultsListProps) { return ( {children} diff --git a/src/renderer/components/SearchResults/SearchResultsListItem.tsx b/src/renderer/components/SnippetResults/SnippetResultsListItem.tsx similarity index 64% rename from src/renderer/components/SearchResults/SearchResultsListItem.tsx rename to src/renderer/components/SnippetResults/SnippetResultsListItem.tsx index 5f5b81b..58c0e11 100644 --- a/src/renderer/components/SearchResults/SearchResultsListItem.tsx +++ b/src/renderer/components/SnippetResults/SnippetResultsListItem.tsx @@ -1,30 +1,34 @@ -import { Flex, Text } from '@chakra-ui/react'; -import { ChartBarsIcon, DotIcon, Logo, Tags } from '@codiga/codiga-components'; -import { - AssistantRecipeWithStats, - RecipeSummary, -} from '../../types/assistantTypes'; +import { Flex, LinkBox, LinkOverlay, Text } from '@chakra-ui/react'; +import { ChartBarsIcon, DotIcon, Logo, Tags } from '@codiga/components'; +import { Link as RouterLink } from 'react-router-dom'; + +import { AssistantRecipeWithStats } from '../../types/assistantTypes'; import FavoriteSnippet from '../Favorite/FavoriteSnippet'; -import Votes from './Votes'; +import Votes from '../Votes'; -type SearchResultsListItemProps = { +type SnippetResultsListItemProps = { recipe: AssistantRecipeWithStats; - changeSnippetInFocus: (recipe: RecipeSummary) => void; + isCurrentSnippet: boolean; }; -export default function SearchResultsListItem({ +export default function SnippetResultsListItem({ recipe, - changeSnippetInFocus, -}: SearchResultsListItemProps) { + isCurrentSnippet, +}: SnippetResultsListItemProps) { return ( - changeSnippetInFocus(recipe)} - cursor="pointer" - tabIndex={0} > - {recipe.name} + + {recipe.name} + 0 && ( )} - + ); } diff --git a/src/renderer/components/SearchResults/SearchResultsListItemLoading.tsx b/src/renderer/components/SnippetResults/SnippetResultsListItemLoading.tsx similarity index 87% rename from src/renderer/components/SearchResults/SearchResultsListItemLoading.tsx rename to src/renderer/components/SnippetResults/SnippetResultsListItemLoading.tsx index 6eb5fef..2ceda5c 100644 --- a/src/renderer/components/SearchResults/SearchResultsListItemLoading.tsx +++ b/src/renderer/components/SnippetResults/SnippetResultsListItemLoading.tsx @@ -1,13 +1,13 @@ import { Flex, Skeleton, SkeletonCircle } from '@chakra-ui/react'; -import { DotIcon } from '@codiga/codiga-components'; +import { DotIcon } from '@codiga/components'; -export default function SearchResultsListItemLoading() { +export default function SnippetResultsListItemLoading() { return ( + + {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((num) => ( + + ))} + + + + + ); +} diff --git a/src/renderer/components/SnippetTable/SnippetTable.tsx b/src/renderer/components/SnippetTable/SnippetTable.tsx index 1e9f5f8..97d5dab 100644 --- a/src/renderer/components/SnippetTable/SnippetTable.tsx +++ b/src/renderer/components/SnippetTable/SnippetTable.tsx @@ -7,41 +7,50 @@ import { Tr, Tbody, Td as ChakraTd, - Tag, TableCellProps, Link, + LinkBox, + LinkOverlay, } from '@chakra-ui/react'; -import { LockIcon, Logo, Avatar, UsersIcon } from '@codiga/codiga-components'; +import { Logo, UsersIcon, Tags } from '@codiga/components'; +import { Link as RouterLink } from 'react-router-dom'; -import { getAvatarUrl } from '../../utils/userUtils'; -import { getGroupUrl, getSnippetUrl } from '../../utils/urlUtils'; +import { getGroupUrl } from '../../utils/urlUtils'; import { AssistantRecipeWithStats } from '../../types/assistantTypes'; -import { PageTypes } from '../../types/pageTypes'; import FavoriteSnippet from '../Favorite/FavoriteSnippet'; -import UserLink from '../UserLink'; -import VotesCurrent from '../VotesCurrent'; +import PrivacyAndVotes from '../PrivacyAndVotes'; +import FormattedDate from '../FormattedDate/FormattedDate'; +import AvatarAndName from '../AvatarAndName/AvatarAndName'; const Td = (props: TableCellProps) => ( ); type SnippetTableProps = { - page: PageTypes; recipes: AssistantRecipeWithStats[]; }; -export default function SnippetTable({ page, recipes }: SnippetTableProps) { +export default function SnippetTable({ recipes }: SnippetTableProps) { return ( - - + +
{recipes.map((recipe) => { return ( - + + + {recipe.groups && recipe.groups.length > 0 && ( )} + - + + + - + ); })} diff --git a/src/renderer/components/SnippetTable/SnippetTableEmpty.tsx b/src/renderer/components/SnippetTable/SnippetTableEmpty.tsx index 6297ce7..e54799b 100644 --- a/src/renderer/components/SnippetTable/SnippetTableEmpty.tsx +++ b/src/renderer/components/SnippetTable/SnippetTableEmpty.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; export default function SnippetTableEmpty() { diff --git a/src/renderer/components/SnippetTable/SnippetTableEmptyFiltered.tsx b/src/renderer/components/SnippetTable/SnippetTableEmptyFiltered.tsx index 2463a0d..1af4768 100644 --- a/src/renderer/components/SnippetTable/SnippetTableEmptyFiltered.tsx +++ b/src/renderer/components/SnippetTable/SnippetTableEmptyFiltered.tsx @@ -1,5 +1,5 @@ import { Button, Flex, Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; import { useFilters } from '../FiltersContext'; diff --git a/src/renderer/components/SnippetTable/SnippetTableError.tsx b/src/renderer/components/SnippetTable/SnippetTableError.tsx index 77af3f8..1cf5d37 100644 --- a/src/renderer/components/SnippetTable/SnippetTableError.tsx +++ b/src/renderer/components/SnippetTable/SnippetTableError.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; export default function SnippetTableError() { diff --git a/src/renderer/components/SnippetTable/SnippetTableLoading.tsx b/src/renderer/components/SnippetTable/SnippetTableLoading.tsx index 19ecb25..4d7b141 100644 --- a/src/renderer/components/SnippetTable/SnippetTableLoading.tsx +++ b/src/renderer/components/SnippetTable/SnippetTableLoading.tsx @@ -15,15 +15,22 @@ const Td = (props: TableCellProps) => ( export default function SnippetTableLoading() { return ( - - + +
+ + + + {recipe.name} + + + + + @@ -68,6 +100,7 @@ export default function SnippetTable({ page, recipes }: SnippetTableProps) { href={`${getGroupUrl( recipe.groups[0].id! )}/snippets`} + _focus={{ boxShadow: 'none' }} > {recipe.groups[0].name} @@ -75,89 +108,33 @@ export default function SnippetTable({ page, recipes }: SnippetTableProps) { - - - - {recipe.name} - - - - - - - - - - - + - - - - {recipe.isPublic ? 'Public' : 'Private'} - - - + - - - {new Date(recipe.creationTimestampMs!).toDateString()} - - + - - {recipe.tags?.slice(0, 1).map((tag) => ( - - {tag} - - ))} - {(recipe.tags || []).length - 1 > 0 ? ( - +{(recipe.tags || []).length - 1} - ) : null} - + {recipe?.tags && recipe?.tags.length > 0 && ( + + )}
{[1, 2, 3, 4, 5, 6, 7].map((num, i) => ( void; type ThemeContextType = { - changeToDarkTheme: () => Promise; - changeToLightTheme: () => Promise; + changeToDarkTheme: () => void; + changeToLightTheme: () => void; }; const ThemeContext = createContext({} as ThemeContextType); export const ThemeProvider = ({ children }: { children: ReactNode }) => { - const toast = useToast(); - const { id: userId } = useUser(); - // CHAKRA'S THEME const { setColorMode } = useColorMode(); // USER'S LOCAL THEME PREFERENCE - // @ts-ignore - const [cacheTheme, cacheStorageTheme, hydrateValue] = useLocalStorage( + const [_, cacheStorageTheme, hydrateValue] = useLocalStorage( CODIGA_THEME, Theme.THEME_LIGHT ) as [ThemeType, CacheStorageThemeType, () => string]; - /** - * If the user is logged in, we check if they have a saved theme preference. - * If they do, we'll update Chakra and the local version - */ - useQuery<{ user: Partial }>(GET_USER_PREFERENCES, { - skip: !!userId, - onCompleted: (respData) => { - if (respData?.user?.preferences) { - const savedTheme = respData.user.preferences - .map((preference) => preference.key) - .includes(UserPreferenceKey.Theme) - ? Theme.THEME_DARK - : Theme.THEME_LIGHT; - cacheStorageTheme(savedTheme); - setColorMode(savedTheme === Theme.THEME_DARK ? 'dark' : 'light'); - } - }, - }); - /** * Because a user can access the app while not authenticated, * we need to update the theme based on locally stored settings @@ -77,57 +39,14 @@ export const ThemeProvider = ({ children }: { children: ReactNode }) => { setColorMode(storedTheme === Theme.THEME_DARK ? 'dark' : 'light'); }, [cacheStorageTheme, hydrateValue, setColorMode]); - // used to set a DARK theme - const [removeUserPreference] = useMutation< - RemoveUserPreferenceData, - RemoveUserPreferenceVariables - >(REMOVE_USER_PREFERENCE); - - // used to set a LIGHT theme - const [updateUserPreference] = useMutation< - UpdateUserPreferenceData, - UpdateUserPreferenceVariables - >(UPDATE_USER_PREFERENCE); - - const changeToDarkTheme = async () => { + const changeToDarkTheme = () => { cacheStorageTheme(Theme.THEME_DARK); setColorMode('dark'); - if (!userId) return; - try { - await updateUserPreference({ - variables: { - key: UserPreferenceKey.Theme, - value: Theme.THEME_DARK, - }, - refetchQueries: [{ query: GET_USER_PREFERENCES }], - }); - } catch (err) { - toast({ - status: 'error', - description: - 'An error occurred while updating your theme. Please try again.', - }); - } }; - const changeToLightTheme = async () => { + const changeToLightTheme = () => { cacheStorageTheme(Theme.THEME_LIGHT); setColorMode('light'); - if (!userId) return; - try { - await removeUserPreference({ - variables: { - key: UserPreferenceKey.Theme, - }, - refetchQueries: [{ query: GET_USER_PREFERENCES }], - }); - } catch (err) { - toast({ - status: 'error', - description: - 'An error occurred while updating your theme. Please try again.', - }); - } }; const themeContext = { diff --git a/src/renderer/components/UserContext/UserContext.tsx b/src/renderer/components/UserContext/UserContext.tsx index 7a74b99..e9b12ff 100644 --- a/src/renderer/components/UserContext/UserContext.tsx +++ b/src/renderer/components/UserContext/UserContext.tsx @@ -15,7 +15,8 @@ export const UserProvider = ({ children }: { children: ReactNode }) => { useQuery<{ user: Partial }>(CHECK_USER, { pollInterval: POLL_USER_FOR_LOGOUT_MSEC, - fetchPolicy: 'no-cache', + fetchPolicy: 'network-only', + nextFetchPolicy: 'network-only', onCompleted: (respData) => { if (respData?.user) { setUser(respData?.user); diff --git a/src/renderer/components/UserLink/UserLink.tsx b/src/renderer/components/UserLink/UserLink.tsx index b3a40ed..34f2528 100644 --- a/src/renderer/components/UserLink/UserLink.tsx +++ b/src/renderer/components/UserLink/UserLink.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { ExternalLinkIcon } from '@codiga/codiga-components'; +import { ExternalLinkIcon } from '@codiga/components'; import { PublicUser } from '../../types/userTypes'; import { getUserUrl } from '../../utils/urlUtils'; @@ -12,7 +12,12 @@ export default function UserLink({ owner = {} }: UserLinkProps) { if (hasSlug && slug) { return ( - + {displayName || 'Anonymous'} diff --git a/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmpty.tsx b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmpty.tsx new file mode 100644 index 0000000..c97cc46 --- /dev/null +++ b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmpty.tsx @@ -0,0 +1,28 @@ +import { Flex, Link } from '@chakra-ui/react'; +import { EmptyState } from '@codiga/components'; +import { useParams } from 'react-router-dom'; +import { APP_URL } from '../../lib/config'; + +export default function ViewCookbookSnippetsEmpty() { + const params = useParams(); + + return ( + + + + Add Snippet to Cookbook + + + + ); +} diff --git a/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmptyFiltered.tsx b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmptyFiltered.tsx new file mode 100644 index 0000000..2679d12 --- /dev/null +++ b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmptyFiltered.tsx @@ -0,0 +1,38 @@ +import { Button, Flex, Link } from '@chakra-ui/react'; +import { EmptyState } from '@codiga/components'; +import { useParams } from 'react-router-dom'; +import { APP_URL } from '../../lib/config'; + +type ViewCookbookSnippetsEmptyFilteredProps = { + clearSearch: () => void; +}; + +export default function ViewCookbookSnippetsEmptyFiltered({ + clearSearch, +}: ViewCookbookSnippetsEmptyFilteredProps) { + const params = useParams(); + + return ( + + + + + + Add Snippet to Cookbook + + + + ); +} diff --git a/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsError.tsx b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsError.tsx new file mode 100644 index 0000000..7b78a16 --- /dev/null +++ b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsError.tsx @@ -0,0 +1,29 @@ +import { Flex, Link } from '@chakra-ui/react'; +import { EmptyState } from '@codiga/components'; +import { Link as RouterLink } from 'react-router-dom'; +import { APP_URL } from '../../lib/config'; + +export default function ViewCookbookSnippetsError() { + return ( + + + + Go Home + + + Contact Support + + + + ); +} diff --git a/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsLoading.tsx b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsLoading.tsx new file mode 100644 index 0000000..c55be64 --- /dev/null +++ b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsLoading.tsx @@ -0,0 +1,21 @@ +import { HStack, Skeleton } from '@chakra-ui/react'; + +export default function ViewCookbookSnippetsLoading() { + return ( + + + + + + + + + ); +} diff --git a/src/renderer/components/ViewSnippet/ViewSnippetError.tsx b/src/renderer/components/ViewSnippet/ViewSnippetError.tsx new file mode 100644 index 0000000..90f2a03 --- /dev/null +++ b/src/renderer/components/ViewSnippet/ViewSnippetError.tsx @@ -0,0 +1,25 @@ +import { Flex, Link } from '@chakra-ui/react'; +import { EmptyState } from '@codiga/components'; +import { APP_URL } from '../../lib/config'; + +export default function ViewSnippetError() { + return ( + + + + Contact Support + + + + ); +} diff --git a/src/renderer/components/ViewSnippet/ViewSnippetLoading.tsx b/src/renderer/components/ViewSnippet/ViewSnippetLoading.tsx new file mode 100644 index 0000000..d1f66d6 --- /dev/null +++ b/src/renderer/components/ViewSnippet/ViewSnippetLoading.tsx @@ -0,0 +1,26 @@ +import { Box, HStack, Skeleton } from '@chakra-ui/react'; +import CodeLoading from '../Code/CodeLoading'; + +export default function ViewSnippetLoading() { + return ( + + + + + + + + + + + + + ); +} diff --git a/src/renderer/components/SearchResults/Votes.tsx b/src/renderer/components/Votes/Votes.tsx similarity index 77% rename from src/renderer/components/SearchResults/Votes.tsx rename to src/renderer/components/Votes/Votes.tsx index 82d2a13..277218e 100644 --- a/src/renderer/components/SearchResults/Votes.tsx +++ b/src/renderer/components/Votes/Votes.tsx @@ -1,6 +1,11 @@ +import { useRef } from 'react'; +import { useInView } from 'framer-motion'; import { useMutation, useQuery } from '@apollo/client'; import { Flex, FlexProps, IconButton, Text, Tooltip } from '@chakra-ui/react'; -import { DownVoteIcon, UpVoteIcon, useToast } from '@codiga/codiga-components'; +import { DownVoteIcon, UpVoteIcon, useToast } from '@codiga/components'; +import { LogArgument } from 'rollbar'; +import { useRollbar } from '@rollbar/react'; + import { useUser } from '../UserContext'; import { AddVoteMutationVariables, @@ -18,27 +23,31 @@ type VotesProps = FlexProps & { downvotes: number; }; -const Votes = ({ +export default function Votes({ upvotes, downvotes, entityId, entityType = 'Recipe', ...props -}: VotesProps) => { +}: VotesProps) { const toast = useToast(); + const rollbar = useRollbar(); const { id: userId } = useUser(); + const ref = useRef(null); + const isInView = useInView(ref); + const { data, refetch } = useQuery(GET_RECIPE_VOTES_QUERY, { - skip: !userId, + skip: !userId || !isInView, variables: { recipeId: entityId, }, }); - const isUpVoted = Boolean(data?.votesData.isUpVoted); - const isDownVoted = Boolean(data?.votesData.isDownVoted); - const upVoteCount = Number(data?.votesData.upvotes); - const downVoteCount = Number(data?.votesData.downvotes); + const isUpVoted = Boolean(data?.votesData?.isUpVoted); + const isDownVoted = Boolean(data?.votesData?.isDownVoted); + const upVoteCount = Number(data?.votesData?.upvotes || upvotes); + const downVoteCount = Number(data?.votesData?.downvotes || downvotes); const voteText = data ? upVoteCount - downVoteCount : upvotes - downvotes; const [addVote] = useMutation(ADD_VOTE); @@ -59,6 +68,11 @@ const Votes = ({ description: 'Snippet upvoted', }); } catch (err) { + rollbar.error('Error upvoting', err as LogArgument, { + userId, + entityId, + entityType, + }); toast({ status: 'error', description: 'An error occured while upvoting. Please refresh.', @@ -79,6 +93,11 @@ const Votes = ({ description: 'Snippet downvoted.', }); } catch (err) { + rollbar.error('Error downvoting', err as LogArgument, { + userId, + entityId, + entityType, + }); toast({ status: 'error', description: 'An error occured while downvoting. Please refresh.', @@ -89,7 +108,7 @@ const Votes = ({ const countColor = isUpVoted || isDownVoted ? 'rose.50' : undefined; return ( - + ); -}; - -export default Votes; +} diff --git a/src/renderer/components/Votes/index.tsx b/src/renderer/components/Votes/index.tsx new file mode 100644 index 0000000..c8d0774 --- /dev/null +++ b/src/renderer/components/Votes/index.tsx @@ -0,0 +1 @@ +export { default } from './Votes'; diff --git a/src/renderer/components/VotesCurrent/VotesCurrent.tsx b/src/renderer/components/VotesCurrent/VotesCurrent.tsx index 792e157..c26493e 100644 --- a/src/renderer/components/VotesCurrent/VotesCurrent.tsx +++ b/src/renderer/components/VotesCurrent/VotesCurrent.tsx @@ -1,5 +1,5 @@ import { Flex, Text } from '@chakra-ui/react'; -import { DownVoteIcon, UpVoteIcon } from '@codiga/codiga-components'; +import { DownVoteIcon, UpVoteIcon } from '@codiga/components'; type VotesCurrentProps = { upvotes?: number; diff --git a/src/renderer/graphql/client.ts b/src/renderer/graphql/client.ts index d28476a..510024d 100644 --- a/src/renderer/graphql/client.ts +++ b/src/renderer/graphql/client.ts @@ -9,7 +9,7 @@ import { setContext } from '@apollo/client/link/context'; import DebounceLink from 'apollo-link-debounce'; import { API_URL, TOKEN } from '../lib/config'; -const DEFAULT_DEBOUNCE_TIMEOUT = 500; +const DEFAULT_DEBOUNCE_TIMEOUT = 300; const Link = ApolloLink.from([ new DebounceLink(DEFAULT_DEBOUNCE_TIMEOUT), diff --git a/src/renderer/graphql/mutations.ts b/src/renderer/graphql/mutations.ts index 8dd2c0c..2d34a21 100644 --- a/src/renderer/graphql/mutations.ts +++ b/src/renderer/graphql/mutations.ts @@ -1,5 +1,5 @@ import { gql } from '@apollo/client'; -import { UserPreferenceKeyType } from 'renderer/types/userTypes'; +import { UserPreferenceKeyType } from '../types/userTypes'; export type RemoveUserPreferenceVariables = { key: UserPreferenceKeyType; diff --git a/src/renderer/graphql/queries.ts b/src/renderer/graphql/queries.ts index 17784d2..d6ba5da 100644 --- a/src/renderer/graphql/queries.ts +++ b/src/renderer/graphql/queries.ts @@ -3,7 +3,7 @@ import { AssistantRecipeWithStats, LanguageEnumeration, LibraryWithAllEnumeration, -} from 'renderer/types/assistantTypes'; +} from '../types/assistantTypes'; export const CHECK_USER = gql` query checkUser { @@ -399,3 +399,118 @@ export const GET_RECIPE_VOTES_QUERY = gql` } } `; + +export const GET_RECIPE = gql` + query getRecipe($recipeId: Long!) { + recipe: assistantRecipe(id: $recipeId) { + id + code + name + tags + uses + imports + upvotes + language + keywords + downvotes + isUpVoted + isDownVoted + description + isSubscribed + commentsCount + averageRating + presentableFormat + creationTimestampMs + owner { + id + slug + displayName + } + cookbook { + id + name + } + dependencyConstraints { + name + } + } + } +`; + +export const GET_COOKBOOK_INFO = gql` + query getCookbookRecipes($cookbookId: Long!) { + cookbook: assistantCookbook(id: $cookbookId) { + id + name + isPublic + isSubscribed + recipesCount + creationTimestampMs + groups { + id + name + } + owner { + id + hasSlug + slug + displayName + } + upvotes + downvotes + languages + } + } +`; + +export const GET_COOKBOOK_RECIPES = gql` + query getCookbookRecipes( + $cookbookId: Long! + $howmany: Long! + $skip: Long! + $name: String + $orderBy: AssistantRecipeQueryOrderBy + $desc: Boolean + ) { + cookbook: assistantCookbook(id: $cookbookId) { + recipes( + howmany: $howmany + skip: $skip + name: $name + orderBy: $orderBy + desc: $desc + ) { + id + name + code + tags + uses + imports + upvotes + language + keywords + downvotes + isUpVoted + isDownVoted + description + isSubscribed + commentsCount + averageRating + presentableFormat + creationTimestampMs + owner { + id + slug + displayName + } + cookbook { + id + name + } + dependencyConstraints { + name + } + } + } + } +`; diff --git a/src/renderer/hooks/useQueryVariables.ts b/src/renderer/hooks/useQueryVariables.ts new file mode 100644 index 0000000..1607c88 --- /dev/null +++ b/src/renderer/hooks/useQueryVariables.ts @@ -0,0 +1,93 @@ +import { useUser } from '../components/UserContext'; +import { + GET_USER_RECIPES_VARIABLES, + GET_USER_SUBSCRIBED_RECIPES_VARIABLES, + GET_SHARED_RECIPES_VARIABLES, + GET_USER_COOKBOOKS_VARIABLES, + GET_USER_SUBSCRIBED_COOKBOOKS_VARIABLES, + GET_SHARED_COOKBOOKS_VARIABLES, +} from '../graphql/variables'; +import { Language } from '../lib/constants'; +import { LanguageEnumeration } from '../types/assistantTypes'; +import { useFilters } from '../components/FiltersContext'; + +type QueryTypes = + | 'home' + | 'my-snippets' + | 'favorite-snippets' + | 'team-snippets' + | 'my-cookbooks' + | 'favorite-cookbooks' + | 'team-cookbooks'; + +export default function useQueryVariables(query: QueryTypes) { + const filters = useFilters(); + const { id: userId } = useUser(); + + switch (query) { + case 'home': + return { + howmany: 100, + skip: 0, + languages: + filters.language && filters.language !== Language.ALL_LANGUAGES + ? ([filters.language] as LanguageEnumeration[]) + : null, + dependencies: filters.library ? [filters.library] : null, + term: filters.searchTerm || null, + tags: filters.tags ? [filters.tags] : null, + onlyPrivate: filters.privacy === 'private' && !!userId ? true : null, + onlyPublic: filters.privacy === 'public' && !!userId ? true : null, + onlySubscribed: userId ? filters.isSubscribed || null : null, + }; + + case 'my-snippets': + return { + ...GET_USER_RECIPES_VARIABLES, + name: filters.searchTerm || null, + language: + filters.language && filters.language !== Language.ALL_LANGUAGES + ? filters.language + : null, + tag: filters.tags || null, + }; + + case 'favorite-snippets': + return { + ...GET_USER_SUBSCRIBED_RECIPES_VARIABLES, + name: filters.searchTerm || null, + }; + + case 'team-snippets': + return { + ...GET_SHARED_RECIPES_VARIABLES, + name: filters.searchTerm || null, + languages: + filters.language && filters.language !== Language.ALL_LANGUAGES + ? [filters.language] + : null, + tag: filters.tags || null, + }; + + case 'my-cookbooks': + return { + ...GET_USER_COOKBOOKS_VARIABLES, + name: filters.searchTerm || null, + }; + + case 'favorite-cookbooks': + return { + ...GET_USER_SUBSCRIBED_COOKBOOKS_VARIABLES, + name: filters.searchTerm || null, + }; + + case 'team-cookbooks': + return { + ...GET_SHARED_COOKBOOKS_VARIABLES, + name: filters.searchTerm || null, + }; + + default: + return {}; + } +} diff --git a/src/renderer/hooks/useUrlQuery.ts b/src/renderer/hooks/useUrlQuery.ts new file mode 100644 index 0000000..b498826 --- /dev/null +++ b/src/renderer/hooks/useUrlQuery.ts @@ -0,0 +1,8 @@ +import { useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; + +export default function useUrlQuery() { + const { search } = useLocation(); + + return useMemo(() => new URLSearchParams(search), [search]); +} diff --git a/src/renderer/lib/constants.ts b/src/renderer/lib/constants.ts index 669599d..c484474 100644 --- a/src/renderer/lib/constants.ts +++ b/src/renderer/lib/constants.ts @@ -1,4 +1,4 @@ -import { RecipeVariableType } from 'renderer/types/assistantTypes'; +import { RecipeVariableType } from '../types/assistantTypes'; export enum Language { LANGUAGE_UNKNOWN = 'Unknown', @@ -8,11 +8,14 @@ export enum Language { LANGUAGE_JSON = 'Json', LANGUAGE_YAML = 'Yaml', LANGUAGE_TYPESCRIPT = 'Typescript', + LANGUAGE_TWIG = 'Twig', LANGUAGE_SWIFT = 'Swift', LANGUAGE_SOLIDITY = 'Solidity', LANGUAGE_SQL = 'Sql', LANGUAGE_SHELL = 'Shell', LANGUAGE_SCALA = 'Scala', + LANGUAGE_SCSS = 'Scss', + LANGUAGE_SASS = 'Sass', LANGUAGE_REACT = 'React', LANGUAGE_PASCAL = 'Pascal', LANGUAGE_RUST = 'Rust', @@ -20,6 +23,7 @@ export enum Language { LANGUAGE_PHP = 'Php', LANGUAGE_PYTHON = 'Python', LANGUAGE_PERL = 'Perl', + LANGUAGE_MARKDOWN = 'Markdown', LANGUAGE_KOTLIN = 'Kotlin', LANGUAGE_JAVASCRIPT = 'Javascript', LANGUAGE_JAVA = 'Java', @@ -456,3 +460,4 @@ export const SSO_PROVIDERS_LOWER = SSO_PROVIDERS.map((val) => ) as Lowercase[]; export const REVALIDATE_USER_PAGE_IN_SECONDS = 60; +export const PAGE_QUERY_POLL_INTERVAL_IN_MS = 10000; diff --git a/src/renderer/lib/rollbar.ts b/src/renderer/lib/rollbar.ts new file mode 100644 index 0000000..ac7e219 --- /dev/null +++ b/src/renderer/lib/rollbar.ts @@ -0,0 +1,20 @@ +import rollbarAccessToken from './rollbarAccessToken'; + +export const rollbarConfig = { + accessToken: rollbarAccessToken, + captureUncaught: true, + captureUnhandledRejections: true, + payload: { environment: process.env.NODE_ENV || 'development' }, + autoInstrument: { + network: true, + networkResponseHeaders: true, + // networkResponseBody: true, + networkRequestBody: true, + log: true, + dom: true, + navigation: true, + connectivity: true, + contentSecurityPolicy: true, + errorOnContentSecurityPolicy: true, + }, +}; diff --git a/src/renderer/lib/rollbarAccessToken.ts b/src/renderer/lib/rollbarAccessToken.ts new file mode 100644 index 0000000..f546b82 --- /dev/null +++ b/src/renderer/lib/rollbarAccessToken.ts @@ -0,0 +1 @@ +export default 'ROLLBAR_TOKEN'; diff --git a/src/renderer/pages/FavoriteCookbooks.tsx b/src/renderer/pages/FavoriteCookbooks.tsx index 0654d94..155e495 100644 --- a/src/renderer/pages/FavoriteCookbooks.tsx +++ b/src/renderer/pages/FavoriteCookbooks.tsx @@ -1,25 +1,24 @@ import { useQuery } from '@apollo/client'; import { GET_USER_SUBSCRIBED_COOKBOOKS } from '../graphql/queries'; -import { GET_USER_SUBSCRIBED_COOKBOOKS_VARIABLES } from '../graphql/variables'; import { AssistantCookbook } from '../types/assistantTypes'; import CookbookTableLoading from '../components/CookbookTable/CookbookTableLoading'; import CookbookTableError from '../components/CookbookTable/CookbookTableError'; import CookbookTableEmpty from '../components/CookbookTable/CookbookTableEmpty'; -import CookbookTableEmptyFiltereed from '../components/CookbookTable/CookbookTableEmptyFiltered'; +import CookbookTableEmptyFiltered from '../components/CookbookTable/CookbookTableEmptyFiltered'; import CookbookTable from '../components/CookbookTable/CookbookTable'; import { useFilters } from '../components/FiltersContext'; -import filterBy from '../components/Filters/filterBy'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function FavoriteCookbooks() { const filters = useFilters(); + const variables = useQueryVariables('favorite-cookbooks'); const { data, loading, error } = useQuery<{ user: { cookbooks: AssistantCookbook[] }; }>(GET_USER_SUBSCRIBED_COOKBOOKS, { - variables: { - ...GET_USER_SUBSCRIBED_COOKBOOKS_VARIABLES, - name: filters.searchTerm, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'favorite-cookbooks', }, @@ -27,15 +26,6 @@ export default function FavoriteCookbooks() { const userCookbooks = data?.user?.cookbooks || []; - // check the recipe against the search filters - const filteredCookbooks = userCookbooks.filter((cookbook) => { - if (!filterBy.name(filters, cookbook.name)) return false; - // if (!filterBy.language(filters, cookbook.language)) return false; - if (!filterBy.privacy(filters, cookbook.isPublic)) return false; - if (!filterBy.isSubscribed(filters, cookbook.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -44,13 +34,13 @@ export default function FavoriteCookbooks() { return ; } - if (userCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0 && !filters.isEmpty) { + return ; } - if (filteredCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/FavoriteSnippets.tsx b/src/renderer/pages/FavoriteSnippets.tsx index 3588701..a6f674f 100644 --- a/src/renderer/pages/FavoriteSnippets.tsx +++ b/src/renderer/pages/FavoriteSnippets.tsx @@ -1,25 +1,24 @@ import { useQuery } from '@apollo/client'; import { GET_USER_SUBSCRIBED_RECIPES } from '../graphql/queries'; -import { GET_USER_SUBSCRIBED_RECIPES_VARIABLES } from '../graphql/variables'; import { AssistantRecipeWithStats } from '../types/assistantTypes'; import SnippetTableLoading from '../components/SnippetTable/SnippetTableLoading'; import SnippetTableError from '../components/SnippetTable/SnippetTableError'; import SnippetTableEmpty from '../components/SnippetTable/SnippetTableEmpty'; import SnippetTableEmptyFiltered from '../components/SnippetTable/SnippetTableEmptyFiltered'; import SnippetTable from '../components/SnippetTable/SnippetTable'; -import filterBy from '../components/Filters/filterBy'; import { useFilters } from '../components/FiltersContext'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function MySnippets() { const filters = useFilters(); + const variables = useQueryVariables('favorite-snippets'); const { data, loading, error } = useQuery<{ user: { recipes: AssistantRecipeWithStats[] }; }>(GET_USER_SUBSCRIBED_RECIPES, { - variables: { - ...GET_USER_SUBSCRIBED_RECIPES_VARIABLES, - name: filters.searchTerm, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'favorite-snippets', }, @@ -27,17 +26,6 @@ export default function MySnippets() { const userFavoriteRecipes = data?.user?.recipes || []; - // check the recipe against the search filters - const filteredRecipes = userFavoriteRecipes.filter((recipe) => { - if (!filterBy.name(filters, recipe.name)) return false; - if (!filterBy.language(filters, recipe.language)) return false; - if (!filterBy.library(filters, recipe.dependencyConstraints)) return false; - if (!filterBy.tags(filters, recipe.tags)) return false; - if (!filterBy.privacy(filters, recipe.isPublic)) return false; - if (!filterBy.isSubscribed(filters, recipe.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -46,13 +34,13 @@ export default function MySnippets() { return ; } - if (userFavoriteRecipes.length === 0) { - return ; + if (userFavoriteRecipes.length === 0 && !filters.isEmpty) { + return ; } - if (filteredRecipes.length === 0) { - return ; + if (userFavoriteRecipes.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/Home.tsx b/src/renderer/pages/Home.tsx index 06f4aa1..b376c45 100644 --- a/src/renderer/pages/Home.tsx +++ b/src/renderer/pages/Home.tsx @@ -1,37 +1,25 @@ import { useQuery } from '@apollo/client'; -import { useFilters } from '../components/FiltersContext'; -import SearchResults from '../components/SearchResults'; +import useQueryVariables from '../hooks/useQueryVariables'; import SearchResultsEmpty from '../components/SearchResults/SearchResultsEmpty'; import SearchResultsError from '../components/SearchResults/SearchResultsError'; -import SearchResultsLoading from '../components/SearchResults/SearchResultsLoading'; +import SnippetResults from '../components/SnippetResults/SnippetResults'; +import SnippetResultsLoading from '../components/SnippetResults/SnippetResultsLoading'; import { GetRecipesSemanticallyData, GetRecipesSemanticallyVariables, GET_RECIPES_SEMANTICALLY, } from '../graphql/queries'; -import { Language } from '../lib/constants'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function Home() { - const filters = useFilters(); + const variables = useQueryVariables('home'); const { data, loading, error } = useQuery< GetRecipesSemanticallyData, GetRecipesSemanticallyVariables >(GET_RECIPES_SEMANTICALLY, { - variables: { - howmany: 100, - skip: 0, - languages: - filters.language && filters.language !== Language.ALL_LANGUAGES - ? [filters.language] - : null, - dependencies: filters.library ? [filters.library] : null, - term: filters.searchTerm || null, - tags: filters.tags ? [filters.tags] : null, - onlyPrivate: filters.privacy === 'private' ? true : null, - onlyPublic: filters.privacy === 'public' ? true : null, - onlySubscribed: filters.isSubscribed || null, - }, + variables: variables as GetRecipesSemanticallyVariables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'search', }, @@ -44,12 +32,12 @@ export default function Home() { } if (loading) { - return ; + return ; } if (results.length === 0) { return ; } - return ; + return ; } diff --git a/src/renderer/pages/MyCookbooks.tsx b/src/renderer/pages/MyCookbooks.tsx index cdd99d0..75f5ddc 100644 --- a/src/renderer/pages/MyCookbooks.tsx +++ b/src/renderer/pages/MyCookbooks.tsx @@ -1,25 +1,24 @@ import { useQuery } from '@apollo/client'; -import { GET_USER_COOKBOOKS_VARIABLES } from '../graphql/variables'; import { GET_USER_COOKBOOKS } from '../graphql/queries'; import { AssistantCookbook } from '../types/assistantTypes'; import CookbookTableLoading from '../components/CookbookTable/CookbookTableLoading'; import CookbookTableError from '../components/CookbookTable/CookbookTableError'; import CookbookTableEmpty from '../components/CookbookTable/CookbookTableEmpty'; -import CookbookTableEmptyFiltereed from '../components/CookbookTable/CookbookTableEmptyFiltered'; +import CookbookTableEmptyFiltered from '../components/CookbookTable/CookbookTableEmptyFiltered'; import CookbookTable from '../components/CookbookTable/CookbookTable'; import { useFilters } from '../components/FiltersContext'; -import filterBy from '../components/Filters/filterBy'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function MyCookbooks() { const filters = useFilters(); + const variables = useQueryVariables('my-cookbooks'); const { data, loading, error } = useQuery<{ user: { cookbooks: AssistantCookbook[] }; }>(GET_USER_COOKBOOKS, { - variables: { - ...GET_USER_COOKBOOKS_VARIABLES, - name: filters.searchTerm, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'my-cookbooks', }, @@ -27,15 +26,6 @@ export default function MyCookbooks() { const userCookbooks = data?.user?.cookbooks || []; - // check the recipe against the search filters - const filteredCookbooks = userCookbooks.filter((cookbook) => { - if (!filterBy.name(filters, cookbook.name)) return false; - // if (!filterBy.language(filters, cookbook.language)) return false; - if (!filterBy.privacy(filters, cookbook.isPublic)) return false; - if (!filterBy.isSubscribed(filters, cookbook.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -44,13 +34,13 @@ export default function MyCookbooks() { return ; } - if (userCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0 && !filters.isEmpty) { + return ; } - if (filteredCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/MySnippets.tsx b/src/renderer/pages/MySnippets.tsx index cc8b183..7fc77f1 100644 --- a/src/renderer/pages/MySnippets.tsx +++ b/src/renderer/pages/MySnippets.tsx @@ -1,31 +1,24 @@ import { useQuery } from '@apollo/client'; import { GET_USER_RECIPES } from '../graphql/queries'; -import { GET_USER_RECIPES_VARIABLES } from '../graphql/variables'; import { AssistantRecipeWithStats } from '../types/assistantTypes'; import SnippetTableLoading from '../components/SnippetTable/SnippetTableLoading'; import SnippetTableError from '../components/SnippetTable/SnippetTableError'; import SnippetTableEmpty from '../components/SnippetTable/SnippetTableEmpty'; +import SnippetTableEmptyFiltered from '../components/SnippetTable/SnippetTableEmptyFiltered'; import SnippetTable from '../components/SnippetTable/SnippetTable'; import { useFilters } from '../components/FiltersContext'; -import filterBy from '../components/Filters/filterBy'; -import SnippetTableEmptyFiltered from '../components/SnippetTable/SnippetTableEmptyFiltered'; -import { Language } from '../lib/constants'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function MySnippets() { const filters = useFilters(); + const variables = useQueryVariables('my-snippets'); const { data, loading, error } = useQuery<{ user: { recipes: AssistantRecipeWithStats[] }; }>(GET_USER_RECIPES, { - variables: { - ...GET_USER_RECIPES_VARIABLES, - name: filters.searchTerm, - language: - filters.language && filters.language !== Language.ALL_LANGUAGES - ? filters.language - : null, - tag: filters.tags, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'my-snippets', }, @@ -33,17 +26,6 @@ export default function MySnippets() { const userRecipes = data?.user?.recipes || []; - // check the recipe against the search filters - const filteredRecipes = userRecipes.filter((recipe) => { - if (!filterBy.name(filters, recipe.name)) return false; - if (!filterBy.language(filters, recipe.language)) return false; - if (!filterBy.library(filters, recipe.dependencyConstraints)) return false; - if (!filterBy.tags(filters, recipe.tags)) return false; - if (!filterBy.privacy(filters, recipe.isPublic)) return false; - if (!filterBy.isSubscribed(filters, recipe.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -52,13 +34,13 @@ export default function MySnippets() { return ; } - if (userRecipes.length === 0) { - return ; + if (userRecipes.length === 0 && !filters.isEmpty) { + return ; } - if (filteredRecipes.length === 0) { - return ; + if (userRecipes.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/TeamCookbooks.tsx b/src/renderer/pages/TeamCookbooks.tsx index 9e740f5..fbe9c7f 100644 --- a/src/renderer/pages/TeamCookbooks.tsx +++ b/src/renderer/pages/TeamCookbooks.tsx @@ -1,25 +1,24 @@ import { useQuery } from '@apollo/client'; import { GET_SHARED_COOKBOOKS } from '../graphql/queries'; -import { GET_SHARED_COOKBOOKS_VARIABLES } from '../graphql/variables'; import { AssistantCookbook } from '../types/assistantTypes'; import CookbookTableLoading from '../components/CookbookTable/CookbookTableLoading'; import CookbookTableError from '../components/CookbookTable/CookbookTableError'; import CookbookTableEmpty from '../components/CookbookTable/CookbookTableEmpty'; -import CookbookTableEmptyFiltereed from '../components/CookbookTable/CookbookTableEmptyFiltered'; +import CookbookTableEmptyFiltered from '../components/CookbookTable/CookbookTableEmptyFiltered'; import CookbookTable from '../components/CookbookTable/CookbookTable'; import { useFilters } from '../components/FiltersContext'; -import filterBy from '../components/Filters/filterBy'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function TeamCookbooks() { const filters = useFilters(); + const variables = useQueryVariables('team-cookbooks'); const { data, loading, error } = useQuery<{ cookbooks: AssistantCookbook[]; }>(GET_SHARED_COOKBOOKS, { - variables: { - ...GET_SHARED_COOKBOOKS_VARIABLES, - name: filters.searchTerm, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'team-cookbooks', }, @@ -27,14 +26,6 @@ export default function TeamCookbooks() { const userCookbooks = data?.cookbooks || []; - // check the recipe against the search filters - const filteredCookbooks = userCookbooks.filter((cookbook) => { - if (!filterBy.name(filters, cookbook.name)) return false; - if (!filterBy.privacy(filters, cookbook.isPublic)) return false; - if (!filterBy.isSubscribed(filters, cookbook.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -43,13 +34,13 @@ export default function TeamCookbooks() { return ; } - if (userCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0 && !filters.isEmpty) { + return ; } - if (filteredCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/TeamSnippets.tsx b/src/renderer/pages/TeamSnippets.tsx index f12e389..4500a0f 100644 --- a/src/renderer/pages/TeamSnippets.tsx +++ b/src/renderer/pages/TeamSnippets.tsx @@ -1,31 +1,24 @@ import { useQuery } from '@apollo/client'; import { GET_SHARED_RECIPES } from '../graphql/queries'; -import { GET_SHARED_RECIPES_VARIABLES } from '../graphql/variables'; import { AssistantRecipeWithStats } from '../types/assistantTypes'; import SnippetTableLoading from '../components/SnippetTable/SnippetTableLoading'; import SnippetTableError from '../components/SnippetTable/SnippetTableError'; import SnippetTableEmpty from '../components/SnippetTable/SnippetTableEmpty'; -import SnippetTableEmptyFiltereed from '../components/SnippetTable/SnippetTableEmptyFiltered'; +import SnippetTableEmptyFiltered from '../components/SnippetTable/SnippetTableEmptyFiltered'; import SnippetTable from '../components/SnippetTable/SnippetTable'; -import filterBy from '../components/Filters/filterBy'; import { useFilters } from '../components/FiltersContext'; -import { Language } from '../lib/constants'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function TeamSnippets() { const filters = useFilters(); + const variables = useQueryVariables('team-snippets'); const { data, loading, error } = useQuery<{ recipes: AssistantRecipeWithStats[]; }>(GET_SHARED_RECIPES, { - variables: { - ...GET_SHARED_RECIPES_VARIABLES, - name: filters.searchTerm, - languages: - filters.language && filters.language !== Language.ALL_LANGUAGES - ? [filters.language] - : null, - tag: filters.tags, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'team-snippets', }, @@ -33,17 +26,6 @@ export default function TeamSnippets() { const teamRecipes = data?.recipes || []; - // check the recipe against the search filters - const filteredRecipes = teamRecipes.filter((recipe) => { - if (!filterBy.name(filters, recipe.name)) return false; - if (!filterBy.language(filters, recipe.language)) return false; - if (!filterBy.library(filters, recipe.dependencyConstraints)) return false; - if (!filterBy.tags(filters, recipe.tags)) return false; - if (!filterBy.privacy(filters, recipe.isPublic)) return false; - if (!filterBy.isSubscribed(filters, recipe.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -52,13 +34,13 @@ export default function TeamSnippets() { return ; } - if (teamRecipes.length === 0) { - return ; + if (teamRecipes.length === 0 && !filters.isEmpty) { + return ; } - if (filteredRecipes.length === 0) { - return ; + if (teamRecipes.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/ViewCookbookSnippets.tsx b/src/renderer/pages/ViewCookbookSnippets.tsx new file mode 100644 index 0000000..f8fa231 --- /dev/null +++ b/src/renderer/pages/ViewCookbookSnippets.tsx @@ -0,0 +1,131 @@ +/* eslint-disable no-nested-ternary */ +import { useState } from 'react'; +import { useQuery } from '@apollo/client'; +import { useParams } from 'react-router-dom'; +import { Box, Flex, HStack, Input, Link, Text } from '@chakra-ui/react'; + +import { getCookbookUrl } from '../utils/urlUtils'; +import { GET_COOKBOOK_INFO, GET_COOKBOOK_RECIPES } from '../graphql/queries'; +import { GET_USER_RECIPES_VARIABLES } from '../graphql/variables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; +import ViewCookbookSnippetsError from '../components/ViewCookbookSnippets/ViewCookbookSnippetsError'; +import ViewCookbookSnippetsLoading from '../components/ViewCookbookSnippets/ViewCookbookSnippetsLoading'; +import ViewCookbookSnippetsEmpty from '../components/ViewCookbookSnippets/ViewCookbookSnippetsEmpty'; +import ViewCookbookSnippetsEmptyFiltered from '../components/ViewCookbookSnippets/ViewCookbookSnippetsEmptyFiltered'; +import BackButton from '../components/BackButton'; +import FavoriteCookbook from '../components/Favorite/FavoriteCookbook'; +import AvatarAndName from '../components/AvatarAndName'; +import PrivacyAndVotes from '../components/PrivacyAndVotes'; +import FormattedDate from '../components/FormattedDate'; +import SnippetResults from '../components/SnippetResults/SnippetResults'; +import SnippetResultsLoading from '../components/SnippetResults/SnippetResultsLoading'; + +export default function ViewCookbookSnippets() { + const params = useParams(); + const [searchTerm, setSearchTerm] = useState(''); + + const { + data: cookbookInfoData, + loading: cookbookInfoLoading, + error: cookbookInfoError, + } = useQuery(GET_COOKBOOK_INFO, { + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, + variables: { + cookbookId: Number(params.cookbookId), + }, + }); + + const { + data: cookbookSnippetData, + loading: cookbookSnippetLoading, + error: cookbookSnippetError, + } = useQuery(GET_COOKBOOK_RECIPES, { + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, + variables: { + cookbookId: Number(params.cookbookId), + ...GET_USER_RECIPES_VARIABLES, + name: searchTerm || null, + }, + context: { + debounceKey: 'view-cookbook-snippets', + }, + }); + + const cookbook = cookbookInfoData?.cookbook; + const snippets = cookbookSnippetData?.cookbook?.recipes || []; + + if (cookbookInfoError || cookbook === null || cookbookSnippetError) { + return ; + } + + return ( + + {/* INFO SECTION */} + {cookbookInfoLoading ? ( + + ) : ( + + + + + + + {cookbook.name} + + + + + + + + + + + + + setSearchTerm(e.target.value)} + /> + + + )} + + {cookbookSnippetLoading ? ( + + ) : snippets.length > 0 ? ( + + ) : searchTerm ? ( + setSearchTerm('')} + /> + ) : ( + + )} + + ); +} diff --git a/src/renderer/pages/ViewSnippet.tsx b/src/renderer/pages/ViewSnippet.tsx new file mode 100644 index 0000000..9d65d84 --- /dev/null +++ b/src/renderer/pages/ViewSnippet.tsx @@ -0,0 +1,86 @@ +import { useParams } from 'react-router-dom'; +import { Box, Flex, HStack, Link, Text } from '@chakra-ui/react'; +import { Logo, Tags } from '@codiga/components'; +import { useQuery } from '@apollo/client'; + +import { getSnippetUrl } from '../utils/urlUtils'; +import { GET_RECIPE } from '../graphql/queries'; +import FavoriteSnippet from '../components/Favorite/FavoriteSnippet'; +import ViewSnippetError from '../components/ViewSnippet/ViewSnippetError'; +import ViewSnippetLoading from '../components/ViewSnippet/ViewSnippetLoading'; +import BackButton from '../components/BackButton'; +import PrivacyAndVotes from '../components/PrivacyAndVotes'; +import FormattedDate from '../components/FormattedDate'; +import AvatarAndName from '../components/AvatarAndName'; +import Code from '../components/Code/Code'; + +export default function ViewSnippet() { + const params = useParams(); + + const { data, loading, error } = useQuery(GET_RECIPE, { + variables: { + recipeId: Number(params.snippetId), + }, + }); + + const recipe = data?.recipe; + + if (loading) { + return ; + } + + if (error || !recipe) { + return ; + } + + return ( + + {/* INFO SECTION */} + + + + + + + + {recipe.name} + + + + + + + + + + + + {recipe?.tags && recipe?.tags.length > 0 && ( + + )} + + + {/* CODE */} + + + ); +} diff --git a/src/renderer/preload.d.ts b/src/renderer/preload.d.ts index 797d275..7e20f12 100644 --- a/src/renderer/preload.d.ts +++ b/src/renderer/preload.d.ts @@ -3,6 +3,7 @@ import { Channels } from 'main/preload'; declare global { interface Window { electron: { + isMac: boolean; ipcRenderer: { sendMessage(channel: Channels, args: unknown[]): void; on( diff --git a/src/renderer/styles/reboot.css b/src/renderer/styles/reboot.css index 3d86a2c..b95ce3c 100644 --- a/src/renderer/styles/reboot.css +++ b/src/renderer/styles/reboot.css @@ -59,7 +59,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; - font-size: 1rem; + font-size: 14px; font-weight: normal; line-height: 1.5; color: #212529; diff --git a/src/renderer/utils/urlUtils.ts b/src/renderer/utils/urlUtils.ts index 61664b6..94f54d4 100644 --- a/src/renderer/utils/urlUtils.ts +++ b/src/renderer/utils/urlUtils.ts @@ -1,28 +1,14 @@ import { APP_URL } from '../lib/config'; -import { PageTypes } from '../types/pageTypes'; -export const getCookbookUrl = ( - page: PageTypes, - id: number, - groupId?: number -) => { - return `${APP_URL}${ - page === 'team' - ? `/assistant/group-sharing/${groupId}/cookbook/${id}/view` - : `/assistant/cookbook/${id}/view` - }`; +export const getCookbookUrl = (id: number) => { + return `${APP_URL}/assistant/cookbook/${id}/view`; }; -export const getSnippetUrl = ( - page: PageTypes, - id: number, - groupId?: number +export const getSnippetUrl: (id: number, page?: 'view' | 'edit') => string = ( + id, + page = 'view' ) => { - return `${APP_URL}${ - page === 'team' - ? `/assistant/group-sharing/${groupId}/snippet/${id}/view` - : `/assistant/snippet/${id}/view` - }`; + return `${APP_URL}/assistant/snippet/${id}/${page}`; }; export const getUserUrl = (slug: string) => {