diff --git a/packages/hoppscotch-app/assets/scss/styles.scss b/packages/hoppscotch-app/assets/scss/styles.scss
index 4da6bc012..2f585bb51 100644
--- a/packages/hoppscotch-app/assets/scss/styles.scss
+++ b/packages/hoppscotch-app/assets/scss/styles.scss
@@ -132,6 +132,7 @@ a {
.cm-tooltip {
.tippy-box {
+ @apply shadow-none;
@apply fixed;
@apply inline-flex;
@apply -mt-6;
@@ -166,10 +167,9 @@ a {
}
.env-icon {
+ @apply transition;
@apply inline-flex;
@apply items-center;
- @apply mr-1;
- @apply text-accentDark;
}
}
diff --git a/packages/hoppscotch-app/src/components/environments/index.vue b/packages/hoppscotch-app/src/components/environments/index.vue
index d5ccf86b2..df9b86520 100644
--- a/packages/hoppscotch-app/src/components/environments/index.vue
+++ b/packages/hoppscotch-app/src/components/environments/index.vue
@@ -155,9 +155,9 @@
@update-selected-team="updateSelectedTeam"
/>
-
+
Environment["variables"]
}>(),
{
diff --git a/packages/hoppscotch-app/src/components/environments/my/index.vue b/packages/hoppscotch-app/src/components/environments/my/index.vue
index 1979197c7..17557af95 100644
--- a/packages/hoppscotch-app/src/components/environments/my/index.vue
+++ b/packages/hoppscotch-app/src/components/environments/my/index.vue
@@ -64,6 +64,7 @@
:show="showModalDetails"
:action="action"
:editing-environment-index="editingEnvironmentIndex"
+ :editing-variable-name="editingVariableName"
@hide-modal="displayModalEdit(false)"
/>
("edit")
const editingEnvironmentIndex = ref(null)
+const editingVariableName = ref("")
const displayModalAdd = (shouldDisplay: boolean) => {
action.value = "new"
@@ -122,4 +126,17 @@ const editEnvironment = (environmentIndex: number | "Global") => {
const resetSelectedData = () => {
editingEnvironmentIndex.value = null
}
+
+defineActionHandler(
+ "modals.my.environment.edit",
+ ({ envName, variableName }) => {
+ editingVariableName.value = variableName
+ const envIndex: number = environments.value.findIndex(
+ (environment: Environment) => {
+ return environment.name === envName
+ }
+ )
+ editEnvironment(envIndex >= 0 ? envIndex : "Global")
+ }
+)
diff --git a/packages/hoppscotch-app/src/components/environments/teams/Details.vue b/packages/hoppscotch-app/src/components/environments/teams/Details.vue
index 1a3abe80b..904f0ad02 100644
--- a/packages/hoppscotch-app/src/components/environments/teams/Details.vue
+++ b/packages/hoppscotch-app/src/components/environments/teams/Details.vue
@@ -65,6 +65,7 @@
/>
(),
{
diff --git a/packages/hoppscotch-app/src/components/environments/teams/index.vue b/packages/hoppscotch-app/src/components/environments/teams/index.vue
index e4910d614..0ef9bdac1 100644
--- a/packages/hoppscotch-app/src/components/environments/teams/index.vue
+++ b/packages/hoppscotch-app/src/components/environments/teams/index.vue
@@ -102,6 +102,7 @@
:action="action"
:editing-environment="editingEnvironment"
:editing-team-id="team?.id"
+ :editing-variable-name="editingVariableName"
:is-viewer="team?.myRole === 'VIEWER'"
@hide-modal="displayModalEdit(false)"
/>
@@ -125,6 +126,7 @@ import IconPlus from "~icons/lucide/plus"
import IconArchive from "~icons/lucide/archive"
import IconHelpCircle from "~icons/lucide/help-circle"
import { Team } from "~/helpers/backend/graphql"
+import { defineActionHandler } from "~/helpers/actions"
const t = useI18n()
@@ -132,7 +134,7 @@ const colorMode = useColorMode()
type SelectedTeam = Team | undefined
-defineProps<{
+const props = defineProps<{
team: SelectedTeam
teamEnvironments: TeamEnvironment[]
adapterError: GQLError | null
@@ -143,6 +145,7 @@ const showModalImportExport = ref(false)
const showModalDetails = ref(false)
const action = ref<"new" | "edit">("edit")
const editingEnvironment = ref(null)
+const editingVariableName = ref("")
const displayModalAdd = (shouldDisplay: boolean) => {
action.value = "new"
@@ -178,4 +181,15 @@ const getErrorMessage = (err: GQLError) => {
}
}
}
+
+defineActionHandler(
+ "modals.team.environment.edit",
+ ({ envName, variableName }) => {
+ editingVariableName.value = variableName
+ const teamEnvToEdit = props.teamEnvironments.find(
+ (environment) => environment.environment.name === envName
+ )
+ if (teamEnvToEdit) editEnvironment(teamEnvToEdit)
+ }
+)
diff --git a/packages/hoppscotch-app/src/components/smart/EnvInput.vue b/packages/hoppscotch-app/src/components/smart/EnvInput.vue
index 2da72850b..dbcbf36d8 100644
--- a/packages/hoppscotch-app/src/components/smart/EnvInput.vue
+++ b/packages/hoppscotch-app/src/components/smart/EnvInput.vue
@@ -27,7 +27,7 @@ import {
keymap,
tooltips,
} from "@codemirror/view"
-import { EditorState, Extension } from "@codemirror/state"
+import { EditorSelection, EditorState, Extension } from "@codemirror/state"
import { clone } from "lodash-es"
import { history, historyKeymap } from "@codemirror/commands"
import { inputTheme } from "~/helpers/editor/themes/baseTheme"
@@ -42,6 +42,7 @@ const props = withDefaults(
styles?: string
envs?: { key: string; value: string; source: string }[] | null
focus?: boolean
+ selectTextOnMount?: boolean
readonly?: boolean
}>(),
{
@@ -203,15 +204,28 @@ const initView = (el: any) => {
})
}
+const triggerTextSelection = () => {
+ nextTick(() => {
+ view.value?.focus()
+ view.value?.dispatch({
+ selection: EditorSelection.create([
+ EditorSelection.range(0, props.modelValue.length),
+ ]),
+ })
+ })
+}
+
onMounted(() => {
if (editor.value) {
if (!view.value) initView(editor.value)
+ if (props.selectTextOnMount) triggerTextSelection()
}
})
watch(editor, () => {
if (editor.value) {
if (!view.value) initView(editor.value)
+ if (props.selectTextOnMount) triggerTextSelection()
} else {
view.value?.destroy()
view.value = undefined
diff --git a/packages/hoppscotch-app/src/helpers/actions.ts b/packages/hoppscotch-app/src/helpers/actions.ts
index 89f43640e..deb0dbe3b 100644
--- a/packages/hoppscotch-app/src/helpers/actions.ts
+++ b/packages/hoppscotch-app/src/helpers/actions.ts
@@ -22,6 +22,8 @@ export type HoppAction =
| "modals.search.toggle" // Shows the search modal
| "modals.support.toggle" // Shows the support modal
| "modals.share.toggle" // Shows the share modal
+ | "modals.my.environment.edit" // Edit current personal environment
+ | "modals.team.environment.edit" // Edit current team environment
| "navigation.jump.rest" // Jump to REST page
| "navigation.jump.graphql" // Jump to GraphQL page
| "navigation.jump.realtime" // Jump to realtime page
@@ -36,31 +38,101 @@ export type HoppAction =
| "response.file.download" // Download response as file
| "response.copy" // Copy response to clipboard
+/**
+ * Defines the arguments, if present for a given type that is required to be passed on
+ * invocation and will be passed to action handlers.
+ *
+ * This type is supposed to be an object with the key being one of the actions mentioned above.
+ * The value to the key can be anything.
+ * If an action has no argument, you do not need to add it to this type.
+ *
+ * NOTE: We can't enforce type checks to make sure the key is Action, you
+ * will know if you got something wrong if there is a type error in this file
+ */
+type HoppActionArgs = {
+ "modals.my.environment.edit": {
+ envName: string
+ variableName: string
+ }
+ "modals.team.environment.edit": {
+ envName: string
+ variableName: string
+ }
+}
+
+/**
+ * HoppActions which require arguments for their invocation
+ */
+type HoppActionWithArgs = keyof HoppActionArgs
+
+/**
+ * HoppActions which do not require arguments for their invocation
+ */
+type HoppActionWithNoArgs = Exclude
+
+/**
+ * Resolves the argument type for a given HoppAction
+ */
+type ArgOfHoppAction = A extends HoppActionWithArgs
+ ? HoppActionArgs[A]
+ : undefined
+
+/**
+ * Resolves the action function for a given HoppAction, used by action handler function defs
+ */
+type ActionFunc = A extends HoppActionWithArgs
+ ? (arg: ArgOfHoppAction) => void
+ : () => void
+
type BoundActionList = {
// eslint-disable-next-line no-unused-vars
- [_ in HoppAction]?: Array<() => void>
+ [A in HoppAction]?: Array>
}
const boundActions: BoundActionList = {}
export const activeActions$ = new BehaviorSubject([])
-export function bindAction(action: HoppAction, handler: () => void) {
+export function bindAction(
+ action: A,
+ handler: ActionFunc
+) {
if (boundActions[action]) {
boundActions[action]?.push(handler)
} else {
- boundActions[action] = [handler]
+ // 'any' assertion because TypeScript doesn't seem to be able to figure out the links.
+ boundActions[action] = [handler] as any
}
activeActions$.next(Object.keys(boundActions) as HoppAction[])
}
-export function invokeAction(action: HoppAction) {
- boundActions[action]?.forEach((handler) => handler())
+type InvokeActionFunc = {
+ (action: HoppActionWithNoArgs, args?: undefined): void
+ (action: A, args: ArgOfHoppAction): void
}
-export function unbindAction(action: HoppAction, handler: () => void) {
- boundActions[action] = boundActions[action]?.filter((x) => x !== handler)
+/**
+ * Invokes a action, triggering action handlers if any registered.
+ * The second argument parameter is optional if your action has no args required
+ * @param action The action to fire
+ * @param args The argument passed to the action handler. Optional if action has no args required
+ */
+export const invokeAction: InvokeActionFunc = (
+ action: A,
+ args: ArgOfHoppAction
+) => {
+ boundActions[action]?.forEach((handler) => handler(args!))
+}
+
+export function unbindAction(
+ action: A,
+ handler: ActionFunc
+) {
+ // 'any' assertion because TypeScript doesn't seem to be able to figure out the links.
+ boundActions[action] = boundActions[action]?.filter(
+ (x) => x !== handler
+ ) as any
if (boundActions[action]?.length === 0) {
delete boundActions[action]
@@ -69,7 +141,10 @@ export function unbindAction(action: HoppAction, handler: () => void) {
activeActions$.next(Object.keys(boundActions) as HoppAction[])
}
-export function defineActionHandler(action: HoppAction, handler: () => void) {
+export function defineActionHandler(
+ action: A,
+ handler: ActionFunc
+) {
onMounted(() => {
bindAction(action, handler)
})
diff --git a/packages/hoppscotch-app/src/helpers/editor/extensions/HoppEnvironment.ts b/packages/hoppscotch-app/src/helpers/editor/extensions/HoppEnvironment.ts
index 497459c89..5acfadce7 100644
--- a/packages/hoppscotch-app/src/helpers/editor/extensions/HoppEnvironment.ts
+++ b/packages/hoppscotch-app/src/helpers/editor/extensions/HoppEnvironment.ts
@@ -16,6 +16,7 @@ import {
getAggregateEnvs,
getSelectedEnvironmentType,
} from "~/newstore/environments"
+import { invokeAction } from "~/helpers/actions"
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
@@ -58,17 +59,13 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
)
return null
- const envName =
- aggregateEnvs.find(
- (env) => env.key === text.slice(start - from, end - from)
- // env.key === word.slice(wordSelection.from + 2, wordSelection.to - 2)
- )?.sourceEnv ?? "Choose an Environment"
+ const parsedEnvKey = text.slice(start - from, end - from)
- const envValue =
- aggregateEnvs.find(
- (env) => env.key === text.slice(start - from, end - from)
- // env.key === word.slice(wordSelection.from + 2, wordSelection.to - 2)
- )?.value ?? "Not found"
+ const tooltipEnv = aggregateEnvs.find((env) => env.key === parsedEnvKey)
+
+ const envName = tooltipEnv?.sourceEnv ?? "Choose an Environment"
+
+ const envValue = tooltipEnv?.value ?? "Not found"
const result = parseTemplateStringE(envValue, aggregateEnvs)
@@ -76,9 +73,27 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
const selectedEnvType = getSelectedEnvironmentType()
- const envTypeIcon = `${
+ const envTypeIcon = `${
selectedEnvType === "TEAM_ENV" ? "people" : "person"
}`
+
+ const appendEditAction = (tooltip: HTMLElement) => {
+ const editIcon = document.createElement("span")
+ editIcon.className =
+ "env-icon ml-2 text-accent cursor-pointer hover:text-accentDark"
+ editIcon.addEventListener("click", () => {
+ const isPersonalEnv =
+ envName === "Global" || selectedEnvType !== "TEAM_ENV"
+ const action = isPersonalEnv ? "my" : "team"
+ invokeAction(`modals.${action}.environment.edit`, {
+ envName,
+ variableName: parsedEnvKey,
+ })
+ })
+ editIcon.innerHTML = `drive_file_rename_outline`
+ tooltip.appendChild(editIcon)
+ }
+
return {
pos: start,
end: to,
@@ -90,11 +105,12 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
const kbd = document.createElement("kbd")
const icon = document.createElement("span")
icon.innerHTML = envTypeIcon
- icon.className = "env-icon"
+ icon.className = "env-icon mr-2"
kbd.textContent = finalEnv
tooltipContainer.appendChild(icon)
tooltipContainer.appendChild(document.createTextNode(`${envName} `))
tooltipContainer.appendChild(kbd)
+ if (tooltipEnv) appendEditAction(tooltipContainer)
tooltipContainer.className = "tippy-content"
dom.className = "tippy-box"
dom.dataset.theme = "tooltip"
diff --git a/packages/hoppscotch-app/src/helpers/editor/themes/baseTheme.ts b/packages/hoppscotch-app/src/helpers/editor/themes/baseTheme.ts
index 2e3567b94..24e65634e 100644
--- a/packages/hoppscotch-app/src/helpers/editor/themes/baseTheme.ts
+++ b/packages/hoppscotch-app/src/helpers/editor/themes/baseTheme.ts
@@ -102,11 +102,14 @@ export const baseTheme = EditorView.theme({
border: "none",
borderRadius: "4px",
},
+ ".cm-tooltip-arrow": {
+ color: "var(--tooltip-color)",
+ },
".cm-tooltip-arrow:after": {
- borderTopColor: "var(--tooltip-color) !important",
+ borderTopColor: "inherit !important",
},
".cm-tooltip-arrow:before": {
- borderTopColor: "var(--tooltip-color) !important",
+ borderTopColor: "inherit !important",
},
".cm-tooltip.cm-tooltip-autocomplete > ul": {
fontFamily: "var(--font-mono)",
@@ -234,11 +237,14 @@ export const inputTheme = EditorView.theme({
border: "none",
borderRadius: "4px",
},
+ ".cm-tooltip-arrow": {
+ color: "var(--tooltip-color)",
+ },
".cm-tooltip-arrow:after": {
- borderTopColor: "var(--tooltip-color) !important",
+ borderTopColor: "currentColor !important",
},
".cm-tooltip-arrow:before": {
- borderTopColor: "var(--tooltip-color) !important",
+ borderTopColor: "currentColor !important",
},
".cm-tooltip.cm-tooltip-autocomplete > ul": {
fontFamily: "var(--font-mono)",