diff --git a/.gitignore b/.gitignore index 07bdab410..0da494ea2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies +bun.lockb node_modules yarn.lock pnpm-lock.yaml diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 79b93f908..179719995 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -17,6 +17,7 @@ "@fortawesome/react-fontawesome": "^0.1.16", "@reduxjs/toolkit": "^1.8.0", "@tabler/icons": "^1.46.0", + "@tailwindcss/forms": "^0.5.7", "@tippyjs/react": "^4.2.6", "@usebruno/common": "0.1.0", "@usebruno/graphql-docs": "0.1.0", diff --git a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js index ecfc4183d..f95518e43 100644 --- a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import importBrunoCollection from 'utils/importers/bruno-collection'; import importPostmanCollection from 'utils/importers/postman-collection'; import importInsomniaCollection from 'utils/importers/insomnia-collection'; @@ -7,6 +7,13 @@ import { toastError } from 'utils/common/error'; import Modal from 'components/Modal'; const ImportCollection = ({ onClose, handleSubmit }) => { + const [options, setOptions] = useState({ + enablePostmanTranslations: { + enabled: true, + label: 'Auto translate postman scripts', + subLabel: "When enabled, Bruno will try as best to translate the scripts from the imported collection to Bruno's format." + } + }) const handleImportBrunoCollection = () => { importBrunoCollection() .then((collection) => { @@ -16,7 +23,7 @@ const ImportCollection = ({ onClose, handleSubmit }) => { }; const handleImportPostmanCollection = () => { - importPostmanCollection() + importPostmanCollection(options) .then((collection) => { handleSubmit(collection); }) @@ -38,21 +45,66 @@ const ImportCollection = ({ onClose, handleSubmit }) => { }) .catch((err) => toastError(err, 'OpenAPI v3 Import collection failed')); }; - + const toggleOptions = (event, optionKey) => { + setOptions({ ...options, [optionKey]: { + ...options[optionKey], + enabled: !options[optionKey].enabled + } }); + }; + const CollectionButton = ({ children, className, onClick }) => { + return ( + + {children} + + ) + } return ( - - - Bruno Collection + + Select the type of your existing collection : + + + Bruno Collection + + + Postman Collection + + + Insomnia Collection + + + OpenAPI V3 Spec + - - Postman Collection - - - Insomnia Collection - - - OpenAPI V3 Spec + + {Object.entries(options || {}).map(([key, option]) => ( + + + toggleOptions(e,key)} + className="h-3.5 w-3.5 rounded border-zinc-300 dark:ring-offset-zinc-800 bg-transparent text-indigo-600 dark:text-indigo-500 focus:ring-indigo-600 dark:focus:ring-indigo-500" + /> + + + + {option.label} + + + {option.subLabel} + + + + ))} diff --git a/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js b/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js index 62a02bdd5..d8cd3dfd2 100644 --- a/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js +++ b/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js @@ -45,7 +45,11 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) => const onSubmit = () => formik.handleSubmit(); return ( - + diff --git a/packages/bruno-app/src/components/Sidebar/TitleBar/index.js b/packages/bruno-app/src/components/Sidebar/TitleBar/index.js index ebf8c608d..6f683a646 100644 --- a/packages/bruno-app/src/components/Sidebar/TitleBar/index.js +++ b/packages/bruno-app/src/components/Sidebar/TitleBar/index.js @@ -26,7 +26,7 @@ const TitleBar = () => { setImportCollectionLocationModalOpen(true); }; - const handleImportCollectionLocation = (collectionLocation) => { + const handleImportCollectionLocation = (collectionLocation, useTranslation) => { dispatch(importCollection(importedCollection, collectionLocation)); setImportCollectionLocationModalOpen(false); setImportedCollection(null); diff --git a/packages/bruno-app/src/components/Welcome/index.js b/packages/bruno-app/src/components/Welcome/index.js index 251d0a9fe..8969f6278 100644 --- a/packages/bruno-app/src/components/Welcome/index.js +++ b/packages/bruno-app/src/components/Welcome/index.js @@ -29,8 +29,8 @@ const Welcome = () => { setImportCollectionLocationModalOpen(true); }; - const handleImportCollectionLocation = (collectionLocation) => { - dispatch(importCollection(importedCollection, collectionLocation)); + const handleImportCollectionLocation = (collectionLocation, enableTRanslation = true) => { + dispatch(importCollection(importedCollection, collectionLocation, enableTranslation)); setImportCollectionLocationModalOpen(false); setImportedCollection(null); toast.success('Collection imported successfully'); diff --git a/packages/bruno-app/src/providers/Theme/index.js b/packages/bruno-app/src/providers/Theme/index.js index 30603f65b..45336b3f8 100644 --- a/packages/bruno-app/src/providers/Theme/index.js +++ b/packages/bruno-app/src/providers/Theme/index.js @@ -9,20 +9,31 @@ export const ThemeProvider = (props) => { const isBrowserThemeLight = window.matchMedia('(prefers-color-scheme: light)').matches; const [displayedTheme, setDisplayedTheme] = useState(isBrowserThemeLight ? 'light' : 'dark'); const [storedTheme, setStoredTheme] = useLocalStorage('bruno.theme', 'system'); + const toggleHtml = () => { + const html = document.querySelector('html'); + if (html) { + html.classList.toggle('dark'); + } + }; useEffect(() => { window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', (e) => { if (storedTheme !== 'system') return; setDisplayedTheme(e.matches ? 'light' : 'dark'); + toggleHtml(); }); }, []); useEffect(() => { + const root = window.document.documentElement + root.classList.remove("light", "dark"); if (storedTheme === 'system') { const isBrowserThemeLight = window.matchMedia('(prefers-color-scheme: light)').matches; setDisplayedTheme(isBrowserThemeLight ? 'light' : 'dark'); + root.classList.add(isBrowserThemeLight ? 'light' : 'dark') } else { setDisplayedTheme(storedTheme); + root.classList.add(storedTheme) } }, [storedTheme, setDisplayedTheme, window.matchMedia]); diff --git a/packages/bruno-app/src/styles/globals.css b/packages/bruno-app/src/styles/globals.css index 301beaea0..29e9196ea 100644 --- a/packages/bruno-app/src/styles/globals.css +++ b/packages/bruno-app/src/styles/globals.css @@ -58,7 +58,7 @@ body::-webkit-scrollbar-thumb, border-radius: 5rem; } -/* +/* * todo: this will be supported in the future to be changed via applying a theme * making all the checkboxes and radios bigger * input[type='checkbox'], diff --git a/packages/bruno-app/src/utils/importers/postman-collection.js b/packages/bruno-app/src/utils/importers/postman-collection.js index faa74a71b..a219be798 100644 --- a/packages/bruno-app/src/utils/importers/postman-collection.js +++ b/packages/bruno-app/src/utils/importers/postman-collection.js @@ -4,6 +4,7 @@ import fileDialog from 'file-dialog'; import { uuid } from 'utils/common'; import { BrunoError } from 'utils/common/error'; import { validateSchema, transformItemsInCollection, hydrateSeqInCollection } from './common'; +import { postmanTranslation } from 'utils/importers/translators/postman_translation'; const readFile = (files) => { return new Promise((resolve, reject) => { @@ -53,7 +54,7 @@ const convertV21Auth = (array) => { }, {}); }; -const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => { +const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) => { brunoParent.items = brunoParent.items || []; const folderMap = {}; @@ -77,7 +78,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => { brunoParent.items.push(brunoFolderItem); folderMap[folderName] = brunoFolderItem; if (i.item && i.item.length) { - importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth); + importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth, options); } } else { if (i.request) { @@ -121,9 +122,13 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => { brunoRequestItem.request.script = {}; } if (Array.isArray(event.script.exec)) { - brunoRequestItem.request.script.req = event.script.exec.map((line) => `// ${line}`).join('\n'); + brunoRequestItem.request.script.req = event.script.exec + .map((line) => options.enablePostmanTranslations.enabled ? + postmanTranslation(line) : `// ${line}`).join('\n') } else { - brunoRequestItem.request.script.req = `// ${event.script.exec[0]} `; + brunoRequestItem.request.script.req = options.enablePostmanTranslations.enabled ? + postmanTranslation(event.script.exec[0]) : + `// ${event.script.exec[0]} `; } } if (event.listen === 'test' && event.script && event.script.exec) { @@ -131,9 +136,13 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => { brunoRequestItem.request.tests = {}; } if (Array.isArray(event.script.exec)) { - brunoRequestItem.request.tests = event.script.exec.map((line) => `// ${line}`).join('\n'); + brunoRequestItem.request.tests = event.script.exec + .map((line) => options.enablePostmanTranslations.enabled ? + postmanTranslation(line) : `// ${line}`).join('\n'); } else { - brunoRequestItem.request.tests = `// ${event.script.exec[0]} `; + brunoRequestItem.request.tests = options.enablePostmanTranslations.enabled ? + postmanTranslation(event.script.exec[0]) : + `// ${event.script.exec[0]} `; } } }); @@ -263,7 +272,7 @@ const searchLanguageByHeader = (headers) => { return contentType; }; -const importPostmanV2Collection = (collection) => { +const importPostmanV2Collection = (collection, options) => { const brunoCollection = { name: collection.info.name, uid: uuid(), @@ -272,12 +281,12 @@ const importPostmanV2Collection = (collection) => { environments: [] }; - importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth); + importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth, options); return brunoCollection; }; -const parsePostmanCollection = (str) => { +const parsePostmanCollection = (str, options) => { return new Promise((resolve, reject) => { try { let collection = JSON.parse(str); @@ -289,7 +298,7 @@ const parsePostmanCollection = (str) => { ]; if (v2Schemas.includes(schema)) { - return resolve(importPostmanV2Collection(collection)); + return resolve(importPostmanV2Collection(collection, options)); } throw new BrunoError('Unknown postman schema'); @@ -304,11 +313,11 @@ const parsePostmanCollection = (str) => { }); }; -const importCollection = () => { +const importCollection = (options) => { return new Promise((resolve, reject) => { fileDialog({ accept: 'application/json' }) .then(readFile) - .then(parsePostmanCollection) + .then((str) => parsePostmanCollection(str, options)) .then(transformItemsInCollection) .then(hydrateSeqInCollection) .then(validateSchema) diff --git a/packages/bruno-app/src/utils/importers/translators/postman_translation.js b/packages/bruno-app/src/utils/importers/translators/postman_translation.js new file mode 100644 index 000000000..8d53d72e8 --- /dev/null +++ b/packages/bruno-app/src/utils/importers/translators/postman_translation.js @@ -0,0 +1,27 @@ +const replacements = { + 'pm\\.environment\\.get\\(([\'"])([^\'"]*)\\1\\)': 'bru.getEnvVar($1$2$1)', + 'pm\\.environment\\.set\\(([\'"])([^\'"]*)\\1, ([\'"])([^\'"]*)\\3\\)': 'bru.setEnvVar($1$2$1, $3$4$3)', + 'pm\\.variables\\.get\\(([\'"])([^\'"]*)\\1\\)': 'bru.getVar($1$2$1)', + 'pm\\.variables\\.set\\(([\'"])([^\'"]*)\\1, ([\'"])([^\'"]*)\\3\\)': 'bru.setVar($1$2$1, $3$4$3)' +}; + +export const postmanTranslation = (script) => { + try { + const modifiedScript = Object.entries(replacements || {}) + .map(([pattern, replacement]) => { + const regex = new RegExp(pattern, 'g'); + return script?.replace(regex, replacement); + }) + .find((modified) => modified !== script); + if (modifiedScript) { + // translation successful + return modifiedScript; + } else { + // non-translatable script + return script?.includes('pm.') ? `// ${script}` : script; + } + } catch (e) { + // non-translatable script + return script?.includes('pm.') ? `// ${script}` : script; + } +}; diff --git a/packages/bruno-app/tailwind.config.js b/packages/bruno-app/tailwind.config.js index 64070abdf..d4d4817e5 100644 --- a/packages/bruno-app/tailwind.config.js +++ b/packages/bruno-app/tailwind.config.js @@ -1,8 +1,22 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'], + darkMode: ["class"], + content: [ + './pages/**/*.{js,jsx}', + './components/**/*.{js,jsx}', + './app/**/*.{js,jsx}', + './src/**/*.{js,jsx}', + ], + prefix: "", theme: { - extend: {} + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: {}, }, - plugins: [] -}; + plugins: [require("@tailwindcss/forms")], +} \ No newline at end of file diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index ae47d6e06..aed984849 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -403,7 +403,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } }); - ipcMain.handle('renderer:import-collection', async (event, collection, collectionLocation) => { + ipcMain.handle('renderer:import-collection', async (event, collection, collectionLocation, enableTranslation) => { try { let collectionName = sanitizeDirectoryName(collection.name); let collectionPath = path.join(collectionLocation, collectionName);
+ {option.subLabel} +