From 7cad46fad25bdab836c1e09de035f89e7e85a0eb Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sat, 16 Nov 2024 21:44:43 +0100 Subject: [PATCH 01/14] fix modules --- apps-host/electron/package.json | 1 - apps-host/electron/scripts/after-pack.js | 10 +++++----- apps-host/electron/scripts/notarize.js | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps-host/electron/package.json b/apps-host/electron/package.json index 7d187981..e0079dc0 100644 --- a/apps-host/electron/package.json +++ b/apps-host/electron/package.json @@ -3,7 +3,6 @@ "description": "A feathery cross-platform API crafting tool", "version": "1.1.7-beta.9", "private": true, - "type": "module", "author": { "name": "Alexander Forbes-Reed (0xdeafcafe)", "email": "info@getbeak.app" diff --git a/apps-host/electron/scripts/after-pack.js b/apps-host/electron/scripts/after-pack.js index af907966..5ea24238 100644 --- a/apps-host/electron/scripts/after-pack.js +++ b/apps-host/electron/scripts/after-pack.js @@ -1,13 +1,13 @@ /* eslint-disable no-sync */ /* eslint-disable @typescript-eslint/no-var-requires */ -import asar from 'asar'; -import path from 'path'; -import fs from 'fs'; -import os from 'os'; +const asar = require('asar'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); const architectures = ['ia32', 'x64', 'armv7l', 'arm64', 'universal']; -export default async function afterPack(context) { +module.exports = async function afterPack(context) { const arch = architectures[context.arch]; const platform = context.packager.platform.nodeName; const tempDirPath = path.join(os.tmpdir(), Date.now().toString()); diff --git a/apps-host/electron/scripts/notarize.js b/apps-host/electron/scripts/notarize.js index 44f591d5..e6b8cfc2 100644 --- a/apps-host/electron/scripts/notarize.js +++ b/apps-host/electron/scripts/notarize.js @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -import { notarize } from '@electron/notarize'; -import path from 'path'; +const { notarize } = require('@electron/notarize'); +const path = require('path'); -export default async function notarizing(context) { +module.exports = async function notarizing(context) { const { electronPlatformName, appOutDir } = context; if (electronPlatformName !== 'darwin') From 7902e2e1e60a48aab0050803123ebb45b035c567 Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sat, 16 Nov 2024 22:49:39 +0100 Subject: [PATCH 02/14] improve selector, added empty state --- .../components/organism/FinderView.tsx | 2 +- .../components/molecules/VariableSelector.tsx | 57 ++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/features/omni-bar/components/organism/FinderView.tsx b/packages/ui/src/features/omni-bar/components/organism/FinderView.tsx index 1db8350c..0cd27566 100644 --- a/packages/ui/src/features/omni-bar/components/organism/FinderView.tsx +++ b/packages/ui/src/features/omni-bar/components/organism/FinderView.tsx @@ -97,7 +97,7 @@ const FinderView: React.FC> = ({ conten {matches.length === 0 && ( - {'No matching requests found... sadface'} + {'No matching requests found'} )} {matches.map((k, idx) => { diff --git a/packages/ui/src/features/variable-input/components/molecules/VariableSelector.tsx b/packages/ui/src/features/variable-input/components/molecules/VariableSelector.tsx index 8f94ff74..835c90d4 100644 --- a/packages/ui/src/features/variable-input/components/molecules/VariableSelector.tsx +++ b/packages/ui/src/features/variable-input/components/molecules/VariableSelector.tsx @@ -2,19 +2,31 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { TypedObject } from '@beak/common/helpers/typescript'; import { RealtimeValueManager } from '@beak/ui/features/realtime-values'; import useRealtimeValueContext from '@beak/ui/features/realtime-values/hooks/use-realtime-value-context'; -import { ipcExtensionsService } from '@beak/ui/lib/ipc'; +import { ipcExplorerService, ipcExtensionsService } from '@beak/ui/lib/ipc'; import { useAppSelector } from '@beak/ui/store/redux'; import { movePosition } from '@beak/ui/utils/arrays'; import { faPlug } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { RealtimeValue, RealtimeValueInformation } from '@getbeak/types-realtime-value'; import Fuse from 'fuse.js'; -import styled from 'styled-components'; +import styled, { keyframes } from 'styled-components'; import * as uuid from 'uuid'; import { createFauxValue } from '../../../realtime-values/values/variable-group-item'; import { NormalizedSelection } from '../../utils/browser-selection'; +const scaleIn = keyframes` + 0% { + transform: scale(.97); + opacity: 0; + } + + 100% { + transform: scale(1); + opacity: 1; + } +`; + interface Position { top: number; left: number; @@ -164,6 +176,32 @@ const VariableSelector: React.FC> if (!position) return null; + if (items.length === 0) { + return ( + { + event.stopPropagation(); + onClose(); + }}> + + + + {'There are no variables matching your search. Try widening '} + {'your horizons.'} + + + + {'Missing a variable you would find useful?'}
+ {'You can build your own with an extension, check the '} + void await ipcExplorerService.launchUrl("https://getbeak.notion.site/Extensions-4c16ca640b35460787056f8be815b904") }> + {'docs'} + + {'.'} +
+
+
+ ); + } + return ( { event.stopPropagation(); @@ -218,6 +256,10 @@ const Wrapper = styled.div<{ $top: number; $left: number }>` border: 1px solid ${p => p.theme.ui.backgroundBorderSeparator}; background: ${p => p.theme.ui.surface}; + transform-origin: center; + animation: ${scaleIn} .2s ease; + transition: transform .1s ease; + font-size: 12px; `; @@ -241,6 +283,13 @@ const Item = styled.div<{ $active: boolean }>` ${p => p.$active ? `background-color: ${p.theme.ui.primaryFill};'` : ''} `; +const NoItems = styled.div` + padding: 10px; + cursor: pointer; + color: ${p => p.theme.ui.textOnSurfaceBackground}; + overflow-x: hidden; +`; + const ExtensionContainer = styled.div` display: inline-block; margin-right: 5px; @@ -252,6 +301,10 @@ const Description = styled.div` padding: 5px; min-height: 30px; + + > a { + color: #ffa210; + } `; export default VariableSelector; From 43ccc0f89fe7cefe015427248efdfc7ef03555f1 Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sun, 17 Nov 2024 18:45:06 +0100 Subject: [PATCH 03/14] rename realtime value types package --- packages/types-realtime-value/README.md | 3 - .../.npmignore | 0 packages/types-variables/README.md | 3 + .../index.d.ts | 39 ++- .../package.json | 15 +- .../tsconfig.json | 0 .../components/molecules/NoVariableGroups.tsx | 38 --- .../molecules/VariableGroupName.tsx | 77 ----- .../components/organisms/VariableGroups.tsx | 102 ------ .../components/RealtimeValueEditor.tsx | 308 ------------------ .../components/atoms/Form.ts | 19 -- .../components/molecules/PreviewContainer.tsx | 38 --- .../utils/render-request-select-options.tsx | 57 ---- .../ui/src/features/realtime-values/index.ts | 129 -------- .../ui/src/features/realtime-values/parser.ts | 79 ----- .../src/features/realtime-values/preview.ts | 13 - .../src/features/realtime-values/renderer.tsx | 82 ----- .../features/realtime-values/utils/request.ts | 11 - .../realtime-values/utils/response.ts | 13 - .../src/features/realtime-values/values.d.ts | 69 ---- .../realtime-values/values/base64-decode.ts | 76 ----- .../realtime-values/values/base64-encode.ts | 79 ----- .../features/realtime-values/values/digest.ts | 90 ----- .../features/realtime-values/values/nonce.ts | 24 -- .../realtime-values/values/private.ts | 97 ------ .../realtime-values/values/request-folder.ts | 31 -- .../realtime-values/values/request-header.ts | 56 ---- .../realtime-values/values/request-method.ts | 26 -- .../realtime-values/values/request-name.ts | 26 -- .../values/response-body-json.ts | 81 ----- .../values/response-body-text.ts | 54 --- .../realtime-values/values/response-header.ts | 69 ---- .../values/response-status-code.ts | 43 --- .../features/realtime-values/values/secure.ts | 88 ----- .../values/special-character.ts | 41 --- .../realtime-values/values/timestamp.ts | 69 ---- .../realtime-values/values/url-decode.ts | 39 --- .../realtime-values/values/url-encode.ts | 41 --- .../features/realtime-values/values/uuid.ts | 54 --- .../molecules/VariableGroupEditorTab.tsx | 44 --- 40 files changed, 36 insertions(+), 2187 deletions(-) delete mode 100755 packages/types-realtime-value/README.md rename packages/{types-realtime-value => types-variables}/.npmignore (100%) create mode 100755 packages/types-variables/README.md rename packages/{types-realtime-value => types-variables}/index.d.ts (70%) rename packages/{types-realtime-value => types-variables}/package.json (63%) rename packages/{types-realtime-value => types-variables}/tsconfig.json (100%) delete mode 100644 packages/ui/src/features/project-pane/components/molecules/NoVariableGroups.tsx delete mode 100644 packages/ui/src/features/project-pane/components/molecules/VariableGroupName.tsx delete mode 100644 packages/ui/src/features/project-pane/components/organisms/VariableGroups.tsx delete mode 100644 packages/ui/src/features/realtime-value-editor/components/RealtimeValueEditor.tsx delete mode 100644 packages/ui/src/features/realtime-value-editor/components/atoms/Form.ts delete mode 100644 packages/ui/src/features/realtime-value-editor/components/molecules/PreviewContainer.tsx delete mode 100644 packages/ui/src/features/realtime-value-editor/utils/render-request-select-options.tsx delete mode 100644 packages/ui/src/features/realtime-values/index.ts delete mode 100644 packages/ui/src/features/realtime-values/parser.ts delete mode 100644 packages/ui/src/features/realtime-values/preview.ts delete mode 100644 packages/ui/src/features/realtime-values/renderer.tsx delete mode 100644 packages/ui/src/features/realtime-values/utils/request.ts delete mode 100644 packages/ui/src/features/realtime-values/utils/response.ts delete mode 100644 packages/ui/src/features/realtime-values/values.d.ts delete mode 100644 packages/ui/src/features/realtime-values/values/base64-decode.ts delete mode 100644 packages/ui/src/features/realtime-values/values/base64-encode.ts delete mode 100644 packages/ui/src/features/realtime-values/values/digest.ts delete mode 100644 packages/ui/src/features/realtime-values/values/nonce.ts delete mode 100644 packages/ui/src/features/realtime-values/values/private.ts delete mode 100644 packages/ui/src/features/realtime-values/values/request-folder.ts delete mode 100644 packages/ui/src/features/realtime-values/values/request-header.ts delete mode 100644 packages/ui/src/features/realtime-values/values/request-method.ts delete mode 100644 packages/ui/src/features/realtime-values/values/request-name.ts delete mode 100644 packages/ui/src/features/realtime-values/values/response-body-json.ts delete mode 100644 packages/ui/src/features/realtime-values/values/response-body-text.ts delete mode 100644 packages/ui/src/features/realtime-values/values/response-header.ts delete mode 100644 packages/ui/src/features/realtime-values/values/response-status-code.ts delete mode 100644 packages/ui/src/features/realtime-values/values/secure.ts delete mode 100644 packages/ui/src/features/realtime-values/values/special-character.ts delete mode 100644 packages/ui/src/features/realtime-values/values/timestamp.ts delete mode 100644 packages/ui/src/features/realtime-values/values/url-decode.ts delete mode 100644 packages/ui/src/features/realtime-values/values/url-encode.ts delete mode 100644 packages/ui/src/features/realtime-values/values/uuid.ts delete mode 100644 packages/ui/src/features/tabs/components/molecules/VariableGroupEditorTab.tsx diff --git a/packages/types-realtime-value/README.md b/packages/types-realtime-value/README.md deleted file mode 100755 index f34c9615..00000000 --- a/packages/types-realtime-value/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `@getbeak/types-realtime-value` - -Contains the TypeScript definitions for Beak's Realtime Value interfaces. diff --git a/packages/types-realtime-value/.npmignore b/packages/types-variables/.npmignore similarity index 100% rename from packages/types-realtime-value/.npmignore rename to packages/types-variables/.npmignore diff --git a/packages/types-variables/README.md b/packages/types-variables/README.md new file mode 100755 index 00000000..2c68ee30 --- /dev/null +++ b/packages/types-variables/README.md @@ -0,0 +1,3 @@ +# `@getbeak/types-variables` + +Contains the TypeScript definitions for Beak Variables. diff --git a/packages/types-realtime-value/index.d.ts b/packages/types-variables/index.d.ts similarity index 70% rename from packages/types-realtime-value/index.d.ts rename to packages/types-variables/index.d.ts index 172d2e9e..f2b965bc 100755 --- a/packages/types-realtime-value/index.d.ts +++ b/packages/types-variables/index.d.ts @@ -1,9 +1,9 @@ /* eslint-disable max-len */ import type { Context } from '@getbeak/types/values'; -export interface RealtimeValueBase { } +export interface VariableBase { } -interface RealtimeValueGetter { +interface VariableGetter { /** * Gets the string value of the value, given the payload body @@ -17,7 +17,7 @@ interface GenericDictionary { [k: string]: any; } -export interface RealtimeValueInformation extends RealtimeValueBase { +export interface VariableStaticInformation extends VariableBase { /** * The public facing name of your extension. @@ -30,12 +30,14 @@ export interface RealtimeValueInformation extends RealtimeValueBase { description: string; /** - * Optional keywords used by Beak when searching for realtime values when the user is typing. + * Optional keywords used by Beak when searching for variables when the user is + * typing. */ keywords?: string[]; /** - * Denotes if the value's output is sensitive, and will be hidden by default in the UI and in copied responses. + * Denotes if the value's output is sensitive, and will be hidden by default in the UI + * and in copied responses. */ sensitive: boolean; @@ -45,19 +47,26 @@ export interface RealtimeValueInformation extends RealtimeValueBase { attributes: Attributes; } -export interface RealtimeValue extends RealtimeValueGetter, RealtimeValueInformation { +export interface Variable extends VariableGetter, VariableStaticInformation { /** * Creates a default payload, if the user doesn't specify any data. * @param {Context} ctx The project context. */ createDefaultPayload: (ctx: Context) => Promise; + + /** + * Gets a name for the variable, with context of the payload of this specific instance of the variable. + * @param {TPayload} payload This instance of the value's payload data. + */ + getContextAwareName?: (payload: TPayload) => string; } -export interface EditableRealtimeValue extends RealtimeValue { +export interface EditableVariable extends Variable { /** - * Details how Beak and user's should interact with the value editor for your realtime value. + * Details how Beak and user's should interact with the value editor for your + * variable. */ editor: Editor; } @@ -79,19 +88,23 @@ interface Editor Promise[]>; /** - * If the payload data isn't the same as the editor state, this will convert Payload -> State + * If the payload data isn't the same as the editor state, this will convert + * Payload -> State. This is optional if no modification of the payload is needed to + * create the editor state. * @param {Context} ctx The project context. * @param {TPayload} payload This instance of the value's payload data. */ - load: (ctx: Context, payload: TPayload) => Promise; + load?: (ctx: Context, payload: TPayload) => Promise; /** - * If the payload data isn't the same as the editor state, this will convert State -> Payload + * If the payload data isn't the same as the editor state, this will convert + * State -> Payload. This is optional if no modification of the state is needed to + * create the payload. * @param {Context} ctx The project context. * @param {TPayload} existingPayload This existing instance of the value's payload data. * @param {TEditorState} state This instance of the updated state data. */ - save: (ctx: Context, existingPayload: TPayload, state: TEditorState) => Promise; + save?: (ctx: Context, existingPayload: TPayload, state: TEditorState) => Promise; } export type UISection> = @@ -146,7 +159,7 @@ declare global { type Level = 'info' | 'warn' | 'error'; interface Beak { - parseValueParts: (ctx: Context, parts: unknown[]) => Promise; + parseValueSections: (ctx: Context, parts: unknown[]) => Promise; log: (level: Level, message: string) => void; } diff --git a/packages/types-realtime-value/package.json b/packages/types-variables/package.json similarity index 63% rename from packages/types-realtime-value/package.json rename to packages/types-variables/package.json index b2118a47..f4788b78 100755 --- a/packages/types-realtime-value/package.json +++ b/packages/types-variables/package.json @@ -1,9 +1,9 @@ { - "name": "@getbeak/types-realtime-value", - "version": "1.0.1", + "name": "@getbeak/types-variables", + "version": "2.1.0", "private": false, - "description": "Beak types for realtime value types", - "homepage": "https://github.com/getbeak/beak/tree/master/packages/types-realtime-value", + "description": "Types for Beak Variables", + "homepage": "https://github.com/getbeak/beak/tree/master/packages/types-variables", "license": "MIT", "contributors": [ { @@ -17,10 +17,9 @@ "repository": { "type": "git", "url": "https://github.com/getbeak/beak.git", - "directory": "packages/types-realtime-value" + "directory": "packages/types-variables" }, "dependencies": { - "@getbeak/types": "1.1.0" - }, - "scripts": {} + "@getbeak/types": "2.1.0" + } } diff --git a/packages/types-realtime-value/tsconfig.json b/packages/types-variables/tsconfig.json similarity index 100% rename from packages/types-realtime-value/tsconfig.json rename to packages/types-variables/tsconfig.json diff --git a/packages/ui/src/features/project-pane/components/molecules/NoVariableGroups.tsx b/packages/ui/src/features/project-pane/components/molecules/NoVariableGroups.tsx deleted file mode 100644 index ea15f038..00000000 --- a/packages/ui/src/features/project-pane/components/molecules/NoVariableGroups.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { useDispatch } from 'react-redux'; -import Button from '@beak/ui/components/atoms/Button'; -import SidebarSectionCard from '@beak/ui/features/sidebar/components/SidebarSectionCard'; -import { sidebarPreferenceSetSelected } from '@beak/ui/store/preferences/actions'; -import { createNewVariableGroup } from '@beak/ui/store/variable-groups/actions'; -import styled from 'styled-components'; - -const NoVariableGroups: React.FC> = () => { - const dispatch = useDispatch(); - - function createVariableGroup() { - dispatch(sidebarPreferenceSetSelected('variables')); - dispatch(createNewVariableGroup({ })); - } - - return ( - - - {'You have no variable groups'} - - - - - ); -}; - -const Container = styled.div` - margin-bottom: 10px; -`; - -const Title = styled.div` - margin-bottom: 5px; -`; - -export default NoVariableGroups; diff --git a/packages/ui/src/features/project-pane/components/molecules/VariableGroupName.tsx b/packages/ui/src/features/project-pane/components/molecules/VariableGroupName.tsx deleted file mode 100644 index 0af9f63a..00000000 --- a/packages/ui/src/features/project-pane/components/molecules/VariableGroupName.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import ksuid from '@beak/ksuid'; -import ContextMenu from '@beak/ui/components/atoms/ContextMenu'; -import tabActions from '@beak/ui/features/tabs/store/actions'; -import sidebarActions from '@beak/ui/store/preferences/actions'; -import { actions as vgActions } from '@beak/ui/store/variable-groups'; -import type { MenuItemConstructorOptions } from 'electron'; -import styled from 'styled-components'; - -interface VariableGroupNameProps { - variableGroupName: string; -} - -export const VariableGroupName: React.FC = ({ variableGroupName }) => { - const [menuItems, setMenuItems] = useState([]); - const targetRef = useRef(); - const dispatch = useDispatch(); - - useEffect(() => { - setMenuItems([{ - id: ksuid.generate('ctxmenuitem').toString(), - label: 'Reveal in sidebar', - click: () => { - dispatch(sidebarActions.sidebarPreferenceSetSelected('variables')); - }, - }, { - id: ksuid.generate('ctxmenuitem').toString(), - label: 'Open in editor', - click: () => { - dispatch(tabActions.changeTab({ - type: 'variable_group_editor', - payload: variableGroupName, - temporary: false, - })); - }, - }, { - id: ksuid.generate('ctxmenuitem').toString(), - type: 'separator', - }, { - id: ksuid.generate('ctxmenuitem').toString(), - label: 'Delete', - click: () => { - dispatch(vgActions.removeVariableGroupFromDisk({ - id: variableGroupName, - withConfirmation: true, - })); - }, - }]); - }, [variableGroupName]); - - return ( - - - targetRef.current = i!} - > - {variableGroupName} - - - - ); -}; - -const Name = styled.abbr` - color: ${p => p.theme.ui.textMinor}; - font-size: 12px; - - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - text-decoration: none; -`; - -export default VariableGroupName; diff --git a/packages/ui/src/features/project-pane/components/organisms/VariableGroups.tsx b/packages/ui/src/features/project-pane/components/organisms/VariableGroups.tsx deleted file mode 100644 index f28432ee..00000000 --- a/packages/ui/src/features/project-pane/components/organisms/VariableGroups.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from 'react'; -import { useDispatch } from 'react-redux'; -import { TypedObject } from '@beak/common/helpers/typescript'; -import useSectionBody from '@beak/ui/features/sidebar/hooks/use-section-body'; -import { editorPreferencesSetSelectedVariableGroup } from '@beak/ui/store/preferences/actions'; -import { useAppSelector } from '@beak/ui/store/redux'; -import styled from 'styled-components'; - -import NoVariableGroups from '../molecules/NoVariableGroups'; -import VariableGroupName from '../molecules/VariableGroupName'; - -const VariableGroups: React.FC> = () => { - const dispatch = useDispatch(); - const { variableGroups } = useAppSelector(s => s.global.variableGroups)!; - const selectedGroups = useAppSelector(s => s.global.preferences.editor.selectedVariableGroups); - const empty = Object.keys(variableGroups).length === 0; - - useSectionBody({ - maxHeight: '120px', - flexShrink: 0, - }); - - if (empty) { - return ( - - - - ); - } - - return ( - - {TypedObject.keys(variableGroups!).map(k => { - const groups = variableGroups![k].groups; - const groupKeys = TypedObject.keys(groups); - const value = selectedGroups[k]; - - return ( - - - - { - dispatch(editorPreferencesSetSelectedVariableGroup({ - variableGroup: k, - groupId: e.target.value, - })); - }} - > - {groupKeys.map(gk => ( - - ))} - - - ); - })} - - ); -}; - -const Container = styled.div` - padding: 4px 5px; - padding-right: 0; - padding-bottom: 0; -`; - -const Item = styled.div` - display: grid; - grid-template-columns: minmax(10px, max-content) minmax(10px, max-content); - justify-content: space-between; - align-items: center; - gap: 5px; - margin: 4px 0; - max-width: calc(100% - 3px); - - &:first-child { - margin-top: 0; - } - - &:not(:last-child) { - margin-bottom: 6px; - } -`; - -const Selector = styled.select` - width: 100%; - font-size: 12px; - border: 0; - border-radius: 4px; - background: none; - color: ${p => p.theme.ui.textMinor}; - text-align-last: right; - text-overflow: ellipsis; - - &:hover, &:active, &:focus { - background: ${p => p.theme.ui.surface}; - outline: none; - } -`; - -export default VariableGroups; diff --git a/packages/ui/src/features/realtime-value-editor/components/RealtimeValueEditor.tsx b/packages/ui/src/features/realtime-value-editor/components/RealtimeValueEditor.tsx deleted file mode 100644 index 4ea0d8fe..00000000 --- a/packages/ui/src/features/realtime-value-editor/components/RealtimeValueEditor.tsx +++ /dev/null @@ -1,308 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import Button from '@beak/ui/components/atoms/Button'; -import Input, { Select } from '@beak/ui/components/atoms/Input'; -import { ValueParts } from '@beak/ui/features/realtime-values/values'; -import { faWarning } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { EditableRealtimeValue, UISection } from '@getbeak/types-realtime-value'; -import styled from 'styled-components'; - -import { RealtimeValueManager } from '../../realtime-values'; -import useRealtimeValueContext from '../../realtime-values/hooks/use-realtime-value-context'; -import { previewValue } from '../../realtime-values/preview'; -import VariableInput from '../../variable-input/components/VariableInput'; -import renderRequestSelectOptions from '../utils/render-request-select-options'; -import { FormGroup, Label } from './atoms/Form'; -import PreviewContainer from './molecules/PreviewContainer'; - -interface RtvEditorContext { - realtimeValue: EditableRealtimeValue; - item: any; - parent: HTMLDivElement; - partIndex: number; - state: Record; -} - -interface RealtimeValueEditorProps { - requestId?: string; - editable: HTMLDivElement; - onSave: (partIndex: number, type: string, item: any) => void; -} - -const RealtimeValueEditor: React.FC> = props => { - const { editable, requestId, onSave } = props; - const initialInputRef = useRef(null); - const [editorContext, setEditorContext] = useState(); - const [uiSections, setUiSections] = useState[]>([]); - const [preview, setPreview] = useState(''); - const context = useRealtimeValueContext(requestId); - - useEffect(() => { - if (!editorContext || !initialInputRef.current) - return; - - initialInputRef.current.focus(); - }, [Boolean(editorContext)]); - - useEffect(() => { - if (!editorContext) { - // If the editor context is reset, then we need to reset some more local state - setUiSections([]); - setPreview(''); - - return; - } - - previewValue(context, editorContext.realtimeValue, editorContext.item, editorContext.state) - .then(setPreview); - - const { createUserInterface } = editorContext.realtimeValue.editor; - - createUserInterface(context).then(setUiSections); - }, [editorContext]); - - useEffect(() => { - const onClick = (event: MouseEvent) => { - const target = event.target as HTMLDivElement; - - if (target.className !== 'bvs-blob') - return; - - const { index, type, payload } = target.dataset; - - if (!type) - return; - - const realtimeValue = RealtimeValueManager.getRealtimeValue(type); - - if (!('editor' in realtimeValue)) - return; - - if (!realtimeValue.editor) - return; - - const item = JSON.parse(payload!); - const partIndex = Number(index!); - - realtimeValue.editor.load(context, item) - .then(state => setEditorContext({ - realtimeValue, - item, - parent: target, - partIndex, - state, - })) - - .catch(console.error); - }; - - editable.addEventListener('click', onClick); - - return () => { - editable.removeEventListener('click', onClick); - }; - }, [editorContext]); - - function updateState(delta: Record) { - if (!editorContext) - return; - - setEditorContext(editorCtx => ({ - ...editorCtx!, - state: { - ...editorCtx!.state, - ...delta, - }, - })); - } - - function close(item: any | null) { - if (editorContext && item) - onSave(editorContext.partIndex, editorContext.realtimeValue.type, item); - - setEditorContext(void 0); - } - - if (!editorContext) - return null; - - const { item, state, parent, realtimeValue } = editorContext; - const boundingRect = parent.getBoundingClientRect(); - const { save } = realtimeValue.editor!; - - return ( - close(null)}> - void event.stopPropagation()} - > - {uiSections.map((section, i) => { - const first = i === 0; - const stateBinding = section.stateBinding as string; - - switch (section.type) { - case 'value_parts_input': - return ( - - {section.label && } - trySetInitialRef(first, i, initialInputRef)} - parts={state[stateBinding] as ValueParts} - onChange={e => updateState({ - [stateBinding]: e, - })} - /> - - ); - - case 'string_input': - return ( - - {section.label && } - trySetInitialRef(first, i, initialInputRef)} - $beakSize={'sm'} - type={'text'} - value={state[stateBinding] as string || ''} - onChange={e => updateState({ - [stateBinding]: e.currentTarget.value, - })} - /> - - ); - - case 'checkbox_input': - return ( - - {section.label && } - trySetInitialRef(first, i, initialInputRef)} - $beakSize={'sm'} - $noStretch - type={'checkbox'} - checked={state[stateBinding] as boolean} - onChange={e => updateState({ - [stateBinding]: e.currentTarget.checked, - })} - /> - - ); - - case 'number_input': - return ( - - {section.label && } - trySetInitialRef(first, i, initialInputRef)} - $beakSize={'sm'} - type={'number'} - value={(state[stateBinding] as number).toString(10)} - onChange={e => updateState({ - [stateBinding]: parseInt(e.currentTarget.value, 10), - })} - /> - - ); - - case 'options_input': - return ( - - {section.label && } - - - ); - - case 'request_select_input': - return ( - - {section.label && } - - - ); - - default: - return null; - } - })} - - - - -
- {editorContext.realtimeValue.external && ( - - - {' This is an extension'} - - )} -
- - -
-
-
- ); -}; - -function trySetInitialRef( - first: boolean, - instance: HTMLElement | null, - ref: React.MutableRefObject, -) { - if (!first) - return; - - // eslint-disable-next-line no-param-reassign - ref.current = instance; -} - -const Container = styled.div` - z-index: 101; - position: fixed; - top: 0; bottom: 0; left: 0; right: 0; -`; - -const Wrapper = styled.div<{ $top: number; $left: number }>` - position: fixed; - margin-top: ${p => p.$top}px; - margin-left: ${p => p.$left}px; - - width: 300px; - padding: 8px 12px; - border: 1px solid ${p => p.theme.ui.backgroundBorderSeparator}; - background: ${p => p.theme.ui.surface}; - z-index: 10000; -`; - -const ButtonContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; -`; - -export default RealtimeValueEditor; diff --git a/packages/ui/src/features/realtime-value-editor/components/atoms/Form.ts b/packages/ui/src/features/realtime-value-editor/components/atoms/Form.ts deleted file mode 100644 index af5efefe..00000000 --- a/packages/ui/src/features/realtime-value-editor/components/atoms/Form.ts +++ /dev/null @@ -1,19 +0,0 @@ -import styled from 'styled-components'; - -export const FormGroup = styled.div` - margin-bottom: 8px; - - > div > article { - font-size: 13px; - padding: 3px 5px; - padding-bottom: 4px; - border-radius: 3px; - } -`; - -export const Label = styled.label` - display: block; - margin-bottom: 4px; - - font-size: 13px; -`; diff --git a/packages/ui/src/features/realtime-value-editor/components/molecules/PreviewContainer.tsx b/packages/ui/src/features/realtime-value-editor/components/molecules/PreviewContainer.tsx deleted file mode 100644 index f82ff128..00000000 --- a/packages/ui/src/features/realtime-value-editor/components/molecules/PreviewContainer.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; - -interface PreviewContainerProps { - text: string; -} - -const PreviewContainer: React.FC = ({ text }) => ( - - {'Preview'} - {text} - -); - -const Container = styled.div` - position: relative; - font-size: 12px; - background: ${p => p.theme.ui.secondarySurface}; - margin: 10px -12px; - margin-bottom: 8px; - padding: 10px 12px; - padding-top: 25px; - - max-height: 100px; - overflow-y: overlay; - overflow-x: hidden; - overflow-wrap: break-word; -`; - -const PreviewHint = styled.div` - position: absolute; - top: 5px; - left: 5px; - text-transform: uppercase; - font-size: 9px; -`; - -export default PreviewContainer; diff --git a/packages/ui/src/features/realtime-value-editor/utils/render-request-select-options.tsx b/packages/ui/src/features/realtime-value-editor/utils/render-request-select-options.tsx deleted file mode 100644 index b2d3bc7b..00000000 --- a/packages/ui/src/features/realtime-value-editor/utils/render-request-select-options.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import { TypedObject } from '@beak/common/helpers/typescript'; -import type { FolderNode, RequestNode } from '@getbeak/types/nodes'; -import type { Context } from '@getbeak/types/values'; - -export default function renderRequestSelectOptions(context: Context) { - const depth = 0; - const formattedNodes = TypedObject.values(context.projectTree) - .filter(t => t.parent === 'tree') - .sort((a, b) => a.name.localeCompare(b.name, void 0, { - numeric: true, - sensitivity: 'base', - })); - - return ( - - {formattedNodes.filter(i => i.type === 'folder') - .map(i => renderFolder(i as FolderNode, context, depth))} - {formattedNodes.filter(i => i.type === 'request') - .map(i => renderRequest(i as RequestNode, depth))} - - ); -} - -function renderFolder(node: FolderNode, context: Context, depth: number) { - const newDepth = depth + 1; - const childNodes = TypedObject.values(context.projectTree) - .filter(i => i.parent === node.filePath) - .sort((a, b) => a.name.localeCompare(b.name, void 0, { - numeric: true, - sensitivity: 'base', - })); - - const folderNodes = childNodes.filter(n => n.type === 'folder') as FolderNode[]; - const nodes = childNodes.filter(n => n.type === 'request') as RequestNode[]; - - return ( - - - {nodes.map(n => renderRequest(n, newDepth))} - - {folderNodes.map(n => renderFolder(n, context, newDepth))} - - ); -} - -function renderRequest(node: RequestNode, depth: number) { - return ( - - ); -} - -function renderDepth(depth: number) { - return '\u00A0'.repeat(depth * 2); -} diff --git a/packages/ui/src/features/realtime-values/index.ts b/packages/ui/src/features/realtime-values/index.ts deleted file mode 100644 index 583f093f..00000000 --- a/packages/ui/src/features/realtime-values/index.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { TypedObject } from '@beak/common/helpers/typescript'; -import { RealtimeValueExtension } from '@beak/common/types/extensions'; -import { ipcExtensionsService } from '@beak/ui/lib/ipc'; -import { EditableRealtimeValue, RealtimeValue } from '@getbeak/types-realtime-value'; - -import './ipc'; -import base64DecodeRtv from './values/base64-decode'; -import base64EncodeRtv from './values/base64-encode'; -import digestRtv from './values/digest'; -import nonceRtv from './values/nonce'; -import privateRtv from './values/private'; -import requestFolderRtv from './values/request-folder'; -import requestHeaderRtv from './values/request-header'; -import requestMethodRtv from './values/request-method'; -import requestNameRtv from './values/request-name'; -import responseBodyJsonRtv from './values/response-body-json'; -import responseBodyTextRtv from './values/response-body-text'; -import responseHeaderRtv from './values/response-header'; -import responseStatusCodeRtv from './values/response-status-code'; -import secureRtv from './values/secure'; -import { characterCarriageReturnRtv, characterNewlineRtv, characterTabRtv } from './values/special-character'; -import timestampRtv from './values/timestamp'; -import urlDecodeRtv from './values/url-decode'; -import urlEncodeRtv from './values/url-encode'; -import uuidRtv from './values/uuid'; -import variableGroupItemRtv from './values/variable-group-item'; - -type Rtv = RealtimeValue | EditableRealtimeValue; - -export class RealtimeValueManager { - private static externalRealtimeValues: Record = { }; - private static internalRealtimeValues: Record = { - [base64DecodeRtv.type]: base64DecodeRtv, - [base64EncodeRtv.type]: base64EncodeRtv, - [characterCarriageReturnRtv.type]: characterCarriageReturnRtv, - [characterNewlineRtv.type]: characterNewlineRtv, - [characterTabRtv.type]: characterTabRtv, - [digestRtv.type]: digestRtv, - [nonceRtv.type]: nonceRtv, - [privateRtv.type]: privateRtv, - [requestFolderRtv.type]: requestFolderRtv, - [requestHeaderRtv.type]: requestHeaderRtv, - [requestMethodRtv.type]: requestMethodRtv, - [requestNameRtv.type]: requestNameRtv, - [responseBodyJsonRtv.type]: responseBodyJsonRtv, - [responseBodyTextRtv.type]: responseBodyTextRtv, - [responseHeaderRtv.type]: responseHeaderRtv, - [responseStatusCodeRtv.type]: responseStatusCodeRtv, - [secureRtv.type]: secureRtv, - [timestampRtv.type]: timestampRtv, - [urlDecodeRtv.type]: urlDecodeRtv, - [urlEncodeRtv.type]: urlEncodeRtv, - [uuidRtv.type]: uuidRtv, - - // Special case! - [variableGroupItemRtv.type]: variableGroupItemRtv, - }; - - static registerExternalRealtimeValue(ext: RealtimeValueExtension) { - const rtv = ext.realtimeValue; - - this.externalRealtimeValues[rtv.type] = { - type: rtv.type, - name: rtv.name, - description: rtv.description, - sensitive: rtv.sensitive, - external: true, - attributes: rtv.attributes, - createDefaultPayload: async ctx => ipcExtensionsService.rtvCreateDefaultPayload({ - type: rtv.type, - context: ctx, - }), - getValue: async (ctx, payload, recursiveDepth) => ipcExtensionsService.rtvGetValue({ - type: rtv.type, - context: ctx, - payload, - recursiveDepth, - }), - }; - - if (!rtv.editable) - return; - - (this.externalRealtimeValues[rtv.type] as EditableRealtimeValue).editor = { - createUserInterface: ctx => ipcExtensionsService.rtvEditorCreateUserInterface({ - type: rtv.type, - context: ctx, - }), - load: (ctx, payload) => ipcExtensionsService.rtvEditorLoad({ - type: rtv.type, - context: ctx, - payload, - }), - save: (ctx, existingPayload, state) => ipcExtensionsService.rtvEditorSave({ - type: rtv.type, - context: ctx, - existingPayload, - state, - }), - }; - } - - static unregisterExternalRealtimeValues(type: string) { - delete this.externalRealtimeValues[type]; - } - - static getRealtimeValue(type: string) { - return this.internalRealtimeValues[type] ?? this.externalRealtimeValues[type]; - } - - static getRealtimeValues(currentRequestId?: string) { - const allRealtimeValues = { - ...this.externalRealtimeValues, - - // Do this second to override any external attempts to override - ...this.internalRealtimeValues, - }; - - return TypedObject.values(allRealtimeValues) - // Remove the variable group item as it's a special case tbh - .filter(v => v.type !== variableGroupItemRtv.type) - .filter(v => { - if (!v.attributes.requiresRequestId) - return true; - - return currentRequestId; - }); - } -} diff --git a/packages/ui/src/features/realtime-values/parser.ts b/packages/ui/src/features/realtime-values/parser.ts deleted file mode 100644 index 0522b146..00000000 --- a/packages/ui/src/features/realtime-values/parser.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { TypedObject } from '@beak/common/helpers/typescript'; -import { generateValueIdent } from '@beak/ui/lib/beak-variable-group/utils'; -import type { Context, ValueParts } from '@getbeak/types/values'; - -import { RealtimeValueManager } from '.'; - -export async function parseValueParts( - ctx: Context, - parts: ValueParts, - depth = 0, - sensitiveMode = false, -): Promise { - const out = await Promise.all(parts.map(async p => { - if (typeof p === 'string') - return p; - - if (typeof p !== 'object') - return ''; - - const rtv = RealtimeValueManager.getRealtimeValue(p.type); - - if (!rtv) - return ''; - - // Oversimplified check for recursion. I'll build a proper system for this later - if (depth >= 5) - return '[Recursion detected]'; - - if (sensitiveMode && rtv.sensitive) - return '[Sensitive mode enabled]'; - - try { - // Easier than using an abort controller - let complete = false; - - const value = Promise.race([ - rtv.getValue(ctx, p.payload, depth + 1), - new Promise(resolve => { - window.setTimeout(() => { - if (!complete) - - console.error(`Fetching value for ${rtv.type} exceeded 600ms`); - - resolve(''); - }, 600); - }), - ]); - - complete = true; - - return value; - } catch { - // TODO(afr): Move this to some sort of alert - - console.error(`Failed to get value from ${rtv.type}`); - - return ''; - } - })); - - return out.join(''); -} - -export function getValueParts(ctx: Context, itemId: string) { - return getValueObject(ctx, itemId); -} - -export function getValueObject(ctx: Context, itemId: string) { - for (const key of TypedObject.keys(ctx.variableGroups)) { - const variableGroup = ctx.variableGroups[key]; - const selectedGroup = ctx.selectedGroups[key]; - const value = variableGroup.values[generateValueIdent(selectedGroup, itemId)]; - - if (value) - return value; - } - - return null; -} diff --git a/packages/ui/src/features/realtime-values/preview.ts b/packages/ui/src/features/realtime-values/preview.ts deleted file mode 100644 index d9f50e84..00000000 --- a/packages/ui/src/features/realtime-values/preview.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Context } from '@getbeak/types/values'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; - -export async function previewValue>( - ctx: Context, - rtv: EditableRealtimeValue, - item: any, - state: T, -) { - const payload = await rtv.editor.save(ctx, item, state); - - return await rtv.getValue(ctx, payload, 0); -} diff --git a/packages/ui/src/features/realtime-values/renderer.tsx b/packages/ui/src/features/realtime-values/renderer.tsx deleted file mode 100644 index 6b051587..00000000 --- a/packages/ui/src/features/realtime-values/renderer.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react'; -import { renderToStaticMarkup } from 'react-dom/server'; -import type { VariableGroups } from '@getbeak/types/variable-groups'; -import * as uuid from 'uuid'; - -import { RealtimeValueManager } from '.'; -import { ValueParts } from './values'; -import { getVariableGroupItemName } from './values/variable-group-item'; - -export default function renderValueParts(parts: ValueParts, variableGroups: VariableGroups) { - let safeParts = parts; - - if (!Array.isArray(parts)) - safeParts = []; - - return renderToStaticMarkup( - - {safeParts.map((p, idx) => { - if (typeof p === 'string') - return {p}; - - if (typeof p !== 'object') { - console.error(`Unknown value part ${p}:(${typeof p})`); - - return null; - } - - const rtv = RealtimeValueManager.getRealtimeValue(p.type); - - if (!rtv) { - return ( -
-   -
- {'[Extension missing]'} -
-   -
- ); - } - - const editable = 'editor' in rtv; - const name = (() => { - if (p.type === 'variable_group_item') { - const payload = p.payload as { itemId: string }; - - return getVariableGroupItemName(payload, variableGroups); - } - - return rtv.name; - })(); - - return ( -
-   - {name} -   -
- ); - })} -
, - ); -} diff --git a/packages/ui/src/features/realtime-values/utils/request.ts b/packages/ui/src/features/realtime-values/utils/request.ts deleted file mode 100644 index db32e10a..00000000 --- a/packages/ui/src/features/realtime-values/utils/request.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { RequestNode } from '@getbeak/types/nodes'; -import type { Context } from '@getbeak/types/values'; - -export function getRequestNode(id: string, ctx: Context) { - const node = ctx.projectTree[id]; - - if (!node || node.type !== 'request' || node.mode !== 'valid') - return null; - - return node as RequestNode; -} diff --git a/packages/ui/src/features/realtime-values/utils/response.ts b/packages/ui/src/features/realtime-values/utils/response.ts deleted file mode 100644 index 66e92fd5..00000000 --- a/packages/ui/src/features/realtime-values/utils/response.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { TypedObject } from '@beak/common/helpers/typescript'; -import type { Context } from '@getbeak/types/values'; - -export function getLatestFlight(id: string, ctx: Context) { - const requestFlightHistory = ctx.flightHistory[id]; - - if (!requestFlightHistory) - return null; - - const latestFlight = TypedObject.values(requestFlightHistory.history).reverse()[0]; - - return latestFlight; -} diff --git a/packages/ui/src/features/realtime-values/values.d.ts b/packages/ui/src/features/realtime-values/values.d.ts deleted file mode 100644 index 37d0fe23..00000000 --- a/packages/ui/src/features/realtime-values/values.d.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { ValuePart as GenericValuePart } from '@getbeak/types/values'; - -export type ValuePart = GenericValuePart; -export type ValueParts = ValuePart[]; - -export interface Base64DecodedRtv { - input: ValueParts; - characterSet: 'base64' | 'websafe_base64'; -} - -export interface Base64EncodedRtv { - input: ValueParts; - characterSet: 'base64' | 'websafe_base64'; - removePadding: boolean; -} - -export interface DigestRtv { - input: ValueParts; - algorithm: 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512' | 'MD5'; - hmac?: string; -} - -export interface PrivateRtv { - iv: string; - identifier: string; -} - -export interface RequestHeaderRtv { - headerName: ValueParts; -} - -export interface ResponseBodyJsonRtv { - requestId: string; - dotPath: ValueParts; -} - -export interface ResponseBodyTextRtv { - requestId: string; -} - -export interface ResponseHeaderRtv { - requestId: string; - headerName: ValueParts; -} - -export interface ResponseStatusCodeRtv { - requestId: string; -} - -export interface SecureRtv { - iv: string; - cipherText: string; - - /** @deprecated use cipherText now */ - datum?: string; -} - -export interface TimestampRtv { - delta?: number; - type: string; -} - -export interface UuidRtv { - version: 'v1' | 'v4'; -} - -export interface VariableGroupItemRtv { - itemId: string; -} diff --git a/packages/ui/src/features/realtime-values/values/base64-decode.ts b/packages/ui/src/features/realtime-values/values/base64-decode.ts deleted file mode 100644 index 8e9009c6..00000000 --- a/packages/ui/src/features/realtime-values/values/base64-decode.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Base64DecodedRtv, ValueParts } from '@beak/ui/features/realtime-values/values'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; - -import { parseValueParts } from '../parser'; - -const invalidBase64Error = 'Failed to execute \'atob\' on \'Window\': The string to be decoded is not correctly encoded.'; - -interface EditorState { - input: ValueParts; - characterSet: Base64DecodedRtv['characterSet']; -} - -const definition: EditableRealtimeValue = { - type: 'base64_decoded', - name: 'Decode (Base64)', - description: 'Decodes a base64 encoded string', - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ - input: [''], - characterSet: 'base64', - }), - - getValue: async (ctx, payload, recursiveDepth) => { - const isArray = Array.isArray(payload.input); - const input = isArray ? payload.input : [payload.input as unknown as string]; - - let encoded = await parseValueParts(ctx, input, recursiveDepth); - - if (payload.characterSet === 'websafe_base64') - encoded = encoded.replaceAll('_', '/').replaceAll('-', '+'); - - try { - return atob(encoded); - } catch (error) { - if (error instanceof Error && error.name === 'InvalidCharacterError' && error.message.includes(invalidBase64Error)) - return ''; - - throw error; - } - }, - - attributes: {}, - - editor: { - createUserInterface: async () => [{ - type: 'value_parts_input', - label: 'Enter the data to decode:', - stateBinding: 'input', - }, { - type: 'options_input', - label: 'Pick the digest algorithm:', - stateBinding: 'characterSet', - options: [{ - key: 'base64', - label: 'Base64', - }, { - key: 'websafe_base64', - label: 'Websafe Base64', - }], - }], - - load: async (_ctx, item) => ({ - characterSet: item.characterSet, - input: item.input, - }), - - save: async (_ctx, _item, state) => ({ - characterSet: state.characterSet, - input: state.input, - }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/base64-encode.ts b/packages/ui/src/features/realtime-values/values/base64-encode.ts deleted file mode 100644 index 6f916bed..00000000 --- a/packages/ui/src/features/realtime-values/values/base64-encode.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Base64EncodedRtv, ValueParts } from '@beak/ui/features/realtime-values/values'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; - -import { parseValueParts } from '../parser'; - -interface EditorState { - input: ValueParts; - characterSet: Base64EncodedRtv['characterSet']; - removePadding: boolean; -} - -const definition: EditableRealtimeValue = { - type: 'base64_encoded', - name: 'Encode (Base64)', - description: 'Generates a base64 encoded string', - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ - input: [''], - characterSet: 'base64', - removePadding: false, - }), - - getValue: async (ctx, payload, recursiveDepth) => { - const isArray = Array.isArray(payload.input); - const input = isArray ? payload.input : [payload.input as unknown as string]; - - const parsed = await parseValueParts(ctx, input, recursiveDepth); - let encoded = btoa(parsed); - - if (payload.characterSet === 'websafe_base64') - encoded = encoded.replaceAll('/', '_').replaceAll('+', '-'); - - if (payload.removePadding) - encoded = encoded.replaceAll('=', ''); - - return encoded; - }, - - attributes: {}, - - editor: { - createUserInterface: async () => [{ - type: 'value_parts_input', - label: 'Enter the data to encode:', - stateBinding: 'input', - }, { - type: 'options_input', - label: 'Pick the digest algorithm:', - stateBinding: 'characterSet', - options: [{ - key: 'base64', - label: 'Base64', - }, { - key: 'websafe_base64', - label: 'Websafe Base64', - }], - }, { - type: 'checkbox_input', - label: 'Remove padding:', - stateBinding: 'removePadding', - }], - - load: async (_ctx, item) => ({ - characterSet: item.characterSet, - input: item.input, - removePadding: item.removePadding, - }), - - save: async (_ctx, _item, state) => ({ - characterSet: state.characterSet, - input: state.input, - removePadding: state.removePadding, - }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/digest.ts b/packages/ui/src/features/realtime-values/values/digest.ts deleted file mode 100644 index 95f7a796..00000000 --- a/packages/ui/src/features/realtime-values/values/digest.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { DigestRtv, ValueParts } from '@beak/ui/features/realtime-values/values'; -import { arrayBufferToHexString } from '@beak/ui/utils/encoding'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; -import { Md5 as MD5 } from 'ts-md5'; - -import { parseValueParts } from '../parser'; - -interface EditorState { - input: ValueParts; - algorithm: DigestRtv['algorithm']; -} - -const definition: EditableRealtimeValue = { - type: 'digest', - name: 'Digest / Hash', - description: 'Generates a digest of a given input. Supports SHA-*, MD5.', - keywords: ['sha', 'md5', 'sha1', 'sha2', 'sha256', 'sha-384', 'sha512'], - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ - algorithm: 'SHA-256', - input: [''], - hmac: void 0, - }), - - getValue: async (ctx, payload, recursiveDepth) => { - const { algorithm, input, hmac } = payload; - const isArray = Array.isArray(input); - const parsed = await parseValueParts(ctx, isArray ? input : [input as unknown as string], recursiveDepth); - - if (algorithm === 'MD5') - return MD5.hashStr(parsed); - - const buf = new ArrayBuffer(parsed.length * 2); - const bufView = new Uint16Array(buf); - - for (let i = 0, strLen = parsed.length; i < strLen; i++) - bufView[i] = parsed.charCodeAt(i); - - if (hmac) { - // return crypto.subtle.sign(algorithm, key, buf); - return ''; - } - - const digest = await crypto.subtle.digest(algorithm, buf); - - return arrayBufferToHexString(digest); - }, - - attributes: {}, - - editor: { - createUserInterface: async () => [{ - type: 'value_parts_input', - label: 'Enter the data for the digest:', - stateBinding: 'input', - }, { - type: 'options_input', - label: 'Pick the digest algorithm:', - stateBinding: 'algorithm', - options: [{ - key: 'SHA-1', - label: 'SHA-1 (Considered unsafe for cryptographic use)', - }, { - key: 'SHA-256', - label: 'SHA-256', - }, { - key: 'SHA-384', - label: 'SHA-384', - }, { - key: 'SHA-512', - label: 'SHA-512', - }, { - key: 'MD5', - label: 'MD5 (Considered unsafe for cryptographic use)', - }], - }], - - load: async (_ctx, item) => ({ algorithm: item.algorithm, input: item.input }), - - save: async (_ctx, _item, state) => ({ - input: state.input, - algorithm: state.algorithm, - hmac: void 0, - }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/nonce.ts b/packages/ui/src/features/realtime-values/values/nonce.ts deleted file mode 100644 index 70155bd7..00000000 --- a/packages/ui/src/features/realtime-values/values/nonce.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { toWebSafeBase64 } from '@beak/ui/lib/base64'; -import type { RealtimeValue } from '@getbeak/types-realtime-value'; - -const definition: RealtimeValue = { - type: 'nonce', - name: 'Nonce', - description: 'Generates a cryptographically random string', - sensitive: false, - external: false, - - createDefaultPayload: async () => void 0, - - getValue: async () => { - const array = new Uint8Array(10); - - crypto.getRandomValues(array); - - return toWebSafeBase64(array); - }, - - attributes: {}, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/private.ts b/packages/ui/src/features/realtime-values/values/private.ts deleted file mode 100644 index 8b006d12..00000000 --- a/packages/ui/src/features/realtime-values/values/private.ts +++ /dev/null @@ -1,97 +0,0 @@ -import ksuid from '@beak/ksuid'; -import { PrivateRtv, ValueParts } from '@beak/ui/features/realtime-values/values'; -import { ipcEncryptionService, ipcFsService } from '@beak/ui/lib/ipc'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; -import path from 'path-browserify'; - -import { parseValueParts } from '../parser'; - -interface EditorState { - value: ValueParts; -} - -const definition: EditableRealtimeValue = { - type: 'private', - name: 'Private', - description: 'A value only stored locally, and never included in the project (it is also encrypted at rest). Useful for PII fields.', - sensitive: true, - external: false, - - createDefaultPayload: async () => { - const iv = await ipcEncryptionService.generateIv(); - const identifier = ksuid.generate('prvval').toString(); - - return { - iv, - identifier, - }; - }, - - getValue: async (ctx, item, recursiveDepth) => { - const encryptionSetup = await ipcEncryptionService.checkStatus(); - - if (!encryptionSetup) return '[Encryption key missing]'; - - // Get from private store - const cipherTextPath = createPath(item.identifier); - const exists = await ipcFsService.pathExists(cipherTextPath); - const cipherText = exists ? await ipcFsService.readText(cipherTextPath) : null; - - if (!cipherText) - return ''; - - const decrypted = await ipcEncryptionService.decryptObject({ - iv: item.iv, - payload: cipherText, - }); - - return await parseValueParts(ctx, decrypted, recursiveDepth); - }, - - attributes: {}, - - editor: { - createUserInterface: async () => [{ - type: 'value_parts_input', - label: 'Enter the value you want to be private:', - stateBinding: 'value', - }], - - load: async (_ctx, item) => { - // Get from private store - const cipherTextPath = createPath(item.identifier); - const exists = await ipcFsService.pathExists(cipherTextPath); - const cipherText = exists ? await ipcFsService.readText(cipherTextPath) : null; - - if (!cipherText) - return { value: [] }; - - const decrypted = await ipcEncryptionService.decryptObject({ - iv: item.iv, - payload: cipherText, - }); - - return { value: decrypted }; - }, - - save: async (_ctx, item, state) => { - // We want to generate a new IV every time - const iv = await ipcEncryptionService.generateIv(); - const cipherText = await ipcEncryptionService.encryptObject({ - iv, - payload: state.value, - }); - - // Write to private store - await ipcFsService.writeText(createPath(item.identifier), cipherText); - - return { iv, identifier: item.identifier }; - }, - }, -}; - -export default definition; - -function createPath(identifier: string) { - return path.join('.beak', 'realtime-values', 'private', `${identifier}`); -} diff --git a/packages/ui/src/features/realtime-values/values/request-folder.ts b/packages/ui/src/features/realtime-values/values/request-folder.ts deleted file mode 100644 index af5be76f..00000000 --- a/packages/ui/src/features/realtime-values/values/request-folder.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { RealtimeValue } from '@getbeak/types-realtime-value'; - -const definition: RealtimeValue = { - type: 'request_folder', - name: 'Request folder', - description: 'Returns the name of the folder the request is inside', - sensitive: false, - external: false, - - createDefaultPayload: async () => void 0, - - getValue: async ctx => { - const node = ctx.projectTree[ctx.currentRequestId!]; - - if (!node || node.type !== 'request' || node.mode !== 'valid') - return ''; - - const parentNode = ctx.projectTree[node.parent!]; - - if (!parentNode || parentNode.type !== 'folder') - return ''; - - return parentNode.name; - }, - - attributes: { - requiresRequestId: true, - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/request-header.ts b/packages/ui/src/features/realtime-values/values/request-header.ts deleted file mode 100644 index 80a8c1e6..00000000 --- a/packages/ui/src/features/realtime-values/values/request-header.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { TypedObject } from '@beak/common/helpers/typescript'; -import { RequestHeaderRtv, ValueParts } from '@beak/ui/features/realtime-values/values'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; - -import { parseValueParts } from '../parser'; - -interface EditorState { - headerName: ValueParts; -} - -const definition: EditableRealtimeValue = { - type: 'request_header', - name: 'Request header', - description: 'Returns the value of a header from the request', - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ - headerName: [''], - }), - - getValue: async (ctx, payload, recursiveDepth) => { - const node = ctx.projectTree[ctx.currentRequestId!]; - - if (!node || node.type !== 'request' || node.mode !== 'valid') - return ''; - - const parsedHeaderName = await parseValueParts(ctx, payload.headerName, recursiveDepth); - const headerKey = TypedObject.keys(node.info.headers) - .find(k => node.info.headers[k].name.toLocaleLowerCase() === parsedHeaderName.toLocaleLowerCase()); - - const header = node.info.headers[headerKey!]; - - if (!header || !header.value) - return ''; - - return await parseValueParts(ctx, header.value, recursiveDepth); - }, - - attributes: { - requiresRequestId: true, - }, - - editor: { - createUserInterface: async () => [{ - type: 'value_parts_input', - label: 'Header name:', - stateBinding: 'headerName', - }], - - load: async (_ctx, item) => ({ headerName: item.headerName }), - save: async (_ctx, _item, state) => ({ headerName: state.headerName }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/request-method.ts b/packages/ui/src/features/realtime-values/values/request-method.ts deleted file mode 100644 index b5576f1a..00000000 --- a/packages/ui/src/features/realtime-values/values/request-method.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { RealtimeValue } from '@getbeak/types-realtime-value'; - -const definition: RealtimeValue = { - type: 'request_method', - name: 'Request method', - description: 'Returns the HTTP method of the this request', - sensitive: false, - external: false, - - createDefaultPayload: async () => void 0, - - getValue: async ctx => { - const node = ctx.projectTree[ctx.currentRequestId!]; - - if (!node || node.type !== 'request' || node.mode !== 'valid') - return ''; - - return node.info.verb; - }, - - attributes: { - requiresRequestId: true, - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/request-name.ts b/packages/ui/src/features/realtime-values/values/request-name.ts deleted file mode 100644 index fd91d0df..00000000 --- a/packages/ui/src/features/realtime-values/values/request-name.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { RealtimeValue } from '@getbeak/types-realtime-value'; - -const definition: RealtimeValue = { - type: 'request_name', - name: 'Request name', - description: 'Returns the name of the this request', - sensitive: false, - external: false, - - createDefaultPayload: async () => void 0, - - getValue: async ctx => { - const node = ctx.projectTree[ctx.currentRequestId!]; - - if (!node || node.type !== 'request') - return ''; - - return node.name; - }, - - attributes: { - requiresRequestId: true, - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/response-body-json.ts b/packages/ui/src/features/realtime-values/values/response-body-json.ts deleted file mode 100644 index da6546a0..00000000 --- a/packages/ui/src/features/realtime-values/values/response-body-json.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { ResponseBodyJsonRtv } from '@beak/ui/features/realtime-values/values'; -import binaryStore from '@beak/ui/lib/binary-store'; -import { attemptTextToJson } from '@beak/ui/utils/json'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; -import get from 'lodash.get'; - -import { parseValueParts } from '../parser'; -import { getRequestNode } from '../utils/request'; -import { getLatestFlight } from '../utils/response'; - -const allowedRawJson = ['string', 'bool', 'number']; - -const definition: EditableRealtimeValue = { - type: 'response_body_json', - name: 'Response body (json)', - description: 'Returns the body text value of the most recent response for a request', - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ - requestId: '', - dotPath: [''], - }), - - getValue: async (ctx, payload, recursiveDepth) => { - const requestNode = getRequestNode(payload.requestId, ctx); - - if (!requestNode) - return ''; - - const latestFlight = getLatestFlight(requestNode.id, ctx); - - if (!latestFlight?.response) - return ''; - - if (!latestFlight.response.hasBody) - return ''; - - const dotPath = await parseValueParts(ctx, payload.dotPath, recursiveDepth); - const binary = binaryStore.get(latestFlight.binaryStoreKey); - const json = new TextDecoder().decode(binary); - const parsed = attemptTextToJson(json); - const resolved = dotPath === '' ? parsed : get(parsed, dotPath, ''); - - if (!resolved) - return ''; - - if (allowedRawJson.includes(typeof resolved)) - return resolved; - - return JSON.stringify(resolved); - }, - - attributes: { - requiresRequestId: true, - }, - - editor: { - createUserInterface: async () => [{ - type: 'request_select_input', - label: 'Select the request:', - stateBinding: 'requestId', - }, { - type: 'value_parts_input', - label: 'JSON dot path:', - stateBinding: 'dotPath', - }], - - load: async (_ctx, item) => ({ - requestId: item.requestId, - dotPath: item.dotPath, - }), - - save: async (_ctx, _item, state) => ({ - requestId: state.requestId, - dotPath: state.dotPath, - }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/response-body-text.ts b/packages/ui/src/features/realtime-values/values/response-body-text.ts deleted file mode 100644 index 303d65f8..00000000 --- a/packages/ui/src/features/realtime-values/values/response-body-text.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ResponseBodyTextRtv } from '@beak/ui/features/realtime-values/values'; -import binaryStore from '@beak/ui/lib/binary-store'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; - -import { getRequestNode } from '../utils/request'; -import { getLatestFlight } from '../utils/response'; - -const definition: EditableRealtimeValue = { - type: 'response_body_text', - name: 'Response body (text)', - description: 'Returns the body text value of the most recent response for a request', - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ requestId: '' }), - - getValue: async (ctx, payload) => { - const requestNode = getRequestNode(payload.requestId, ctx); - - if (!requestNode) - return ''; - - const latestFlight = getLatestFlight(requestNode.id, ctx); - - if (!latestFlight?.response) - return ''; - - if (!latestFlight.response.hasBody) - return ''; - - const binary = binaryStore.get(latestFlight.binaryStoreKey); - const body = new TextDecoder().decode(binary); - - return body; - }, - - attributes: { - requiresRequestId: true, - }, - - editor: { - createUserInterface: async () => [{ - type: 'request_select_input', - label: 'Select the request:', - stateBinding: 'requestId', - }], - - load: async (_ctx, item) => ({ requestId: item.requestId }), - - save: async (_ctx, _item, state) => ({ requestId: state.requestId }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/response-header.ts b/packages/ui/src/features/realtime-values/values/response-header.ts deleted file mode 100644 index db705547..00000000 --- a/packages/ui/src/features/realtime-values/values/response-header.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { TypedObject } from '@beak/common/helpers/typescript'; -import { ResponseHeaderRtv } from '@beak/ui/features/realtime-values/values'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; - -import { parseValueParts } from '../parser'; -import { getRequestNode } from '../utils/request'; -import { getLatestFlight } from '../utils/response'; - -const definition: EditableRealtimeValue = { - type: 'response_header', - name: 'Response header', - description: 'Returns the header value of the most recent response for a request', - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ - requestId: '', - headerName: [''], - }), - - getValue: async (ctx, payload, recursiveDepth) => { - const requestNode = getRequestNode(payload.requestId, ctx); - - if (!requestNode) - return ''; - - const latestFlight = getLatestFlight(requestNode.id, ctx); - - if (!latestFlight?.response) - return ''; - - const headers = latestFlight.response.headers; - const parsedHeaderName = await parseValueParts(ctx, payload.headerName, recursiveDepth); - const headerKey = TypedObject.keys(headers) - .find(k => k.toLocaleLowerCase() === parsedHeaderName.toLocaleLowerCase()); - - const header = headers[headerKey!]; - - return header ?? ''; - }, - - attributes: { - requiresRequestId: true, - }, - - editor: { - createUserInterface: async () => [{ - type: 'request_select_input', - label: 'Select the request:', - stateBinding: 'requestId', - }, { - type: 'value_parts_input', - label: 'Header name:', - stateBinding: 'headerName', - }], - - load: async (_ctx, item) => ({ - requestId: item.requestId, - headerName: item.headerName, - }), - - save: async (_ctx, _item, state) => ({ - requestId: state.requestId, - headerName: state.headerName, - }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/response-status-code.ts b/packages/ui/src/features/realtime-values/values/response-status-code.ts deleted file mode 100644 index 5b94ddca..00000000 --- a/packages/ui/src/features/realtime-values/values/response-status-code.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ResponseStatusCodeRtv } from '@beak/ui/features/realtime-values/values'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; - -import { getRequestNode } from '../utils/request'; -import { getLatestFlight } from '../utils/response'; - -const definition: EditableRealtimeValue = { - type: 'response_status_code', - name: 'Response status code', - description: 'Returns HTTP status code of the most recent response for a request', - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ requestId: '' }), - - getValue: async (ctx, payload) => { - const requestNode = getRequestNode(payload.requestId, ctx); - - if (!requestNode) - return ''; - - const latestFlight = getLatestFlight(requestNode.id, ctx); - - return latestFlight?.response?.status.toString() ?? ''; - }, - - attributes: { - requiresRequestId: true, - }, - - editor: { - createUserInterface: async () => [{ - type: 'request_select_input', - label: 'Select the request:', - stateBinding: 'requestId', - }], - - load: async (_ctx, item) => ({ requestId: item.requestId }), - save: async (_ctx, _item, state) => ({ requestId: state.requestId }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/secure.ts b/packages/ui/src/features/realtime-values/values/secure.ts deleted file mode 100644 index 578f6501..00000000 --- a/packages/ui/src/features/realtime-values/values/secure.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { SecureRtv, ValueParts } from '@beak/ui/features/realtime-values/values'; -import { ipcEncryptionService } from '@beak/ui/lib/ipc'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; - -import { parseValueParts } from '../parser'; - -interface EditorState { - value: ValueParts; -} - -const definition: EditableRealtimeValue = { - type: 'secure', - name: 'Secure', - description: 'A value protected by Beak project encryption', - sensitive: true, - external: false, - - createDefaultPayload: async () => { - const iv = await ipcEncryptionService.generateIv(); - - return { - iv, - cipherText: '', - }; - }, - - getValue: async (ctx, item) => { - const encryptionSetup = await ipcEncryptionService.checkStatus(); - - if (!encryptionSetup) return '[Encryption key missing]'; - - // handle legacy - if (item.datum !== void 0) { - return await ipcEncryptionService.decryptString({ - iv: item.iv, - payload: item.datum, - }); - } - - const decrypted = await ipcEncryptionService.decryptObject({ - iv: item.iv, - payload: item.cipherText, - }); - - return await parseValueParts(ctx, decrypted); - }, - - attributes: {}, - - editor: { - createUserInterface: async () => [{ - type: 'value_parts_input', - label: 'Enter the value you want to be encrypted:', - stateBinding: 'value', - }], - - load: async (_ctx, item) => { - if (item.datum !== void 0) { - const decrypted = await ipcEncryptionService.decryptString({ - iv: item.iv, - payload: item.datum, - }); - - return { value: [decrypted] }; - } - - const decrypted = await ipcEncryptionService.decryptObject({ - iv: item.iv, - payload: item.cipherText, - }); - - return { value: decrypted }; - }, - - save: async (_ctx, _item, state) => { - // We want to generate a new IV every time - const iv = await ipcEncryptionService.generateIv(); - const cipherText = await ipcEncryptionService.encryptObject({ - iv, - payload: state.value, - }); - - return { iv, cipherText }; - }, - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/special-character.ts b/packages/ui/src/features/realtime-values/values/special-character.ts deleted file mode 100644 index c28c2fe5..00000000 --- a/packages/ui/src/features/realtime-values/values/special-character.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { RealtimeValue } from '@getbeak/types-realtime-value'; - -const characters = { - character_carriage_return: { - name: '\\r', - description: 'Inserts a carriage return character', - character: '\r', - }, - character_newline: { - name: '\\n', - description: 'Inserts a newline character', - character: '\n', - }, - character_tab: { - name: '\\t', - description: 'Inserts a tab character', - character: '\t', - }, -}; - -export const characterCarriageReturnRtv = createCharacter('character_carriage_return'); -export const characterNewlineRtv = createCharacter('character_newline'); -export const characterTabRtv = createCharacter('character_tab'); - -function createCharacter(type: keyof typeof characters): RealtimeValue { - const character = characters[type]; - - return { - type, - name: character.name, - description: character.description, - sensitive: false, - external: false, - - createDefaultPayload: async () => void 0, - - getValue: async () => character.character, - - attributes: {}, - }; -} diff --git a/packages/ui/src/features/realtime-values/values/timestamp.ts b/packages/ui/src/features/realtime-values/values/timestamp.ts deleted file mode 100644 index 566cd592..00000000 --- a/packages/ui/src/features/realtime-values/values/timestamp.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { TimestampRtv } from '@beak/ui/features/realtime-values/values'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; -import { add } from 'date-fns'; - -interface EditorState { - delta: number; - type: string; -} - -const definition: EditableRealtimeValue = { - type: 'timestamp', - name: 'Datetime', - description: 'Render a date-time in a specific format, with an optional delta', - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ - delta: 0, - type: 'iso_8601', - }), - - getValue: async (_ctx, item) => { - const now = new Date(); - const value = item.delta ? add(now, { seconds: item.delta }) : now; - - switch (item.type) { - case 'iso_8601': - return value.toISOString(); - - case 'unix_s': - return Math.round(value.getTime() / 1000).toString(10); - - case 'unix_ms': - return value.getTime().toString(); - - default: - return 'unknown_type'; - } - }, - - attributes: {}, - - editor: { - createUserInterface: async () => [{ - type: 'options_input', - label: 'Pick a date format:', - stateBinding: 'type', - options: [{ - key: 'iso_8601', - label: 'ISO-8601', - }, { - key: 'unix_s', - label: 'Unix timestamp (seconds)', - }, { - key: 'unix_ms', - label: 'Unix timestamp (ms)', - }], - }, { - type: 'number_input', - label: 'Delta (in seconds):', - stateBinding: 'delta', - }], - - load: async (_ctx, item) => ({ type: item.type, delta: item.delta ?? 0 }), - save: async (_ctx, _item, state) => ({ type: state.type, delta: state.delta }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/url-decode.ts b/packages/ui/src/features/realtime-values/values/url-decode.ts deleted file mode 100644 index 41e6cd2d..00000000 --- a/packages/ui/src/features/realtime-values/values/url-decode.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ValueParts } from '@beak/ui/features/realtime-values/values'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; - -import { parseValueParts } from '../parser'; - -interface EditorState { - input: ValueParts; -} - -const definition: EditableRealtimeValue = { - type: 'url_decode', - name: 'Decode (URL)', - description: 'Decodes a url encoded string', - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ input: [''] }), - - getValue: async (ctx, payload, recursiveDepth) => { - const parsed = await parseValueParts(ctx, payload.input, recursiveDepth); - - return decodeURIComponent(parsed); - }, - - attributes: {}, - - editor: { - createUserInterface: async () => [{ - type: 'value_parts_input', - label: 'Enter the data to decode:', - stateBinding: 'input', - }], - - load: async (_ctx, item) => ({ input: item.input }), - save: async (_ctx, _item, state) => ({ input: state.input }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/url-encode.ts b/packages/ui/src/features/realtime-values/values/url-encode.ts deleted file mode 100644 index 39495527..00000000 --- a/packages/ui/src/features/realtime-values/values/url-encode.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ValueParts } from '@beak/ui/features/realtime-values/values'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; - -import { parseValueParts } from '../parser'; - -interface EditorState { - input: ValueParts; -} - -const definition: EditableRealtimeValue = { - type: 'url_encode', - name: 'Encode (URL)', - description: 'Generates a url encoded string', - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ - input: [''], - }), - - getValue: async (ctx, payload, recursiveDepth) => { - const parsed = await parseValueParts(ctx, payload.input, recursiveDepth); - - return encodeURIComponent(parsed); - }, - - attributes: {}, - - editor: { - createUserInterface: async () => [{ - type: 'value_parts_input', - label: 'Enter the data to encode:', - stateBinding: 'input', - }], - - load: async (_ctx, item) => ({ input: item.input }), - save: async (_ctx, _item, state) => ({ input: state.input }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/realtime-values/values/uuid.ts b/packages/ui/src/features/realtime-values/values/uuid.ts deleted file mode 100644 index f9a366ae..00000000 --- a/packages/ui/src/features/realtime-values/values/uuid.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { UuidRtv } from '@beak/ui/features/realtime-values/values'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value'; -import * as uuid from 'uuid'; - -interface EditorState { - version: UuidRtv['version']; -} - -const definition: EditableRealtimeValue = { - type: 'uuid', - name: 'UUID', - description: 'Generate a UUID', - sensitive: false, - external: false, - - createDefaultPayload: async () => ({ - version: 'v4', - }), - - getValue: async (_ctx, item) => { - switch (item.version) { - case 'v1': - return uuid.v1(); - - case 'v4': - return uuid.v4(); - - default: - return 'unknown_version'; - } - }, - - attributes: {}, - - editor: { - createUserInterface: async () => [{ - type: 'options_input', - label: 'Pick a UUID format:', - stateBinding: 'version', - options: [{ - key: 'v1', - label: 'UUID v1', - }, { - key: 'v4', - label: 'UUID v4', - }], - }], - - load: async (_ctx, item) => ({ version: item.version }), - save: async (_ctx, _item, state) => ({ version: state.version }), - }, -}; - -export default definition; diff --git a/packages/ui/src/features/tabs/components/molecules/VariableGroupEditorTab.tsx b/packages/ui/src/features/tabs/components/molecules/VariableGroupEditorTab.tsx deleted file mode 100644 index 2f00489b..00000000 --- a/packages/ui/src/features/tabs/components/molecules/VariableGroupEditorTab.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { VariableGroupEditorTabItem } from '@beak/common/types/beak-project'; -import { useAppSelector } from '@beak/ui/store/redux'; - -import TabItem from '../../../../components/atoms/TabItem'; -import { changeTab, makeTabPermanent } from '../../store/actions'; -import TabContextMenuWrapper from '../atoms/GenericTabContextMenuWrapper'; - -interface VariableGroupEditorTabProps { - tab: VariableGroupEditorTabItem; -} - -const VariableGroupEditorTab: React.FC> = ({ tab }) => { - const dispatch = useDispatch(); - const selectedTabPayload = useAppSelector(s => s.features.tabs.selectedTab); - const [target, setTarget] = useState(); - - return ( - - setTarget(i!)} - onClick={() => dispatch(changeTab(tab))} - onDoubleClick={() => { - if (!tab.temporary) - return; - - dispatch(makeTabPermanent(tab.payload)); - }} - > - {tab.temporary && {rendererToName(tab)}} - {!tab.temporary && rendererToName(tab)} - - - ); -}; - -function rendererToName(tab: VariableGroupEditorTabItem) { - return `Variable group (${tab.payload})`; -} - -export default VariableGroupEditorTab; From 70978b6cb02a03a80e6a9dfbed7c40e40d27072b Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sun, 17 Nov 2024 18:45:20 +0100 Subject: [PATCH 04/14] update types package --- packages/types/body-editor-json.d.ts | 6 +++--- packages/types/index.d.ts | 2 +- packages/types/package.json | 2 +- packages/types/request.d.ts | 6 +++--- packages/types/values.d.ts | 10 +++++----- packages/types/variable-groups.d.ts | 9 --------- packages/types/variable-sets.d.ts | 9 +++++++++ 7 files changed, 22 insertions(+), 22 deletions(-) delete mode 100644 packages/types/variable-groups.d.ts create mode 100644 packages/types/variable-sets.d.ts diff --git a/packages/types/body-editor-json.d.ts b/packages/types/body-editor-json.d.ts index ac6ce18f..a212167d 100644 --- a/packages/types/body-editor-json.d.ts +++ b/packages/types/body-editor-json.d.ts @@ -1,4 +1,4 @@ -import { ValueParts } from './values'; +import { ValueSections } from './values'; export type EntryMap = Record; export type EntryType = 'string' | 'number' | 'boolean' | 'null' | 'object' | 'array'; @@ -13,12 +13,12 @@ export interface NamedEntryBase { name: string } export interface StringEntry extends Base { type: 'string'; - value: ValueParts; + value: ValueSections; } export interface NumberEntry extends Base { type: 'number'; - value: ValueParts; + value: ValueSections; } export interface BooleanEntry extends Base { diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index 2a68f820..ddde0d65 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -6,4 +6,4 @@ export { default as Request } from './request'; export { default as Response } from './response'; export { default as Squawk } from './squawk'; export { default as Values } from './values'; -export { default as VariableGroups } from './variable-groups'; +export { default as VariableSets } from './variable-sets'; diff --git a/packages/types/package.json b/packages/types/package.json index 4bb37577..378b1a5c 100755 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@getbeak/types", - "version": "1.1.0", + "version": "2.1.0", "private": false, "description": "Just general beak types", "homepage": "https://github.com/getbeak/beak/tree/master/packages/types", diff --git a/packages/types/request.d.ts b/packages/types/request.d.ts index 984e86fb..6f1d3d44 100644 --- a/packages/types/request.d.ts +++ b/packages/types/request.d.ts @@ -1,9 +1,9 @@ import { EntryMap } from './body-editor-json'; -import { ValueParts } from './values'; +import { ValueSections } from './values'; export interface RequestOverview { verb: string; - url: ValueParts; + url: ValueSections; query: Record; headers: Record; body: RequestBody; @@ -62,6 +62,6 @@ export interface RequestOptions { export interface ToggleKeyValue { name: string; - value: ValueParts; + value: ValueSections; enabled: boolean; } diff --git a/packages/types/values.d.ts b/packages/types/values.d.ts index 5e3d17ea..e181b617 100644 --- a/packages/types/values.d.ts +++ b/packages/types/values.d.ts @@ -1,13 +1,13 @@ import { FlightHistory } from './flight'; import { Tree } from './nodes'; -import { VariableGroups } from './variable-groups'; +import { VariableSets } from './variable-sets'; -export type ValueParts = ValuePart[]; -export type ValuePart = string | { type: string; payload: unknown }; +export type ValueSections = ValueSection[]; +export type ValueSection = string | { type: string; payload: unknown }; export interface Context { - selectedGroups: Record; - variableGroups: VariableGroups; + selectedSets: Record; + variableSets: VariableSets; projectTree: Tree; flightHistory: Record; currentRequestId?: string; diff --git a/packages/types/variable-groups.d.ts b/packages/types/variable-groups.d.ts deleted file mode 100644 index 9a31bf76..00000000 --- a/packages/types/variable-groups.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ValueParts } from './values'; - -export type VariableGroups = Record; - -export interface VariableGroup { - groups: Record; - items: Record; - values: Record; -} diff --git a/packages/types/variable-sets.d.ts b/packages/types/variable-sets.d.ts new file mode 100644 index 00000000..0d2edeb3 --- /dev/null +++ b/packages/types/variable-sets.d.ts @@ -0,0 +1,9 @@ +import { ValueSections } from './values'; + +export type VariableSets = Record; + +export interface VariableSet { + sets: Record; + items: Record; + values: Record; +} From a1d2acc07a8915d31d7274e904ed07511bb083fa Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sun, 17 Nov 2024 18:46:08 +0100 Subject: [PATCH 05/14] update imports --- apps-host/electron/package.json | 4 ++-- apps-host/web/package.json | 4 ++-- packages/common-host/package.json | 4 ++-- packages/common/package.json | 4 ++-- packages/requester-node/package.json | 2 +- packages/ui/package.json | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps-host/electron/package.json b/apps-host/electron/package.json index e0079dc0..3db8ba49 100644 --- a/apps-host/electron/package.json +++ b/apps-host/electron/package.json @@ -45,8 +45,8 @@ }, "devDependencies": { "@electron/notarize": "^2.5.0", - "@getbeak/types": "^1.0.0", - "@getbeak/types-realtime-value": "^1.0.0", + "@getbeak/types": "^2.1.0", + "@getbeak/types-variables": "^2.1.0", "@types/electron-devtools-installer": "^2.2.5", "@types/fs-extra": "^11.0.4", "@types/lodash.clonedeep": "^4.5.9", diff --git a/apps-host/web/package.json b/apps-host/web/package.json index 0fea1544..373cd146 100644 --- a/apps-host/web/package.json +++ b/apps-host/web/package.json @@ -26,8 +26,8 @@ "@beak/ksuid": "*", "@beak/ui": "*", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", - "@getbeak/types": "^1.0.0", - "@getbeak/types-realtime-value": "^1.0.0", + "@getbeak/types": "^2.1.0", + "@getbeak/types-variables": "^2.1.0", "@isomorphic-git/lightning-fs": "^4.6.0", "path-browserify": "^1.0.1" }, diff --git a/packages/common-host/package.json b/packages/common-host/package.json index fb80903d..a80f3831 100644 --- a/packages/common-host/package.json +++ b/packages/common-host/package.json @@ -17,7 +17,7 @@ "dependencies": { "@beak/common": "*", "@beak/ksuid": "*", - "@getbeak/types": "^1.0.0", - "@getbeak/types-realtime-value": "^1.0.0" + "@getbeak/types": "^2.1.0", + "@getbeak/types-variables": "^2.1.0" } } diff --git a/packages/common/package.json b/packages/common/package.json index 9fb8356c..45950301 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -16,8 +16,8 @@ }, "dependencies": { "@beak/ksuid": "*", - "@getbeak/types": "^1.0.0", - "@getbeak/types-realtime-value": "^1.0.0" + "@getbeak/types": "^2.1.0", + "@getbeak/types-variables": "^2.1.0" }, "devDependencies": { "chokidar": "^4.0.1", diff --git a/packages/requester-node/package.json b/packages/requester-node/package.json index afbee477..69344f8c 100644 --- a/packages/requester-node/package.json +++ b/packages/requester-node/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@beak/common": "*", - "@getbeak/types": "^1.0.0", + "@getbeak/types": "^2.1.0", "node-fetch": "^2.6.1" }, "devDependencies": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 2cc93b59..a1f4a9d3 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -24,8 +24,8 @@ "@beak/common": "*", "@beak/design-system": "*", "@beak/ksuid": "*", - "@getbeak/types": "^1.0.0", - "@getbeak/types-realtime-value": "^1.0.0" + "@getbeak/types": "^2.1.0", + "@getbeak/types-variables": "^2.1.0" }, "devDependencies": { "@fortawesome/fontawesome-svg-core": "^6.6.0", From 9408c077c9c7f49383f42eb6c550fc716442d835 Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sun, 17 Nov 2024 18:46:15 +0100 Subject: [PATCH 06/14] update tsconfig --- tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 429f8020..8a6de362 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -62,8 +62,8 @@ "@getbeak/types": ["packages/types/src"], "@getbeak/types/*": ["packages/types/src/*"], - "@getbeak/types-realtime-value": ["packages/types-realtime-value/src"], - "@getbeak/types-realtime-value/*": ["packages/types-realtime-value/src/*"] + "@getbeak/types-variables": ["packages/types-variables/src"], + "@getbeak/types-variables/*": ["packages/types-variables/src/*"] } }, "exclude": [ From 9a6d54683bda25e304daef80ad075d9c7b608ab1 Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sun, 17 Nov 2024 18:48:16 +0100 Subject: [PATCH 07/14] rename realtime values->variables, variable groups->sets, update usages --- packages/common/src/augmentations.d.ts | 6 +- packages/common/src/ipc/extensions.ts | 28 +- packages/common/src/types/beak-hub.d.ts | 2 +- packages/common/src/types/beak-project.d.ts | 6 +- packages/common/src/types/extensions.d.ts | 8 +- packages/design-system/src/animations.ts | 23 ++ packages/ui/src/augmentations.d.ts | 22 +- .../components/molecules/NewProjectIntro.tsx | 14 +- .../ui/src/components/molecules/Tooltips.tsx | 2 +- packages/ui/src/containers/ProjectMain.tsx | 4 +- .../components/BasicTableEditor.tsx | 4 +- .../features/basic-table-editor/parsers.ts | 4 +- .../components/GraphQlQueryEditor.tsx | 14 +- .../ui/src/features/json-editor/parsers.ts | 6 +- .../features/omni-bar/components/Omnibar.tsx | 24 +- .../components/organism/FinderView.tsx | 4 +- .../project-pane/components/ProjectPane.tsx | 6 +- .../components/molecules/NoVariableSets.tsx | 38 ++ .../components/molecules/VariableSetName.tsx | 77 ++++ .../components/organisms/VariableSets.tsx | 102 ++++++ .../hooks/use-realtime-value-context.ts | 11 - .../ui/src/features/realtime-values/ipc.ts | 18 - .../values/variable-group-item.ts | 68 ---- .../components/molecules/RequestOutput.tsx | 16 +- .../components/organisms/BodyTab.tsx | 8 +- .../components/organisms/Header.tsx | 12 +- .../organisms/RequestPaneSplitter.tsx | 4 +- .../components/molecules/Header.tsx | 4 +- .../components/organisms/RequestTab.tsx | 10 +- .../features/sidebar/components/Sidebar.tsx | 2 +- .../src/features/tabs/components/Router.tsx | 12 +- .../src/features/tabs/components/TabView.tsx | 6 +- .../molecules/VariableSetEditorTab.tsx | 44 +++ .../store/sagas/attempt-reconciliation.ts | 14 +- .../components/molecules/NodeRenamer.tsx | 2 +- .../components/VariableInput.tsx | 124 ++++--- .../components/molecules/VariableSelector.tsx | 49 +-- .../features/variable-input/utils/copying.ts | 6 +- .../variable-input/utils/sanitation.ts | 8 +- .../variable-input/utils/variables.ts | 8 +- .../components/VariableSetEditor.tsx} | 88 ++--- .../components/atoms/Cells.ts | 0 .../components/atoms/Structure.ts | 0 .../molecules/CellDeletionAction.tsx | 0 .../components/molecules/CreateNewSplash.tsx | 14 +- .../components/VariableEditor.tsx | 332 ++++++++++++++++++ .../variables-editor/components/atoms/Form.ts | 19 + .../components/molecules/PreviewContainer.tsx | 38 ++ .../utils/render-request-select-options.tsx | 57 +++ .../components/VariablesPane.tsx | 18 +- .../components/organisms/VariableSets.tsx} | 40 +-- .../variables/hooks/use-variable-context.ts | 11 + packages/ui/src/features/variables/index.ts | 129 +++++++ packages/ui/src/features/variables/ipc.ts | 18 + packages/ui/src/features/variables/parser.ts | 79 +++++ packages/ui/src/features/variables/preview.ts | 19 + .../ui/src/features/variables/renderer.tsx | 85 +++++ .../src/features/variables/utils/request.ts | 11 + .../src/features/variables/utils/response.ts | 13 + .../ui/src/features/variables/values.d.ts | 69 ++++ .../variables/values/base64-decode.ts | 76 ++++ .../variables/values/base64-encode.ts | 79 +++++ .../src/features/variables/values/digest.ts | 92 +++++ .../ui/src/features/variables/values/nonce.ts | 24 ++ .../src/features/variables/values/private.ts | 97 +++++ .../variables/values/request-folder.ts | 31 ++ .../variables/values/request-header.ts | 56 +++ .../variables/values/request-method.ts | 26 ++ .../features/variables/values/request-name.ts | 26 ++ .../variables/values/response-body-json.ts | 83 +++++ .../variables/values/response-body-text.ts | 54 +++ .../variables/values/response-header.ts | 69 ++++ .../variables/values/response-status-code.ts | 43 +++ .../src/features/variables/values/secure.ts | 88 +++++ .../variables/values/special-character.ts | 41 +++ .../features/variables/values/timestamp.ts | 71 ++++ .../features/variables/values/url-decode.ts | 39 ++ .../features/variables/values/url-encode.ts | 41 +++ .../ui/src/features/variables/values/uuid.ts | 54 +++ .../variables/values/variable-set-item.ts | 69 ++++ .../beak-hub/schemas/editor-preferences.json | 2 +- .../lib/beak-hub/schemas/tab-preferences.json | 6 +- packages/ui/src/lib/beak-project/project.ts | 2 +- .../src/lib/beak-project/schemas/request.json | 10 +- .../{variable-groups.ts => variable-sets.ts} | 30 +- .../ui/src/lib/beak-variable-group/index.ts | 22 -- .../lib/beak-variable-group/schema/index.ts | 5 - .../ui/src/lib/beak-variable-group/utils.ts | 3 - .../ui/src/lib/beak-variable-set/index.ts | 22 ++ .../src/lib/beak-variable-set/schema/index.ts | 5 + .../schema/variable-set.json} | 4 +- .../ui/src/lib/beak-variable-set/utils.ts | 3 + .../ui/src/lib/keyboard-shortcuts/index.ts | 8 +- .../ui/src/store/extensions/sagas/index.ts | 2 +- .../extensions/sagas/start-extensions.ts | 4 +- packages/ui/src/store/extensions/types.ts | 4 +- .../src/store/flight/sagas/request-flight.ts | 30 +- packages/ui/src/store/git/sagas/index.ts | 2 +- packages/ui/src/store/index.ts | 12 +- packages/ui/src/store/preferences/actions.ts | 6 +- packages/ui/src/store/preferences/reducers.ts | 6 +- .../sagas/catch-load-preferences.ts | 2 +- packages/ui/src/store/preferences/types.ts | 8 +- .../src/store/project/sagas/start-project.ts | 4 +- packages/ui/src/store/project/types.ts | 12 +- .../ui/src/store/variable-groups/actions.ts | 79 ----- .../sagas/create-variable-group.ts | 18 - .../ui/src/store/variable-groups/types.ts | 93 ----- .../ui/src/store/variable-sets/actions.ts | 79 +++++ .../index.ts | 0 .../reducers.ts | 56 +-- .../sagas/catch-updates.ts | 18 +- .../sagas/create-variable-set.ts | 18 + .../sagas/index.ts | 26 +- .../sagas/remove-node-from-disk.ts | 14 +- .../sagas/start-variable-sets.ts} | 50 +-- .../sagas/variable-set-rename.ts} | 16 +- packages/ui/src/store/variable-sets/types.ts | 93 +++++ packages/ui/src/utils/uri.ts | 6 +- 119 files changed, 3007 insertions(+), 792 deletions(-) create mode 100644 packages/design-system/src/animations.ts create mode 100644 packages/ui/src/features/project-pane/components/molecules/NoVariableSets.tsx create mode 100644 packages/ui/src/features/project-pane/components/molecules/VariableSetName.tsx create mode 100644 packages/ui/src/features/project-pane/components/organisms/VariableSets.tsx delete mode 100644 packages/ui/src/features/realtime-values/hooks/use-realtime-value-context.ts delete mode 100644 packages/ui/src/features/realtime-values/ipc.ts delete mode 100644 packages/ui/src/features/realtime-values/values/variable-group-item.ts create mode 100644 packages/ui/src/features/tabs/components/molecules/VariableSetEditorTab.tsx rename packages/ui/src/features/{variable-groups/components/VariableGroupEditor.tsx => variable-sets/components/VariableSetEditor.tsx} (67%) rename packages/ui/src/features/{variable-groups => variable-sets}/components/atoms/Cells.ts (100%) rename packages/ui/src/features/{variable-groups => variable-sets}/components/atoms/Structure.ts (100%) rename packages/ui/src/features/{variable-groups => variable-sets}/components/molecules/CellDeletionAction.tsx (100%) rename packages/ui/src/features/{variable-groups => variable-sets}/components/molecules/CreateNewSplash.tsx (71%) create mode 100644 packages/ui/src/features/variables-editor/components/VariableEditor.tsx create mode 100644 packages/ui/src/features/variables-editor/components/atoms/Form.ts create mode 100644 packages/ui/src/features/variables-editor/components/molecules/PreviewContainer.tsx create mode 100644 packages/ui/src/features/variables-editor/utils/render-request-select-options.tsx rename packages/ui/src/features/{variables => variables-sets}/components/VariablesPane.tsx (65%) rename packages/ui/src/features/{variables/components/organisms/VariableGroups.tsx => variables-sets/components/organisms/VariableSets.tsx} (77%) create mode 100644 packages/ui/src/features/variables/hooks/use-variable-context.ts create mode 100644 packages/ui/src/features/variables/index.ts create mode 100644 packages/ui/src/features/variables/ipc.ts create mode 100644 packages/ui/src/features/variables/parser.ts create mode 100644 packages/ui/src/features/variables/preview.ts create mode 100644 packages/ui/src/features/variables/renderer.tsx create mode 100644 packages/ui/src/features/variables/utils/request.ts create mode 100644 packages/ui/src/features/variables/utils/response.ts create mode 100644 packages/ui/src/features/variables/values.d.ts create mode 100644 packages/ui/src/features/variables/values/base64-decode.ts create mode 100644 packages/ui/src/features/variables/values/base64-encode.ts create mode 100644 packages/ui/src/features/variables/values/digest.ts create mode 100644 packages/ui/src/features/variables/values/nonce.ts create mode 100644 packages/ui/src/features/variables/values/private.ts create mode 100644 packages/ui/src/features/variables/values/request-folder.ts create mode 100644 packages/ui/src/features/variables/values/request-header.ts create mode 100644 packages/ui/src/features/variables/values/request-method.ts create mode 100644 packages/ui/src/features/variables/values/request-name.ts create mode 100644 packages/ui/src/features/variables/values/response-body-json.ts create mode 100644 packages/ui/src/features/variables/values/response-body-text.ts create mode 100644 packages/ui/src/features/variables/values/response-header.ts create mode 100644 packages/ui/src/features/variables/values/response-status-code.ts create mode 100644 packages/ui/src/features/variables/values/secure.ts create mode 100644 packages/ui/src/features/variables/values/special-character.ts create mode 100644 packages/ui/src/features/variables/values/timestamp.ts create mode 100644 packages/ui/src/features/variables/values/url-decode.ts create mode 100644 packages/ui/src/features/variables/values/url-encode.ts create mode 100644 packages/ui/src/features/variables/values/uuid.ts create mode 100644 packages/ui/src/features/variables/values/variable-set-item.ts rename packages/ui/src/lib/beak-project/{variable-groups.ts => variable-sets.ts} (63%) delete mode 100644 packages/ui/src/lib/beak-variable-group/index.ts delete mode 100644 packages/ui/src/lib/beak-variable-group/schema/index.ts delete mode 100644 packages/ui/src/lib/beak-variable-group/utils.ts create mode 100644 packages/ui/src/lib/beak-variable-set/index.ts create mode 100644 packages/ui/src/lib/beak-variable-set/schema/index.ts rename packages/ui/src/lib/{beak-variable-group/schema/variable-group.json => beak-variable-set/schema/variable-set.json} (87%) create mode 100644 packages/ui/src/lib/beak-variable-set/utils.ts delete mode 100644 packages/ui/src/store/variable-groups/actions.ts delete mode 100644 packages/ui/src/store/variable-groups/sagas/create-variable-group.ts delete mode 100644 packages/ui/src/store/variable-groups/types.ts create mode 100644 packages/ui/src/store/variable-sets/actions.ts rename packages/ui/src/store/{variable-groups => variable-sets}/index.ts (100%) rename packages/ui/src/store/{variable-groups => variable-sets}/reducers.ts (51%) rename packages/ui/src/store/{variable-groups => variable-sets}/sagas/catch-updates.ts (59%) create mode 100644 packages/ui/src/store/variable-sets/sagas/create-variable-set.ts rename packages/ui/src/store/{variable-groups => variable-sets}/sagas/index.ts (55%) rename packages/ui/src/store/{variable-groups => variable-sets}/sagas/remove-node-from-disk.ts (66%) rename packages/ui/src/store/{variable-groups/sagas/start-variable-groups.ts => variable-sets/sagas/start-variable-sets.ts} (62%) rename packages/ui/src/store/{variable-groups/sagas/variable-group-rename.ts => variable-sets/sagas/variable-set-rename.ts} (70%) create mode 100644 packages/ui/src/store/variable-sets/types.ts diff --git a/packages/common/src/augmentations.d.ts b/packages/common/src/augmentations.d.ts index dab00d86..ec2aedc0 100644 --- a/packages/common/src/augmentations.d.ts +++ b/packages/common/src/augmentations.d.ts @@ -1,7 +1,7 @@ -import '@getbeak/types-realtime-value'; +import '@getbeak/types-variables'; -declare module '@getbeak/types-realtime-value' { - interface RealtimeValueBase { +declare module '@getbeak/types-variables' { + interface VariableBase { type: string; external: boolean; } diff --git a/packages/common/src/ipc/extensions.ts b/packages/common/src/ipc/extensions.ts index 60cb9b63..03b50640 100644 --- a/packages/common/src/ipc/extensions.ts +++ b/packages/common/src/ipc/extensions.ts @@ -1,8 +1,8 @@ -import { Context, ValueParts } from '@getbeak/types/values'; -import { UISection } from '@getbeak/types-realtime-value'; +import { Context, ValueSections } from '@getbeak/types/values'; +import { UISection } from '@getbeak/types-variables'; import type { IpcMain, WebContents } from 'electron'; -import { RealtimeValueExtension } from '../types/extensions'; +import { VariableExtension } from '../types/extensions'; import { IpcServiceMain, IpcServiceRenderer, Listener, PartialIpcRenderer } from './ipc'; export const ExtensionsMessages = { @@ -12,8 +12,8 @@ export const ExtensionsMessages = { RtvEditorCreateUserInterface: 'rtv_editor_create_user_interface', RtvEditorLoad: 'rtv_editor_load', RtvEditorSave: 'rtv_editor_save', - RtvParseValueParts: 'rtv_parse_value_parts', - RtvParseValuePartsResponse: 'rtv_parse_value_parts_response', + RtvParseValueSections: 'rtv_parse_value_parts', + RtvParseValueSectionsResponse: 'rtv_parse_value_parts_response', }; interface RegisterRtvPayload { extensionFilePath: string } @@ -41,13 +41,13 @@ interface RtvEditorSave extends RtvBase { state: unknown; } -export interface RtvParseValueParts extends Omit { +export interface RtvParseValueSections extends Omit { uniqueSessionId: string; recursiveDepth: number; - parts: ValueParts; + parts: ValueSections; } -export interface RtvParseValuePartsResponse { +export interface RtvParseValueSectionsResponse { uniqueSessionId: string; parsed: string; } @@ -57,7 +57,7 @@ export class IpcExtensionsServiceRenderer extends IpcServiceRenderer { super('extensions', ipc); } - async registerRtv(payload: RegisterRtvPayload): Promise { + async registerRtv(payload: RegisterRtvPayload): Promise { return await this.invoke(ExtensionsMessages.RegisterRtv, payload); } @@ -81,8 +81,8 @@ export class IpcExtensionsServiceRenderer extends IpcServiceRenderer { return await this.invoke(ExtensionsMessages.RtvEditorSave, payload); } - registerRtvParseValueParts(fn: Listener) { - this.registerListener(ExtensionsMessages.RtvParseValueParts, fn); + registerRtvParseValueSections(fn: Listener) { + this.registerListener(ExtensionsMessages.RtvParseValueSections, fn); } } @@ -91,7 +91,7 @@ export class IpcExtensionsServiceMain extends IpcServiceMain { super('extensions', ipc); } - registerRegisterRtv(fn: Listener) { + registerRegisterRtv(fn: Listener) { this.registerListener(ExtensionsMessages.RegisterRtv, fn); } @@ -115,9 +115,9 @@ export class IpcExtensionsServiceMain extends IpcServiceMain { this.registerListener(ExtensionsMessages.RtvEditorSave, fn); } - rtvParseValueParts(wc: WebContents, payload: RtvParseValueParts) { + rtvParseValueSections(wc: WebContents, payload: RtvParseValueSections) { wc.send(this.channel, { - code: ExtensionsMessages.RtvParseValueParts, + code: ExtensionsMessages.RtvParseValueSections, payload, }); } diff --git a/packages/common/src/types/beak-hub.d.ts b/packages/common/src/types/beak-hub.d.ts index 77705f50..4edabef6 100644 --- a/packages/common/src/types/beak-hub.d.ts +++ b/packages/common/src/types/beak-hub.d.ts @@ -32,7 +32,7 @@ export interface RequestPreference { } export interface EditorPreferences { - selectedVariableGroups: Record; + selectedVariableSets: Record; } export type SidebarVariant = 'project' | 'variables'; diff --git a/packages/common/src/types/beak-project.d.ts b/packages/common/src/types/beak-project.d.ts index 19f1c02e..2b9448ca 100644 --- a/packages/common/src/types/beak-project.d.ts +++ b/packages/common/src/types/beak-project.d.ts @@ -4,7 +4,7 @@ export interface ProjectEncryption { } // NOTE(afr): Adding a new tab item? Don't forget to update tab-preferences schema too! -export type TabItem = RequestTabItem | VariableGroupEditorTabItem | NewProjectIntroTabItem; +export type TabItem = RequestTabItem | VariableSetEditorTabItem | NewProjectIntroTabItem; export interface TabBase { type: string; @@ -17,8 +17,8 @@ export interface RequestTabItem extends TabBase { payload: string; } -export interface VariableGroupEditorTabItem extends TabBase { - type: 'variable_group_editor'; +export interface VariableSetEditorTabItem extends TabBase { + type: 'variable_set_editor'; payload: string; } diff --git a/packages/common/src/types/extensions.d.ts b/packages/common/src/types/extensions.d.ts index 9a08e3a8..eb47d9a5 100644 --- a/packages/common/src/types/extensions.d.ts +++ b/packages/common/src/types/extensions.d.ts @@ -1,12 +1,12 @@ -import { RealtimeValueInformation } from '@getbeak/types-realtime-value'; +import { VariableStaticInformation } from '@getbeak/types-variables'; -export interface RealtimeValueExtension { +export interface VariableExtension { name: string; version: string; filePath: string; valid: true; - realtimeValue: { + variable: { type: string; editable: boolean; - } & RealtimeValueInformation; + } & VariableStaticInformation; } diff --git a/packages/design-system/src/animations.ts b/packages/design-system/src/animations.ts new file mode 100644 index 00000000..051cc6bf --- /dev/null +++ b/packages/design-system/src/animations.ts @@ -0,0 +1,23 @@ +import { keyframes } from 'styled-components'; + +export const scaleIn = keyframes` + 0% { + transform: scale(.97); + opacity: 0; + } + + 100% { + transform: scale(1); + opacity: 1; + } +`; + +export const fadeIn = keyframes` + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +`; diff --git a/packages/ui/src/augmentations.d.ts b/packages/ui/src/augmentations.d.ts index 3ef6f80b..99249545 100644 --- a/packages/ui/src/augmentations.d.ts +++ b/packages/ui/src/augmentations.d.ts @@ -32,28 +32,34 @@ declare module 'electron' { } } -declare module '@getbeak/types-realtime-value' { +declare module '@getbeak/types-variables' { interface GenericDictionary { [k: string]: any; } - interface RealtimeValueBase { + interface VariableBase { type: string; external: boolean; } - interface RealtimeValue { + interface Variable { /** * Gets the string value of the value, given the payload body * @param {Context} ctx The project context. * @param {TPayload} payload This instance of the value's payload data. - * @param {number} recursiveDepth The current depth of realtime value recursion. + * @param {number} recursiveDepth The current depth of value recursion. */ getValue: (ctx: Context, payload: TPayload, recursiveDepth: number) => Promise; + + /** + * Gets a name for the variable, with context of the payload of this specific instance of the variable. + * @param {TPayload} payload This instance of the value's payload data. + */ + getContextAwareName?: (payload: TPayload) => string; } - interface EditableRealtimeValue { + interface EditableVariable { /** * Gets the string value of the value, given the payload body @@ -61,5 +67,11 @@ declare module '@getbeak/types-realtime-value' { * @param {TPayload} payload This instance of the value's payload data. */ getValue: (ctx: Context, payload: TPayload, recursiveDepth: number) => Promise; + + /** + * Gets a name for the variable, with context of the payload of this specific instance of the variable. + * @param {TPayload} payload This instance of the value's payload data. + */ + getContextAwareName?: (payload: TPayload) => string; } } diff --git a/packages/ui/src/components/molecules/NewProjectIntro.tsx b/packages/ui/src/components/molecules/NewProjectIntro.tsx index c7630e56..74932a64 100644 --- a/packages/ui/src/components/molecules/NewProjectIntro.tsx +++ b/packages/ui/src/components/molecules/NewProjectIntro.tsx @@ -41,13 +41,13 @@ const NewProjectIntro: React.FC> = () => { - {'Variable groups'} + {'Variable sets'} - {'Easily share common values between requests, and group them '} - {'by trait.'} + {'Easily share common variables between requests, and group them '} + {'in sets.'} - @@ -61,12 +61,12 @@ const NewProjectIntro: React.FC> = () => { - {'Realtime values'} + {'Variables'} {'Variables can be inserted into your request, and are '} {'calculated for every request.'} - diff --git a/packages/ui/src/components/molecules/Tooltips.tsx b/packages/ui/src/components/molecules/Tooltips.tsx index 1fe126a6..4557692b 100644 --- a/packages/ui/src/components/molecules/Tooltips.tsx +++ b/packages/ui/src/components/molecules/Tooltips.tsx @@ -66,7 +66,7 @@ const tooltips: TooltipDefinition[] = [{ }, { anchor: 'tt-omni-bar-finder-request-uri', }, { - anchor: 'tt-realtime-values-renderer-extension-missing', + anchor: 'tt-variables-renderer-extension-missing', }, { anchor: 'tt-sidebar-menu-item', }]; diff --git a/packages/ui/src/containers/ProjectMain.tsx b/packages/ui/src/containers/ProjectMain.tsx index e75741b9..c355907a 100644 --- a/packages/ui/src/containers/ProjectMain.tsx +++ b/packages/ui/src/containers/ProjectMain.tsx @@ -30,11 +30,11 @@ const ProjectMain: React.FC = () => { const [setup, setSetup] = useState(false); const collapsedSidebar = useAppSelector(s => s.global.preferences.sidebar.collapsed.sidebar); const project = useAppSelector(s => s.global.project); - const variableGroups = useAppSelector(s => s.global.variableGroups); + const variableSets = useAppSelector(s => s.global.variableSets); const tabs = useAppSelector(s => s.features.tabs); const activeTab = tabs.activeTabs.find(t => t.payload === tabs.selectedTab); - const loaded = project.loaded && variableGroups.loaded && tabs.loaded; + const loaded = project.loaded && variableSets.loaded && tabs.loaded; const projectLoading = useProjectLoading(loaded, setup); useApplicationMenuEventListener(); diff --git a/packages/ui/src/features/basic-table-editor/components/BasicTableEditor.tsx b/packages/ui/src/features/basic-table-editor/components/BasicTableEditor.tsx index a977cada..df4f3eb7 100644 --- a/packages/ui/src/features/basic-table-editor/components/BasicTableEditor.tsx +++ b/packages/ui/src/features/basic-table-editor/components/BasicTableEditor.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { TypedObject } from '@beak/common/helpers/typescript'; import DebouncedInput from '@beak/ui/components/atoms/DebouncedInput'; -import { ValueParts } from '@beak/ui/features/realtime-values/values'; +import { ValueSections } from '@beak/ui/features/variables/values'; import type { ToggleKeyValue } from '@getbeak/types/request'; import styled from 'styled-components'; @@ -27,7 +27,7 @@ interface BasicTableEditorProps { disableItemToggle?: boolean; addItem?: () => void; - updateItem?: (type: keyof ToggleKeyValue, ident: string, value: string | boolean | ValueParts) => void; + updateItem?: (type: keyof ToggleKeyValue, ident: string, value: string | boolean | ValueSections) => void; removeItem?: (ident: string) => void; } diff --git a/packages/ui/src/features/basic-table-editor/parsers.ts b/packages/ui/src/features/basic-table-editor/parsers.ts index b39327b1..42ef9c6a 100644 --- a/packages/ui/src/features/basic-table-editor/parsers.ts +++ b/packages/ui/src/features/basic-table-editor/parsers.ts @@ -3,7 +3,7 @@ import ksuid from '@beak/ksuid'; import type { ToggleKeyValue } from '@getbeak/types/request'; import type { Context } from '@getbeak/types/values'; -import { parseValueParts } from '../realtime-values/parser'; +import { parseValueSections } from '../variables/parser'; const queryStringRegex = /[a-z0-9%=+-[\]]+/; @@ -12,7 +12,7 @@ export async function convertKeyValueToString(context: Context, items: Record i.enabled); const resolved = await Promise.all(eligible.map(async e => ({ name: e.name, - value: await parseValueParts(context, e.value), + value: await parseValueSections(context, e.value), }))); for (const resolve of resolved) diff --git a/packages/ui/src/features/graphql-editor/components/GraphQlQueryEditor.tsx b/packages/ui/src/features/graphql-editor/components/GraphQlQueryEditor.tsx index ef461fb5..9cfc0055 100644 --- a/packages/ui/src/features/graphql-editor/components/GraphQlQueryEditor.tsx +++ b/packages/ui/src/features/graphql-editor/components/GraphQlQueryEditor.tsx @@ -17,8 +17,8 @@ import { initializeMode } from 'monaco-graphql/esm/initializeMode'; import { RequestBodyGraphQl } from 'packages/types/request'; import styled from 'styled-components'; -import useRealtimeValueContext from '../../realtime-values/hooks/use-realtime-value-context'; -import { parseValueParts } from '../../realtime-values/parser'; +import useVariableContext from '../../variables/hooks/use-variable-context'; +import { parseValueSections } from '../../variables/parser'; import { extractVariableNamesFromQuery } from '../utils'; import GraphQlError from './molecules/GraphQlError'; import GraphQlLoading from './molecules/GraphQlLoading'; @@ -36,8 +36,8 @@ const GraphQlQueryEditor: React.FC = props => { const [schemaFlightId, setSchemaFlightId] = useState('impossible-yolo'); const [hasSchema, setHasSchema] = useState(() => Boolean(schemaCache[node.id])); const [schemaFetchError, setSchemaFetchError] = useState(null); - const variableGroups = useAppSelector(s => s.global.variableGroups.variableGroups); - const selectedGroups = useAppSelector(s => s.global.preferences.editor.selectedVariableGroups); + const variableSets = useAppSelector(s => s.global.variableSets.variableSets); + const selectedGroups = useAppSelector(s => s.global.preferences.editor.selectedVariableSets); const schemaFlight = useAppSelector(s => s.global.flight.flightHistory[node.id]?.history[schemaFlightId]); const operationsUri = `${node.id}/operations.graphql`; @@ -45,7 +45,7 @@ const GraphQlQueryEditor: React.FC = props => { const schemaUri = `${node.id}/schema.graphql`; const body = node.info.body as RequestBodyGraphQl; - const context = useRealtimeValueContext(node.id); + const context = useVariableContext(node.id); // TODO(afr): Also run this when schema _changes_ useEffect(() => { @@ -108,7 +108,7 @@ const GraphQlQueryEditor: React.FC = props => { .filter(key => node.info.headers[key].enabled) .map(async key => ({ key: node.info.headers[key].name, - value: await parseValueParts(context, node.info.headers[key].value), + value: await parseValueSections(context, node.info.headers[key].value), }))); const graphQlBody: FetcherParams = { query: getIntrospectionQuery() }; @@ -154,7 +154,7 @@ const GraphQlQueryEditor: React.FC = props => { node.info.verb, node.info.url, JSON.stringify(node.info.query), - JSON.stringify(variableGroups), + JSON.stringify(variableSets), JSON.stringify(selectedGroups), ]); diff --git a/packages/ui/src/features/json-editor/parsers.ts b/packages/ui/src/features/json-editor/parsers.ts index 4fbfbeea..3fdbc272 100644 --- a/packages/ui/src/features/json-editor/parsers.ts +++ b/packages/ui/src/features/json-editor/parsers.ts @@ -3,7 +3,7 @@ import ksuid from '@beak/ksuid'; import type { Entries, EntryMap, NamedEntries, StringEntry } from '@getbeak/types/body-editor-json'; import type { Context } from '@getbeak/types/values'; -import { parseValueParts } from '../realtime-values/parser'; +import { parseValueSections } from '../variables/parser'; type JsonTypes = null | string | number | boolean | Record | unknown[]; @@ -27,10 +27,10 @@ async function convertEntry( return entry.value; case 'number': - return Number(await parseValueParts(context, entry.value)); + return Number(await parseValueSections(context, entry.value)); case 'string': - return await parseValueParts(context, entry.value); + return await parseValueSections(context, entry.value); case 'array': { const children = TypedObject diff --git a/packages/ui/src/features/omni-bar/components/Omnibar.tsx b/packages/ui/src/features/omni-bar/components/Omnibar.tsx index a09dc861..e0678160 100644 --- a/packages/ui/src/features/omni-bar/components/Omnibar.tsx +++ b/packages/ui/src/features/omni-bar/components/Omnibar.tsx @@ -1,35 +1,15 @@ import React, { useEffect, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; +import { fadeIn, scaleIn } from '@beak/design-system/animations'; import { toHexAlpha } from '@beak/design-system/utils'; import { checkShortcut } from '@beak/ui/lib/keyboard-shortcuts'; import { useAppSelector } from '@beak/ui/store/redux'; -import styled, { keyframes } from 'styled-components'; +import styled from 'styled-components'; import { actions } from '../store'; import CommandsView from './organism/CommandsView'; import FinderView from './organism/FinderView'; -const scaleIn = keyframes` - 0% { - transform: scale(.97); - opacity: 0; - } - - 100% { - transform: scale(1); - opacity: 1; - } -`; -const fadeIn = keyframes` - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -`; - const Omnibar: React.FC> = () => { const { open, mode } = useAppSelector(s => s.features.omniBar); const [content, setContent] = useState(''); diff --git a/packages/ui/src/features/omni-bar/components/organism/FinderView.tsx b/packages/ui/src/features/omni-bar/components/organism/FinderView.tsx index 0cd27566..b71155e4 100644 --- a/packages/ui/src/features/omni-bar/components/organism/FinderView.tsx +++ b/packages/ui/src/features/omni-bar/components/organism/FinderView.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { TypedObject } from '@beak/common/helpers/typescript'; -import useRealtimeValueContext from '@beak/ui/features/realtime-values/hooks/use-realtime-value-context'; import { changeTab } from '@beak/ui/features/tabs/store/actions'; +import useVariableContext from '@beak/ui/features/variables/hooks/use-variable-context'; import { checkShortcut } from '@beak/ui/lib/keyboard-shortcuts'; import { useAppSelector } from '@beak/ui/store/redux'; import { movePosition } from '@beak/ui/utils/arrays'; @@ -24,7 +24,7 @@ const FinderView: React.FC> = ({ conten const flattened = TypedObject.values(tree).filter(t => t.type === 'request') as ValidRequestNode[]; const [matches, setMatches] = useState([]); const [active, setActive] = useState(-1); - const context = useRealtimeValueContext(); + const context = useVariableContext(); const activeRef = useRef(null); const fuse = new Fuse(flattened, { diff --git a/packages/ui/src/features/project-pane/components/ProjectPane.tsx b/packages/ui/src/features/project-pane/components/ProjectPane.tsx index 51c45ad8..b1121ffd 100644 --- a/packages/ui/src/features/project-pane/components/ProjectPane.tsx +++ b/packages/ui/src/features/project-pane/components/ProjectPane.tsx @@ -16,7 +16,7 @@ import TreeView from '../../tree-view/components/TreeView'; import { TreeViewItem } from '../../tree-view/types'; import RequestFlightStatus from './molecules/RequestFlightStatus'; import Git from './organisms/Git'; -import VariableGroups from './organisms/VariableGroups'; +import VariableSets from './organisms/VariableSets'; const ProjectPane: React.FC> = () => { const { id, tree, name } = useAppSelector(s => s.global.project); @@ -159,8 +159,8 @@ const ProjectPane: React.FC> = () => { - - + + > = () => { + const dispatch = useDispatch(); + + function createVariableSet() { + dispatch(sidebarPreferenceSetSelected('variables')); + dispatch(createNewVariableSet({ })); + } + + return ( + + + {'You have no variable sets'} + + + + + ); +}; + +const Container = styled.div` + margin-bottom: 10px; +`; + +const Title = styled.div` + margin-bottom: 5px; +`; + +export default NoVariableSets; diff --git a/packages/ui/src/features/project-pane/components/molecules/VariableSetName.tsx b/packages/ui/src/features/project-pane/components/molecules/VariableSetName.tsx new file mode 100644 index 00000000..ad11a272 --- /dev/null +++ b/packages/ui/src/features/project-pane/components/molecules/VariableSetName.tsx @@ -0,0 +1,77 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import ksuid from '@beak/ksuid'; +import ContextMenu from '@beak/ui/components/atoms/ContextMenu'; +import tabActions from '@beak/ui/features/tabs/store/actions'; +import sidebarActions from '@beak/ui/store/preferences/actions'; +import { actions as vgActions } from '@beak/ui/store/variable-sets'; +import type { MenuItemConstructorOptions } from 'electron'; +import styled from 'styled-components'; + +interface VariableSetNameProps { + variableSetName: string; +} + +export const VariableSetName: React.FC = ({ variableSetName }) => { + const [menuItems, setMenuItems] = useState([]); + const targetRef = useRef(); + const dispatch = useDispatch(); + + useEffect(() => { + setMenuItems([{ + id: ksuid.generate('ctxmenuitem').toString(), + label: 'Reveal in sidebar', + click: () => { + dispatch(sidebarActions.sidebarPreferenceSetSelected('variables')); + }, + }, { + id: ksuid.generate('ctxmenuitem').toString(), + label: 'Open in editor', + click: () => { + dispatch(tabActions.changeTab({ + type: 'variable_set_editor', + payload: variableSetName, + temporary: false, + })); + }, + }, { + id: ksuid.generate('ctxmenuitem').toString(), + type: 'separator', + }, { + id: ksuid.generate('ctxmenuitem').toString(), + label: 'Delete', + click: () => { + dispatch(vgActions.removeVariableSetFromDisk({ + id: variableSetName, + withConfirmation: true, + })); + }, + }]); + }, [variableSetName]); + + return ( + + + targetRef.current = i!} + > + {variableSetName} + + + + ); +}; + +const Name = styled.abbr` + color: ${p => p.theme.ui.textMinor}; + font-size: 12px; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-decoration: none; +`; + +export default VariableSetName; diff --git a/packages/ui/src/features/project-pane/components/organisms/VariableSets.tsx b/packages/ui/src/features/project-pane/components/organisms/VariableSets.tsx new file mode 100644 index 00000000..4ed9950d --- /dev/null +++ b/packages/ui/src/features/project-pane/components/organisms/VariableSets.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { useDispatch } from 'react-redux'; +import { TypedObject } from '@beak/common/helpers/typescript'; +import useSectionBody from '@beak/ui/features/sidebar/hooks/use-section-body'; +import { editorPreferencesSetSelectedVariableSet } from '@beak/ui/store/preferences/actions'; +import { useAppSelector } from '@beak/ui/store/redux'; +import styled from 'styled-components'; + +import NoVariableSets from '../molecules/NoVariableSets'; +import VariableSetName from '../molecules/VariableSetName'; + +const VariableSets: React.FC> = () => { + const dispatch = useDispatch(); + const { variableSets } = useAppSelector(s => s.global.variableSets)!; + const selectedSets = useAppSelector(s => s.global.preferences.editor.selectedVariableSets); + const empty = Object.keys(variableSets).length === 0; + + useSectionBody({ + maxHeight: '120px', + flexShrink: 0, + }); + + if (empty) { + return ( + + + + ); + } + + return ( + + {TypedObject.keys(variableSets!).map(k => { + const sets = variableSets![k].sets; + const setKeys = TypedObject.keys(sets); + const value = selectedSets[k]; + + return ( + + + + { + dispatch(editorPreferencesSetSelectedVariableSet({ + variableSet: k, + setId: e.target.value, + })); + }} + > + {setKeys.map(gk => ( + + ))} + + + ); + })} + + ); +}; + +const Container = styled.div` + padding: 4px 5px; + padding-right: 0; + padding-bottom: 0; +`; + +const Item = styled.div` + display: grid; + grid-template-columns: minmax(10px, max-content) minmax(10px, max-content); + justify-content: space-between; + align-items: center; + gap: 5px; + margin: 4px 0; + max-width: calc(100% - 3px); + + &:first-child { + margin-top: 0; + } + + &:not(:last-child) { + margin-bottom: 6px; + } +`; + +const Selector = styled.select` + width: 100%; + font-size: 12px; + border: 0; + border-radius: 4px; + background: none; + color: ${p => p.theme.ui.textMinor}; + text-align-last: right; + text-overflow: ellipsis; + + &:hover, &:active, &:focus { + background: ${p => p.theme.ui.surface}; + outline: none; + } +`; + +export default VariableSets; diff --git a/packages/ui/src/features/realtime-values/hooks/use-realtime-value-context.ts b/packages/ui/src/features/realtime-values/hooks/use-realtime-value-context.ts deleted file mode 100644 index f5c79138..00000000 --- a/packages/ui/src/features/realtime-values/hooks/use-realtime-value-context.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useAppSelector } from '@beak/ui/store/redux'; -import type { Context } from '@getbeak/types/values'; - -export default function useRealtimeValueContext(requestId?: string): Context { - const variableGroups = useAppSelector(s => s.global.variableGroups.variableGroups); - const projectTree = useAppSelector(s => s.global.project.tree); - const flightHistory = useAppSelector(s => s.global.flight.flightHistory); - const selectedGroups = useAppSelector(s => s.global.preferences.editor.selectedVariableGroups); - - return { variableGroups, selectedGroups, flightHistory, projectTree, currentRequestId: requestId }; -} diff --git a/packages/ui/src/features/realtime-values/ipc.ts b/packages/ui/src/features/realtime-values/ipc.ts deleted file mode 100644 index 8f08d80f..00000000 --- a/packages/ui/src/features/realtime-values/ipc.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ExtensionsMessages, RtvParseValuePartsResponse } from '@beak/common/ipc/extensions'; -import { RequestPayload } from '@beak/common/ipc/ipc'; -import { ipcExtensionsService } from '@beak/ui/lib/ipc'; - -import { parseValueParts } from './parser'; - -ipcExtensionsService.registerRtvParseValueParts(async (event, payload) => { - const parsed = await parseValueParts(payload.context, payload.parts, payload.recursiveDepth); - const message: RequestPayload = { - code: ExtensionsMessages.RtvParseValuePartsResponse, - payload: { - parsed, - uniqueSessionId: payload.uniqueSessionId, - }, - }; - - event.sender.send(ipcExtensionsService.getChannel(), message); -}); diff --git a/packages/ui/src/features/realtime-values/values/variable-group-item.ts b/packages/ui/src/features/realtime-values/values/variable-group-item.ts deleted file mode 100644 index 111d578b..00000000 --- a/packages/ui/src/features/realtime-values/values/variable-group-item.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { TypedObject } from '@beak/common/helpers/typescript'; -import { VariableGroupItemRtv } from '@beak/ui/features/realtime-values/values'; -import type { VariableGroups } from '@getbeak/types/variable-groups'; -import { RealtimeValue } from '@getbeak/types-realtime-value'; - -import { getValueParts, parseValueParts } from '../parser'; - -const type = 'variable_group_item'; - -const definition: RealtimeValue = { - type, - name: 'Variable group item', - description: 'A realtime value, you can edit it\'s value from the Variable Group editor', - sensitive: false, - external: false, - - createDefaultPayload: () => { - throw new Error('Not supported, this should not happen.'); - }, - - getValue: async (ctx, item, recursiveDepth) => { - const parts = getValueParts(ctx, item.itemId) || []; - - return await parseValueParts(ctx, parts, recursiveDepth); - }, - - attributes: {}, -}; - -export function createFauxValue( - item: VariableGroupItemRtv, - variableGroups: VariableGroups, -): RealtimeValue { - return { - type, - name: getVariableGroupItemName(item, variableGroups), - description: 'A realtime value, you can edit it\'s value from the Variable Group editor', - sensitive: false, - external: false, - - createDefaultPayload: async () => item, - - getValue: () => { - throw new Error('Not supported, this should not happen.'); - }, - - attributes: {}, - }; -} - -export function getVariableGroupItemName(item: VariableGroupItemRtv, variableGroups: VariableGroups) { - if (!variableGroups) - return 'Unknown'; - - const keys = TypedObject.keys(variableGroups); - - for (const key of keys) { - const vg = variableGroups[key]; - const itemValue = vg.items[item.itemId]; - - if (itemValue) - return `${key} (${itemValue})`; - } - - return 'Unknown'; -} - -export default definition; diff --git a/packages/ui/src/features/request-pane/components/molecules/RequestOutput.tsx b/packages/ui/src/features/request-pane/components/molecules/RequestOutput.tsx index c2483275..cb8d28e6 100644 --- a/packages/ui/src/features/request-pane/components/molecules/RequestOutput.tsx +++ b/packages/ui/src/features/request-pane/components/molecules/RequestOutput.tsx @@ -5,8 +5,8 @@ import EditorView from '@beak/ui/components/atoms/EditorView'; import WindowSessionContext, { WindowSession } from '@beak/ui/contexts/window-session-context'; import { convertKeyValueToString } from '@beak/ui/features/basic-table-editor/parsers'; import { convertToRealJson } from '@beak/ui/features/json-editor/parsers'; -import useRealtimeValueContext from '@beak/ui/features/realtime-values/hooks/use-realtime-value-context'; -import { parseValueParts } from '@beak/ui/features/realtime-values/parser'; +import useVariableContext from '@beak/ui/features/variables/hooks/use-variable-context'; +import { parseValueSections } from '@beak/ui/features/variables/parser'; import useComponentMounted from '@beak/ui/hooks/use-component-mounted'; import { ipcFsService } from '@beak/ui/lib/ipc'; import { useAppSelector } from '@beak/ui/store/redux'; @@ -22,12 +22,12 @@ export interface RequestOutputProps { const RequestOutput: React.FC> = props => { const node = props.selectedNode; - const variableGroups = useAppSelector(s => s.global.variableGroups.variableGroups); - const selectedGroups = useAppSelector(s => s.global.preferences.editor.selectedVariableGroups); + const variableSets = useAppSelector(s => s.global.variableSets.variableSets); + const selectedGroups = useAppSelector(s => s.global.preferences.editor.selectedVariableSets); const windowSession = useContext(WindowSessionContext); const [output, setOutput] = useState(''); const mounted = useComponentMounted(); - const context = useRealtimeValueContext(node.id); + const context = useVariableContext(node.id); useEffect(() => { createBasicHttpOutput(node.info, context, windowSession) @@ -37,7 +37,7 @@ const RequestOutput: React.FC> = pro setOutput(response); }); - }, [node, selectedGroups, variableGroups]); + }, [node, selectedGroups, variableSets]); return ( q.enabled) - .map(async value => queryBuilder.append(value.name, await parseValueParts(context, value.value))), + .map(async value => queryBuilder.append(value.name, await parseValueSections(context, value.value))), ); if (!requestAllowsBody(verb) && body.type === 'graphql') @@ -107,7 +107,7 @@ export async function createBasicHttpOutput(overview: RequestOverview, context: out.push(...(await Promise.all( TypedObject.values(headers) .filter(h => h.enabled) - .map(async ({ name, value }) => `${name}: ${await parseValueParts(context, value)}`))), + .map(async ({ name, value }) => `${name}: ${await parseValueSections(context, value)}`))), ); } diff --git a/packages/ui/src/features/request-pane/components/organisms/BodyTab.tsx b/packages/ui/src/features/request-pane/components/organisms/BodyTab.tsx index bd4980af..3b90e6cd 100644 --- a/packages/ui/src/features/request-pane/components/organisms/BodyTab.tsx +++ b/packages/ui/src/features/request-pane/components/organisms/BodyTab.tsx @@ -10,8 +10,8 @@ import { EditorMode } from '@beak/ui/features/graphql-editor/types'; import { editorTabSubItems } from '@beak/ui/features/graphql-editor/utils'; import JsonEditor from '@beak/ui/features/json-editor/components/JsonEditor'; import { convertToEntryJson, convertToRealJson } from '@beak/ui/features/json-editor/parsers'; -import useRealtimeValueContext from '@beak/ui/features/realtime-values/hooks/use-realtime-value-context'; -import { ValueParts } from '@beak/ui/features/realtime-values/values'; +import useVariableContext from '@beak/ui/features/variables/hooks/use-variable-context'; +import { ValueSections } from '@beak/ui/features/variables/values'; import { ipcDialogService } from '@beak/ui/lib/ipc'; import actions, { requestBodyTextChanged } from '@beak/ui/store/project/actions'; import { RequestBodyTypeChangedPayload } from '@beak/ui/store/project/types'; @@ -31,7 +31,7 @@ export interface BodyTabProps { const BodyTab: React.FC> = props => { const dispatch = useDispatch(); - const context = useRealtimeValueContext(); + const context = useVariableContext(); const { node } = props; const { body } = node.info; const [graphQlMode, setGraphQlMode] = useState('query'); @@ -237,7 +237,7 @@ const BodyTab: React.FC> = props => { dispatch(actions.requestBodyUrlEncodedEditorValueChange({ requestId: node.id, id, - value: value as ValueParts, + value: value as ValueSections, })); } }} diff --git a/packages/ui/src/features/request-pane/components/organisms/Header.tsx b/packages/ui/src/features/request-pane/components/organisms/Header.tsx index 6f571d6a..3f9c943a 100644 --- a/packages/ui/src/features/request-pane/components/organisms/Header.tsx +++ b/packages/ui/src/features/request-pane/components/organisms/Header.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { useDispatch } from 'react-redux'; -import useRealtimeValueContext from '@beak/ui/features/realtime-values/hooks/use-realtime-value-context'; -import { parseValueParts } from '@beak/ui/features/realtime-values/parser'; -import { ValueParts } from '@beak/ui/features/realtime-values/values'; import VariableInput from '@beak/ui/features/variable-input/components/VariableInput'; +import useVariableContext from '@beak/ui/features/variables/hooks/use-variable-context'; +import { parseValueSections } from '@beak/ui/features/variables/parser'; +import { ValueSections } from '@beak/ui/features/variables/values'; import { requestPreferenceSetReqMainTab } from '@beak/ui/store/preferences/actions'; import { useAppSelector } from '@beak/ui/store/redux'; import { faSpinner } from '@fortawesome/free-solid-svg-icons'; @@ -25,7 +25,7 @@ const Header: React.FC> = props => { const currentFlight = useAppSelector(s => s.global.flight.currentFlight); const flighting = currentFlight && currentFlight.flighting && currentFlight.requestId === props.node.id; const { node } = props; - const context = useRealtimeValueContext(node.id); + const context = useVariableContext(node.id); const verb = node.info.verb; function dispatchFlightRequest() { @@ -36,8 +36,8 @@ const Header: React.FC> = props => { dispatch(requestPreferenceSetReqMainTab({ id: node.id, tab: 'url_query' })); } - async function handleUrlChange(parts: ValueParts) { - const value = await parseValueParts(context, parts); + async function handleUrlChange(parts: ValueSections) { + const value = await parseValueSections(context, parts); let sanitizedParts = [...parts]; const parsed = new URL(value, true); diff --git a/packages/ui/src/features/request-pane/components/organisms/RequestPaneSplitter.tsx b/packages/ui/src/features/request-pane/components/organisms/RequestPaneSplitter.tsx index 3c4c12b6..279f6f4d 100644 --- a/packages/ui/src/features/request-pane/components/organisms/RequestPaneSplitter.tsx +++ b/packages/ui/src/features/request-pane/components/organisms/RequestPaneSplitter.tsx @@ -1,6 +1,6 @@ import React, { useContext } from 'react'; import WindowSessionContext from '@beak/ui/contexts/window-session-context'; -import useRealtimeValueContext from '@beak/ui/features/realtime-values/hooks/use-realtime-value-context'; +import useVariableContext from '@beak/ui/features/variables/hooks/use-variable-context'; import useShareLink from '@beak/ui/hooks/use-share-link'; import { faCopy, faShareFromSquare } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -15,7 +15,7 @@ interface RequestPaneSplitterProps { const RequestPaneSplitter: React.FC = props => { const { selectedNode } = props; - const context = useRealtimeValueContext(selectedNode.id); + const context = useVariableContext(selectedNode.id); const windowSession = useContext(WindowSessionContext); const shareUrl = useShareLink(selectedNode.id); diff --git a/packages/ui/src/features/response-pane/components/molecules/Header.tsx b/packages/ui/src/features/response-pane/components/molecules/Header.tsx index c95bbeaa..b5ee0bbb 100644 --- a/packages/ui/src/features/response-pane/components/molecules/Header.tsx +++ b/packages/ui/src/features/response-pane/components/molecules/Header.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { statusToColor } from '@beak/design-system/helpers'; -import useRealtimeValueContext from '@beak/ui/features/realtime-values/hooks/use-realtime-value-context'; +import useVariableContext from '@beak/ui/features/variables/hooks/use-variable-context'; import { getStatusReasonPhrase } from '@beak/ui/utils/http'; import { convertRequestToUrl } from '@beak/ui/utils/uri'; import type { Flight } from '@getbeak/types/flight'; @@ -12,7 +12,7 @@ export interface HeaderProps { const Header: React.FC> = props => { const { error, request, response } = props.selectedFlight; - const context = useRealtimeValueContext(props.selectedFlight.requestId); + const context = useVariableContext(props.selectedFlight.requestId); const [url, setUrl] = useState(''); useEffect(() => { diff --git a/packages/ui/src/features/response-pane/components/organisms/RequestTab.tsx b/packages/ui/src/features/response-pane/components/organisms/RequestTab.tsx index 53377ef2..7b9c0ecd 100644 --- a/packages/ui/src/features/response-pane/components/organisms/RequestTab.tsx +++ b/packages/ui/src/features/response-pane/components/organisms/RequestTab.tsx @@ -3,7 +3,7 @@ import { useDispatch } from 'react-redux'; import EditorView from '@beak/ui/components/atoms/EditorView'; import WindowSessionContext from '@beak/ui/contexts/window-session-context'; import BasicTableEditor from '@beak/ui/features/basic-table-editor/components/BasicTableEditor'; -import useRealtimeValueContext from '@beak/ui/features/realtime-values/hooks/use-realtime-value-context'; +import useVariableContext from '@beak/ui/features/variables/hooks/use-variable-context'; import { requestPreferenceSetResSubTab } from '@beak/ui/store/preferences/actions'; import { useAppSelector } from '@beak/ui/store/redux'; import type { Flight } from '@getbeak/types/flight'; @@ -27,14 +27,14 @@ const RequestTab: React.FC> = props => const requestId = flight.requestId; const dispatch = useDispatch(); const [output, setOutput] = useState(''); - const { variableGroups } = useAppSelector(s => s.global.variableGroups); - const selectedGroups = useAppSelector(s => s.global.preferences.editor.selectedVariableGroups); + const { variableSets } = useAppSelector(s => s.global.variableSets); + const selectedGroups = useAppSelector(s => s.global.preferences.editor.selectedVariableSets); const tab = useAppSelector( s => s.global.preferences.requests[requestId].response.subTab.request, ) as Tab | undefined; const windowSession = useContext(WindowSessionContext); - const context = useRealtimeValueContext(requestId); + const context = useVariableContext(requestId); // Ensure we have a valid tab useEffect(() => { @@ -48,7 +48,7 @@ const RequestTab: React.FC> = props => useEffect(() => { createBasicHttpOutput(flight.request, context, windowSession).then(setOutput); - }, [flight.request, selectedGroups, variableGroups]); + }, [flight.request, selectedGroups, variableSets]); return ( diff --git a/packages/ui/src/features/sidebar/components/Sidebar.tsx b/packages/ui/src/features/sidebar/components/Sidebar.tsx index 4722995c..0f16864b 100644 --- a/packages/ui/src/features/sidebar/components/Sidebar.tsx +++ b/packages/ui/src/features/sidebar/components/Sidebar.tsx @@ -10,7 +10,7 @@ import { useAppSelector } from '@beak/ui/store/redux'; import styled, { css } from 'styled-components'; import ProjectPane from '../../project-pane/components/ProjectPane'; -import VariablesPane from '../../variables/components/VariablesPane'; +import VariablesPane from '../../variables-sets/components/VariablesPane'; import SidebarMenuHighlighter from './molecules/SidebarMenuHighlighter'; import SidebarMenuItem from './molecules/SidebarMenuItem'; diff --git a/packages/ui/src/features/tabs/components/Router.tsx b/packages/ui/src/features/tabs/components/Router.tsx index 8163c31f..7849948d 100644 --- a/packages/ui/src/features/tabs/components/Router.tsx +++ b/packages/ui/src/features/tabs/components/Router.tsx @@ -11,7 +11,7 @@ import type { RequestNode } from '@getbeak/types/nodes'; import BrokenRequest from '../../broken-request/components/BrokenRequest'; import RequestPane from '../../request-pane/components/RequestPane'; import ResponsePane from '../../response-pane/components/ResponsePane'; -import VariableGroupEditor from '../../variable-groups/components/VariableGroupEditor'; +import VariableSetEditor from '../../variable-sets/components/VariableSetEditor'; interface RouterProps { selectedTab: TabItem | undefined; @@ -62,13 +62,13 @@ const Router: React.FC> = ({ selectedTab }) } switch (selectedTab.type) { - case 'variable_group_editor': { - const variableGroupName = selectedTab.payload; + case 'variable_set_editor': { + const variableSetName = selectedTab.payload; return ( - ); } diff --git a/packages/ui/src/features/tabs/components/TabView.tsx b/packages/ui/src/features/tabs/components/TabView.tsx index d1904235..ec36a59f 100644 --- a/packages/ui/src/features/tabs/components/TabView.tsx +++ b/packages/ui/src/features/tabs/components/TabView.tsx @@ -8,7 +8,7 @@ import TB from '../../../components/atoms/TabBar'; import { changeTabNext, changeTabPrevious, closeTab, closeTabsAll, closeTabsOther } from '../store/actions'; import NewProjectIntroTab from './molecules/NewProjectIntroTab'; import RequestTab from './molecules/RequestTab'; -import VariableGroupEditorTab from './molecules/VariableGroupEditorTab'; +import VariableSetEditorTab from './molecules/VariableSetEditorTab'; import Router from './Router'; interface TabViewProps { @@ -62,8 +62,8 @@ const TabView: React.FC> = ({ selectedTab, if (t.type === 'request') return ; - if (t.type === 'variable_group_editor') - return ; + if (t.type === 'variable_set_editor') + return ; if (t.type === 'new_project_intro') return ; diff --git a/packages/ui/src/features/tabs/components/molecules/VariableSetEditorTab.tsx b/packages/ui/src/features/tabs/components/molecules/VariableSetEditorTab.tsx new file mode 100644 index 00000000..973be442 --- /dev/null +++ b/packages/ui/src/features/tabs/components/molecules/VariableSetEditorTab.tsx @@ -0,0 +1,44 @@ +import React, { useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { VariableSetEditorTabItem } from '@beak/common/types/beak-project'; +import { useAppSelector } from '@beak/ui/store/redux'; + +import TabItem from '../../../../components/atoms/TabItem'; +import { changeTab, makeTabPermanent } from '../../store/actions'; +import TabContextMenuWrapper from '../atoms/GenericTabContextMenuWrapper'; + +interface VariableSetEditorTabProps { + tab: VariableSetEditorTabItem; +} + +const VariableSetEditorTab: React.FC> = ({ tab }) => { + const dispatch = useDispatch(); + const selectedTabPayload = useAppSelector(s => s.features.tabs.selectedTab); + const [target, setTarget] = useState(); + + return ( + + setTarget(i!)} + onClick={() => dispatch(changeTab(tab))} + onDoubleClick={() => { + if (!tab.temporary) + return; + + dispatch(makeTabPermanent(tab.payload)); + }} + > + {tab.temporary && {rendererToName(tab)}} + {!tab.temporary && rendererToName(tab)} + + + ); +}; + +function rendererToName(tab: VariableSetEditorTabItem) { + return `Variable set (${tab.payload})`; +} + +export default VariableSetEditorTab; diff --git a/packages/ui/src/features/tabs/store/sagas/attempt-reconciliation.ts b/packages/ui/src/features/tabs/store/sagas/attempt-reconciliation.ts index fd7dc220..f4a0e450 100644 --- a/packages/ui/src/features/tabs/store/sagas/attempt-reconciliation.ts +++ b/packages/ui/src/features/tabs/store/sagas/attempt-reconciliation.ts @@ -3,7 +3,7 @@ import { TabItem } from '@beak/common/types/beak-project'; import { ApplicationState } from '@beak/ui/store'; import { createTakeLatestSagaSet } from '@beak/ui/utils/redux/sagas'; import type { Tree } from '@getbeak/types/nodes'; -import type { VariableGroups } from '@getbeak/types/variable-groups'; +import type { VariableSets } from '@getbeak/types/variable-sets'; import { delay, put, select } from '@redux-saga/core/effects'; import actions, { closeTab, reconciliationComplete } from '../actions'; @@ -16,12 +16,12 @@ export default createTakeLatestSagaSet(actions.attemptReconciliation, function* const tabs: TabItem[] = yield select((s: ApplicationState) => s.features.tabs.activeTabs); const tree: Tree = yield select((s: ApplicationState) => s.global.project.tree); - const variableGroups: VariableGroups = yield select( - (s: ApplicationState) => s.global.variableGroups.variableGroups, + const variableSets: VariableSets = yield select( + (s: ApplicationState) => s.global.variableSets.variableSets, ); const nodes = TypedObject.values(tree); - const variableGroupNames = TypedObject.keys(variableGroups); + const variableSetNames = TypedObject.keys(variableSets); for (const tab of tabs) { switch (tab.type) { @@ -33,10 +33,10 @@ export default createTakeLatestSagaSet(actions.attemptReconciliation, function* break; } - case 'variable_group_editor': { - const variableGroup = variableGroupNames.find(n => n === tab.payload); + case 'variable_set_editor': { + const variableSet = variableSetNames.find(n => n === tab.payload); - if (!variableGroup) + if (!variableSet) yield put(closeTab(tab.payload)); break; } diff --git a/packages/ui/src/features/tree-view/components/molecules/NodeRenamer.tsx b/packages/ui/src/features/tree-view/components/molecules/NodeRenamer.tsx index bee40b1a..6c6b92c2 100644 --- a/packages/ui/src/features/tree-view/components/molecules/NodeRenamer.tsx +++ b/packages/ui/src/features/tree-view/components/molecules/NodeRenamer.tsx @@ -58,7 +58,7 @@ const NodeRenamer: React.FC> = props = if (!renaming) { return ( void; + onChange: (parts: ValueSections) => void; onUrlQueryStringDetection?: () => void; } @@ -46,15 +46,15 @@ const VariableInput = React.forwardRef((props, const [showSelector, setShowSelector] = useState(() => false); const [query, setQuery] = useState(''); - const { variableGroups } = useAppSelector(s => s.global.variableGroups); - const selectedGroups = useAppSelector(s => s.global.preferences.editor.selectedVariableGroups); + const { variableSets } = useAppSelector(s => s.global.variableSets); + const selectedGroups = useAppSelector(s => s.global.preferences.editor.selectedVariableSets); - const context = useRealtimeValueContext(props.requestId); + const context = useVariableContext(props.requestId); const editableRef = useRef(null); const placeholderRef = useRef(null); const unmanagedStateRef = useRef({ lastUpstreamReport: 0, - valueParts: incomingParts, + ValueSections: incomingParts, }); // Setup ref @@ -69,15 +69,15 @@ const VariableInput = React.forwardRef((props, unmanagedStateRef.current = { lastUpstreamReport: 0, - valueParts: incomingParts, + ValueSections: incomingParts, }; if (readOnly) elem.contentEditable = 'false'; - elem.innerHTML = renderValueParts( - unmanagedStateRef.current.valueParts, - variableGroups, + elem.innerHTML = renderValueSections( + unmanagedStateRef.current.ValueSections, + variableSets, ); }, [requestId, readOnly, latestForceRerender]); @@ -99,7 +99,7 @@ const VariableInput = React.forwardRef((props, elem.removeEventListener('blur', handleBlur); elem.removeEventListener('paste', handlePaste); }; - }, [requestId, showSelector, query, variableGroups, selectedGroups]); + }, [requestId, showSelector, query, variableSets, selectedGroups]); useEffect(() => { if (!editableRef.current) @@ -113,7 +113,7 @@ const VariableInput = React.forwardRef((props, useEffect(() => { // Update unmanaged state if the change comes in more than 100ms after our last known write if (unmanagedStateRef.current.lastUpstreamReport + 100 < Date.now()) { - unmanagedStateRef.current.valueParts = incomingParts; + unmanagedStateRef.current.ValueSections = incomingParts; forceRerender(); } @@ -137,7 +137,7 @@ const VariableInput = React.forwardRef((props, return; } - const { variableSelectionState, valueParts } = unmanagedStateRef.current; + const { variableSelectionState, ValueSections } = unmanagedStateRef.current; // Sanity check, but by now the selector will be open if (!variableSelectionState) { @@ -149,7 +149,7 @@ const VariableInput = React.forwardRef((props, } // Selector is open, so update query - const part = valueParts[variableSelectionState.queryStartSelection.partIndex]; + const part = ValueSections[variableSelectionState.queryStartSelection.partIndex]; if (typeof part !== 'string') { if (showSelector) @@ -168,17 +168,17 @@ const VariableInput = React.forwardRef((props, } async function handleCopy(event: ClipboardEvent) { - const { anomalyDetected, valueParts } = parseDomState(); + const { anomalyDetected, ValueSections } = parseDomState(); - if (anomalyDetected || valueParts.length === 0) + if (anomalyDetected || ValueSections.length === 0) return; - const relevantParts = detectRelevantCopiedValueParts(valueParts); + const relevantParts = detectRelevantCopiedValueSections(ValueSections); if (relevantParts === null) return; - const parsed = await parseValueParts(context, relevantParts); + const parsed = await parseValueSections(context, relevantParts); const clipboard = await navigator.clipboard.read(); const html = await clipboard[0].getType('text/html'); @@ -216,9 +216,9 @@ const VariableInput = React.forwardRef((props, function parseDomState() { if (!editableRef.current) - return { anomalyDetected: false, valueParts: [] }; + return { anomalyDetected: false, ValueSections: [] }; - const reconciledParts: ValueParts = []; + const reconciledParts: ValueSections = []; const children = editableRef.current.childNodes; // If we detect some invalid state, we just ask React to re-render to clear out any @@ -268,7 +268,7 @@ const VariableInput = React.forwardRef((props, const type = elem.dataset.type!; // TODO(afr): Detect if payload is corrected, if it is ignore and mark the - // entire realtime value as an anomaly + // entire variable as an anomaly const purePayload = elem.dataset.payload; reconciledParts.push({ @@ -279,21 +279,21 @@ const VariableInput = React.forwardRef((props, return; }); - return { anomalyDetected, valueParts: reconciledParts }; + return { anomalyDetected, ValueSections: reconciledParts }; } function reconcile() { if (!editableRef.current) return; - const { anomalyDetected, valueParts } = parseDomState(); + const { anomalyDetected, ValueSections } = parseDomState(); const { lastSelectionPosition } = unmanagedStateRef.current; - unmanagedStateRef.current.valueParts = valueParts; + unmanagedStateRef.current.ValueSections = ValueSections; unmanagedStateRef.current.lastSelectionPosition = normalizeSelection(lastSelectionPosition); if (placeholderRef.current) - placeholderRef.current.style.display = valueParts.length === 0 ? 'block' : 'none'; + placeholderRef.current.style.display = ValueSections.length === 0 ? 'block' : 'none'; // This means something really weird has happened, such as an unknown element getting into our DOM state. If // that happens we just tell React to re-render and override with the state that we know about @@ -324,7 +324,7 @@ const VariableInput = React.forwardRef((props, return; } - const { debounceHandler, lastSelectionPosition, valueParts } = unmanagedStateRef.current; + const { debounceHandler, lastSelectionPosition, ValueSections } = unmanagedStateRef.current; // Make sure we aren't double reporting! if (debounceHandler) @@ -335,14 +335,14 @@ const VariableInput = React.forwardRef((props, // Cleanup the format before we pass it back to the store. If we do this internally it'll mess up some of the // DOM <-> State management. Very bad design from me. - onChange(sanitiseValueParts(valueParts)); + onChange(sanitiseValueSections(ValueSections)); } function openSelector() { setShowSelector(true); const selection = normalizeSelection(unmanagedStateRef.current.lastSelectionPosition); - const part = unmanagedStateRef.current.valueParts[selection.partIndex]; + const part = unmanagedStateRef.current.ValueSections[selection.partIndex]; if (typeof part !== 'string') return; @@ -354,7 +354,7 @@ const VariableInput = React.forwardRef((props, } function insertVariable(variable: ValuePart) { - const { valueParts, variableSelectionState } = unmanagedStateRef.current; + const { ValueSections, variableSelectionState } = unmanagedStateRef.current; if (!variableSelectionState) return; @@ -362,35 +362,35 @@ const VariableInput = React.forwardRef((props, const { queryStartSelection, queryTrailingLength } = variableSelectionState; const { offset, partIndex } = queryStartSelection; const queryLength = query.length; - const mode = determineInsertionMode(valueParts, variableSelectionState, queryLength); + const mode = determineInsertionMode(ValueSections, variableSelectionState, queryLength); const newPartSelectionIndex = mode === 'append' ? partIndex + 2 : partIndex + 1; - const mutatedValueParts = [...valueParts]; + const mutatedValueSections = [...ValueSections]; if (['prepend', 'append'].includes(mode)) { let finalPartIndex = partIndex; if (mode === 'prepend') { finalPartIndex += 1; - mutatedValueParts.splice(partIndex, 0, variable); + mutatedValueSections.splice(partIndex, 0, variable); } else { // append - mutatedValueParts.splice(partIndex + 1, 0, variable); + mutatedValueSections.splice(partIndex + 1, 0, variable); } - const part = (mutatedValueParts[finalPartIndex] as string); + const part = (mutatedValueSections[finalPartIndex] as string); const partWithoutQuery = [ part.substring(0, offset - 1), part.substr(part.length - queryTrailingLength), ].join(''); - mutatedValueParts[finalPartIndex] = partWithoutQuery; + mutatedValueSections[finalPartIndex] = partWithoutQuery; } else if (mode === 'inject') { - const part = (mutatedValueParts[partIndex] as string); + const part = (mutatedValueSections[partIndex] as string); const pre = part.substring(0, offset - 1); const post = part.substr(part.length - queryTrailingLength); - mutatedValueParts[partIndex] = pre; - mutatedValueParts.splice(partIndex + 1, 0, variable); - mutatedValueParts.splice(partIndex + 2, 0, post); + mutatedValueSections[partIndex] = pre; + mutatedValueSections.splice(partIndex + 1, 0, variable); + mutatedValueSections.splice(partIndex + 2, 0, post); } else { closeSelector(); @@ -404,7 +404,7 @@ const VariableInput = React.forwardRef((props, }; unmanagedStateRef.current.lastSelectionPosition = newSelectionPosition; - unmanagedStateRef.current.valueParts = mutatedValueParts; + unmanagedStateRef.current.ValueSections = mutatedValueSections; closeSelector(); @@ -415,8 +415,8 @@ const VariableInput = React.forwardRef((props, } function variableEditSaved(partIndex: number, type: string, item: any) { - const valueParts = unmanagedStateRef.current.valueParts; - const part = unmanagedStateRef.current.valueParts[partIndex]; + const ValueSections = unmanagedStateRef.current.ValueSections; + const part = unmanagedStateRef.current.ValueSections[partIndex]; if (typeof part !== 'object' || part.type !== type) { console.error(`Part ordering change mid edit, cannot continue. expected ${type}`); @@ -424,7 +424,7 @@ const VariableInput = React.forwardRef((props, return; } - const newParts = [...valueParts]; + const newParts = [...ValueSections]; const existingPart = newParts[partIndex] as ValuePart; if (typeof existingPart !== 'string') { @@ -434,7 +434,7 @@ const VariableInput = React.forwardRef((props, }; } - unmanagedStateRef.current.valueParts = newParts; + unmanagedStateRef.current.ValueSections = newParts; window.setTimeout(() => { reportChange(); @@ -443,9 +443,9 @@ const VariableInput = React.forwardRef((props, } function internalPartUpdate() { - editableRef.current!.innerHTML = renderValueParts( - unmanagedStateRef.current.valueParts, - variableGroups, + editableRef.current!.innerHTML = renderValueSections( + unmanagedStateRef.current.ValueSections, + variableSets, ); // eslint-disable-next-line no-new @@ -458,6 +458,16 @@ const VariableInput = React.forwardRef((props, setShowSelector(false); setQuery(''); + // if (editableRef.current && unmanagedStateRef.current.variableSelectionState) { + // const node = editableRef.current.childNodes[unmanagedStateRef.current.variableSelectionState.queryStartSelection.partIndex]; + + // if (node && node.nodeName == 'SPAN') { + // const span = node as HTMLSpanElement; + + // span.innerText = span.innerText.substring(0, unmanagedStateRef.current.variableSelectionState.queryStartSelection.offset); + // } + // } + unmanagedStateRef.current.variableSelectionState = void 0; } @@ -467,7 +477,7 @@ const VariableInput = React.forwardRef((props, {placeholder && ( {placeholder} @@ -483,7 +493,7 @@ const VariableInput = React.forwardRef((props, /> )} {editableRef.current && ( - > = props => { const { editableElement, sel, query, requestId, onClose, onDone } = props; - const { variableGroups } = useAppSelector(s => s.global.variableGroups); + const { variableSets } = useAppSelector(s => s.global.variableSets); const activeRef = useRef(null); const [position, setPosition] = useState(null); const [active, setActive] = useState(0); - const context = useRealtimeValueContext(requestId); + const context = useVariableContext(requestId); - const items: RealtimeValueInformation[] = useMemo(() => { - const all: RealtimeValueInformation[] = [ - ...RealtimeValueManager.getRealtimeValues(requestId), + const items: VariableStaticInformation[] = useMemo(() => { + const all: VariableStaticInformation[] = [ + ...VariableManager.getVariables(requestId), - // Variable groups act a little differently - ...TypedObject.keys(variableGroups) + // Variable sets act a little differently + ...TypedObject.keys(variableSets) .map(vgKey => { - const vg = variableGroups[vgKey]; + const vg = variableSets[vgKey]; - return TypedObject.keys(vg.items).map(i => createFauxValue({ itemId: i }, variableGroups)); + return TypedObject.keys(vg.items).map(i => createFauxValue({ itemId: i }, variableSets)); }) .flat(), ]; @@ -80,7 +69,7 @@ const VariableSelector: React.FC> .sort() .map(r => r.item) .sort(); - }, [variableGroups, query]); + }, [variableSets, query]); useEffect(() => { if (!sel) @@ -162,13 +151,13 @@ const VariableSelector: React.FC> return () => window.removeEventListener('keydown', onKeyDown); }, [active, items]); - async function createDefaultVariable(item: RealtimeValueInformation) { + async function createDefaultVariable(item: VariableStaticInformation) { let payload: any; if (item.external) payload = await ipcExtensionsService.rtvCreateDefaultPayload({ type: item.type, context }); else - payload = await (item as RealtimeValue).createDefaultPayload(context); + payload = await (item as Variable).createDefaultPayload(context); onDone({ type: item.type, payload }); } @@ -192,7 +181,7 @@ const VariableSelector: React.FC> {'Missing a variable you would find useful?'}
{'You can build your own with an extension, check the '} - void await ipcExplorerService.launchUrl("https://getbeak.notion.site/Extensions-4c16ca640b35460787056f8be815b904") }> + void await ipcExplorerService.launchUrl("https://getbeak.notion.site/Extensions-4c16ca640b35460787056f8be815b904") }> {'docs'} {'.'} diff --git a/packages/ui/src/features/variable-input/utils/copying.ts b/packages/ui/src/features/variable-input/utils/copying.ts index 32b42d42..5c4860c3 100644 --- a/packages/ui/src/features/variable-input/utils/copying.ts +++ b/packages/ui/src/features/variable-input/utils/copying.ts @@ -1,6 +1,6 @@ -import { ValueParts } from '@beak/ui/features/realtime-values/values'; +import { ValueSections } from '@beak/ui/features/variables/values'; -export function detectRelevantCopiedValueParts(valueParts: ValueParts) { +export function detectRelevantCopiedValueSections(ValueSections: ValueSections) { const sel = window.getSelection()!; let startNode = sel.anchorNode!; @@ -62,7 +62,7 @@ export function detectRelevantCopiedValueParts(valueParts: ValueParts) { if (startIndex === -1 || endIndex === -1) return null; - const relevantParts = valueParts.slice(startIndex, endIndex + 1); + const relevantParts = ValueSections.slice(startIndex, endIndex + 1); const samePart = startIndex === endIndex; if (samePart) { diff --git a/packages/ui/src/features/variable-input/utils/sanitation.ts b/packages/ui/src/features/variable-input/utils/sanitation.ts index ca793ed1..43282f4e 100644 --- a/packages/ui/src/features/variable-input/utils/sanitation.ts +++ b/packages/ui/src/features/variable-input/utils/sanitation.ts @@ -1,11 +1,11 @@ -import { ValueParts } from '@getbeak/types/values'; +import { ValueSections } from '@getbeak/types/values'; -export function sanitiseValueParts(valueParts: ValueParts) { +export function sanitiseValueSections(ValueSections: ValueSections) { // Now we need to slightly sanitise the reconciled state. The outcome of this must: // - Remove leading empty string parts // - Remove trailing empty string parts // - Collapse 2 or more consecutive empty string parts into one - const sanitisedParts: ValueParts = valueParts.reduce((acc, value) => { + const sanitisedParts: ValueSections = ValueSections.reduce((acc, value) => { if (typeof value !== 'string') return [...acc, value]; @@ -17,7 +17,7 @@ export function sanitiseValueParts(valueParts: ValueParts) { acc[acc.length - 1] = `${acc[acc.length - 1]}${value}`; return acc; - }, [] as ValueParts); + }, [] as ValueSections); if (sanitisedParts[0] === '') sanitisedParts.shift(); diff --git a/packages/ui/src/features/variable-input/utils/variables.ts b/packages/ui/src/features/variable-input/utils/variables.ts index cb1ba669..0c003d0d 100644 --- a/packages/ui/src/features/variable-input/utils/variables.ts +++ b/packages/ui/src/features/variable-input/utils/variables.ts @@ -1,4 +1,4 @@ -import { ValueParts } from '@beak/ui/features/realtime-values/values'; +import { ValueSections } from '@beak/ui/features/variables/values'; import { NormalizedSelection } from './browser-selection'; @@ -10,7 +10,7 @@ export interface VariableSelectionState { } export function determineInsertionMode( - valueParts: ValueParts, + ValueSections: ValueSections, variableSelectionState: VariableSelectionState, queryLength: number, ): Mode { @@ -21,8 +21,8 @@ export function determineInsertionMode( if (partIndex === 0 && offset === 1) return 'prepend'; - if (partIndex === valueParts.length - 1) { - const part = valueParts[partIndex] as string; + if (partIndex === ValueSections.length - 1) { + const part = ValueSections[partIndex] as string; if (part.length - queryLength === offset + 1) return 'append'; diff --git a/packages/ui/src/features/variable-groups/components/VariableGroupEditor.tsx b/packages/ui/src/features/variable-sets/components/VariableSetEditor.tsx similarity index 67% rename from packages/ui/src/features/variable-groups/components/VariableGroupEditor.tsx rename to packages/ui/src/features/variable-sets/components/VariableSetEditor.tsx index f8dda506..3da765cf 100644 --- a/packages/ui/src/features/variable-groups/components/VariableGroupEditor.tsx +++ b/packages/ui/src/features/variable-sets/components/VariableSetEditor.tsx @@ -2,10 +2,10 @@ import React, { useEffect, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { TypedObject } from '@beak/common/helpers/typescript'; import DebouncedInput from '@beak/ui/components/atoms/DebouncedInput'; -import { generateValueIdent } from '@beak/ui/lib/beak-variable-group/utils'; +import { generateValueIdent } from '@beak/ui/lib/beak-variable-set/utils'; import { useAppSelector } from '@beak/ui/store/redux'; -import { actions } from '@beak/ui/store/variable-groups'; -import { insertNewGroup, insertNewItem, removeGroup, removeItem } from '@beak/ui/store/variable-groups/actions'; +import { actions } from '@beak/ui/store/variable-sets'; +import { insertNewGroup, insertNewItem, removeGroup, removeItem } from '@beak/ui/store/variable-sets/actions'; import styled, { css } from 'styled-components'; import VariableInput from '../../variable-input/components/VariableInput'; @@ -14,14 +14,14 @@ import { Body, Header, Row } from './atoms/Structure'; import CellDeletionAction from './molecules/CellDeletionAction'; import CreateNewSplash from './molecules/CreateNewSplash'; -interface VariableGroupEditorProps { - variableGroupName: string; +interface VariableSetEditorProps { + variableSetName: string; } -const VariableGroupEditor: React.FC> = ({ variableGroupName }) => { +const VariableSetEditor: React.FC> = ({ variableSetName }) => { const dispatch = useDispatch(); - const variableGroups = useAppSelector(s => s.global.variableGroups); - const variableGroup = variableGroups.variableGroups[variableGroupName]; + const variableSets = useAppSelector(s => s.global.variableSets); + const variableSet = variableSets.variableSets[variableSetName]; const [newItem, setNewItem] = useState(void 0); const newItemRef = useRef(null); @@ -33,7 +33,7 @@ const VariableGroupEditor: React.FC { if (!newGroup) return; - if (!TypedObject.values(variableGroup.groups).includes(newGroup)) + if (!TypedObject.values(variableSet.sets).includes(newGroup)) return; if (newGroupRef?.current === null) @@ -55,48 +55,48 @@ const VariableGroupEditor: React.FC - {variableGroup && groupKeys.length === 0 && ( - + {variableSet && setKeys.length === 0 && ( + )} - {variableGroup && groupKeys.length > 0 && ( + {variableSet && setKeys.length > 0 && (
- + - {variableGroup && groupKeys.map(k => ( + {variableSet && setKeys.map(k => ( { dispatch(actions.updateGroupName({ - id: variableGroupName, - groupId: k, + id: variableSetName, + setId: k, updatedName: v, })); }} /> dispatch(removeGroup({ - id: variableGroupName, - groupId: k, + id: variableSetName, + setId: k, }))} /> @@ -109,8 +109,8 @@ const VariableGroupEditor: React.FC { setNewGroup(e.target.value); dispatch(insertNewGroup({ - id: variableGroupName, - groupName: e.target.value, + id: variableSetName, + setName: e.target.value, })); }} /> @@ -119,16 +119,16 @@ const VariableGroupEditor: React.FC - {variableGroup && itemKeys.map(ik => ( - + {variableSet && itemKeys.map(ik => ( + { dispatch(actions.updateItemName({ - id: variableGroupName, + id: variableSetName, itemId: ik, updatedName: v, })); @@ -136,17 +136,17 @@ const VariableGroupEditor: React.FC dispatch(removeItem({ - id: variableGroupName, + id: variableSetName, itemId: ik, }))} /> - {groupKeys.map(gk => { + {setKeys.map(gk => { const key = generateValueIdent(gk, ik); - const value = variableGroup.values[key]; + const value = variableSet.values[key]; return ( @@ -154,8 +154,8 @@ const VariableGroupEditor: React.FC { dispatch(actions.updateValue({ - id: variableGroupName, - groupId: gk, + id: variableSetName, + setId: gk, itemId: ik, updated: parts, })); @@ -171,7 +171,7 @@ const VariableGroupEditor: React.FC ))} - + { setNewItem(e.target.value); dispatch(insertNewItem({ - id: variableGroupName, + id: variableSetName, itemName: e.target.value, })); }} /> - {variableGroup && groupKeys.map(k => ( + {variableSet && setKeys.map(k => ( @@ -231,4 +231,4 @@ const inputCss = css<{ $center?: boolean }>` const StyledDebounce = styled(DebouncedInput)<{ $center?: boolean }>`${inputCss}`; const EmptyInput = styled.input<{ $center?: boolean }>`${inputCss}`; -export default VariableGroupEditor; +export default VariableSetEditor; diff --git a/packages/ui/src/features/variable-groups/components/atoms/Cells.ts b/packages/ui/src/features/variable-sets/components/atoms/Cells.ts similarity index 100% rename from packages/ui/src/features/variable-groups/components/atoms/Cells.ts rename to packages/ui/src/features/variable-sets/components/atoms/Cells.ts diff --git a/packages/ui/src/features/variable-groups/components/atoms/Structure.ts b/packages/ui/src/features/variable-sets/components/atoms/Structure.ts similarity index 100% rename from packages/ui/src/features/variable-groups/components/atoms/Structure.ts rename to packages/ui/src/features/variable-sets/components/atoms/Structure.ts diff --git a/packages/ui/src/features/variable-groups/components/molecules/CellDeletionAction.tsx b/packages/ui/src/features/variable-sets/components/molecules/CellDeletionAction.tsx similarity index 100% rename from packages/ui/src/features/variable-groups/components/molecules/CellDeletionAction.tsx rename to packages/ui/src/features/variable-sets/components/molecules/CellDeletionAction.tsx diff --git a/packages/ui/src/features/variable-groups/components/molecules/CreateNewSplash.tsx b/packages/ui/src/features/variable-sets/components/molecules/CreateNewSplash.tsx similarity index 71% rename from packages/ui/src/features/variable-groups/components/molecules/CreateNewSplash.tsx rename to packages/ui/src/features/variable-sets/components/molecules/CreateNewSplash.tsx index 49181187..7836eda6 100644 --- a/packages/ui/src/features/variable-groups/components/molecules/CreateNewSplash.tsx +++ b/packages/ui/src/features/variable-sets/components/molecules/CreateNewSplash.tsx @@ -1,26 +1,26 @@ import React from 'react'; import { useDispatch } from 'react-redux'; import Button from '@beak/ui/components/atoms/Button'; -import { insertNewGroup } from '@beak/ui/store/variable-groups/actions'; +import { insertNewGroup } from '@beak/ui/store/variable-sets/actions'; import styled from 'styled-components'; interface CreateNewSplashProps { - type: 'group'; - variableGroup: string; + type: 'set'; + variableSet: string; } -const CreateNewSplash: React.FC = ({ type, variableGroup }) => { +const CreateNewSplash: React.FC = ({ type, variableSet }) => { const dispatch = useDispatch(); return (
- {'Looks like you have no groups in here?'} + {'Looks like you have no sets in here?'}
+ +
+ + ); +}; + +function trySetInitialRef( + first: boolean, + instance: HTMLElement | null, + ref: React.MutableRefObject, +) { + if (!first) + return; + + // eslint-disable-next-line no-param-reassign + ref.current = instance; +} + +const Container = styled.div` + z-index: 101; + position: fixed; + top: 0; bottom: 0; left: 0; right: 0; +`; + +const Wrapper = styled.div<{ $top: number; $left: number }>` + position: fixed; + margin-top: ${p => p.$top}px; + margin-left: ${p => p.$left}px; + + width: 300px; + padding: 8px 12px; + border: 1px solid ${p => p.theme.ui.backgroundBorderSeparator}; + background: ${p => p.theme.ui.surface}; + z-index: 10000; + + transform-origin: center; + animation: ${scaleIn} .2s ease; + transition: transform .1s ease; +`; + +const ButtonContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +export default VariableEditor; diff --git a/packages/ui/src/features/variables-editor/components/atoms/Form.ts b/packages/ui/src/features/variables-editor/components/atoms/Form.ts new file mode 100644 index 00000000..af5efefe --- /dev/null +++ b/packages/ui/src/features/variables-editor/components/atoms/Form.ts @@ -0,0 +1,19 @@ +import styled from 'styled-components'; + +export const FormGroup = styled.div` + margin-bottom: 8px; + + > div > article { + font-size: 13px; + padding: 3px 5px; + padding-bottom: 4px; + border-radius: 3px; + } +`; + +export const Label = styled.label` + display: block; + margin-bottom: 4px; + + font-size: 13px; +`; diff --git a/packages/ui/src/features/variables-editor/components/molecules/PreviewContainer.tsx b/packages/ui/src/features/variables-editor/components/molecules/PreviewContainer.tsx new file mode 100644 index 00000000..f82ff128 --- /dev/null +++ b/packages/ui/src/features/variables-editor/components/molecules/PreviewContainer.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import styled from 'styled-components'; + +interface PreviewContainerProps { + text: string; +} + +const PreviewContainer: React.FC = ({ text }) => ( + + {'Preview'} + {text} + +); + +const Container = styled.div` + position: relative; + font-size: 12px; + background: ${p => p.theme.ui.secondarySurface}; + margin: 10px -12px; + margin-bottom: 8px; + padding: 10px 12px; + padding-top: 25px; + + max-height: 100px; + overflow-y: overlay; + overflow-x: hidden; + overflow-wrap: break-word; +`; + +const PreviewHint = styled.div` + position: absolute; + top: 5px; + left: 5px; + text-transform: uppercase; + font-size: 9px; +`; + +export default PreviewContainer; diff --git a/packages/ui/src/features/variables-editor/utils/render-request-select-options.tsx b/packages/ui/src/features/variables-editor/utils/render-request-select-options.tsx new file mode 100644 index 00000000..b2d3bc7b --- /dev/null +++ b/packages/ui/src/features/variables-editor/utils/render-request-select-options.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { TypedObject } from '@beak/common/helpers/typescript'; +import type { FolderNode, RequestNode } from '@getbeak/types/nodes'; +import type { Context } from '@getbeak/types/values'; + +export default function renderRequestSelectOptions(context: Context) { + const depth = 0; + const formattedNodes = TypedObject.values(context.projectTree) + .filter(t => t.parent === 'tree') + .sort((a, b) => a.name.localeCompare(b.name, void 0, { + numeric: true, + sensitivity: 'base', + })); + + return ( + + {formattedNodes.filter(i => i.type === 'folder') + .map(i => renderFolder(i as FolderNode, context, depth))} + {formattedNodes.filter(i => i.type === 'request') + .map(i => renderRequest(i as RequestNode, depth))} + + ); +} + +function renderFolder(node: FolderNode, context: Context, depth: number) { + const newDepth = depth + 1; + const childNodes = TypedObject.values(context.projectTree) + .filter(i => i.parent === node.filePath) + .sort((a, b) => a.name.localeCompare(b.name, void 0, { + numeric: true, + sensitivity: 'base', + })); + + const folderNodes = childNodes.filter(n => n.type === 'folder') as FolderNode[]; + const nodes = childNodes.filter(n => n.type === 'request') as RequestNode[]; + + return ( + + + {nodes.map(n => renderRequest(n, newDepth))} + + {folderNodes.map(n => renderFolder(n, context, newDepth))} + + ); +} + +function renderRequest(node: RequestNode, depth: number) { + return ( + + ); +} + +function renderDepth(depth: number) { + return '\u00A0'.repeat(depth * 2); +} diff --git a/packages/ui/src/features/variables/components/VariablesPane.tsx b/packages/ui/src/features/variables-sets/components/VariablesPane.tsx similarity index 65% rename from packages/ui/src/features/variables/components/VariablesPane.tsx rename to packages/ui/src/features/variables-sets/components/VariablesPane.tsx index 3fc08c8f..2a7336d1 100644 --- a/packages/ui/src/features/variables/components/VariablesPane.tsx +++ b/packages/ui/src/features/variables-sets/components/VariablesPane.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { useDispatch } from 'react-redux'; import ksuid from '@beak/ksuid'; -import { actions } from '@beak/ui/store/variable-groups'; +import { actions } from '@beak/ui/store/variable-sets'; import SidebarPane from '../../sidebar/components/SidebarPane'; import SidebarPaneSection from '../../sidebar/components/SidebarPaneSection'; import { changeTab } from '../../tabs/store/actions'; -import VariableGroups from './organisms/VariableGroups'; +import VariableSets from './organisms/VariableSets'; const VariablesPane: React.FC> = () => { const dispatch = useDispatch(); @@ -14,23 +14,23 @@ const VariablesPane: React.FC> = () => { return ( { - dispatch(actions.createNewVariableGroup({ })); + dispatch(actions.createNewVariableSet({ })); dispatch(changeTab({ - type: 'variable_group_editor', - payload: 'New variable group', + type: 'variable_set_editor', + payload: 'New variable set', temporary: false, })); }, }]} > - + ); diff --git a/packages/ui/src/features/variables/components/organisms/VariableGroups.tsx b/packages/ui/src/features/variables-sets/components/organisms/VariableSets.tsx similarity index 77% rename from packages/ui/src/features/variables/components/organisms/VariableGroups.tsx rename to packages/ui/src/features/variables-sets/components/organisms/VariableSets.tsx index aa4ca9a0..ffe84d6d 100644 --- a/packages/ui/src/features/variables/components/organisms/VariableGroups.tsx +++ b/packages/ui/src/features/variables-sets/components/organisms/VariableSets.tsx @@ -9,40 +9,40 @@ import { TreeViewItem, TreeViewNodes } from '@beak/ui/features/tree-view/types'; import { ipcExplorerService } from '@beak/ui/lib/ipc'; import { checkShortcut } from '@beak/ui/lib/keyboard-shortcuts'; import { useAppSelector } from '@beak/ui/store/redux'; -import { actions } from '@beak/ui/store/variable-groups'; -import { removeVariableGroupFromDisk } from '@beak/ui/store/variable-groups/actions'; +import { actions } from '@beak/ui/store/variable-sets'; +import { removeVariableSetFromDisk } from '@beak/ui/store/variable-sets/actions'; import { renderAcceleratorDefinition } from '@beak/ui/utils/keyboard-rendering'; import type { MenuItemConstructorOptions } from 'electron'; import styled from 'styled-components'; -const VariableGroups: React.FC> = () => { +const VariableSets: React.FC> = () => { const dispatch = useDispatch(); const selectedTabId = useAppSelector(s => s.features.tabs.selectedTab); - const variableGroups = useAppSelector(s => s.global.variableGroups.variableGroups); - const variableGroupKeys = TypedObject.keys(variableGroups); + const variableSets = useAppSelector(s => s.global.variableSets.variableSets); + const variableSetKeys = TypedObject.keys(variableSets); const windowSession = useContext(WindowSessionContext); const darwin = windowSession.isDarwin(); - const tree = variableGroupKeys.reduce((acc, k) => ({ + const tree = variableSetKeys.reduce((acc, k) => ({ ...acc, [k]: { id: k, - type: 'variable-group', - filePath: `variable-groups/${k}.json`, + type: 'variable-set', + filePath: `variable-sets/${k}.json`, name: k, - parent: 'variable-groups', + parent: 'variable-sets', }, }), {} as TreeViewNodes); - const empty = variableGroupKeys.length === 0; + const empty = variableSetKeys.length === 0; function generateContextMenu(node: TreeViewItem): MenuItemConstructorOptions[] { return [{ id: ksuid.generate('ctxmenuitem').toString(), label: 'New variable group', click: () => { - dispatch(actions.createNewVariableGroup({ })); + dispatch(actions.createNewVariableSet({ })); }, }, { id: ksuid.generate('ctxmenuitem').toString(), @@ -93,7 +93,7 @@ const VariableGroups: React.FC> = () => { accelerator: renderAcceleratorDefinition('tree-view.node.delete'), enabled: node.id !== 'root', click: () => { - dispatch(actions.removeVariableGroupFromDisk({ id: node.id, withConfirmation: true })); + dispatch(actions.removeVariableSetFromDisk({ id: node.id, withConfirmation: true })); }, }]; } @@ -103,7 +103,7 @@ const VariableGroups: React.FC> = () => { return; dispatch(changeTab({ - type: 'variable_group_editor', + type: 'variable_set_editor', payload: node.id, temporary: true, })); @@ -118,12 +118,12 @@ const VariableGroups: React.FC> = () => { function handleNodeKeyDown(event: React.KeyboardEvent, node: TreeViewItem) { switch (true) { - case checkShortcut('variable-groups.variable-group.open', event) && node.type !== 'folder': - dispatch(changeTab({ type: 'variable_group_editor', payload: node.id, temporary: false })); + case checkShortcut('variable-sets.variable-set.open', event) && node.type !== 'folder': + dispatch(changeTab({ type: 'variable_set_editor', payload: node.id, temporary: false })); break; - case checkShortcut('variable-groups.variable-group.delete', event) && node.type !== 'folder': - dispatch(removeVariableGroupFromDisk({ id: node.id, withConfirmation: true })); + case checkShortcut('variable-sets.variable-set.delete', event) && node.type !== 'folder': + dispatch(removeVariableSetFromDisk({ id: node.id, withConfirmation: true })); break; default: return; @@ -141,9 +141,9 @@ const VariableGroups: React.FC> = () => { activeNodeId={selectedTabId} focusedNodeId={selectedTabId} allowRootContextMenu - rootParentName={'variable-groups'} + rootParentName={'variable-sets'} - renameSelector={(_node, state) => state.global.variableGroups.activeRename} + renameSelector={(_node, state) => state.global.variableSets.activeRename} onRenameStarted={node => dispatch(actions.renameStarted({ id: node.id }))} onRenameEnded={node => dispatch(actions.renameCancelled({ id: node.id }))} onRenameUpdated={(node, name) => dispatch(actions.renameUpdated({ id: node.id, name }))} @@ -164,4 +164,4 @@ const EmptyWarning = styled.div` font-size: 13px; `; -export default VariableGroups; +export default VariableSets; diff --git a/packages/ui/src/features/variables/hooks/use-variable-context.ts b/packages/ui/src/features/variables/hooks/use-variable-context.ts new file mode 100644 index 00000000..be618fea --- /dev/null +++ b/packages/ui/src/features/variables/hooks/use-variable-context.ts @@ -0,0 +1,11 @@ +import { useAppSelector } from '@beak/ui/store/redux'; +import type { Context } from '@getbeak/types/values'; + +export default function useVariableContext(requestId?: string): Context { + const variableSets = useAppSelector(s => s.global.variableSets.variableSets); + const projectTree = useAppSelector(s => s.global.project.tree); + const flightHistory = useAppSelector(s => s.global.flight.flightHistory); + const selectedSets = useAppSelector(s => s.global.preferences.editor.selectedVariableSets); + + return { variableSets, selectedSets, flightHistory, projectTree, currentRequestId: requestId }; +} diff --git a/packages/ui/src/features/variables/index.ts b/packages/ui/src/features/variables/index.ts new file mode 100644 index 00000000..9a7bdf2d --- /dev/null +++ b/packages/ui/src/features/variables/index.ts @@ -0,0 +1,129 @@ +import { TypedObject } from '@beak/common/helpers/typescript'; +import { VariableExtension } from '@beak/common/types/extensions'; +import { ipcExtensionsService } from '@beak/ui/lib/ipc'; +import { EditableVariable, Variable } from '@getbeak/types-variables'; + +import './ipc'; +import base64DecodeRtv from './values/base64-decode'; +import base64EncodeRtv from './values/base64-encode'; +import digestRtv from './values/digest'; +import nonceRtv from './values/nonce'; +import privateRtv from './values/private'; +import requestFolderRtv from './values/request-folder'; +import requestHeaderRtv from './values/request-header'; +import requestMethodRtv from './values/request-method'; +import requestNameRtv from './values/request-name'; +import responseBodyJsonRtv from './values/response-body-json'; +import responseBodyTextRtv from './values/response-body-text'; +import responseHeaderRtv from './values/response-header'; +import responseStatusCodeRtv from './values/response-status-code'; +import secureRtv from './values/secure'; +import { characterCarriageReturnRtv, characterNewlineRtv, characterTabRtv } from './values/special-character'; +import timestampRtv from './values/timestamp'; +import urlDecodeRtv from './values/url-decode'; +import urlEncodeRtv from './values/url-encode'; +import uuidRtv from './values/uuid'; +import variableSetItemRtv from './values/variable-set-item'; + +type BasicOrEditableVariable = Variable | EditableVariable; + +export class VariableManager { + private static externalVariables: Record = { }; + private static internalVariables: Record = { + [base64DecodeRtv.type]: base64DecodeRtv, + [base64EncodeRtv.type]: base64EncodeRtv, + [characterCarriageReturnRtv.type]: characterCarriageReturnRtv, + [characterNewlineRtv.type]: characterNewlineRtv, + [characterTabRtv.type]: characterTabRtv, + [digestRtv.type]: digestRtv, + [nonceRtv.type]: nonceRtv, + [privateRtv.type]: privateRtv, + [requestFolderRtv.type]: requestFolderRtv, + [requestHeaderRtv.type]: requestHeaderRtv, + [requestMethodRtv.type]: requestMethodRtv, + [requestNameRtv.type]: requestNameRtv, + [responseBodyJsonRtv.type]: responseBodyJsonRtv, + [responseBodyTextRtv.type]: responseBodyTextRtv, + [responseHeaderRtv.type]: responseHeaderRtv, + [responseStatusCodeRtv.type]: responseStatusCodeRtv, + [secureRtv.type]: secureRtv, + [timestampRtv.type]: timestampRtv, + [urlDecodeRtv.type]: urlDecodeRtv, + [urlEncodeRtv.type]: urlEncodeRtv, + [uuidRtv.type]: uuidRtv, + + // Special case! + [variableSetItemRtv.type]: variableSetItemRtv, + }; + + static registerExternalVariable(ext: VariableExtension) { + const rtv = ext.variable; + + this.externalVariables[rtv.type] = { + type: rtv.type, + name: rtv.name, + description: rtv.description, + sensitive: rtv.sensitive, + external: true, + attributes: rtv.attributes, + createDefaultPayload: async ctx => ipcExtensionsService.rtvCreateDefaultPayload({ + type: rtv.type, + context: ctx, + }), + getValue: async (ctx, payload, recursiveDepth) => ipcExtensionsService.rtvGetValue({ + type: rtv.type, + context: ctx, + payload, + recursiveDepth, + }), + }; + + if (!rtv.editable) + return; + + (this.externalVariables[rtv.type] as EditableVariable).editor = { + createUserInterface: ctx => ipcExtensionsService.rtvEditorCreateUserInterface({ + type: rtv.type, + context: ctx, + }), + load: (ctx, payload) => ipcExtensionsService.rtvEditorLoad({ + type: rtv.type, + context: ctx, + payload, + }), + save: (ctx, existingPayload, state) => ipcExtensionsService.rtvEditorSave({ + type: rtv.type, + context: ctx, + existingPayload, + state, + }), + }; + } + + static unregisterExternalVariable(type: string) { + delete this.externalVariables[type]; + } + + static getVariable(type: string) { + return this.internalVariables[type] ?? this.externalVariables[type]; + } + + static getVariables(currentRequestId?: string) { + const allVariables = { + ...this.externalVariables, + + // Do this second to override any external attempts to override + ...this.internalVariables, + }; + + return TypedObject.values(allVariables) + // Remove the variable set item as it's a special case tbh + .filter(v => v.type !== variableSetItemRtv.type) + .filter(v => { + if (!v.attributes.requiresRequestId) + return true; + + return currentRequestId; + }); + } +} diff --git a/packages/ui/src/features/variables/ipc.ts b/packages/ui/src/features/variables/ipc.ts new file mode 100644 index 00000000..cd038aee --- /dev/null +++ b/packages/ui/src/features/variables/ipc.ts @@ -0,0 +1,18 @@ +import { ExtensionsMessages, RtvParseValueSectionsResponse } from '@beak/common/ipc/extensions'; +import { RequestPayload } from '@beak/common/ipc/ipc'; +import { ipcExtensionsService } from '@beak/ui/lib/ipc'; + +import { parseValueSections } from './parser'; + +ipcExtensionsService.registerRtvParseValueSections(async (event, payload) => { + const parsed = await parseValueSections(payload.context, payload.parts, payload.recursiveDepth); + const message: RequestPayload = { + code: ExtensionsMessages.RtvParseValueSectionsResponse, + payload: { + parsed, + uniqueSessionId: payload.uniqueSessionId, + }, + }; + + event.sender.send(ipcExtensionsService.getChannel(), message); +}); diff --git a/packages/ui/src/features/variables/parser.ts b/packages/ui/src/features/variables/parser.ts new file mode 100644 index 00000000..5ee03014 --- /dev/null +++ b/packages/ui/src/features/variables/parser.ts @@ -0,0 +1,79 @@ +import { TypedObject } from '@beak/common/helpers/typescript'; +import { generateValueIdent } from '@beak/ui/lib/beak-variable-set/utils'; +import type { Context, ValueSections } from '@getbeak/types/values'; + +import { VariableManager } from '.'; + +export async function parseValueSections( + ctx: Context, + parts: ValueSections, + depth = 0, + sensitiveMode = false, +): Promise { + const out = await Promise.all(parts.map(async p => { + if (typeof p === 'string') + return p; + + if (typeof p !== 'object') + return ''; + + const rtv = VariableManager.getVariable(p.type); + + if (!rtv) + return ''; + + // Oversimplified check for recursion. I'll build a proper system for this later + if (depth >= 5) + return '[Recursion detected]'; + + if (sensitiveMode && rtv.sensitive) + return '[Sensitive mode enabled]'; + + try { + // Easier than using an abort controller + let complete = false; + + const value = Promise.race([ + rtv.getValue(ctx, p.payload, depth + 1), + new Promise(resolve => { + window.setTimeout(() => { + if (!complete) + + console.error(`Fetching value for ${rtv.type} exceeded 600ms`); + + resolve(''); + }, 600); + }), + ]); + + complete = true; + + return value; + } catch { + // TODO(afr): Move this to some sort of alert + + console.error(`Failed to get value from ${rtv.type}`); + + return ''; + } + })); + + return out.join(''); +} + +export function getValueSections(ctx: Context, itemId: string) { + return getValueObject(ctx, itemId); +} + +export function getValueObject(ctx: Context, itemId: string) { + for (const key of TypedObject.keys(ctx.variableSets)) { + const variableSet = ctx.variableSets[key]; + const selectedSet = ctx.selectedSets[key]; + const value = variableSet.values[generateValueIdent(selectedSet, itemId)]; + + if (value) + return value; + } + + return null; +} diff --git a/packages/ui/src/features/variables/preview.ts b/packages/ui/src/features/variables/preview.ts new file mode 100644 index 00000000..231e4cbb --- /dev/null +++ b/packages/ui/src/features/variables/preview.ts @@ -0,0 +1,19 @@ +import type { Context } from '@getbeak/types/values'; +import { EditableVariable } from '@getbeak/types-variables'; + +export async function previewValue>( + ctx: Context, + rtv: EditableVariable, + item: any, + state: T, +) { + if (!rtv.editor) + return 'Editor not available'; + + if (!rtv.editor.save) + return await rtv.getValue(ctx, state, 0); + + const payload = await rtv.editor.save(ctx, item, state); + + return await rtv.getValue(ctx, payload, 0); +} diff --git a/packages/ui/src/features/variables/renderer.tsx b/packages/ui/src/features/variables/renderer.tsx new file mode 100644 index 00000000..6284dc55 --- /dev/null +++ b/packages/ui/src/features/variables/renderer.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import type { VariableSets } from '@getbeak/types/variable-sets'; +import * as uuid from 'uuid'; + +import { VariableManager } from '.'; +import { ValueSections } from './values'; +import { getVariableSetItemName } from './values/variable-set-item'; + +export default function renderValueSections(parts: ValueSections, variableSets: VariableSets) { + let safeParts = parts; + + if (!Array.isArray(parts)) + safeParts = []; + + return renderToStaticMarkup( + + {safeParts.map((p, idx) => { + if (typeof p === 'string') + return {p}; + + if (typeof p !== 'object') { + console.error(`Unknown value part ${p}:(${typeof p})`); + + return null; + } + + const rtv = VariableManager.getVariable(p.type); + + if (!rtv) { + return ( +
+   +
+ {'[Extension missing]'} +
+   +
+ ); + } + + const editable = 'editor' in rtv; + const name = (() => { + if (p.type === 'variable_set_item') { + const payload = p.payload as { itemId: string }; + + return getVariableSetItemName(payload, variableSets); + } + + if (rtv.getContextAwareName !== void 0) + return rtv.getContextAwareName(p.payload); + + return rtv.name; + })(); + + return ( +
+   + {name} +   +
+ ); + })} +
, + ); +} diff --git a/packages/ui/src/features/variables/utils/request.ts b/packages/ui/src/features/variables/utils/request.ts new file mode 100644 index 00000000..db32e10a --- /dev/null +++ b/packages/ui/src/features/variables/utils/request.ts @@ -0,0 +1,11 @@ +import type { RequestNode } from '@getbeak/types/nodes'; +import type { Context } from '@getbeak/types/values'; + +export function getRequestNode(id: string, ctx: Context) { + const node = ctx.projectTree[id]; + + if (!node || node.type !== 'request' || node.mode !== 'valid') + return null; + + return node as RequestNode; +} diff --git a/packages/ui/src/features/variables/utils/response.ts b/packages/ui/src/features/variables/utils/response.ts new file mode 100644 index 00000000..66e92fd5 --- /dev/null +++ b/packages/ui/src/features/variables/utils/response.ts @@ -0,0 +1,13 @@ +import { TypedObject } from '@beak/common/helpers/typescript'; +import type { Context } from '@getbeak/types/values'; + +export function getLatestFlight(id: string, ctx: Context) { + const requestFlightHistory = ctx.flightHistory[id]; + + if (!requestFlightHistory) + return null; + + const latestFlight = TypedObject.values(requestFlightHistory.history).reverse()[0]; + + return latestFlight; +} diff --git a/packages/ui/src/features/variables/values.d.ts b/packages/ui/src/features/variables/values.d.ts new file mode 100644 index 00000000..c2ddbf24 --- /dev/null +++ b/packages/ui/src/features/variables/values.d.ts @@ -0,0 +1,69 @@ +import type { ValuePart as GenericValuePart } from '@getbeak/types/values'; + +export type ValuePart = GenericValuePart; +export type ValueSections = ValuePart[]; + +export interface Base64DecodedRtv { + input: ValueSections; + characterSet: 'base64' | 'websafe_base64'; +} + +export interface Base64EncodedRtv { + input: ValueSections; + characterSet: 'base64' | 'websafe_base64'; + removePadding: boolean; +} + +export interface DigestRtv { + input: ValueSections; + algorithm: 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512' | 'MD5'; + hmac?: string; +} + +export interface PrivateRtv { + iv: string; + identifier: string; +} + +export interface RequestHeaderRtv { + headerName: ValueSections; +} + +export interface ResponseBodyJsonRtv { + requestId: string; + dotPath: ValueSections; +} + +export interface ResponseBodyTextRtv { + requestId: string; +} + +export interface ResponseHeaderRtv { + requestId: string; + headerName: ValueSections; +} + +export interface ResponseStatusCodeRtv { + requestId: string; +} + +export interface SecureRtv { + iv: string; + cipherText: string; + + /** @deprecated use cipherText now */ + datum?: string; +} + +export interface TimestampRtv { + delta?: number; + type: string; +} + +export interface UuidRtv { + version: 'v1' | 'v4'; +} + +export interface VariableSetItemRtv { + itemId: string; +} diff --git a/packages/ui/src/features/variables/values/base64-decode.ts b/packages/ui/src/features/variables/values/base64-decode.ts new file mode 100644 index 00000000..da241cb4 --- /dev/null +++ b/packages/ui/src/features/variables/values/base64-decode.ts @@ -0,0 +1,76 @@ +import { Base64DecodedRtv, ValueSections } from '@beak/ui/features/variables/values'; +import { EditableVariable } from '@getbeak/types-variables'; + +import { parseValueSections } from '../parser'; + +const invalidBase64Error = 'Failed to execute \'atob\' on \'Window\': The string to be decoded is not correctly encoded.'; + +interface EditorState { + input: ValueSections; + characterSet: Base64DecodedRtv['characterSet']; +} + +const definition: EditableVariable = { + type: 'base64_decoded', + name: 'Decode (Base64)', + description: 'Decodes a base64 encoded string', + sensitive: false, + external: false, + + createDefaultPayload: async () => ({ + input: [''], + characterSet: 'base64', + }), + + getValue: async (ctx, payload, recursiveDepth) => { + const isArray = Array.isArray(payload.input); + const input = isArray ? payload.input : [payload.input as unknown as string]; + + let encoded = await parseValueSections(ctx, input, recursiveDepth); + + if (payload.characterSet === 'websafe_base64') + encoded = encoded.replaceAll('_', '/').replaceAll('-', '+'); + + try { + return atob(encoded); + } catch (error) { + if (error instanceof Error && error.name === 'InvalidCharacterError' && error.message.includes(invalidBase64Error)) + return ''; + + throw error; + } + }, + + attributes: {}, + + editor: { + createUserInterface: async () => [{ + type: 'value_parts_input', + label: 'Enter the data to decode:', + stateBinding: 'input', + }, { + type: 'options_input', + label: 'Pick the digest algorithm:', + stateBinding: 'characterSet', + options: [{ + key: 'base64', + label: 'Base64', + }, { + key: 'websafe_base64', + label: 'Websafe Base64', + }], + }], + + load: async (_ctx, item) => ({ + characterSet: item.characterSet, + input: item.input, + }), + + save: async (_ctx, _item, state) => ({ + characterSet: state.characterSet, + input: state.input, + }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/base64-encode.ts b/packages/ui/src/features/variables/values/base64-encode.ts new file mode 100644 index 00000000..d25a5bf1 --- /dev/null +++ b/packages/ui/src/features/variables/values/base64-encode.ts @@ -0,0 +1,79 @@ +import { Base64EncodedRtv, ValueSections } from '@beak/ui/features/variables/values'; +import { EditableVariable } from '@getbeak/types-variables'; + +import { parseValueSections } from '../parser'; + +interface EditorState { + input: ValueSections; + characterSet: Base64EncodedRtv['characterSet']; + removePadding: boolean; +} + +const definition: EditableVariable = { + type: 'base64_encoded', + name: 'Encode (Base64)', + description: 'Generates a base64 encoded string', + sensitive: false, + external: false, + + createDefaultPayload: async () => ({ + input: [''], + characterSet: 'base64', + removePadding: false, + }), + + getValue: async (ctx, payload, recursiveDepth) => { + const isArray = Array.isArray(payload.input); + const input = isArray ? payload.input : [payload.input as unknown as string]; + + const parsed = await parseValueSections(ctx, input, recursiveDepth); + let encoded = btoa(parsed); + + if (payload.characterSet === 'websafe_base64') + encoded = encoded.replaceAll('/', '_').replaceAll('+', '-'); + + if (payload.removePadding) + encoded = encoded.replaceAll('=', ''); + + return encoded; + }, + + attributes: {}, + + editor: { + createUserInterface: async () => [{ + type: 'value_parts_input', + label: 'Enter the data to encode:', + stateBinding: 'input', + }, { + type: 'options_input', + label: 'Pick the digest algorithm:', + stateBinding: 'characterSet', + options: [{ + key: 'base64', + label: 'Base64', + }, { + key: 'websafe_base64', + label: 'Websafe Base64', + }], + }, { + type: 'checkbox_input', + label: 'Remove padding:', + stateBinding: 'removePadding', + }], + + load: async (_ctx, item) => ({ + characterSet: item.characterSet, + input: item.input, + removePadding: item.removePadding, + }), + + save: async (_ctx, _item, state) => ({ + characterSet: state.characterSet, + input: state.input, + removePadding: state.removePadding, + }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/digest.ts b/packages/ui/src/features/variables/values/digest.ts new file mode 100644 index 00000000..670dabbc --- /dev/null +++ b/packages/ui/src/features/variables/values/digest.ts @@ -0,0 +1,92 @@ +import { DigestRtv, ValueSections } from '@beak/ui/features/variables/values'; +import { arrayBufferToHexString } from '@beak/ui/utils/encoding'; +import { EditableVariable } from '@getbeak/types-variables'; +import { Md5 as MD5 } from 'ts-md5'; + +import { parseValueSections } from '../parser'; + +interface EditorState { + input: ValueSections; + algorithm: DigestRtv['algorithm']; +} + +const definition: EditableVariable = { + type: 'digest', + name: 'Hash', + description: 'Generates a digest of a given input, with a specified hash function Supports SHA-*, MD5.', + keywords: ['sha', 'md5', 'sha1', 'sha2', 'sha256', 'sha-384', 'sha512'], + sensitive: false, + external: false, + + getContextAwareName: payload => `Hash (${payload.algorithm})`, + + createDefaultPayload: async () => ({ + algorithm: 'SHA-256', + input: [''], + hmac: void 0, + }), + + getValue: async (ctx, payload, recursiveDepth) => { + const { algorithm, input, hmac } = payload; + const isArray = Array.isArray(input); + const parsed = await parseValueSections(ctx, isArray ? input : [input as unknown as string], recursiveDepth); + + if (algorithm === 'MD5') + return MD5.hashStr(parsed); + + const buf = new ArrayBuffer(parsed.length * 2); + const bufView = new Uint16Array(buf); + + for (let i = 0, strLen = parsed.length; i < strLen; i++) + bufView[i] = parsed.charCodeAt(i); + + if (hmac) { + // return crypto.subtle.sign(algorithm, key, buf); + return ''; + } + + const digest = await crypto.subtle.digest(algorithm, buf); + + return arrayBufferToHexString(digest); + }, + + attributes: {}, + + editor: { + createUserInterface: async () => [{ + type: 'value_parts_input', + label: 'Enter the data for the digest:', + stateBinding: 'input', + }, { + type: 'options_input', + label: 'Pick the digest algorithm:', + stateBinding: 'algorithm', + options: [{ + key: 'SHA-1', + label: 'SHA-1 (Considered unsafe for cryptographic use)', + }, { + key: 'SHA-256', + label: 'SHA-256', + }, { + key: 'SHA-384', + label: 'SHA-384', + }, { + key: 'SHA-512', + label: 'SHA-512', + }, { + key: 'MD5', + label: 'MD5 (Considered unsafe for cryptographic use)', + }], + }], + + load: async (_ctx, item) => ({ algorithm: item.algorithm, input: item.input }), + + save: async (_ctx, _item, state) => ({ + input: state.input, + algorithm: state.algorithm, + hmac: void 0, + }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/nonce.ts b/packages/ui/src/features/variables/values/nonce.ts new file mode 100644 index 00000000..417c040a --- /dev/null +++ b/packages/ui/src/features/variables/values/nonce.ts @@ -0,0 +1,24 @@ +import { toWebSafeBase64 } from '@beak/ui/lib/base64'; +import type { Variable } from '@getbeak/types-variables'; + +const definition: Variable = { + type: 'nonce', + name: 'Nonce', + description: 'Generates a cryptographically random string', + sensitive: false, + external: false, + + createDefaultPayload: async () => void 0, + + getValue: async () => { + const array = new Uint8Array(10); + + crypto.getRandomValues(array); + + return toWebSafeBase64(array); + }, + + attributes: {}, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/private.ts b/packages/ui/src/features/variables/values/private.ts new file mode 100644 index 00000000..fb920d7b --- /dev/null +++ b/packages/ui/src/features/variables/values/private.ts @@ -0,0 +1,97 @@ +import ksuid from '@beak/ksuid'; +import { PrivateRtv, ValueSections } from '@beak/ui/features/variables/values'; +import { ipcEncryptionService, ipcFsService } from '@beak/ui/lib/ipc'; +import { EditableVariable } from '@getbeak/types-variables'; +import path from 'path-browserify'; + +import { parseValueSections } from '../parser'; + +interface EditorState { + value: ValueSections; +} + +const definition: EditableVariable = { + type: 'private', + name: 'Private', + description: 'A value only stored locally, and never included in the project (it is also encrypted at rest). Useful for PII fields.', + sensitive: true, + external: false, + + createDefaultPayload: async () => { + const iv = await ipcEncryptionService.generateIv(); + const identifier = ksuid.generate('prvval').toString(); + + return { + iv, + identifier, + }; + }, + + getValue: async (ctx, item, recursiveDepth) => { + const encryptionSetup = await ipcEncryptionService.checkStatus(); + + if (!encryptionSetup) return '[Encryption key missing]'; + + // Get from private store + const cipherTextPath = createPath(item.identifier); + const exists = await ipcFsService.pathExists(cipherTextPath); + const cipherText = exists ? await ipcFsService.readText(cipherTextPath) : null; + + if (!cipherText) + return ''; + + const decrypted = await ipcEncryptionService.decryptObject({ + iv: item.iv, + payload: cipherText, + }); + + return await parseValueSections(ctx, decrypted, recursiveDepth); + }, + + attributes: {}, + + editor: { + createUserInterface: async () => [{ + type: 'value_parts_input', + label: 'Enter the value you want to be private:', + stateBinding: 'value', + }], + + load: async (_ctx, item) => { + // Get from private store + const cipherTextPath = createPath(item.identifier); + const exists = await ipcFsService.pathExists(cipherTextPath); + const cipherText = exists ? await ipcFsService.readText(cipherTextPath) : null; + + if (!cipherText) + return { value: [] }; + + const decrypted = await ipcEncryptionService.decryptObject({ + iv: item.iv, + payload: cipherText, + }); + + return { value: decrypted }; + }, + + save: async (_ctx, item, state) => { + // We want to generate a new IV every time + const iv = await ipcEncryptionService.generateIv(); + const cipherText = await ipcEncryptionService.encryptObject({ + iv, + payload: state.value, + }); + + // Write to private store + await ipcFsService.writeText(createPath(item.identifier), cipherText); + + return { iv, identifier: item.identifier }; + }, + }, +}; + +export default definition; + +function createPath(identifier: string) { + return path.join('.beak', 'variables', 'private', `${identifier}`); +} diff --git a/packages/ui/src/features/variables/values/request-folder.ts b/packages/ui/src/features/variables/values/request-folder.ts new file mode 100644 index 00000000..47671dfd --- /dev/null +++ b/packages/ui/src/features/variables/values/request-folder.ts @@ -0,0 +1,31 @@ +import { Variable } from '@getbeak/types-variables'; + +const definition: Variable = { + type: 'request_folder', + name: 'Request folder', + description: 'Returns the name of the folder the request is inside', + sensitive: false, + external: false, + + createDefaultPayload: async () => void 0, + + getValue: async ctx => { + const node = ctx.projectTree[ctx.currentRequestId!]; + + if (!node || node.type !== 'request' || node.mode !== 'valid') + return ''; + + const parentNode = ctx.projectTree[node.parent!]; + + if (!parentNode || parentNode.type !== 'folder') + return ''; + + return parentNode.name; + }, + + attributes: { + requiresRequestId: true, + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/request-header.ts b/packages/ui/src/features/variables/values/request-header.ts new file mode 100644 index 00000000..d9d02d68 --- /dev/null +++ b/packages/ui/src/features/variables/values/request-header.ts @@ -0,0 +1,56 @@ +import { TypedObject } from '@beak/common/helpers/typescript'; +import { RequestHeaderRtv, ValueSections } from '@beak/ui/features/variables/values'; +import { EditableVariable } from '@getbeak/types-variables'; + +import { parseValueSections } from '../parser'; + +interface EditorState { + headerName: ValueSections; +} + +const definition: EditableVariable = { + type: 'request_header', + name: 'Request header', + description: 'Returns the value of a header from the request', + sensitive: false, + external: false, + + createDefaultPayload: async () => ({ + headerName: [''], + }), + + getValue: async (ctx, payload, recursiveDepth) => { + const node = ctx.projectTree[ctx.currentRequestId!]; + + if (!node || node.type !== 'request' || node.mode !== 'valid') + return ''; + + const parsedHeaderName = await parseValueSections(ctx, payload.headerName, recursiveDepth); + const headerKey = TypedObject.keys(node.info.headers) + .find(k => node.info.headers[k].name.toLocaleLowerCase() === parsedHeaderName.toLocaleLowerCase()); + + const header = node.info.headers[headerKey!]; + + if (!header || !header.value) + return ''; + + return await parseValueSections(ctx, header.value, recursiveDepth); + }, + + attributes: { + requiresRequestId: true, + }, + + editor: { + createUserInterface: async () => [{ + type: 'value_parts_input', + label: 'Header name:', + stateBinding: 'headerName', + }], + + load: async (_ctx, item) => ({ headerName: item.headerName }), + save: async (_ctx, _item, state) => ({ headerName: state.headerName }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/request-method.ts b/packages/ui/src/features/variables/values/request-method.ts new file mode 100644 index 00000000..e8c842b6 --- /dev/null +++ b/packages/ui/src/features/variables/values/request-method.ts @@ -0,0 +1,26 @@ +import { Variable } from '@getbeak/types-variables'; + +const definition: Variable = { + type: 'request_method', + name: 'Request method', + description: 'Returns the HTTP method of the this request', + sensitive: false, + external: false, + + createDefaultPayload: async () => void 0, + + getValue: async ctx => { + const node = ctx.projectTree[ctx.currentRequestId!]; + + if (!node || node.type !== 'request' || node.mode !== 'valid') + return ''; + + return node.info.verb; + }, + + attributes: { + requiresRequestId: true, + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/request-name.ts b/packages/ui/src/features/variables/values/request-name.ts new file mode 100644 index 00000000..b261c0c7 --- /dev/null +++ b/packages/ui/src/features/variables/values/request-name.ts @@ -0,0 +1,26 @@ +import { Variable } from '@getbeak/types-variables'; + +const definition: Variable = { + type: 'request_name', + name: 'Request name', + description: 'Returns the name of the this request', + sensitive: false, + external: false, + + createDefaultPayload: async () => void 0, + + getValue: async ctx => { + const node = ctx.projectTree[ctx.currentRequestId!]; + + if (!node || node.type !== 'request') + return ''; + + return node.name; + }, + + attributes: { + requiresRequestId: true, + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/response-body-json.ts b/packages/ui/src/features/variables/values/response-body-json.ts new file mode 100644 index 00000000..f5ac6918 --- /dev/null +++ b/packages/ui/src/features/variables/values/response-body-json.ts @@ -0,0 +1,83 @@ +import { ResponseBodyJsonRtv } from '@beak/ui/features/variables/values'; +import binaryStore from '@beak/ui/lib/binary-store'; +import { attemptTextToJson } from '@beak/ui/utils/json'; +import { EditableVariable } from '@getbeak/types-variables'; +import get from 'lodash.get'; + +import { parseValueSections } from '../parser'; +import { getRequestNode } from '../utils/request'; +import { getLatestFlight } from '../utils/response'; + +const allowedRawJson = ['string', 'bool', 'number']; + +const definition: EditableVariable = { + type: 'response_body_json', + name: 'Response body (JSON)', + getContextAwareName: payload => `Response body JSON (${payload.dotPath.join('.')})`, + + description: 'Returns the body text value of the most recent response for a request', + sensitive: false, + external: false, + + createDefaultPayload: async () => ({ + requestId: '', + dotPath: [''], + }), + + getValue: async (ctx, payload, recursiveDepth) => { + const requestNode = getRequestNode(payload.requestId, ctx); + + if (!requestNode) + return ''; + + const latestFlight = getLatestFlight(requestNode.id, ctx); + + if (!latestFlight?.response) + return ''; + + if (!latestFlight.response.hasBody) + return ''; + + const dotPath = await parseValueSections(ctx, payload.dotPath, recursiveDepth); + const binary = binaryStore.get(latestFlight.binaryStoreKey); + const json = new TextDecoder().decode(binary); + const parsed = attemptTextToJson(json); + const resolved = dotPath === '' ? parsed : get(parsed, dotPath, ''); + + if (!resolved) + return ''; + + if (allowedRawJson.includes(typeof resolved)) + return resolved; + + return JSON.stringify(resolved); + }, + + attributes: { + requiresRequestId: true, + }, + + editor: { + createUserInterface: async () => [{ + type: 'request_select_input', + label: 'Select the request:', + stateBinding: 'requestId', + }, { + type: 'value_parts_input', + label: 'JSON dot path:', + stateBinding: 'dotPath', + }], + + load: async (_ctx, item) => ({ + requestId: item.requestId, + dotPath: item.dotPath, + }), + + save: async (_ctx, _item, state) => ({ + requestId: state.requestId, + dotPath: state.dotPath, + }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/response-body-text.ts b/packages/ui/src/features/variables/values/response-body-text.ts new file mode 100644 index 00000000..af91350b --- /dev/null +++ b/packages/ui/src/features/variables/values/response-body-text.ts @@ -0,0 +1,54 @@ +import { ResponseBodyTextRtv } from '@beak/ui/features/variables/values'; +import binaryStore from '@beak/ui/lib/binary-store'; +import { EditableVariable } from '@getbeak/types-variables'; + +import { getRequestNode } from '../utils/request'; +import { getLatestFlight } from '../utils/response'; + +const definition: EditableVariable = { + type: 'response_body_text', + name: 'Response body Text', + description: 'Returns the body text value of the most recent response for a request', + sensitive: false, + external: false, + + createDefaultPayload: async () => ({ requestId: '' }), + + getValue: async (ctx, payload) => { + const requestNode = getRequestNode(payload.requestId, ctx); + + if (!requestNode) + return ''; + + const latestFlight = getLatestFlight(requestNode.id, ctx); + + if (!latestFlight?.response) + return ''; + + if (!latestFlight.response.hasBody) + return ''; + + const binary = binaryStore.get(latestFlight.binaryStoreKey); + const body = new TextDecoder().decode(binary); + + return body; + }, + + attributes: { + requiresRequestId: true, + }, + + editor: { + createUserInterface: async () => [{ + type: 'request_select_input', + label: 'Select the request:', + stateBinding: 'requestId', + }], + + load: async (_ctx, item) => ({ requestId: item.requestId }), + + save: async (_ctx, _item, state) => ({ requestId: state.requestId }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/response-header.ts b/packages/ui/src/features/variables/values/response-header.ts new file mode 100644 index 00000000..e20f1843 --- /dev/null +++ b/packages/ui/src/features/variables/values/response-header.ts @@ -0,0 +1,69 @@ +import { TypedObject } from '@beak/common/helpers/typescript'; +import { ResponseHeaderRtv } from '@beak/ui/features/variables/values'; +import { EditableVariable } from '@getbeak/types-variables'; + +import { parseValueSections } from '../parser'; +import { getRequestNode } from '../utils/request'; +import { getLatestFlight } from '../utils/response'; + +const definition: EditableVariable = { + type: 'response_header', + name: 'Response header', + description: 'Returns the header value of the most recent response for a request', + sensitive: false, + external: false, + + createDefaultPayload: async () => ({ + requestId: '', + headerName: [''], + }), + + getValue: async (ctx, payload, recursiveDepth) => { + const requestNode = getRequestNode(payload.requestId, ctx); + + if (!requestNode) + return ''; + + const latestFlight = getLatestFlight(requestNode.id, ctx); + + if (!latestFlight?.response) + return ''; + + const headers = latestFlight.response.headers; + const parsedHeaderName = await parseValueSections(ctx, payload.headerName, recursiveDepth); + const headerKey = TypedObject.keys(headers) + .find(k => k.toLocaleLowerCase() === parsedHeaderName.toLocaleLowerCase()); + + const header = headers[headerKey!]; + + return header ?? ''; + }, + + attributes: { + requiresRequestId: true, + }, + + editor: { + createUserInterface: async () => [{ + type: 'request_select_input', + label: 'Select the request:', + stateBinding: 'requestId', + }, { + type: 'value_parts_input', + label: 'Header name:', + stateBinding: 'headerName', + }], + + load: async (_ctx, item) => ({ + requestId: item.requestId, + headerName: item.headerName, + }), + + save: async (_ctx, _item, state) => ({ + requestId: state.requestId, + headerName: state.headerName, + }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/response-status-code.ts b/packages/ui/src/features/variables/values/response-status-code.ts new file mode 100644 index 00000000..ecd498ce --- /dev/null +++ b/packages/ui/src/features/variables/values/response-status-code.ts @@ -0,0 +1,43 @@ +import { ResponseStatusCodeRtv } from '@beak/ui/features/variables/values'; +import { EditableVariable } from '@getbeak/types-variables'; + +import { getRequestNode } from '../utils/request'; +import { getLatestFlight } from '../utils/response'; + +const definition: EditableVariable = { + type: 'response_status_code', + name: 'Response status code', + description: 'Returns HTTP status code of the most recent response for a request', + sensitive: false, + external: false, + + createDefaultPayload: async () => ({ requestId: '' }), + + getValue: async (ctx, payload) => { + const requestNode = getRequestNode(payload.requestId, ctx); + + if (!requestNode) + return ''; + + const latestFlight = getLatestFlight(requestNode.id, ctx); + + return latestFlight?.response?.status.toString() ?? ''; + }, + + attributes: { + requiresRequestId: true, + }, + + editor: { + createUserInterface: async () => [{ + type: 'request_select_input', + label: 'Select the request:', + stateBinding: 'requestId', + }], + + load: async (_ctx, item) => ({ requestId: item.requestId }), + save: async (_ctx, _item, state) => ({ requestId: state.requestId }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/secure.ts b/packages/ui/src/features/variables/values/secure.ts new file mode 100644 index 00000000..8d48646c --- /dev/null +++ b/packages/ui/src/features/variables/values/secure.ts @@ -0,0 +1,88 @@ +import { SecureRtv, ValueSections } from '@beak/ui/features/variables/values'; +import { ipcEncryptionService } from '@beak/ui/lib/ipc'; +import { EditableVariable } from '@getbeak/types-variables'; + +import { parseValueSections } from '../parser'; + +interface EditorState { + value: ValueSections; +} + +const definition: EditableVariable = { + type: 'secure', + name: 'Secure', + description: 'A value protected by Beak project encryption', + sensitive: true, + external: false, + + createDefaultPayload: async () => { + const iv = await ipcEncryptionService.generateIv(); + + return { + iv, + cipherText: '', + }; + }, + + getValue: async (ctx, item) => { + const encryptionSetup = await ipcEncryptionService.checkStatus(); + + if (!encryptionSetup) return '[Encryption key missing]'; + + // handle legacy + if (item.datum !== void 0) { + return await ipcEncryptionService.decryptString({ + iv: item.iv, + payload: item.datum, + }); + } + + const decrypted = await ipcEncryptionService.decryptObject({ + iv: item.iv, + payload: item.cipherText, + }); + + return await parseValueSections(ctx, decrypted); + }, + + attributes: {}, + + editor: { + createUserInterface: async () => [{ + type: 'value_parts_input', + label: 'Enter the value you want to be encrypted:', + stateBinding: 'value', + }], + + load: async (_ctx, item) => { + if (item.datum !== void 0) { + const decrypted = await ipcEncryptionService.decryptString({ + iv: item.iv, + payload: item.datum, + }); + + return { value: [decrypted] }; + } + + const decrypted = await ipcEncryptionService.decryptObject({ + iv: item.iv, + payload: item.cipherText, + }); + + return { value: decrypted }; + }, + + save: async (_ctx, _item, state) => { + // We want to generate a new IV every time + const iv = await ipcEncryptionService.generateIv(); + const cipherText = await ipcEncryptionService.encryptObject({ + iv, + payload: state.value, + }); + + return { iv, cipherText }; + }, + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/special-character.ts b/packages/ui/src/features/variables/values/special-character.ts new file mode 100644 index 00000000..cf17e79f --- /dev/null +++ b/packages/ui/src/features/variables/values/special-character.ts @@ -0,0 +1,41 @@ +import { Variable } from '@getbeak/types-variables'; + +const characters = { + character_carriage_return: { + name: '\\r', + description: 'Inserts a carriage return character', + character: '\r', + }, + character_newline: { + name: '\\n', + description: 'Inserts a newline character', + character: '\n', + }, + character_tab: { + name: '\\t', + description: 'Inserts a tab character', + character: '\t', + }, +}; + +export const characterCarriageReturnRtv = createCharacter('character_carriage_return'); +export const characterNewlineRtv = createCharacter('character_newline'); +export const characterTabRtv = createCharacter('character_tab'); + +function createCharacter(type: keyof typeof characters): Variable { + const character = characters[type]; + + return { + type, + name: character.name, + description: character.description, + sensitive: false, + external: false, + + createDefaultPayload: async () => void 0, + + getValue: async () => character.character, + + attributes: {}, + }; +} diff --git a/packages/ui/src/features/variables/values/timestamp.ts b/packages/ui/src/features/variables/values/timestamp.ts new file mode 100644 index 00000000..c9d8d9d0 --- /dev/null +++ b/packages/ui/src/features/variables/values/timestamp.ts @@ -0,0 +1,71 @@ +import { TimestampRtv } from '@beak/ui/features/variables/values'; +import { EditableVariable } from '@getbeak/types-variables'; +import { add } from 'date-fns'; + +interface EditorState { + delta: number; + type: string; +} + +const definition: EditableVariable = { + type: 'timestamp', + name: 'Datetime', + getContextAwareName: payload => `Datetime (${payload.type})`, + + description: 'Render a date-time in a specific format, with an optional delta', + sensitive: false, + external: false, + + createDefaultPayload: async () => ({ + delta: 0, + type: 'iso_8601', + }), + + getValue: async (_ctx, item) => { + const now = new Date(); + const value = item.delta ? add(now, { seconds: item.delta }) : now; + + switch (item.type) { + case 'iso_8601': + return value.toISOString(); + + case 'unix_s': + return Math.round(value.getTime() / 1000).toString(10); + + case 'unix_ms': + return value.getTime().toString(); + + default: + return 'unknown_type'; + } + }, + + attributes: {}, + + editor: { + createUserInterface: async () => [{ + type: 'options_input', + label: 'Pick a date format:', + stateBinding: 'type', + options: [{ + key: 'iso_8601', + label: 'ISO-8601', + }, { + key: 'unix_s', + label: 'Unix timestamp (seconds)', + }, { + key: 'unix_ms', + label: 'Unix timestamp (ms)', + }], + }, { + type: 'number_input', + label: 'Delta (in seconds):', + stateBinding: 'delta', + }], + + load: async (_ctx, item) => ({ type: item.type, delta: item.delta ?? 0 }), + save: async (_ctx, _item, state) => ({ type: state.type, delta: state.delta }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/url-decode.ts b/packages/ui/src/features/variables/values/url-decode.ts new file mode 100644 index 00000000..f29a5cdc --- /dev/null +++ b/packages/ui/src/features/variables/values/url-decode.ts @@ -0,0 +1,39 @@ +import { ValueSections } from '@beak/ui/features/variables/values'; +import { EditableVariable } from '@getbeak/types-variables'; + +import { parseValueSections } from '../parser'; + +interface EditorState { + input: ValueSections; +} + +const definition: EditableVariable = { + type: 'url_decode', + name: 'Decode (URL)', + description: 'Decodes a url encoded string', + sensitive: false, + external: false, + + createDefaultPayload: async () => ({ input: [''] }), + + getValue: async (ctx, payload, recursiveDepth) => { + const parsed = await parseValueSections(ctx, payload.input, recursiveDepth); + + return decodeURIComponent(parsed); + }, + + attributes: {}, + + editor: { + createUserInterface: async () => [{ + type: 'value_parts_input', + label: 'Enter the data to decode:', + stateBinding: 'input', + }], + + load: async (_ctx, item) => ({ input: item.input }), + save: async (_ctx, _item, state) => ({ input: state.input }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/url-encode.ts b/packages/ui/src/features/variables/values/url-encode.ts new file mode 100644 index 00000000..51645433 --- /dev/null +++ b/packages/ui/src/features/variables/values/url-encode.ts @@ -0,0 +1,41 @@ +import { ValueSections } from '@beak/ui/features/variables/values'; +import { EditableVariable } from '@getbeak/types-variables'; + +import { parseValueSections } from '../parser'; + +interface EditorState { + input: ValueSections; +} + +const definition: EditableVariable = { + type: 'url_encode', + name: 'Encode (URL)', + description: 'Generates a url encoded string', + sensitive: false, + external: false, + + createDefaultPayload: async () => ({ + input: [''], + }), + + getValue: async (ctx, payload, recursiveDepth) => { + const parsed = await parseValueSections(ctx, payload.input, recursiveDepth); + + return encodeURIComponent(parsed); + }, + + attributes: {}, + + editor: { + createUserInterface: async () => [{ + type: 'value_parts_input', + label: 'Enter the data to encode:', + stateBinding: 'input', + }], + + load: async (_ctx, item) => ({ input: item.input }), + save: async (_ctx, _item, state) => ({ input: state.input }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/uuid.ts b/packages/ui/src/features/variables/values/uuid.ts new file mode 100644 index 00000000..98a847d0 --- /dev/null +++ b/packages/ui/src/features/variables/values/uuid.ts @@ -0,0 +1,54 @@ +import { UuidRtv } from '@beak/ui/features/variables/values'; +import { EditableVariable } from '@getbeak/types-variables'; +import * as uuid from 'uuid'; + +interface EditorState { + version: UuidRtv['version']; +} + +const definition: EditableVariable = { + type: 'uuid', + name: 'UUID', + description: 'Generate a UUID', + sensitive: false, + external: false, + + createDefaultPayload: async () => ({ + version: 'v4', + }), + + getValue: async (_ctx, item) => { + switch (item.version) { + case 'v1': + return uuid.v1(); + + case 'v4': + return uuid.v4(); + + default: + return 'unknown_version'; + } + }, + + attributes: {}, + + editor: { + createUserInterface: async () => [{ + type: 'options_input', + label: 'Pick a UUID format:', + stateBinding: 'version', + options: [{ + key: 'v1', + label: 'UUID v1', + }, { + key: 'v4', + label: 'UUID v4', + }], + }], + + load: async (_ctx, item) => ({ version: item.version }), + save: async (_ctx, _item, state) => ({ version: state.version }), + }, +}; + +export default definition; diff --git a/packages/ui/src/features/variables/values/variable-set-item.ts b/packages/ui/src/features/variables/values/variable-set-item.ts new file mode 100644 index 00000000..d8cafaa5 --- /dev/null +++ b/packages/ui/src/features/variables/values/variable-set-item.ts @@ -0,0 +1,69 @@ +import { TypedObject } from '@beak/common/helpers/typescript'; +import { VariableSetItemRtv } from '@beak/ui/features/variables/values'; +import type { VariableSets } from '@getbeak/types/variable-sets'; +import { Variable } from '@getbeak/types-variables'; + +import { getValueSections, parseValueSections } from '../parser'; + +const type = 'variable_set_item'; + +const definition: Variable = { + type, + name: 'Variable set item', + description: 'A variable from a variable set, you can edit it\'s value from the Variable Group editor', + sensitive: false, + external: false, + + createDefaultPayload: () => { + throw new Error('Not supported, this should not happen.'); + }, + + getValue: async (ctx, item, recursiveDepth) => { + const parts = getValueSections(ctx, item.itemId) || []; + + return await parseValueSections(ctx, parts, recursiveDepth); + }, + + attributes: {}, +}; + +export function createFauxValue( + item: VariableSetItemRtv, + variableSets: VariableSets, +): Variable { + return { + type, + name: getVariableSetItemName(item, variableSets), + description: 'A variable from a variable set, you can edit it\'s value from the Variable Group editor', + sensitive: false, + external: false, + + getContextAwareName: void 0, + createDefaultPayload: async () => item, + + getValue: () => { + throw new Error('Not supported, this should not happen.'); + }, + + attributes: {}, + }; +} + +export function getVariableSetItemName(item: VariableSetItemRtv, variableSets: VariableSets) { + if (!variableSets) + return 'Unknown'; + + const keys = TypedObject.keys(variableSets); + + for (const key of keys) { + const vg = variableSets[key]; + const itemValue = vg.items[item.itemId]; + + if (itemValue) + return `${key} (${itemValue})`; + } + + return 'Unknown'; +} + +export default definition; diff --git a/packages/ui/src/lib/beak-hub/schemas/editor-preferences.json b/packages/ui/src/lib/beak-hub/schemas/editor-preferences.json index 87ffcd9d..2b5c271e 100644 --- a/packages/ui/src/lib/beak-hub/schemas/editor-preferences.json +++ b/packages/ui/src/lib/beak-hub/schemas/editor-preferences.json @@ -3,7 +3,7 @@ "additionalProperties": false, "properties": { - "selectedVariableGroups": { + "selectedVariableSets": { "type": "object", "patternProperties": { diff --git a/packages/ui/src/lib/beak-hub/schemas/tab-preferences.json b/packages/ui/src/lib/beak-hub/schemas/tab-preferences.json index 39b16ac7..86f06b81 100644 --- a/packages/ui/src/lib/beak-hub/schemas/tab-preferences.json +++ b/packages/ui/src/lib/beak-hub/schemas/tab-preferences.json @@ -27,7 +27,7 @@ } }, - "variableGroupEditorTab": { + "variableSetEditorTab": { "type": "object", "additionalProperties": false, @@ -38,7 +38,7 @@ ], "properties": { - "type": { "const": "variable_group_editor" }, + "type": { "const": "variable_set_editor" }, "payload": { "type": "string", "minLength": 1 @@ -77,7 +77,7 @@ "items": { "oneOf": [ { "$ref": "#/definitions/requestTab" }, - { "$ref": "#/definitions/variableGroupEditorTab" }, + { "$ref": "#/definitions/variableSetEditorTab" }, { "$ref": "#/definitions/newProjectIntroTab" } ] } diff --git a/packages/ui/src/lib/beak-project/project.ts b/packages/ui/src/lib/beak-project/project.ts index 793d8f9a..cce42d22 100644 --- a/packages/ui/src/lib/beak-project/project.ts +++ b/packages/ui/src/lib/beak-project/project.ts @@ -5,7 +5,7 @@ import { readJsonAndValidate } from '../fs'; import { ipcFsService } from '../ipc'; import { projectSchema } from './schemas'; -const latestSupported = '0.3.0'; +const latestSupported = '0.4.0'; export async function readProjectFile() { const { file } = await readJsonAndValidate('project.json', projectSchema); diff --git a/packages/ui/src/lib/beak-project/schemas/request.json b/packages/ui/src/lib/beak-project/schemas/request.json index b249daa3..2bbbb941 100644 --- a/packages/ui/src/lib/beak-project/schemas/request.json +++ b/packages/ui/src/lib/beak-project/schemas/request.json @@ -3,7 +3,7 @@ "additionalProperties": false, "definitions": { - "valueParts": { + "ValueSections": { "type": "array", "items": { "oneOf": [ @@ -96,7 +96,7 @@ }, "then": { "properties": { - "value": { "$ref": "#/definitions/valueParts" } + "value": { "$ref": "#/definitions/ValueSections" } } } }, @@ -109,7 +109,7 @@ }, "then": { "properties": { - "value": { "$ref": "#/definitions/valueParts" } + "value": { "$ref": "#/definitions/ValueSections" } } } }, @@ -153,7 +153,7 @@ "properties": { "name": { "type": "string" }, - "value": { "$ref": "#/definitions/valueParts" }, + "value": { "$ref": "#/definitions/ValueSections" }, "enabled": { "type": "boolean" } } }, @@ -308,7 +308,7 @@ "type": "string" }, - "url": { "$ref": "#/definitions/valueParts" }, + "url": { "$ref": "#/definitions/ValueSections" }, "query": { "type": "object", diff --git a/packages/ui/src/lib/beak-project/variable-groups.ts b/packages/ui/src/lib/beak-project/variable-sets.ts similarity index 63% rename from packages/ui/src/lib/beak-project/variable-groups.ts rename to packages/ui/src/lib/beak-project/variable-sets.ts index 98a0315b..0d7403cb 100644 --- a/packages/ui/src/lib/beak-project/variable-groups.ts +++ b/packages/ui/src/lib/beak-project/variable-sets.ts @@ -1,11 +1,11 @@ import ksuid from '@beak/ksuid'; -import type { VariableGroup } from '@getbeak/types/variable-groups'; +import type { VariableSet } from '@getbeak/types/variable-sets'; import path from 'path-browserify'; import { ipcFsService } from '../ipc'; import { generateSafeNewPath } from './utils'; -const variableGroupNameWordlist = [ +const variableSetNameWordlist = [ 'Anhinga', 'Albatross', 'Kingfisher', @@ -61,12 +61,12 @@ const variableGroupNameWordlist = [ 'Wren', ]; -export async function createVariableGroup(directory: string, name?: string) { - const id = name ?? generateVariableGroupName(); +export async function createVariableSet(directory: string, name?: string) { + const id = name ?? generateVariableSetName(); const { fullPath } = await generateSafeNewPath(id, directory, '.json'); - const variableGroup: VariableGroup = { - groups: { - [ksuid.generate('group').toString()]: 'Group', + const variableSet: VariableSet = { + sets: { + [ksuid.generate('set').toString()]: 'Set', }, items: { [ksuid.generate('item').toString()]: 'Item', @@ -74,22 +74,22 @@ export async function createVariableGroup(directory: string, name?: string) { values: {}, }; - await ipcFsService.writeJson(fullPath, variableGroup, { spaces: '\t' }); + await ipcFsService.writeJson(fullPath, variableSet, { spaces: '\t' }); return id; } -export async function renameVariableGroup(oldName: string, newName: string) { - const oldFilePath = path.join('variable-groups', `${oldName}.json`); - const newFilePath = path.join('variable-groups', `${newName}.json`); +export async function renameVariableSet(oldName: string, newName: string) { + const oldFilePath = path.join('variable-sets', `${oldName}.json`); + const newFilePath = path.join('variable-sets', `${newName}.json`); if (await ipcFsService.pathExists(newFilePath)) - throw new Error('Variable group already exists'); + throw new Error('Variable set already exists'); await ipcFsService.move(oldFilePath, newFilePath); } -function generateVariableGroupName() { +function generateVariableSetName() { const firstWord = getWordIndex(); const secondWord = getWordIndex(); @@ -101,8 +101,8 @@ function generateVariableGroupName() { function getWordIndex() { const min = 0; - const max = variableGroupNameWordlist.length; + const max = variableSetNameWordlist.length; const index = Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive - return variableGroupNameWordlist[index]; + return variableSetNameWordlist[index]; } diff --git a/packages/ui/src/lib/beak-variable-group/index.ts b/packages/ui/src/lib/beak-variable-group/index.ts deleted file mode 100644 index 52e00ab3..00000000 --- a/packages/ui/src/lib/beak-variable-group/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { VariableGroup } from '@getbeak/types/variable-groups'; -import path from 'path-browserify'; - -import { readJsonAndValidate } from '../fs'; -import { ipcFsService } from '../ipc'; -import { variableGroupSchema } from './schema'; - -export async function readVariableGroup(vgFilePath: string) { - return await readJsonAndValidate(vgFilePath, variableGroupSchema); -} - -export async function writeVariableGroup(name: string, variableGroup: VariableGroup) { - const filePath = path.join('variable-groups', `${name}.json`); - - await ipcFsService.writeJson(filePath, variableGroup, { spaces: '\t' }); -} - -export async function removeVariableGroup(name: string) { - const filePath = path.join('variable-groups', `${name}.json`); - - await ipcFsService.remove(filePath); -} diff --git a/packages/ui/src/lib/beak-variable-group/schema/index.ts b/packages/ui/src/lib/beak-variable-group/schema/index.ts deleted file mode 100644 index a1713cae..00000000 --- a/packages/ui/src/lib/beak-variable-group/schema/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import variableGroupSchema from './variable-group.json'; - -export { - variableGroupSchema, -}; diff --git a/packages/ui/src/lib/beak-variable-group/utils.ts b/packages/ui/src/lib/beak-variable-group/utils.ts deleted file mode 100644 index 81d1e379..00000000 --- a/packages/ui/src/lib/beak-variable-group/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function generateValueIdent(groupId: string, itemId: string) { - return `${groupId}&${itemId}`; -} diff --git a/packages/ui/src/lib/beak-variable-set/index.ts b/packages/ui/src/lib/beak-variable-set/index.ts new file mode 100644 index 00000000..8027d085 --- /dev/null +++ b/packages/ui/src/lib/beak-variable-set/index.ts @@ -0,0 +1,22 @@ +import type { VariableSet } from '@getbeak/types/variable-sets'; +import path from 'path-browserify'; + +import { readJsonAndValidate } from '../fs'; +import { ipcFsService } from '../ipc'; +import { variableSetSchema } from './schema'; + +export async function readVariableSet(vgFilePath: string) { + return await readJsonAndValidate(vgFilePath, variableSetSchema); +} + +export async function writeVariableSet(name: string, variableSet: VariableSet) { + const filePath = path.join('variable-sets', `${name}.json`); + + await ipcFsService.writeJson(filePath, variableSet, { spaces: '\t' }); +} + +export async function removeVariableSet(name: string) { + const filePath = path.join('variable-sets', `${name}.json`); + + await ipcFsService.remove(filePath); +} diff --git a/packages/ui/src/lib/beak-variable-set/schema/index.ts b/packages/ui/src/lib/beak-variable-set/schema/index.ts new file mode 100644 index 00000000..729408f1 --- /dev/null +++ b/packages/ui/src/lib/beak-variable-set/schema/index.ts @@ -0,0 +1,5 @@ +import variableSetSchema from './variable-set.json'; + +export { + variableSetSchema, +}; diff --git a/packages/ui/src/lib/beak-variable-group/schema/variable-group.json b/packages/ui/src/lib/beak-variable-set/schema/variable-set.json similarity index 87% rename from packages/ui/src/lib/beak-variable-group/schema/variable-group.json rename to packages/ui/src/lib/beak-variable-set/schema/variable-set.json index 1b8e4fbf..e31f412c 100644 --- a/packages/ui/src/lib/beak-variable-group/schema/variable-group.json +++ b/packages/ui/src/lib/beak-variable-set/schema/variable-set.json @@ -15,13 +15,13 @@ }, "required": [ - "groups", + "sets", "items", "values" ], "properties": { - "groups": { "$ref": "#/definitions/mapping" }, + "sets": { "$ref": "#/definitions/mapping" }, "items": { "$ref": "#/definitions/mapping" }, "values": { diff --git a/packages/ui/src/lib/beak-variable-set/utils.ts b/packages/ui/src/lib/beak-variable-set/utils.ts new file mode 100644 index 00000000..3f7e52ca --- /dev/null +++ b/packages/ui/src/lib/beak-variable-set/utils.ts @@ -0,0 +1,3 @@ +export function generateValueIdent(setId: string, itemId: string) { + return `${setId}&${itemId}`; +} diff --git a/packages/ui/src/lib/keyboard-shortcuts/index.ts b/packages/ui/src/lib/keyboard-shortcuts/index.ts index 93fa9208..9f2857b7 100644 --- a/packages/ui/src/lib/keyboard-shortcuts/index.ts +++ b/packages/ui/src/lib/keyboard-shortcuts/index.ts @@ -17,8 +17,8 @@ export type Shortcuts = | 'tree-view.node.rename' | 'tree-view.node.delete' - | 'variable-groups.variable-group.open' - | 'variable-groups.variable-group.delete' + | 'variable-sets.variable-set.open' + | 'variable-sets.variable-set.delete' | 'project-explorer.request.open' | 'project-explorer.request.duplicate' @@ -67,14 +67,14 @@ export const shortcutDefinitions: Record { try { const extension = await ipcExtensionsService.registerRtv({ extensionFilePath: dependencyPath }); - RealtimeValueManager.registerExternalRealtimeValue(extension); + VariableManager.registerExternalVariable(extension); return extension; } catch (error) { diff --git a/packages/ui/src/store/extensions/types.ts b/packages/ui/src/store/extensions/types.ts index 26c1ff18..6281185e 100644 --- a/packages/ui/src/store/extensions/types.ts +++ b/packages/ui/src/store/extensions/types.ts @@ -1,4 +1,4 @@ -import { RealtimeValueExtension } from '@beak/common/types/extensions'; +import { VariableExtension } from '@beak/common/types/extensions'; import Squawk from '@beak/common/utils/squawk'; export const ActionTypes = { @@ -21,7 +21,7 @@ export interface FailedExtension { error: Squawk; } -export type Extension = FailedExtension | RealtimeValueExtension; +export type Extension = FailedExtension | VariableExtension; export interface ExtensionsOpenedPayload { extensions: Extension[]; diff --git a/packages/ui/src/store/flight/sagas/request-flight.ts b/packages/ui/src/store/flight/sagas/request-flight.ts index fe56131b..3e54b558 100644 --- a/packages/ui/src/store/flight/sagas/request-flight.ts +++ b/packages/ui/src/store/flight/sagas/request-flight.ts @@ -4,7 +4,7 @@ import ksuid from '@beak/ksuid'; import { instance as windowSessionInstance } from '@beak/ui/contexts/window-session-context'; import { convertKeyValueToString } from '@beak/ui/features/basic-table-editor/parsers'; import { convertToRealJson } from '@beak/ui/features/json-editor/parsers'; -import { parseValueParts } from '@beak/ui/features/realtime-values/parser'; +import { parseValueSections } from '@beak/ui/features/variables/parser'; import { ipcDialogService, ipcFsService } from '@beak/ui/lib/ipc'; import { requestAllowsBody } from '@beak/ui/utils/http'; import { convertRequestToUrl } from '@beak/ui/utils/uri'; @@ -17,7 +17,7 @@ import type { ToggleKeyValue, } from '@getbeak/types/request'; import type { Context } from '@getbeak/types/values'; -import type { VariableGroups } from '@getbeak/types/variable-groups'; +import type { VariableSets } from '@getbeak/types/variable-sets'; import { FetcherParams } from '@graphiql/toolkit'; import { call, put, select } from '@redux-saga/core/effects'; @@ -45,16 +45,16 @@ export default function* requestFlightWorker() { const flightHistory: Record = yield select( (s: ApplicationState) => s.global.flight.flightHistory, ); - const variableGroups: VariableGroups = yield select( - (s: ApplicationState) => s.global.variableGroups.variableGroups, + const variableSets: VariableSets = yield select( + (s: ApplicationState) => s.global.variableSets.variableSets, ); - const selectedGroups: Record = yield select( - (s: ApplicationState) => s.global.preferences.editor.selectedVariableGroups, + const selectedSets: Record = yield select( + (s: ApplicationState) => s.global.preferences.editor.selectedVariableSets, ); const context: Context = { - selectedGroups, - variableGroups, + selectedSets, + variableSets, flightHistory, projectTree, currentRequestId: requestId, @@ -91,7 +91,7 @@ export default function* requestFlightWorker() { async function prepareRequest(overview: RequestOverview, context: Context): Promise { const url = await convertRequestToUrl(context, overview); - const headers = await flattenToggleValueParts(context, overview.headers); + const headers = await flattenToggleValueSections(context, overview.headers); if (!hasHeader('user-agent', headers)) { headers[ksuid.generate('header').toString()] = { @@ -124,14 +124,14 @@ async function prepareRequest(overview: RequestOverview, context: Context): Prom return requestOverview; } -async function flattenToggleValueParts(context: Context, toggleValueParts: Record) { +async function flattenToggleValueSections(context: Context, toggleValueSections: Record) { const out: Record = {}; - await Promise.all(TypedObject.keys(toggleValueParts).map(async k => { + await Promise.all(TypedObject.keys(toggleValueSections).map(async k => { out[k] = { - enabled: toggleValueParts[k].enabled, - name: toggleValueParts[k].name, - value: [await parseValueParts(context, toggleValueParts[k].value)], + enabled: toggleValueSections[k].enabled, + name: toggleValueSections[k].name, + value: [await parseValueSections(context, toggleValueSections[k].value)], }; })); @@ -143,7 +143,7 @@ async function flattenQuery( overview: RequestOverview, ): Promise> { const { body, query, verb } = overview; - const resolvedQuery = await flattenToggleValueParts(context, query); + const resolvedQuery = await flattenToggleValueSections(context, query); // When using GraphQL body-less verbs require the body to be sent via the url if (!requestAllowsBody(verb) && body.type === 'graphql') { diff --git a/packages/ui/src/store/git/sagas/index.ts b/packages/ui/src/store/git/sagas/index.ts index 4c651d04..99c886c8 100644 --- a/packages/ui/src/store/git/sagas/index.ts +++ b/packages/ui/src/store/git/sagas/index.ts @@ -3,7 +3,7 @@ import { all, fork, takeEvery } from '@redux-saga/core/effects'; import { ActionTypes } from '../types'; import startGit from './start-git'; -export default function* variableGroupsSaga() { +export default function* variableSetsSaga() { yield all([ fork(function* startGitWatcher() { yield takeEvery(ActionTypes.START_GIT, startGit); diff --git a/packages/ui/src/store/index.ts b/packages/ui/src/store/index.ts index 47ed5a07..2354f591 100644 --- a/packages/ui/src/store/index.ts +++ b/packages/ui/src/store/index.ts @@ -22,8 +22,8 @@ import * as preferencesStore from './preferences'; import { State as PreferencesState } from './preferences/types'; import * as projectStore from './project'; import { State as ProjectState } from './project/types'; -import * as variableGroupsStore from './variable-groups'; -import { State as VariableGroupState } from './variable-groups/types'; +import * as variableSetsStore from './variable-sets'; +import { State as VariableSetState } from './variable-sets/types'; export interface ApplicationState { features: { @@ -38,7 +38,7 @@ export interface ApplicationState { git: GitState; preferences: PreferencesState; project: ProjectState; - variableGroups: VariableGroupState; + variableSets: VariableSetState; }; } @@ -56,7 +56,7 @@ function createRootReducer() { git: gitStore.reducers, preferences: preferencesStore.reducers, project: projectStore.reducers, - variableGroups: variableGroupsStore.reducers, + variableSets: variableSetsStore.reducers, }), }); } @@ -70,7 +70,7 @@ function* rootSaga() { fork(gitStore.sagas), fork(preferencesStore.sagas), fork(projectStore.sagas), - fork(variableGroupsStore.sagas), + fork(variableSetsStore.sagas), ]); } @@ -88,7 +88,7 @@ function createInitialState(): ApplicationState { git: gitStore.types.initialState, preferences: preferencesStore.types.initialState, project: projectStore.types.initialState, - variableGroups: variableGroupsStore.types.initialState, + variableSets: variableSetsStore.types.initialState, }, }; } diff --git a/packages/ui/src/store/preferences/actions.ts b/packages/ui/src/store/preferences/actions.ts index ffdae729..cac58717 100644 --- a/packages/ui/src/store/preferences/actions.ts +++ b/packages/ui/src/store/preferences/actions.ts @@ -4,7 +4,7 @@ import { createAction } from '@reduxjs/toolkit'; import { ActionTypes as AT, - EditorPreferencesSetSelectedVariableGroupPayload, + EditorPreferencesSetSelectedVariableSetPayload, ProjectPaneCollapsePayload, RequestPreferencePayload, RequestPreferencesLoadedPayload, @@ -26,7 +26,7 @@ export const requestPreferenceSetResPrettyLanguage = createAction(AT.EDITOR_PREFERENCES_LOADED); -export const editorPreferencesSetSelectedVariableGroup = createAction(AT.EDITOR_PREFERENCES_SET_SELECTED_VARIABLE_GROUP); +export const editorPreferencesSetSelectedVariableSet = createAction(AT.EDITOR_PREFERENCES_SET_SELECTED_VARIABLE_GROUP); export const loadSidebarPreferences = createAction(AT.LOAD_SIDEBAR_PREFERENCES); export const sidebarPreferencesLoaded = createAction(AT.SIDEBAR_PREFERENCES_LOADED); @@ -48,7 +48,7 @@ export default { loadEditorPreferences, editorPreferencesLoaded, - editorPreferencesSetSelectedVariableGroup, + editorPreferencesSetSelectedVariableSet, loadSidebarPreferences, sidebarPreferencesLoaded, diff --git a/packages/ui/src/store/preferences/reducers.ts b/packages/ui/src/store/preferences/reducers.ts index 31efea34..a4dc0c37 100644 --- a/packages/ui/src/store/preferences/reducers.ts +++ b/packages/ui/src/store/preferences/reducers.ts @@ -32,10 +32,10 @@ const reducer = createReducer(initialState, builder => { .addCase(actions.editorPreferencesLoaded, (state, { payload }) => { state.editor = payload; }) - .addCase(actions.editorPreferencesSetSelectedVariableGroup, (state, { payload }) => { - const { variableGroup, groupId } = payload; + .addCase(actions.editorPreferencesSetSelectedVariableSet, (state, { payload }) => { + const { variableSet, setId: setId } = payload; - state.editor.selectedVariableGroups[variableGroup] = groupId; + state.editor.selectedVariableSets[variableSet] = setId; }) .addCase(actions.sidebarPreferencesLoaded, (state, { payload }) => { diff --git a/packages/ui/src/store/preferences/sagas/catch-load-preferences.ts b/packages/ui/src/store/preferences/sagas/catch-load-preferences.ts index 4d540029..cbfa8a69 100644 --- a/packages/ui/src/store/preferences/sagas/catch-load-preferences.ts +++ b/packages/ui/src/store/preferences/sagas/catch-load-preferences.ts @@ -94,7 +94,7 @@ async function loadRequestPreferences(id: string) { async function loadEditorPreferences() { const preferencesPath = path.join('.beak', 'preferences', 'editor.json'); const defaultPreferences: EditorPreferences = { - selectedVariableGroups: {}, + selectedVariableSets: {}, }; if (!await ipcFsService.pathExists(preferencesPath)) diff --git a/packages/ui/src/store/preferences/types.ts b/packages/ui/src/store/preferences/types.ts index 61ae4dae..6dda1dd2 100644 --- a/packages/ui/src/store/preferences/types.ts +++ b/packages/ui/src/store/preferences/types.ts @@ -40,7 +40,7 @@ export interface State { export const initialState: State = { requests: {}, editor: { - selectedVariableGroups: {}, + selectedVariableSets: {}, }, sidebar: { selected: 'project', @@ -68,9 +68,9 @@ export type RequestPreferencesSetResPrettyLanguagePayload = RequestPreferencePay language: string | null; }>; -export interface EditorPreferencesSetSelectedVariableGroupPayload { - variableGroup: string; - groupId: string; +export interface EditorPreferencesSetSelectedVariableSetPayload { + variableSet: string; + setId: string; } export interface SidebarCollapsePayload { diff --git a/packages/ui/src/store/project/sagas/start-project.ts b/packages/ui/src/store/project/sagas/start-project.ts index 85e96a14..2a6c131a 100644 --- a/packages/ui/src/store/project/sagas/start-project.ts +++ b/packages/ui/src/store/project/sagas/start-project.ts @@ -14,7 +14,7 @@ import { call, put, select, take } from '@redux-saga/core/effects'; import path from 'path-browserify'; import { ApplicationState } from '../..'; -import { startVariableGroups } from '../../variable-groups/actions'; +import { startVariableSets } from '../../variable-sets/actions'; import { LatestWrite } from '../types'; interface Event { @@ -32,7 +32,7 @@ export default function* workerStartProject() { channel = createFsEmitter('tree', { followSymlinks: false }); yield put(actions.insertProjectInfo({ id: project.id, name: project.name })); - yield put(startVariableGroups()); + yield put(startVariableSets()); yield initialImport('tree'); yield put(loadTabState()); } catch (error) { diff --git a/packages/ui/src/store/project/types.ts b/packages/ui/src/store/project/types.ts index 20799564..542579b8 100644 --- a/packages/ui/src/store/project/types.ts +++ b/packages/ui/src/store/project/types.ts @@ -1,7 +1,7 @@ import Squawk from '@beak/common/utils/squawk'; import { ExtractedVariables } from '@beak/ui/features/graphql-editor/types'; -import { ValueParts } from '@beak/ui/features/realtime-values/values'; import { ActiveRename } from '@beak/ui/features/tree-view/types'; +import { ValueSections } from '@beak/ui/features/variables/values'; import type { EntryMap, EntryType } from '@getbeak/types/body-editor-json'; import type { Tree } from '@getbeak/types/nodes'; import type { ToggleKeyValue } from '@getbeak/types/request'; @@ -115,19 +115,19 @@ export interface ProjectOpenedPayload { tree: Tree } export interface RequestIdPayload { requestId: string } export interface RequestUriUpdatedPayload extends RequestIdPayload { - url?: ValueParts; + url?: ValueSections; verb?: string; } export interface ToggleableItemAddedPayload extends RequestIdPayload { name?: string; - value?: ValueParts; + value?: ValueSections; } export interface ToggleableItemUpdatedPayload extends RequestIdPayload { identifier: string; name?: string; - value?: ValueParts; + value?: ValueSections; enabled?: boolean; } @@ -218,7 +218,7 @@ export interface RequestBodyJsonEditorNameChangePayload extends RequestIdPayload export interface RequestBodyJsonEditorValueChangePayload extends RequestIdPayload { id: string; - value: ValueParts | boolean | null; + value: ValueSections | boolean | null; } export interface RequestBodyJsonEditorTypeChangePayload extends RequestIdPayload { @@ -241,7 +241,7 @@ export interface RequestBodyUrlEncodedEditorNameChangePayload extends RequestIdP export interface RequestBodyUrlEncodedEditorValueChangePayload extends RequestIdPayload { id: string; - value: ValueParts; + value: ValueSections; } export interface RequestBodyUrlEncodedEditorEnabledChangePayload extends RequestIdPayload { diff --git a/packages/ui/src/store/variable-groups/actions.ts b/packages/ui/src/store/variable-groups/actions.ts deleted file mode 100644 index 2fcbf9f1..00000000 --- a/packages/ui/src/store/variable-groups/actions.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* eslint-disable max-len */ - -import { createAction } from '@reduxjs/toolkit'; - -import { - ActionTypes as AT, - CreateNewVariableGroupPayload, - InsertNewGroupPayload, - InsertNewItemPayload, - InsertNewVariableGroupPayload, - RemoveGroupPayload, - RemoveItemPayload, - RemoveVariableGroupFromDiskPayload, - UpdateGroupNamePayload, - UpdateItemNamePayload, - UpdateValuePayload, - VariableGroupRenameCancelled, - VariableGroupRenameResolved, - VariableGroupRenameStarted, - VariableGroupRenameSubmitted, - VariableGroupRenameUpdated, - VariableGroupsOpenedPayload, -} from './types'; - -export const startVariableGroups = createAction(AT.START_VARIABLE_GROUPS); -export const variableGroupsOpened = createAction(AT.VARIABLE_GROUPS_OPENED); - -export const createNewVariableGroup = createAction(AT.CREATE_NEW_VARIABLE_GROUP); -export const insertNewVariableGroup = createAction(AT.INSERT_NEW_VARIABLE_GROUP); -export const insertNewGroup = createAction(AT.INSERT_NEW_GROUP); -export const insertNewItem = createAction(AT.INSERT_NEW_ITEM); - -export const updateGroupName = createAction(AT.UPDATE_GROUP_NAME); -export const updateItemName = createAction(AT.UPDATE_ITEM_NAME); -export const updateValue = createAction(AT.UPDATE_VALUE); - -export const removeVariableGroupFromStore = createAction(AT.REMOVE_VARIABLE_GROUP_FROM_STORE); -export const removeVariableGroupFromDisk = createAction(AT.REMOVE_VARIABLE_GROUP_FROM_DISK); - -export const removeGroup = createAction(AT.REMOVE_GROUP); -export const removeItem = createAction(AT.REMOVE_ITEM); - -export const renameStarted = createAction(AT.RENAME_STARTED); -export const renameUpdated = createAction(AT.RENAME_UPDATED); -export const renameCancelled = createAction(AT.RENAME_CANCELLED); -export const renameSubmitted = createAction(AT.RENAME_SUBMITTED); -export const renameResolved = createAction(AT.RENAME_RESOLVED); - -export const setLatestWrite = createAction(AT.SET_LATEST_WRITE); -export const setWriteDebounce = createAction(AT.SET_WRITE_DEBOUNCE); - -export default { - startVariableGroups, - variableGroupsOpened, - - createNewVariableGroup, - insertNewVariableGroup, - insertNewGroup, - insertNewItem, - - updateGroupName, - updateItemName, - updateValue, - - removeVariableGroupFromStore, - removeVariableGroupFromDisk, - - removeGroup, - removeItem, - - renameStarted, - renameUpdated, - renameCancelled, - renameSubmitted, - renameResolved, - - setLatestWrite, - setWriteDebounce, -}; diff --git a/packages/ui/src/store/variable-groups/sagas/create-variable-group.ts b/packages/ui/src/store/variable-groups/sagas/create-variable-group.ts deleted file mode 100644 index 2a6cbd2e..00000000 --- a/packages/ui/src/store/variable-groups/sagas/create-variable-group.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { changeTab } from '@beak/ui/features/tabs/store/actions'; -import { createVariableGroup } from '@beak/ui/lib/beak-project/variable-groups'; -import { call, delay, put, race, take } from '@redux-saga/core/effects'; -import { PayloadAction } from '@reduxjs/toolkit'; - -import { renameStarted } from '../actions'; -import { ActionTypes, CreateNewVariableGroupPayload } from '../types'; - -export default function* workerCreateNewVariableGroup({ payload }: PayloadAction) { - const id: string = yield call(createVariableGroup, 'variable-groups', payload.name); - - yield put(changeTab({ type: 'variable_group_editor', payload: id, temporary: true })); - yield race([ - take(ActionTypes.INSERT_NEW_VARIABLE_GROUP), - delay(250), - ]); - yield put(renameStarted({ id })); -} diff --git a/packages/ui/src/store/variable-groups/types.ts b/packages/ui/src/store/variable-groups/types.ts deleted file mode 100644 index 08fb7db7..00000000 --- a/packages/ui/src/store/variable-groups/types.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { ValueParts } from '@beak/ui/features/realtime-values/values'; -import { ActiveRename } from '@beak/ui/features/tree-view/types'; -import type { VariableGroup, VariableGroups } from '@getbeak/types/variable-groups'; - -export const ActionTypes = { - START_VARIABLE_GROUPS: '@beak/global/variable-groups/START_VARIABLE_GROUPS', - VARIABLE_GROUPS_OPENED: '@beak/global/variable-groups/VARIABLE_GROUPS_OPENED', - - CREATE_NEW_VARIABLE_GROUP: '@beak/global/variable-groups/CREATE_NEW_VARIABLE_GROUP', - INSERT_NEW_VARIABLE_GROUP: '@beak/global/variable-groups/INSERT_NEW_VARIABLE_GROUP', - INSERT_NEW_GROUP: '@beak/global/variable-groups/INSERT_NEW_GROUP', - INSERT_NEW_ITEM: '@beak/global/variable-groups/INSERT_NEW_ITEM', - - UPDATE_GROUP_NAME: '@beak/global/variable-groups/UPDATE_GROUP_NAME', - UPDATE_ITEM_NAME: '@beak/global/variable-groups/UPDATE_ITEM_NAME', - UPDATE_VALUE: '@beak/global/variable-groups/UPDATE_VALUE', - - REMOVE_VARIABLE_GROUP_FROM_STORE: '@beak/global/variable-groups/REMOVE_VARIABLE_GROUP_FROM_STORE', - REMOVE_VARIABLE_GROUP_FROM_DISK: '@beak/global/variable-groups/REMOVE_VARIABLE_GROUP_FROM_DISK', - - REMOVE_GROUP: '@beak/global/variable-groups/REMOVE_GROUP', - REMOVE_ITEM: '@beak/global/variable-groups/REMOVE_ITEM', - - RENAME_STARTED: '@beak/global/variable-groups/RENAME_STARTED', - RENAME_UPDATED: '@beak/global/variable-groups/RENAME_UPDATED', - RENAME_CANCELLED: '@beak/global/variable-groups/RENAME_CANCELLED', - RENAME_SUBMITTED: '@beak/global/variable-groups/RENAME_SUBMITTED', - RENAME_RESOLVED: '@beak/global/variable-groups/RENAME_RESOLVED', - - SET_LATEST_WRITE: '@beak/global/variable-groups/SET_LATEST_WRITE', - SET_WRITE_DEBOUNCE: '@beak/global/variable-groups/SET_WRITE_DEBOUNCE', -}; - -export interface State { - loaded: boolean; - - variableGroups: VariableGroups; - - activeRename?: ActiveRename; - latestWrite?: number; - writeDebouncer: string; -} - -export const initialState: State = { - loaded: false, - - variableGroups: {}, - - latestWrite: 0, - writeDebouncer: '', -}; - -export interface VariableGroupId { id: string } - -export interface VariableGroupsOpenedPayload { variableGroups: VariableGroups } - -export interface CreateNewVariableGroupPayload { name?: string } -export interface InsertNewVariableGroupPayload { id: string; variableGroup: VariableGroup } -export interface InsertNewGroupPayload extends VariableGroupId { groupName: string } -export interface InsertNewItemPayload extends VariableGroupId { itemName: string } - -export interface UpdateGroupNamePayload extends VariableGroupId { - groupId: string; - updatedName: string; -} -export interface UpdateItemNamePayload extends VariableGroupId { - itemId: string; - updatedName: string; -} -export interface UpdateValuePayload extends VariableGroupId { - groupId: string; - itemId: string; - updated: ValueParts; -} - -export interface RemoveVariableGroupFromDiskPayload { - id: string; - withConfirmation: boolean; -} - -export interface RemoveGroupPayload extends VariableGroupId { groupId: string } -export interface RemoveItemPayload extends VariableGroupId { itemId: string } - -export interface VariableGroupRenameStarted extends VariableGroupId { } -export interface VariableGroupRenameCancelled extends VariableGroupId { } -export interface VariableGroupRenameSubmitted extends VariableGroupId { } -export interface VariableGroupRenameResolved extends VariableGroupId { } -export interface VariableGroupRenameUpdated extends VariableGroupId { name: string } - -export default { - ActionTypes, - initialState, -}; diff --git a/packages/ui/src/store/variable-sets/actions.ts b/packages/ui/src/store/variable-sets/actions.ts new file mode 100644 index 00000000..fc7dfe92 --- /dev/null +++ b/packages/ui/src/store/variable-sets/actions.ts @@ -0,0 +1,79 @@ +/* eslint-disable max-len */ + +import { createAction } from '@reduxjs/toolkit'; + +import { + ActionTypes as AT, + CreateNewVariableSetPayload, + InsertNewGroupPayload, + InsertNewItemPayload, + InsertNewVariableSetPayload, + RemoveGroupPayload, + RemoveItemPayload, + RemoveVariableSetFromDiskPayload, + UpdateGroupNamePayload, + UpdateItemNamePayload, + UpdateValuePayload, + VariableSetRenameCancelled, + VariableSetRenameResolved, + VariableSetRenameStarted, + VariableSetRenameSubmitted, + VariableSetRenameUpdated, + VariableSetsOpenedPayload, +} from './types'; + +export const startVariableSets = createAction(AT.START_VARIABLE_GROUPS); +export const variableSetsOpened = createAction(AT.VARIABLE_GROUPS_OPENED); + +export const createNewVariableSet = createAction(AT.CREATE_NEW_VARIABLE_GROUP); +export const insertNewVariableSet = createAction(AT.INSERT_NEW_VARIABLE_GROUP); +export const insertNewGroup = createAction(AT.INSERT_NEW_GROUP); +export const insertNewItem = createAction(AT.INSERT_NEW_ITEM); + +export const updateGroupName = createAction(AT.UPDATE_GROUP_NAME); +export const updateItemName = createAction(AT.UPDATE_ITEM_NAME); +export const updateValue = createAction(AT.UPDATE_VALUE); + +export const removeVariableSetFromStore = createAction(AT.REMOVE_VARIABLE_GROUP_FROM_STORE); +export const removeVariableSetFromDisk = createAction(AT.REMOVE_VARIABLE_GROUP_FROM_DISK); + +export const removeGroup = createAction(AT.REMOVE_GROUP); +export const removeItem = createAction(AT.REMOVE_ITEM); + +export const renameStarted = createAction(AT.RENAME_STARTED); +export const renameUpdated = createAction(AT.RENAME_UPDATED); +export const renameCancelled = createAction(AT.RENAME_CANCELLED); +export const renameSubmitted = createAction(AT.RENAME_SUBMITTED); +export const renameResolved = createAction(AT.RENAME_RESOLVED); + +export const setLatestWrite = createAction(AT.SET_LATEST_WRITE); +export const setWriteDebounce = createAction(AT.SET_WRITE_DEBOUNCE); + +export default { + startVariableSets, + variableSetsOpened, + + createNewVariableSet, + insertNewVariableSet, + insertNewGroup, + insertNewItem, + + updateGroupName, + updateItemName, + updateValue, + + removeVariableSetFromStore, + removeVariableSetFromDisk, + + removeGroup, + removeItem, + + renameStarted, + renameUpdated, + renameCancelled, + renameSubmitted, + renameResolved, + + setLatestWrite, + setWriteDebounce, +}; diff --git a/packages/ui/src/store/variable-groups/index.ts b/packages/ui/src/store/variable-sets/index.ts similarity index 100% rename from packages/ui/src/store/variable-groups/index.ts rename to packages/ui/src/store/variable-sets/index.ts diff --git a/packages/ui/src/store/variable-groups/reducers.ts b/packages/ui/src/store/variable-sets/reducers.ts similarity index 51% rename from packages/ui/src/store/variable-groups/reducers.ts rename to packages/ui/src/store/variable-sets/reducers.ts index 7babd31e..ae1df39f 100644 --- a/packages/ui/src/store/variable-groups/reducers.ts +++ b/packages/ui/src/store/variable-sets/reducers.ts @@ -1,76 +1,76 @@ import { TypedObject } from '@beak/common/helpers/typescript'; import ksuid from '@beak/ksuid'; -import { generateValueIdent } from '@beak/ui/lib/beak-variable-group/utils'; +import { generateValueIdent } from '@beak/ui/lib/beak-variable-set/utils'; import { createReducer } from '@reduxjs/toolkit'; import * as actions from './actions'; import { initialState } from './types'; -const variableGroupsReducer = createReducer(initialState, builder => { +const variableSetsReducer = createReducer(initialState, builder => { builder - .addCase(actions.startVariableGroups, state => { + .addCase(actions.startVariableSets, state => { state.loaded = false; }) - .addCase(actions.variableGroupsOpened, (state, { payload }) => { - state.variableGroups = payload.variableGroups; + .addCase(actions.variableSetsOpened, (state, { payload }) => { + state.variableSets = payload.variableSets; state.loaded = true; }) - .addCase(actions.insertNewVariableGroup, (state, { payload }) => { - state.variableGroups[payload.id] = payload.variableGroup; + .addCase(actions.insertNewVariableSet, (state, { payload }) => { + state.variableSets[payload.id] = payload.variableSet; }) .addCase(actions.insertNewGroup, (state, { payload }) => { - state.variableGroups![payload.id].groups[ksuid.generate('group').toString()] = payload.groupName; + state.variableSets![payload.id].sets[ksuid.generate('set').toString()] = payload.setName; }) .addCase(actions.insertNewItem, (state, { payload }) => { - state.variableGroups![payload.id].items[ksuid.generate('item').toString()] = payload.itemName; + state.variableSets![payload.id].items[ksuid.generate('item').toString()] = payload.itemName; }) .addCase(actions.updateGroupName, (state, { payload }) => { - state.variableGroups![payload.id].groups[payload.groupId] = payload.updatedName; + state.variableSets![payload.id].sets[payload.setId] = payload.updatedName; }) .addCase(actions.updateItemName, (state, { payload }) => { - state.variableGroups![payload.id].items[payload.itemId] = payload.updatedName; + state.variableSets![payload.id].items[payload.itemId] = payload.updatedName; }) .addCase(actions.updateValue, (state, { payload }) => { - const { id, groupId, itemId, updated } = payload; - const valueIdentifier = generateValueIdent(groupId, itemId); - const variableGroup = state.variableGroups[id]; - const exists = variableGroup.values[valueIdentifier]; + const { id, setId: setId, itemId, updated } = payload; + const valueIdentifier = generateValueIdent(setId, itemId); + const variableSet = state.variableSets[id]; + const exists = variableSet.values[valueIdentifier]; const empty = updated.length === 0 || (updated.length === 1 && updated[0] === ''); if (exists) { if (empty) { - delete variableGroup.values[valueIdentifier]; + delete variableSet.values[valueIdentifier]; return; } - variableGroup.values[valueIdentifier] = updated; + variableSet.values[valueIdentifier] = updated; } else { - variableGroup.values[valueIdentifier] = updated; + variableSet.values[valueIdentifier] = updated; } }) - .addCase(actions.removeVariableGroupFromStore, (state, { payload }) => { - delete state.variableGroups[payload]; + .addCase(actions.removeVariableSetFromStore, (state, { payload }) => { + delete state.variableSets[payload]; }) .addCase(actions.removeGroup, (state, { payload }) => { - delete state.variableGroups[payload.id].groups[payload.groupId]; + delete state.variableSets[payload.id].sets[payload.setId]; TypedObject - .keys(state.variableGroups[payload.id].values) - .filter(k => k.startsWith(`${payload.groupId}&`)) - .forEach(k => delete state.variableGroups[payload.id].values[k]); + .keys(state.variableSets[payload.id].values) + .filter(k => k.startsWith(`${payload.setId}&`)) + .forEach(k => delete state.variableSets[payload.id].values[k]); }) .addCase(actions.removeItem, (state, { payload }) => { - delete state.variableGroups[payload.id].items[payload.itemId]; + delete state.variableSets[payload.id].items[payload.itemId]; TypedObject - .keys(state.variableGroups[payload.id].values) + .keys(state.variableSets[payload.id].values) .filter(k => k.endsWith(`&${payload.itemId}`)) - .forEach(k => delete state.variableGroups[payload.id].values[k]); + .forEach(k => delete state.variableSets[payload.id].values[k]); }) .addCase(actions.renameStarted, (state, action) => { @@ -106,4 +106,4 @@ const variableGroupsReducer = createReducer(initialState, builder => { }); }); -export default variableGroupsReducer; +export default variableSetsReducer; diff --git a/packages/ui/src/store/variable-groups/sagas/catch-updates.ts b/packages/ui/src/store/variable-sets/sagas/catch-updates.ts similarity index 59% rename from packages/ui/src/store/variable-groups/sagas/catch-updates.ts rename to packages/ui/src/store/variable-sets/sagas/catch-updates.ts index 7ebd4925..ccec638f 100644 --- a/packages/ui/src/store/variable-groups/sagas/catch-updates.ts +++ b/packages/ui/src/store/variable-sets/sagas/catch-updates.ts @@ -1,21 +1,21 @@ -import { writeVariableGroup } from '@beak/ui/lib/beak-variable-group'; -import type { VariableGroup } from '@getbeak/types/variable-groups'; +import { writeVariableSet } from '@beak/ui/lib/beak-variable-set'; +import type { VariableSet } from '@getbeak/types/variable-sets'; import { call, delay, put, select } from '@redux-saga/core/effects'; import { PayloadAction } from '@reduxjs/toolkit'; import * as uuid from 'uuid'; import { ApplicationState } from '../..'; import { actions } from '..'; -import { VariableGroupId } from '../types'; +import { VariableSetId } from '../types'; -export default function* workerCatchUpdates({ payload }: PayloadAction) { +export default function* workerCatchUpdates({ payload }: PayloadAction) { const { id } = payload; - const variableGroup: VariableGroup = yield select( - (s: ApplicationState) => s.global.variableGroups.variableGroups[id], + const variableSet: VariableSet = yield select( + (s: ApplicationState) => s.global.variableSets.variableSets[id], ); - if (!variableGroup) + if (!variableSet) return; const nonce = uuid.v4(); @@ -23,12 +23,12 @@ export default function* workerCatchUpdates({ payload }: PayloadAction s.global.variableGroups.writeDebouncer); + const debounce: string = yield select((s: ApplicationState) => s.global.variableSets.writeDebouncer); // This prevents us writing the file too often while data is changing if (debounce !== nonce) return; yield put(actions.setLatestWrite(Date.now())); - yield call(writeVariableGroup, id, variableGroup); + yield call(writeVariableSet, id, variableSet); } diff --git a/packages/ui/src/store/variable-sets/sagas/create-variable-set.ts b/packages/ui/src/store/variable-sets/sagas/create-variable-set.ts new file mode 100644 index 00000000..bd92487c --- /dev/null +++ b/packages/ui/src/store/variable-sets/sagas/create-variable-set.ts @@ -0,0 +1,18 @@ +import { changeTab } from '@beak/ui/features/tabs/store/actions'; +import { createVariableSet } from '@beak/ui/lib/beak-project/variable-sets'; +import { call, delay, put, race, take } from '@redux-saga/core/effects'; +import { PayloadAction } from '@reduxjs/toolkit'; + +import { renameStarted } from '../actions'; +import { ActionTypes, CreateNewVariableSetPayload } from '../types'; + +export default function* workerCreateNewVariableSet({ payload }: PayloadAction) { + const id: string = yield call(createVariableSet, 'variable-sets', payload.name); + + yield put(changeTab({ type: 'variable_set_editor', payload: id, temporary: true })); + yield race([ + take(ActionTypes.INSERT_NEW_VARIABLE_GROUP), + delay(250), + ]); + yield put(renameStarted({ id })); +} diff --git a/packages/ui/src/store/variable-groups/sagas/index.ts b/packages/ui/src/store/variable-sets/sagas/index.ts similarity index 55% rename from packages/ui/src/store/variable-groups/sagas/index.ts rename to packages/ui/src/store/variable-sets/sagas/index.ts index 68d68c47..81cde50e 100644 --- a/packages/ui/src/store/variable-groups/sagas/index.ts +++ b/packages/ui/src/store/variable-sets/sagas/index.ts @@ -2,10 +2,10 @@ import { all, fork, takeEvery, takeLatest } from '@redux-saga/core/effects'; import { ActionTypes } from '../types'; import catchUpdates from './catch-updates'; -import createNewVariableGroup from './create-variable-group'; -import removeVariableGroupFromDisk from './remove-node-from-disk'; -import startVariableGroups from './start-variable-groups'; -import variableGroupRename from './variable-group-rename'; +import createNewVariableSet from './create-variable-set'; +import removeVariableSetFromDisk from './remove-node-from-disk'; +import startVariableSets from './start-variable-sets'; +import variableSetRename from './variable-set-rename'; const updateWatcherActions = [ ActionTypes.INSERT_NEW_VARIABLE_GROUP, @@ -18,22 +18,22 @@ const updateWatcherActions = [ ActionTypes.REMOVE_ITEM, ]; -export default function* variableGroupsSaga() { +export default function* variableSetsSaga() { yield all([ - fork(function* startVariableGroupsWatcher() { - yield takeEvery(ActionTypes.START_VARIABLE_GROUPS, startVariableGroups); + fork(function* startVariableSetsWatcher() { + yield takeEvery(ActionTypes.START_VARIABLE_GROUPS, startVariableSets); }), fork(function* catchNodeUpdatesWatcher() { yield takeEvery(updateWatcherActions, catchUpdates); }), - fork(function* createVariableGroupWatcher() { - yield takeEvery(ActionTypes.CREATE_NEW_VARIABLE_GROUP, createNewVariableGroup); + fork(function* createVariableSetWatcher() { + yield takeEvery(ActionTypes.CREATE_NEW_VARIABLE_GROUP, createNewVariableSet); }), - fork(function* variableGroupRenameWatcher() { - yield takeLatest(ActionTypes.RENAME_SUBMITTED, variableGroupRename); + fork(function* variableSetRenameWatcher() { + yield takeLatest(ActionTypes.RENAME_SUBMITTED, variableSetRename); }), - fork(function* removeVariableGroupFromDiskWatcher() { - yield takeEvery(ActionTypes.REMOVE_VARIABLE_GROUP_FROM_DISK, removeVariableGroupFromDisk); + fork(function* removeVariableSetFromDiskWatcher() { + yield takeEvery(ActionTypes.REMOVE_VARIABLE_GROUP_FROM_DISK, removeVariableSetFromDisk); }), ]); } diff --git a/packages/ui/src/store/variable-groups/sagas/remove-node-from-disk.ts b/packages/ui/src/store/variable-sets/sagas/remove-node-from-disk.ts similarity index 66% rename from packages/ui/src/store/variable-groups/sagas/remove-node-from-disk.ts rename to packages/ui/src/store/variable-sets/sagas/remove-node-from-disk.ts index 625bc911..da404fae 100644 --- a/packages/ui/src/store/variable-groups/sagas/remove-node-from-disk.ts +++ b/packages/ui/src/store/variable-sets/sagas/remove-node-from-disk.ts @@ -1,21 +1,19 @@ -/* eslint-disable max-len */ - import { ShowMessageBoxRes } from '@beak/common/ipc/dialog'; import { attemptReconciliation } from '@beak/ui/features/tabs/store/actions'; -import { removeVariableGroup } from '@beak/ui/lib/beak-variable-group'; +import { removeVariableSet } from '@beak/ui/lib/beak-variable-set'; import { ipcDialogService } from '@beak/ui/lib/ipc'; import { call, put } from '@redux-saga/core/effects'; import { PayloadAction } from '@reduxjs/toolkit'; import actions from '../actions'; -import { RemoveVariableGroupFromDiskPayload } from '../types'; +import { RemoveVariableSetFromDiskPayload } from '../types'; -export default function* workerRemoveVariableGroupFromDisk({ payload }: PayloadAction) { +export default function* workerRemoveVariableSetFromDisk({ payload }: PayloadAction) { const { id, withConfirmation } = payload; if (withConfirmation) { const response: ShowMessageBoxRes = yield call([ipcDialogService, ipcDialogService.showMessageBox], { - title: 'Deleting variable group', + title: 'Deleting variable set', message: `You are about to delete '${id}' from your machine. Are you sure you want to continue?`, detail: 'This action is irreversible inside Beak!', type: 'warning', @@ -27,7 +25,7 @@ export default function* workerRemoveVariableGroupFromDisk({ payload }: PayloadA return; } - yield call(removeVariableGroup, id); - yield put(actions.removeVariableGroupFromStore(id)); + yield call(removeVariableSet, id); + yield put(actions.removeVariableSetFromStore(id)); yield put(attemptReconciliation()); } diff --git a/packages/ui/src/store/variable-groups/sagas/start-variable-groups.ts b/packages/ui/src/store/variable-sets/sagas/start-variable-sets.ts similarity index 62% rename from packages/ui/src/store/variable-groups/sagas/start-variable-groups.ts rename to packages/ui/src/store/variable-sets/sagas/start-variable-sets.ts index e330b5a0..d2faf534 100644 --- a/packages/ui/src/store/variable-groups/sagas/start-variable-groups.ts +++ b/packages/ui/src/store/variable-sets/sagas/start-variable-sets.ts @@ -1,15 +1,15 @@ import { TypedObject } from '@beak/common/helpers/typescript'; import { EditorPreferences } from '@beak/common/types/beak-hub'; import { attemptReconciliation } from '@beak/ui/features/tabs/store/actions'; -import { readVariableGroup } from '@beak/ui/lib/beak-variable-group'; +import { readVariableSet } from '@beak/ui/lib/beak-variable-set'; import createFsEmitter, { scanDirectoryRecursively, ScanResult } from '@beak/ui/lib/fs-emitter'; import { ipcDialogService, ipcFsService } from '@beak/ui/lib/ipc'; -import type { VariableGroups } from '@getbeak/types/variable-groups'; +import type { VariableSets } from '@getbeak/types/variable-sets'; import { call, put, select, take } from '@redux-saga/core/effects'; import path from 'path-browserify'; import { ApplicationState } from '../..'; -import { editorPreferencesSetSelectedVariableGroup } from '../../preferences/actions'; +import { editorPreferencesSetSelectedVariableSet } from '../../preferences/actions'; import * as actions from '../actions'; interface Emitter { @@ -17,10 +17,10 @@ interface Emitter { path: string; } -export default function* workerStartVariableGroups() { - const channel = createFsEmitter('variable-groups', { depth: 0, followSymlinks: false }); +export default function* workerStartVariableSets() { + const channel = createFsEmitter('variable-sets', { depth: 0, followSymlinks: false }); - yield initialImport('variable-groups'); + yield initialImport('variable-sets'); while (true) { const result: Emitter = yield take(channel); @@ -38,7 +38,7 @@ export default function* workerStartVariableGroups() { // Protection to only read changes if they haven't been recently written by Beak if (result.type === 'change') { - const lastWrite: number = yield select((s: ApplicationState) => s.global.variableGroups.latestWrite); + const lastWrite: number = yield select((s: ApplicationState) => s.global.variableSets.latestWrite); if (lastWrite) { const now = Date.now(); @@ -51,9 +51,9 @@ export default function* workerStartVariableGroups() { if (['add', 'change'].includes(result.type)) { try { - const { file, name } = yield call(readVariableGroup, result.path); + const { file, name } = yield call(readVariableSet, result.path); - yield put(actions.insertNewVariableGroup({ id: name, variableGroup: file })); + yield put(actions.insertNewVariableSet({ id: name, variableSet: file })); } catch (error) { if (!(error instanceof Error)) return; @@ -61,7 +61,7 @@ export default function* workerStartVariableGroups() { yield call([ipcDialogService, ipcDialogService.showMessageBox], { type: 'error', title: 'Project data error', - message: 'There was a problem reading a variable group file in your project', + message: 'There was a problem reading a variable set file in your project', detail: [ error.message, error.stack, @@ -70,9 +70,9 @@ export default function* workerStartVariableGroups() { } } else if (result.type === 'unlink') { try { - const variableGroupName = path.basename(result.path, path.extname(result.path)); + const variableSetName = path.basename(result.path, path.extname(result.path)); - yield put(actions.removeVariableGroupFromStore(variableGroupName)); + yield put(actions.removeVariableSetFromStore(variableSetName)); yield put(attemptReconciliation()); } catch (error) { if (!(error instanceof Error)) @@ -81,7 +81,7 @@ export default function* workerStartVariableGroups() { yield call([ipcDialogService, ipcDialogService.showMessageBox], { type: 'error', title: 'Project data error', - message: 'There was a problem deleting a variable group from your project', + message: 'There was a problem deleting a variable set from your project', detail: [ error.message, error.stack, @@ -93,35 +93,35 @@ export default function* workerStartVariableGroups() { } function* initialImport(vgPath: string) { - const folderExists: boolean = yield call([ipcFsService, ipcFsService.pathExists], 'variable-groups'); + const folderExists: boolean = yield call([ipcFsService, ipcFsService.pathExists], 'variable-sets'); if (!folderExists) { - yield put(actions.variableGroupsOpened({ variableGroups: {} })); + yield put(actions.variableSetsOpened({ variableSets: {} })); return; } const items: ScanResult[] = yield scanDirectoryRecursively(vgPath); const files = items.filter(i => !i.isDirectory).map(i => i.path); - const variableGroups: VariableGroups = yield call(readVariableGroups, files); + const variableSets: VariableSets = yield call(readVariableSets, files); const editorPreferences: EditorPreferences = yield select((s: ApplicationState) => s.global.preferences.editor); - for (const vgk of TypedObject.keys(variableGroups)) { - const vg = variableGroups[vgk]; + for (const vgk of TypedObject.keys(variableSets)) { + const vg = variableSets[vgk]; - if (editorPreferences.selectedVariableGroups[vgk] === void 0) { - yield put(editorPreferencesSetSelectedVariableGroup({ - variableGroup: vgk, - groupId: TypedObject.keys(vg.groups)[0], + if (editorPreferences.selectedVariableSets[vgk] === void 0) { + yield put(editorPreferencesSetSelectedVariableSet({ + variableSet: vgk, + setId: TypedObject.keys(vg.sets)[0], })); } } - yield put(actions.variableGroupsOpened({ variableGroups })); + yield put(actions.variableSetsOpened({ variableSets })); } -async function readVariableGroups(filePaths: string[]) { - const results = await Promise.all(filePaths.map(f => readVariableGroup(f))); +async function readVariableSets(filePaths: string[]) { + const results = await Promise.all(filePaths.map(f => readVariableSet(f))); return results.reduce((acc, { name, file }) => ({ ...acc, diff --git a/packages/ui/src/store/variable-groups/sagas/variable-group-rename.ts b/packages/ui/src/store/variable-sets/sagas/variable-set-rename.ts similarity index 70% rename from packages/ui/src/store/variable-groups/sagas/variable-group-rename.ts rename to packages/ui/src/store/variable-sets/sagas/variable-set-rename.ts index c2b7b2c6..f8e82793 100644 --- a/packages/ui/src/store/variable-groups/sagas/variable-group-rename.ts +++ b/packages/ui/src/store/variable-sets/sagas/variable-set-rename.ts @@ -1,16 +1,16 @@ import { changeTab } from '@beak/ui/features/tabs/store/actions'; import { ActiveRename } from '@beak/ui/features/tree-view/types'; -import { renameVariableGroup } from '@beak/ui/lib/beak-project/variable-groups'; +import { renameVariableSet } from '@beak/ui/lib/beak-project/variable-sets'; import { ipcDialogService } from '@beak/ui/lib/ipc'; import { call, delay, put, select } from '@redux-saga/core/effects'; import { PayloadAction } from '@reduxjs/toolkit'; import { ApplicationState } from '../..'; import actions from '../actions'; -import { VariableGroupRenameSubmitted } from '../types'; +import { VariableSetRenameSubmitted } from '../types'; -export default function* workerRequestRename({ payload }: PayloadAction) { - const activeRename: ActiveRename = yield select((s: ApplicationState) => s.global.variableGroups.activeRename); +export default function* workerRequestRename({ payload }: PayloadAction) { + const activeRename: ActiveRename = yield select((s: ApplicationState) => s.global.variableSets.activeRename); const { id } = payload; if (!activeRename || activeRename.id !== id) @@ -23,15 +23,15 @@ export default function* workerRequestRename({ payload }: PayloadAction, ) { - const value = await parseValueParts(context, info.url); + const value = await parseValueSections(context, info.url); const url = new URL(value, true); const options = { includeQuery: true, @@ -34,7 +34,7 @@ export async function convertRequestToUrl( for (const query of TypedObject.values(info.query).filter(q => q.enabled)) { // eslint-disable-next-line no-await-in-loop - outQuery[query.name] = await parseValueParts(context, query.value); + outQuery[query.name] = await parseValueSections(context, query.value); } url.set('query', outQuery); From 42538b45b62e6d8f45135493e9cf77b3371d1ec2 Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sun, 17 Nov 2024 18:48:29 +0100 Subject: [PATCH 08/14] added new migrations for realtime value changes --- .../common-host/src/project/extensions.ts | 4 +- packages/common-host/src/project/index.ts | 37 ++--- .../src/project/migrations/standard.ts | 133 +++++++++++++++++- 3 files changed, 152 insertions(+), 22 deletions(-) diff --git a/packages/common-host/src/project/extensions.ts b/packages/common-host/src/project/extensions.ts index 05bf58ce..266d035f 100644 --- a/packages/common-host/src/project/extensions.ts +++ b/packages/common-host/src/project/extensions.ts @@ -25,8 +25,8 @@ export default class BeakExtensions extends BeakBase { '', '## Getting started', 'Below are some useful resources for getting started with Beak\'s extensions', - '- [Extensions manual](https://getbeak.notion.site/Extensions-realtime-values-4c16ca640b35460787056f8be815b904)', - '- [GitHub extension template](https://github.com/getbeak/realtime-value-extension-template)', + '- [Extensions manual](https://www.notion.so/getbeak/Extensions-4c16ca640b35460787056f8be815b904)', + '- [GitHub extension template](https://github.com/getbeak/extension-variable-template)', '', ].join('\n'); } diff --git a/packages/common-host/src/project/index.ts b/packages/common-host/src/project/index.ts index c50717d2..85f811d9 100644 --- a/packages/common-host/src/project/index.ts +++ b/packages/common-host/src/project/index.ts @@ -1,8 +1,7 @@ -import { ProjectEncryption } from '@beak/common/types/beak-project'; import ksuid from '@beak/ksuid'; import type { RequestNodeFile } from '@getbeak/types/nodes'; import type { ProjectFile } from '@getbeak/types/project'; -import type { VariableGroup } from '@getbeak/types/variable-groups'; +import type { VariableSet } from '@getbeak/types/variable-sets'; import git from 'isomorphic-git'; import { BeakBase, Providers } from '../base'; @@ -62,21 +61,27 @@ export default class BeakProject extends BeakBase { }, }; - const productionGroupId = ksuid.generate('group').toString(); - const localGroupId = ksuid.generate('group').toString(); + const productionSetId = ksuid.generate('set').toString(); + const stagingSetId = ksuid.generate('set').toString(); + const developmentSetId = ksuid.generate('set').toString(); + const localSetId = ksuid.generate('set').toString(); const environmentIdentifierItemId = ksuid.generate('item').toString(); - const variableGroup: VariableGroup = { - groups: { - [productionGroupId]: 'Production', - [localGroupId]: 'Local', + const variableSet: VariableSet = { + sets: { + [productionSetId]: 'Production', + [stagingSetId]: 'Staging', + [developmentSetId]: 'Development', + [localSetId]: 'Local', }, items: { [environmentIdentifierItemId]: 'env_identifier', }, values: { - [`${productionGroupId}&${environmentIdentifierItemId}`]: ['prod'], - [`${localGroupId}&${environmentIdentifierItemId}`]: ['local'], + [`${productionSetId}&${environmentIdentifierItemId}`]: ['production'], + [`${stagingSetId}&${environmentIdentifierItemId}`]: ['staging'], + [`${developmentSetId}&${environmentIdentifierItemId}`]: ['development'], + [`${localSetId}&${environmentIdentifierItemId}`]: ['local'], }, }; @@ -96,13 +101,13 @@ export default class BeakProject extends BeakBase { await this.p.node.fs.promises.readFile(this.p.node.path.join(projectFolderPath, 'tree', 'Request.json'), 'utf8'); - // Create variable groups structure + // Create variable sets structure await this.p.node.fs.promises.mkdir( - this.p.node.path.join(projectFolderPath, 'variable-groups'), + this.p.node.path.join(projectFolderPath, 'variable-sets'), ); await this.p.node.fs.promises.writeFile( - this.p.node.path.join(projectFolderPath, 'variable-groups', 'Environment.json'), - JSON.stringify(variableGroup, null, '\t'), + this.p.node.path.join(projectFolderPath, 'variable-sets', 'Environment.json'), + JSON.stringify(variableSet, null, '\t'), 'utf8', ); @@ -165,7 +170,7 @@ export default class BeakProject extends BeakBase { try { // TODO(afr): validate schema of project file! projectFile = JSON.parse(projectFileJson) as ProjectFile; - } catch (error) { + } catch { return null; } @@ -195,7 +200,7 @@ export default class BeakProject extends BeakBase { const profileFile: ProjectFile = { id: projectId ?? ksuid.generate('project').toString(), name, - version: '0.3.0', + version: '0.4.0', }; await this.p.node.fs.promises.writeFile( diff --git a/packages/common-host/src/project/migrations/standard.ts b/packages/common-host/src/project/migrations/standard.ts index f4b2b27d..35a96794 100644 --- a/packages/common-host/src/project/migrations/standard.ts +++ b/packages/common-host/src/project/migrations/standard.ts @@ -25,8 +25,9 @@ export default class BeakStandardMigrations extends BeakBase { private readonly beakExtensions: BeakExtensions; private readonly migrationHistory: Record = { - '0.2.0': this.handle_0_2_0_to_0_2_1, // Migrate from 0.2.0 -> 0.2.1 - '0.2.1': this.handle_0_2_1_to_0_3_0, // Migrate from 0.2.1 -> 0.3.0 + '0.2.0': this.handle_0_2_0_to_0_2_1.bind(this), // Migrate from 0.2.0 -> 0.2.1 + '0.2.1': this.handle_0_2_1_to_0_3_0.bind(this), // Migrate from 0.2.1 -> 0.3.0 + '0.3.0': this.handle_0_3_0_to_0_4_0.bind(this), // Migrate from 0.3.0 -> 0.4.0 } as const; constructor(providers: Providers, beakExtensions: BeakExtensions) { @@ -58,6 +59,18 @@ export default class BeakStandardMigrations extends BeakBase { } } + /** + * Handles the migration of a project file from version 0.2.0 to 0.2.1. + * + * This function checks for the existence of a `supersecret.json` file in the project's `.beak` directory. + * If the file exists, it reads and parses the file to extract encryption details. + * The extracted encryption details are then set for the project using the `setProjectEncryption` method. + * Finally, the `supersecret.json` file is removed and the project file version is updated to 0.2.1. + * + * @param projectFile - The project file object that needs to be migrated. + * @param projectFolderPath - The path to the project folder. + * @returns A promise that resolves when the migration is complete. + */ private async handle_0_2_0_to_0_2_1(projectFile: ProjectFile, projectFolderPath: string): Promise { const supersecretFilePath = this.p.node.path.join(projectFolderPath, '.beak', 'supersecret.json'); @@ -82,6 +95,19 @@ export default class BeakStandardMigrations extends BeakBase { } } + /** + * Handles the migration of a project file from version 0.2.1 to 0.3.0. + * + * This function checks if the 'extensions' directory exists within the project folder. + * If the directory does not exist, it creates the directory and adds a 'package.json' + * and 'README.md' file with the appropriate content. + * + * Finally, it updates the project file version to '0.3.0'. + * + * @param projectFile - The project file to be migrated. + * @param projectFolderPath - The path to the project folder. + * @returns A promise that resolves when the migration is complete. + */ private async handle_0_2_1_to_0_3_0(projectFile: ProjectFile, projectFolderPath: string): Promise { const extensionsPath = this.p.node.path.join(projectFolderPath, 'extensions'); @@ -91,7 +117,6 @@ export default class BeakStandardMigrations extends BeakBase { hasExtensions = true; // Only try and migrate if extensions don't exist... May be left over from beta testers - // eslint-disable-next-line no-sync if (!hasExtensions) { await this.p.node.fs.promises.mkdir(extensionsPath); @@ -112,6 +137,106 @@ export default class BeakStandardMigrations extends BeakBase { await this.changeProjectFileVersion(projectFile, projectFolderPath, '0.3.0'); } + /** + * Handles the migration of a project from version 0.3.0 to 0.4.0. + * + * This migration performs the following tasks: + * + * 1. Updates hidden files, replacing references to variable groups with variable sets. + * + * 2. Renames folders, changing 'realtime-values' to 'variables' and 'variable-groups' to 'variable-sets'. + * + * 3. Edits variable group files, replacing references to 'groups' with 'sets'. + * + * 4. Edits request files, replacing references to variable groups with variable sets. + * + * 5. Updates the project file version to '0.4.0'. + * + * @param projectFile - The project file to be migrated. + * @param projectFolderPath - The path to the project folder. + * @returns A promise that resolves when the migration is complete. + */ + private async handle_0_3_0_to_0_4_0(projectFile: ProjectFile, projectFolderPath: string): Promise { + // Edit hidden files + await this.replaceStringInBeakFile(projectFolderPath, '.beak/editor.json', 'selectedVariableGroups', 'selectedVariableSets'); + await this.replaceStringInBeakFile(projectFolderPath, '.beak/tab-state.json', '{"type":"variable_group_editor"', '"type":"variable_set_editor"'); + await this.replaceStringInBeakFile(projectFolderPath, '.beak/sidebar.json', 'beak.project.variable-groups', 'beak.project.variable-sets'); + + // Rename folders + if (await fileExists(this, this.p.node.path.join(projectFolderPath, '.beak', 'realtime-values'))) { + await this.p.node.fs.promises.rename( + this.p.node.path.join(projectFolderPath, '.beak', 'realtime-values'), + this.p.node.path.join(projectFolderPath, '.beak', 'variables'), + ); + } + + if (await fileExists(this, this.p.node.path.join(projectFolderPath, 'variable-groups'))) { + await this.p.node.fs.promises.rename( + this.p.node.path.join(projectFolderPath, 'variable-groups'), + this.p.node.path.join(projectFolderPath, 'variable-sets'), + ); + } + + // Edit variable group files + const variableSetFilePaths = await this.p.node.fs.promises.readdir( + this.p.node.path.join(projectFolderPath, 'variable-sets'), + { withFileTypes: true }, + ); + + await Promise.all(variableSetFilePaths.map(async file => { + if (!file.isFile() || !file.name.endsWith('.json')) + return; + + await this.replaceStringInFile( + this.p.node.path.join(file.parentPath, file.name), + '"groups"', '"sets"', + ); + })); + + // Edit requests + const requestFilePaths = await this.p.node.fs.promises.readdir( + this.p.node.path.join(projectFolderPath, 'tree'), + { withFileTypes: true, recursive: true }, + ); + + await Promise.all(requestFilePaths.map(async file => { + if (!file.isFile() || !file.name.endsWith('.json')) + return; + + await this.replaceStringInFile( + this.p.node.path.join(file.parentPath, file.name), + '"type": "variable_group_item"', + '"type": "variable_set_item"', + ); + })); + + await this.changeProjectFileVersion(projectFile, projectFolderPath, '0.4.0'); + } + + private async replaceStringInBeakFile( + projectFolderPath: string, + filePath: string, + search: string, + replace: string, + ) { + const fullFilePath = this.p.node.path.join(projectFolderPath, filePath); + + if (!await fileExists(this, fullFilePath)) + return; + + await this.replaceStringInFile(fullFilePath, search, replace); + } + + private async replaceStringInFile(filePath: string, search: string, replace: string) { + if (!await fileExists(this, filePath)) + return; + + const fileContent = await this.p.node.fs.promises.readFile(filePath, 'utf8'); + const updatedFile = fileContent.replace(new RegExp(search, 'g'), replace); + + await this.p.node.fs.promises.writeFile(filePath, updatedFile, 'utf8'); + } + private async changeProjectFileVersion( projectFile: ProjectFile, projectFolderPath: string, @@ -123,7 +248,7 @@ export default class BeakStandardMigrations extends BeakBase { }; await this.p.node.fs.promises.writeFile( - projectFolderPath, + this.p.node.path.join(projectFolderPath, 'project.json'), JSON.stringify(newProjectFile, null, '\t'), { encoding: 'utf8' }, ); From 315cd6ca897624a0b29853c530f2d4bffdb0b8a5 Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sun, 17 Nov 2024 18:48:41 +0100 Subject: [PATCH 09/14] update host impls --- apps-host/electron/src/augmentations.d.ts | 8 ++-- .../src/ipc-layer/encryption-service.ts | 4 +- apps-host/electron/src/lib/extension/index.ts | 42 +++++++++++-------- apps-host/web/src/augmentations.d.ts | 10 ++--- apps-host/web/src/ipc/encryption-service.ts | 4 +- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/apps-host/electron/src/augmentations.d.ts b/apps-host/electron/src/augmentations.d.ts index 9d71f8b4..69c9bd84 100644 --- a/apps-host/electron/src/augmentations.d.ts +++ b/apps-host/electron/src/augmentations.d.ts @@ -1,16 +1,16 @@ import type { Context } from '@getbeak/types/values'; -declare module '@getbeak/types-realtime-value' { +declare module '@getbeak/types-variables' { interface GenericDictionary { [k: string]: any; } - interface RealtimeValueBase { + interface VariableBase { type: string; external: boolean; } - interface RealtimeValue { + interface Variable { /** * Gets the string value of the value, given the payload body @@ -21,7 +21,7 @@ declare module '@getbeak/types-realtime-value' { getValue: (ctx: Context, payload: TPayload, recursiveDepth: number) => Promise; } - interface EditableRealtimeValue { + interface EditableVariable { /** * Gets the string value of the value, given the payload body diff --git a/apps-host/electron/src/ipc-layer/encryption-service.ts b/apps-host/electron/src/ipc-layer/encryption-service.ts index b93c9625..011975d0 100644 --- a/apps-host/electron/src/ipc-layer/encryption-service.ts +++ b/apps-host/electron/src/ipc-layer/encryption-service.ts @@ -1,6 +1,6 @@ import { IpcEncryptionServiceMain } from '@beak/common/ipc/encryption'; import { clipboard, ipcMain } from 'electron'; -import { ValueParts } from 'packages/types/values'; +import { ValueSections } from 'packages/types/values'; import getBeakHost from '../host'; import { getProjectId } from './utils'; @@ -58,7 +58,7 @@ service.registerEncryptObject(async (event, { iv, payload }) => { return await getBeakHost().providers.aes.encryptString(json, encryption.key, iv); }); -service.registerDecryptObject(async (event, { iv, payload }): Promise => { +service.registerDecryptObject(async (event, { iv, payload }): Promise => { const projectId = getProjectId(event); const encryption = await getBeakHost().providers.credentials.getProjectEncryption(projectId); diff --git a/apps-host/electron/src/lib/extension/index.ts b/apps-host/electron/src/lib/extension/index.ts index 1120663f..db25a14c 100644 --- a/apps-host/electron/src/lib/extension/index.ts +++ b/apps-host/electron/src/lib/extension/index.ts @@ -1,12 +1,12 @@ import { ensureWithinProject } from '@beak/apps-host-electron/ipc-layer/fs-service'; import { getProjectFilePathWindowMapping } from '@beak/apps-host-electron/ipc-layer/fs-shared'; -import { ExtensionsMessages, IpcExtensionsServiceMain, RtvParseValuePartsResponse } from '@beak/common/ipc/extensions'; +import { ExtensionsMessages, IpcExtensionsServiceMain, RtvParseValueSectionsResponse } from '@beak/common/ipc/extensions'; import { IpcEvent, RequestPayload } from '@beak/common/ipc/ipc'; -import { RealtimeValueExtension } from '@beak/common/types/extensions'; +import { VariableExtension } from '@beak/common/types/extensions'; import Squawk from '@beak/common/utils/squawk'; import ksuid from '@beak/ksuid'; -import { Context, ValueParts } from '@getbeak/types/values'; -import { EditableRealtimeValue } from '@getbeak/types-realtime-value/'; +import { Context, ValueSections } from '@getbeak/types/values'; +import { EditableVariable } from '@getbeak/types-variables'; import { ipcMain, WebContents } from 'electron'; import fs from 'fs-extra'; import clone from 'lodash.clonedeep'; @@ -26,7 +26,7 @@ interface RtvExtensionStorage { scriptContent: string; vm: NodeVM; script: VMScript; - extension: EditableRealtimeValue; + extension: EditableVariable; } const logger = new Logger({ name: 'extensions' }); @@ -41,7 +41,7 @@ export default class ExtensionManager { this.extensionService = extensionService; } - async registerRtv(event: IpcEvent, projectId: string, extensionPath: string): Promise { + async registerRtv(event: IpcEvent, projectId: string, extensionPath: string): Promise { if (!this.projectExtensions[projectId]) this.projectExtensions[projectId] = {}; @@ -57,7 +57,7 @@ export default class ExtensionManager { sandbox: { beakApi: { log: (level: LogLevel, message: string) => (logger[level] ?? logger.warn)(message), - parseValueParts: (_ctx: unknown, _parts: unknown, _recursiveSet: unknown) => [], + parseValueSections: (_ctx: unknown, _parts: unknown, _recursiveSet: unknown) => [], }, }, }); @@ -71,7 +71,7 @@ export default class ExtensionManager { const compiledScript = new VMScript(scriptContent); const extensionContext = extensionVm.run(compiledScript); - const extension = extensionContext?.default as EditableRealtimeValue; + const extension = extensionContext?.default as EditableVariable; this.validateExtensionSignature(extension); this.projectExtensions[projectId][type] = { @@ -88,7 +88,7 @@ export default class ExtensionManager { version, filePath: extensionPath, valid: true, - realtimeValue: { + variable: { type, external: true, name: extension.name, @@ -119,11 +119,11 @@ export default class ExtensionManager { ) { const { vm, extension } = this.getExtensionContext(projectId, type); - vm.sandbox.beakApi.parseValueParts = async (ctx: Context, parts: ValueParts) => { + vm.sandbox.beakApi.parseValueSections = async (ctx: Context, parts: ValueSections) => { const uniqueSessionId = ksuid.generate('rtvparsersp').toString(); // send IPC request - this.extensionService.rtvParseValueParts(webContents, { + this.extensionService.rtvParseValueSections(webContents, { uniqueSessionId, context: ctx, parts, @@ -132,9 +132,9 @@ export default class ExtensionManager { return await new Promise(resolve => { ipcMain.on(this.extensionService.getChannel(), async (_event, message) => { - const { code, payload } = message as RequestPayload; + const { code, payload } = message as RequestPayload; - if (code !== ExtensionsMessages.RtvParseValuePartsResponse) + if (code !== ExtensionsMessages.RtvParseValueSectionsResponse) return; if (payload.uniqueSessionId !== uniqueSessionId) @@ -157,6 +157,10 @@ export default class ExtensionManager { async rtvEditorLoad(projectId: string, type: string, context: Context, payload: unknown) { const { extension } = this.getExtensionContext(projectId, type); + + if (extension.editor.load === void 0) + return payload; + const editorState = await extension.editor.load(context, payload); return clone(editorState); @@ -164,6 +168,10 @@ export default class ExtensionManager { async rtvEditorSave(projectId: string, type: string, context: Context, existingPayload: unknown, state: unknown) { const { extension } = this.getExtensionContext(projectId, type); + + if (extension.editor.save === void 0) + return state; + const payload = await extension.editor.save(context, existingPayload, state); return clone(payload); @@ -175,7 +183,7 @@ export default class ExtensionManager { if (!extensionStorage) throw new Squawk('unknown_registered_extension', { projectId, type }); - extensionStorage.vm.sandbox.beakApi.parseValueParts = () => []; + extensionStorage.vm.sandbox.beakApi.parseValueSections = () => []; return { vm: extensionStorage.vm, extension: extensionStorage.extension }; } @@ -185,11 +193,11 @@ export default class ExtensionManager { const packageJson = await fs.readJson(packageJsonPath); const { name, version, beakExtensionType, main } = packageJson; - if (beakExtensionType !== 'realtime-value') { + if (beakExtensionType !== 'variable') { throw new Squawk('invalid_extension_type', { extensionPath, packageJsonKey: 'beakExtensionType', - expected: 'realtime-value', + expected: 'variable', actual: beakExtensionType ?? '[missing]', }); } @@ -235,7 +243,7 @@ export default class ExtensionManager { return { main, name, scriptPath, version }; } - private validateExtensionSignature(extension: EditableRealtimeValue) { + private validateExtensionSignature(extension: EditableVariable) { if (!('name' in extension)) throw new Squawk('incorrect_extension_signature', { key: 'name', reason: 'missing' }); if (typeof extension.name !== 'string') diff --git a/apps-host/web/src/augmentations.d.ts b/apps-host/web/src/augmentations.d.ts index 078c1792..60c7e19e 100644 --- a/apps-host/web/src/augmentations.d.ts +++ b/apps-host/web/src/augmentations.d.ts @@ -33,28 +33,28 @@ declare module 'electron' { } } -declare module '@getbeak/types-realtime-value' { +declare module '@getbeak/types-variables' { interface GenericDictionary { [k: string]: any; } - interface RealtimeValueBase { + interface VariableBase { type: string; external: boolean; } - interface RealtimeValue { + interface Variable { /** * Gets the string value of the value, given the payload body * @param {Context} ctx The project context. * @param {TPayload} payload This instance of the value's payload data. - * @param {number} recursiveDepth The current depth of realtime value recursion. + * @param {number} recursiveDepth The current depth of value recursion. */ getValue: (ctx: Context, payload: TPayload, recursiveDepth: number) => Promise; } - interface EditableRealtimeValue { + interface EditableVariable { /** * Gets the string value of the value, given the payload body diff --git a/apps-host/web/src/ipc/encryption-service.ts b/apps-host/web/src/ipc/encryption-service.ts index 0c6b6d67..169c6032 100644 --- a/apps-host/web/src/ipc/encryption-service.ts +++ b/apps-host/web/src/ipc/encryption-service.ts @@ -1,5 +1,5 @@ import { IpcEncryptionServiceMain } from '@beak/common/ipc/encryption'; -import { ValueParts } from 'packages/types/values'; +import { ValueSections } from 'packages/types/values'; import getBeakHost from '../host'; import { webIpcMain } from './ipc'; @@ -72,7 +72,7 @@ service.registerEncryptObject(async (_event, { iv, payload }) => { return await getBeakHost().providers.aes.encryptString(json, encryption.key, iv); }); -service.registerDecryptObject(async (_event, { iv, payload }): Promise => { +service.registerDecryptObject(async (_event, { iv, payload }): Promise => { const projectId = getCurrentProjectId(); if (!projectId) return ['Project not loaded']; From 166eae53ac7df33647dd9f22721a134de971d5f3 Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sun, 17 Nov 2024 18:48:49 +0100 Subject: [PATCH 10/14] update web mentions of RTV's --- .../home/components/molecules/FeatureOverview.tsx | 12 ++++++------ .../pricing/components/organisms/PricingCard.tsx | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps-web/marketing/src/features/home/components/molecules/FeatureOverview.tsx b/apps-web/marketing/src/features/home/components/molecules/FeatureOverview.tsx index d5b1ad2d..122acdaa 100644 --- a/apps-web/marketing/src/features/home/components/molecules/FeatureOverview.tsx +++ b/apps-web/marketing/src/features/home/components/molecules/FeatureOverview.tsx @@ -34,7 +34,7 @@ const FeatureOverview: React.FC> = () => { {'Powerful feature set'} - {'From support for large API projects, realtime values, '} + {'From support for large API projects, variables, '} {'rich value editors, to baked in project encryption, for '} {'your most secretive of secrets 🤫.'} @@ -48,12 +48,12 @@ const FeatureOverview: React.FC> = () => { /> - {'Realtime values'} + {'Variables'} - {'Realtime values are inline variables you can insert into '} - {'any part of your request that are calculated in real time '} - {'as you type, and as you send requests. '} + {'Variables are inline values you can insert into any part '} + {'of your request that are calculated dynamically in real '} + {'time as you type, and as you send requests.'} @@ -105,7 +105,7 @@ const FeatureOverview: React.FC> = () => { {'Coming soon, is an expansive extensions API, allowing you '} - {'to create custom extensions for realtime values, '} + {'to create custom extensions for variables, '} {'API workflows, and more. Make Beak your own.'} diff --git a/apps-web/marketing/src/features/pricing/components/organisms/PricingCard.tsx b/apps-web/marketing/src/features/pricing/components/organisms/PricingCard.tsx index 6c4ee974..1a065e39 100644 --- a/apps-web/marketing/src/features/pricing/components/organisms/PricingCard.tsx +++ b/apps-web/marketing/src/features/pricing/components/organisms/PricingCard.tsx @@ -20,7 +20,7 @@ const versions: Record = { items: [ 'Requests', 'Responses', - 'Realtime values', + 'Variables', 'Literally everything', ], }, @@ -30,7 +30,7 @@ const versions: Record = { items: [ 'Requests', 'Responses', - 'Realtime values', + 'Variables', 'Literally everything', ], }, @@ -40,7 +40,7 @@ const versions: Record = { items: [ 'Requests', 'Responses', - 'Realtime values', + 'Variables', 'Literally everything', ], }, From 369079dfddc15c293ddec3d0003cbfc361cac8c2 Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sun, 17 Nov 2024 18:48:53 +0100 Subject: [PATCH 11/14] update ksuid docs --- docs/ksuid-resources.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/ksuid-resources.md b/docs/ksuid-resources.md index 50dac18e..cf88933f 100644 --- a/docs/ksuid-resources.md +++ b/docs/ksuid-resources.md @@ -12,6 +12,7 @@ While we don't use the environment portion of the KSUID (for obvious reasons), w | `flight` | A flight (or historical request) made by Beak. | | `query` | A query item, used in the request info. | | `header` | A header item, used in the request info. | -| `item` | An item in a variable group. Closely related to `group`. | -| `group` | A group in a variable group. Closely related to `item`. | -| `value` | A value linking an `item` and a `group` from a variable group. | +| `item` | An item in a variable set. Closely related to `set`. | +| `group` | A group in a variable group. Legacy, this is called `set` going forward. Closely related to `item`. | +| `set` | A set in a variable set. Closely related to `item`. | +| `value` | A value linking an `item` and a `set` from a variable set. | From 361a2e6290cf8caa9f798a1ae59c63a04a846010 Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Sun, 17 Nov 2024 18:48:57 +0100 Subject: [PATCH 12/14] update readme and lock --- README.md | 5 +++-- yarn.lock | 50 -------------------------------------------------- 2 files changed, 3 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 50a3041c..88da2615 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,9 @@ Beak is a simple, extensible, and powerful API creation and management tool. Bui Beak includes: - Powerful feature set for creating APIs interactions -- Realtime values for dynamic variable replacement -- Extensions API to create custom realtime values +- Variables, which allow you to use dynamic values in requests +- Variable Sets, which allow you to group variables together and easily switch between sets +- An extensions API to create custom Beak Variables - Un-opinionated project syncing, just use the version control of your choice - Beautiful design language - And of course, fully cross platform diff --git a/yarn.lock b/yarn.lock index 3a86e868..29a59988 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1941,101 +1941,51 @@ tslib "^2.3.0" yargs-parser "21.1.1" -"@nx/nx-darwin-arm64@20.1.1": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.1.1.tgz#6a18f97b03f5dd9057a3864ade0e069b18c34ca4" - integrity sha512-Ah0ShPQaMfvzVfhsyuI6hNB0bmwLHJqqrWldZeF97SFPhv6vfKdcdlZmSnask+V4N5z9TOCUmCMu2asMQa7+kw== - "@nx/nx-darwin-arm64@20.1.2": version "20.1.2" resolved "https://registry.yarnpkg.com/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.1.2.tgz#880dc02d2256c3f6ec98e9ab51e7c88ceedf3610" integrity sha512-PJ91TQhd28kitDBubKUOXMYvrtSDrG+rr8MsIe9cHo1CvU9smcGVBwuHBxniq0DXsyOX/5GL6ngq7hjN2nQ3XQ== -"@nx/nx-darwin-x64@20.1.1": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@nx/nx-darwin-x64/-/nx-darwin-x64-20.1.1.tgz#676cdb657db8560057d1e8d9da2842d20d7f117a" - integrity sha512-TmdX6pbzclvPGsttTTaZhdF46HV1vfvYSHJaSMsYJX68l3gcQnAJ1ZRDksEgkYeAy+O9KrPimD84NM5W/JvqcQ== - "@nx/nx-darwin-x64@20.1.2": version "20.1.2" resolved "https://registry.yarnpkg.com/@nx/nx-darwin-x64/-/nx-darwin-x64-20.1.2.tgz#e9dc306affcb18a9900efbdbcadd514ecd5fbda5" integrity sha512-1fopau7nxIhTF26vDTIzMxl15AtW4FvUSdy+r1mNRKrKyjjpqnlu00SQBW7JzGV0agDD1B/61yYei5Q2aMOt7Q== -"@nx/nx-freebsd-x64@20.1.1": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.1.1.tgz#2dfd02d1432f4279a09aecf5281b194f580e6066" - integrity sha512-7/7f3GbUbdvtTFOb/8wcaSQYkhVIxcC4UzFJM5yEyXPJmIrglk+RX3SLuOFRBFJnO+Z7D6jLUnLOBHKCGfqLVw== - "@nx/nx-freebsd-x64@20.1.2": version "20.1.2" resolved "https://registry.yarnpkg.com/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.1.2.tgz#ca967f9da06f93e0039b92c0c072d0c9a93b7776" integrity sha512-55YgIp3v4zz7xMzJO93dtglbOTER2XdS6jrCt8GbKaWGFl5drRrBoNGONtiGNU7C3hLx1VsorbynCkJT18PjKQ== -"@nx/nx-linux-arm-gnueabihf@20.1.1": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.1.1.tgz#5a9f5ef294e9f0c7db3b7e94b2da33ef33dccb16" - integrity sha512-VxpMz5jCZ5gnk1gP2jDBCheYs7qOwQoJmzGbEB8hNy0CwRH/G8pL4RRo4Sz+4aiF6Z+9eax5RM2/Syh+bS0uJw== - "@nx/nx-linux-arm-gnueabihf@20.1.2": version "20.1.2" resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.1.2.tgz#c9bad02ecbf0c47e1cf1e49bc348e962665074bd" integrity sha512-sMhNA8uAV43UYVEXEa8TZ8Fjpom4CGq1umTptEGOF4TTtdNn2AUBreg+0bVODM8MMSzRWGI1VbkZzHESnAPwqw== -"@nx/nx-linux-arm64-gnu@20.1.1": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.1.1.tgz#dbaad0e10afb1201b2f44f51860190d770c667f0" - integrity sha512-8T2+j4KvsWb6ljW1Y2s/uCSt4Drtlsr3GSrGdvcETW0IKaTfKZAJlxTLAWQHEF88hP6GAJRGxNrgmUHMr8HwUA== - "@nx/nx-linux-arm64-gnu@20.1.2": version "20.1.2" resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.1.2.tgz#713e83f11a20bb1dc3832faf9b08c938b2cf4c31" integrity sha512-bsevarNHglaYLmIvPNQOdHrBnBgaW3EOUM0flwaXdWuZbL1bWx8GoVwHp9yJpZOAOfIF/Nhq5iTpaZB2nYFrAA== -"@nx/nx-linux-arm64-musl@20.1.1": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.1.1.tgz#df883505388069ddf91226db39c51c594c10a1d6" - integrity sha512-TI964w+HFUqG6elriKwQPRX7QRxVRMz5YKdNPgf4+ab4epQ379kwJQEHlyOHR72ir8Tl46z3BoPjvmaLylrT4Q== - "@nx/nx-linux-arm64-musl@20.1.2": version "20.1.2" resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.1.2.tgz#5091e70d20603b7cd70b0bd6022b07c0a9779d79" integrity sha512-GFZTptkhZPL/iZ3tYDmspIcPEaXyy/L/o59gyp33GoFAAyDhiXIF7J1Lz81Xn8VKrX6TvEY8/9qSh86pb7qzDQ== -"@nx/nx-linux-x64-gnu@20.1.1": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.1.1.tgz#97bee1fffca92218104da87a8112b3554834fa41" - integrity sha512-Sg2tQ0v3KP9cAqQST16YR+dT/NbirPts6by+A4vhOtaBrZFVqm9P89K9UdcJf4Aj1CaGbs84lotp2aM4E4bQPA== - "@nx/nx-linux-x64-gnu@20.1.2": version "20.1.2" resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.1.2.tgz#8c0089fd7e797950bbe6e6898c0762bbfa55716d" integrity sha512-yqEW/iglKT4d9lgfnwSNhmDzPxCkRhtdmZqOYpGDM0eZFwYwJF+WRGjW8xIqMj8PA1yrGItzXZOmyFjJqHAF2w== -"@nx/nx-linux-x64-musl@20.1.1": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.1.1.tgz#f4337d21bf3c913255842d1f704ba8e725b10fec" - integrity sha512-ekKvuIMRJRhZnkWIWEr4TRVEAyKVDgEMwqk83ilB0Mqpj2RoOKbw7jZFvWcxJWI4kSeZjTea3xCWGNPa1GfCww== - "@nx/nx-linux-x64-musl@20.1.2": version "20.1.2" resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.1.2.tgz#121d6c276b97017f62168c0a018e709ef679ca01" integrity sha512-SP6PpWT4cQVrC4WJQdpfADrYJQzkbhgmcGleWbpr7II1HJgOsAcvoDwQGpPQX+3Wo+VBiNecvUAOzacMQkXPGw== -"@nx/nx-win32-arm64-msvc@20.1.1": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.1.1.tgz#f48cbefb3abe08762e5ecaefce9aab787a5350b4" - integrity sha512-JRycFkk6U8A1sXaDmSFA2HMKT2js3HK/+nI+auyITRqVbV79/r6ir/oFSgIjKth8j/vVbGDL8I4E3nEQ7leZYw== - "@nx/nx-win32-arm64-msvc@20.1.2": version "20.1.2" resolved "https://registry.yarnpkg.com/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.1.2.tgz#37a57b4c21e23bb377a5ddffa034939f2e465211" integrity sha512-JZQx9gr39LY3D7uleiXlpxUsavuOrOQNBocwKHkAMnykaT/e1VCxTnm/hk+2b4foWwfURTqoRiFEba70iiCdYg== -"@nx/nx-win32-x64-msvc@20.1.1": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.1.1.tgz#17e0277f4a31a044f2bcee90d605e9d77c8d6fcc" - integrity sha512-VwxmJU7o8KqTZ+KYk7atoWOUykKd8D4hdgKqqltdq/UBfsAWD/JCFt5OB/VFvrGDbK6I6iKpMvXWlHy4gkXQiw== - "@nx/nx-win32-x64-msvc@20.1.2": version "20.1.2" resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.1.2.tgz#8b24ea16d06e5ebf97b9b9438bae5e3c2f57a0d3" From 991076855250b4e59db34e5bb105e9a3e1c64640 Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Mon, 18 Nov 2024 08:35:56 +0100 Subject: [PATCH 13/14] improve comment --- .../variable-input/components/VariableInput.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/features/variable-input/components/VariableInput.tsx b/packages/ui/src/features/variable-input/components/VariableInput.tsx index a8089b9c..b03c7c74 100644 --- a/packages/ui/src/features/variable-input/components/VariableInput.tsx +++ b/packages/ui/src/features/variable-input/components/VariableInput.tsx @@ -458,13 +458,22 @@ const VariableInput = React.forwardRef((props, setShowSelector(false); setQuery(''); - // if (editableRef.current && unmanagedStateRef.current.variableSelectionState) { - // const node = editableRef.current.childNodes[unmanagedStateRef.current.variableSelectionState.queryStartSelection.partIndex]; + if (!unmanagedStateRef.current.variableSelectionState) + return; + + // const { queryStartSelection } = unmanagedStateRef.current.variableSelectionState; - // if (node && node.nodeName == 'SPAN') { + // if (editableRef.current) { + // const node = editableRef.current.childNodes[queryStartSelection.partIndex]; + + // if (node && node.nodeName === 'SPAN') { // const span = node as HTMLSpanElement; - // span.innerText = span.innerText.substring(0, unmanagedStateRef.current.variableSelectionState.queryStartSelection.offset); + // console.log('span', span.innerText); + // console.log('queryStartSelection.offset', queryStartSelection.offset); + // console.log('queryStartSelection.partIndex', queryStartSelection.partIndex); + + // span.innerText = span.innerText.substring(0, queryStartSelection.offset); // } // } From 8656a4ad64fc8a1269f962bd3ef74844d7a99a18 Mon Sep 17 00:00:00 2001 From: Alexander Forbes-Reed Date: Mon, 18 Nov 2024 21:13:17 +0100 Subject: [PATCH 14/14] change after-pack to esm --- apps-host/electron/scripts/after-pack.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps-host/electron/scripts/after-pack.js b/apps-host/electron/scripts/after-pack.js index 5ea24238..af907966 100644 --- a/apps-host/electron/scripts/after-pack.js +++ b/apps-host/electron/scripts/after-pack.js @@ -1,13 +1,13 @@ /* eslint-disable no-sync */ /* eslint-disable @typescript-eslint/no-var-requires */ -const asar = require('asar'); -const path = require('path'); -const fs = require('fs'); -const os = require('os'); +import asar from 'asar'; +import path from 'path'; +import fs from 'fs'; +import os from 'os'; const architectures = ['ia32', 'x64', 'armv7l', 'arm64', 'universal']; -module.exports = async function afterPack(context) { +export default async function afterPack(context) { const arch = architectures[context.arch]; const platform = context.packager.platform.nodeName; const tempDirPath = path.join(os.tmpdir(), Date.now().toString());