Compare commits
41 Commits
feat/condi
...
chore/dock
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c9f8ed4e8 | ||
|
|
f8bbf320fb | ||
|
|
633d98bbbc | ||
|
|
44fabe6570 | ||
|
|
8acfe8afb0 | ||
|
|
e233f36ce0 | ||
|
|
e1cbe6e003 | ||
|
|
1c35ea6e65 | ||
|
|
6eb0426aca | ||
|
|
fc0c113e00 | ||
|
|
9e595ec594 | ||
|
|
1b1a09c675 | ||
|
|
6454d83486 | ||
|
|
191fa376d2 | ||
|
|
6efae3a395 | ||
|
|
cb8678f07f | ||
|
|
b32b0f9bcb | ||
|
|
5a91fb53b2 | ||
|
|
b0b6edc58e | ||
|
|
8c57d81718 | ||
|
|
10bb68a538 | ||
|
|
d4d1e27ba9 | ||
|
|
d5c887f311 | ||
|
|
ce7adf6da3 | ||
|
|
c626fb9241 | ||
|
|
f21ed30e10 | ||
|
|
b55970cc7a | ||
|
|
74ad2e43a4 | ||
|
|
2d6282cf8b | ||
|
|
e255c46455 | ||
|
|
15c2c7bb5b | ||
|
|
71bcd22444 | ||
|
|
2d104160f2 | ||
|
|
f7c1825de5 | ||
|
|
2c1fd5d711 | ||
|
|
085fbb2a9b | ||
|
|
05f2d8817b | ||
|
|
81fbb22c51 | ||
|
|
01cf59c663 | ||
|
|
5c8ebaff3e | ||
|
|
0e70c28324 |
@@ -1 +1,2 @@
|
|||||||
*/**/node_modules
|
node_modules
|
||||||
|
**/*/node_modules
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ SESSION_SECRET='add some secret here'
|
|||||||
# Hoppscotch App Domain Config
|
# Hoppscotch App Domain Config
|
||||||
REDIRECT_URL="http://localhost:3000"
|
REDIRECT_URL="http://localhost:3000"
|
||||||
WHITELISTED_ORIGINS = "http://localhost:3170,http://localhost:3000,http://localhost:3100"
|
WHITELISTED_ORIGINS = "http://localhost:3170,http://localhost:3000,http://localhost:3100"
|
||||||
ALLOWED_AUTH_PROVIDERS = GOOGLE,GITHUB,MICROSOFT,EMAIL
|
VITE_ALLOWED_AUTH_PROVIDERS = GOOGLE,GITHUB,MICROSOFT,EMAIL
|
||||||
|
|
||||||
# Google Auth Config
|
# Google Auth Config
|
||||||
GOOGLE_CLIENT_ID="************************************************"
|
GOOGLE_CLIENT_ID="************************************************"
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
semi: false
|
semi: false,
|
||||||
|
trailingComma: "es5",
|
||||||
|
singleQuote: false,
|
||||||
|
printWidth: 80,
|
||||||
|
useTabs: false,
|
||||||
|
tabWidth: 2
|
||||||
}
|
}
|
||||||
|
|||||||
11
aio.Caddyfile
Normal file
11
aio.Caddyfile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
:3000 {
|
||||||
|
try_files {path} /
|
||||||
|
root * /site/selfhost-web
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
:3100 {
|
||||||
|
try_files {path} /
|
||||||
|
root * /site/sh-admin
|
||||||
|
file_server
|
||||||
|
}
|
||||||
72
aio_run.mjs
Normal file
72
aio_run.mjs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/local/bin/node
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
import { execSync, spawn } from "child_process"
|
||||||
|
import fs from "fs"
|
||||||
|
import process from "process"
|
||||||
|
|
||||||
|
function runChildProcessWithPrefix(command, args, prefix) {
|
||||||
|
const childProcess = spawn(command, args);
|
||||||
|
|
||||||
|
childProcess.stdout.on('data', (data) => {
|
||||||
|
const output = data.toString().trim().split('\n');
|
||||||
|
output.forEach((line) => {
|
||||||
|
console.log(`${prefix} | ${line}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
childProcess.stderr.on('data', (data) => {
|
||||||
|
const error = data.toString().trim().split('\n');
|
||||||
|
error.forEach((line) => {
|
||||||
|
console.error(`${prefix} | ${line}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
childProcess.on('close', (code) => {
|
||||||
|
console.log(`${prefix} Child process exited with code ${code}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
childProcess.on('error', (stuff) => {
|
||||||
|
console.log("error")
|
||||||
|
console.log(stuff)
|
||||||
|
})
|
||||||
|
|
||||||
|
return childProcess
|
||||||
|
}
|
||||||
|
|
||||||
|
const envFileContent = Object.entries(process.env)
|
||||||
|
.filter(([env]) => env.startsWith("VITE_"))
|
||||||
|
.map(([env, val]) => `${env}=${
|
||||||
|
(val.startsWith("\"") && val.endsWith("\""))
|
||||||
|
? val
|
||||||
|
: `"${val}"`
|
||||||
|
}`)
|
||||||
|
.join("\n")
|
||||||
|
|
||||||
|
fs.writeFileSync("build.env", envFileContent)
|
||||||
|
|
||||||
|
execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`)
|
||||||
|
|
||||||
|
fs.rmSync("build.env")
|
||||||
|
|
||||||
|
const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"], "App/Admin Dashboard Caddy")
|
||||||
|
const backendProcess = runChildProcessWithPrefix("pnpm", ["run", "start:prod"], "Backend Server")
|
||||||
|
|
||||||
|
caddyProcess.on("exit", (code) => {
|
||||||
|
console.log(`Exiting process because Caddy Server exited with code ${code}`)
|
||||||
|
process.exit(code)
|
||||||
|
})
|
||||||
|
|
||||||
|
backendProcess.on("exit", (code) => {
|
||||||
|
console.log(`Exiting process because Backend Server exited with code ${code}`)
|
||||||
|
process.exit(code)
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
console.log("SIGINT received, exiting...")
|
||||||
|
|
||||||
|
caddyProcess.kill("SIGINT")
|
||||||
|
backendProcess.kill("SIGINT")
|
||||||
|
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
@@ -17,12 +17,12 @@
|
|||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/language": "^6.2.0",
|
"@codemirror/language": "^6.9.0",
|
||||||
"@lezer/highlight": "^1.0.0",
|
"@lezer/highlight": "^1.1.6",
|
||||||
"@lezer/lr": "^1.2.0"
|
"@lezer/lr": "^1.3.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lezer/generator": "^1.1.0",
|
"@lezer/generator": "^1.5.0",
|
||||||
"mocha": "^9.2.2",
|
"mocha": "^9.2.2",
|
||||||
"rollup": "^2.70.2",
|
"rollup": "^2.70.2",
|
||||||
"rollup-plugin-dts": "^4.2.1",
|
"rollup-plugin-dts": "^4.2.1",
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
"@nestjs/passport": "^9.0.0",
|
"@nestjs/passport": "^9.0.0",
|
||||||
"@nestjs/platform-express": "^9.2.1",
|
"@nestjs/platform-express": "^9.2.1",
|
||||||
"@nestjs/throttler": "^4.0.0",
|
"@nestjs/throttler": "^4.0.0",
|
||||||
"@prisma/client": "^4.7.1",
|
"@prisma/client": "^4.16.2",
|
||||||
"apollo-server-express": "^3.11.1",
|
"apollo-server-express": "^3.11.1",
|
||||||
"apollo-server-plugin-base": "^3.7.1",
|
"apollo-server-plugin-base": "^3.7.1",
|
||||||
"argon2": "^0.30.3",
|
"argon2": "^0.30.3",
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"passport-microsoft": "^1.0.0",
|
"passport-microsoft": "^1.0.0",
|
||||||
"prisma": "^4.7.1",
|
"prisma": "^4.16.2",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.6.0"
|
"rxjs": "^7.6.0"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ datasource db {
|
|||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
binaryTargets = ["native", "debian-openssl-1.1.x"]
|
binaryTargets = ["native", "debian-openssl-1.1.x", "debian-openssl-3.0.x"]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Team {
|
model Team {
|
||||||
|
|||||||
9
packages/hoppscotch-backend/src/app.controller.ts
Normal file
9
packages/hoppscotch-backend/src/app.controller.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Controller('ping')
|
||||||
|
export class AppController {
|
||||||
|
@Get()
|
||||||
|
ping(): string {
|
||||||
|
return 'Success';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import { UserCollectionModule } from './user-collection/user-collection.module';
|
|||||||
import { ShortcodeModule } from './shortcode/shortcode.module';
|
import { ShortcodeModule } from './shortcode/shortcode.module';
|
||||||
import { COOKIES_NOT_FOUND } from './errors';
|
import { COOKIES_NOT_FOUND } from './errors';
|
||||||
import { ThrottlerModule } from '@nestjs/throttler';
|
import { ThrottlerModule } from '@nestjs/throttler';
|
||||||
|
import { AppController } from './app/app.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -81,5 +82,6 @@ import { ThrottlerModule } from '@nestjs/throttler';
|
|||||||
ShortcodeModule,
|
ShortcodeModule,
|
||||||
],
|
],
|
||||||
providers: [GQLComplexityPlugin],
|
providers: [GQLComplexityPlugin],
|
||||||
|
controllers: [AppController],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export const subscriptionContextCookieParser = (rawCookies: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check to see if given auth provider is present in the ALLOWED_AUTH_PROVIDERS env variable
|
* Check to see if given auth provider is present in the VITE_ALLOWED_AUTH_PROVIDERS env variable
|
||||||
*
|
*
|
||||||
* @param provider Provider we want to check the presence of
|
* @param provider Provider we want to check the presence of
|
||||||
* @returns Boolean if provider specified is present or not
|
* @returns Boolean if provider specified is present or not
|
||||||
@@ -117,8 +117,8 @@ export function authProviderCheck(provider: string) {
|
|||||||
throwErr(AUTH_PROVIDER_NOT_SPECIFIED);
|
throwErr(AUTH_PROVIDER_NOT_SPECIFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
const envVariables = process.env.ALLOWED_AUTH_PROVIDERS
|
const envVariables = process.env.VITE_ALLOWED_AUTH_PROVIDERS
|
||||||
? process.env.ALLOWED_AUTH_PROVIDERS.split(',').map((provider) =>
|
? process.env.VITE_ALLOWED_AUTH_PROVIDERS.split(',').map((provider) =>
|
||||||
provider.trim().toUpperCase(),
|
provider.trim().toUpperCase(),
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|||||||
@@ -29,22 +29,22 @@ export const JSON_INVALID = 'json_invalid';
|
|||||||
export const AUTH_PROVIDER_NOT_SPECIFIED = 'auth/provider_not_specified';
|
export const AUTH_PROVIDER_NOT_SPECIFIED = 'auth/provider_not_specified';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Environment variable "ALLOWED_AUTH_PROVIDERS" is not present in .env file
|
* Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" is not present in .env file
|
||||||
*/
|
*/
|
||||||
export const ENV_NOT_FOUND_KEY_AUTH_PROVIDERS =
|
export const ENV_NOT_FOUND_KEY_AUTH_PROVIDERS =
|
||||||
'"ALLOWED_AUTH_PROVIDERS" is not present in .env file';
|
'"VITE_ALLOWED_AUTH_PROVIDERS" is not present in .env file';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Environment variable "ALLOWED_AUTH_PROVIDERS" is empty in .env file
|
* Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" is empty in .env file
|
||||||
*/
|
*/
|
||||||
export const ENV_EMPTY_AUTH_PROVIDERS =
|
export const ENV_EMPTY_AUTH_PROVIDERS =
|
||||||
'"ALLOWED_AUTH_PROVIDERS" is empty in .env file';
|
'"VITE_ALLOWED_AUTH_PROVIDERS" is empty in .env file';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Environment variable "ALLOWED_AUTH_PROVIDERS" contains unsupported provider in .env file
|
* Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" contains unsupported provider in .env file
|
||||||
*/
|
*/
|
||||||
export const ENV_NOT_SUPPORT_AUTH_PROVIDERS =
|
export const ENV_NOT_SUPPORT_AUTH_PROVIDERS =
|
||||||
'"ALLOWED_AUTH_PROVIDERS" contains an unsupported auth provider in .env file';
|
'"VITE_ALLOWED_AUTH_PROVIDERS" contains an unsupported auth provider in .env file';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tried to delete a user data document from fb firestore but failed.
|
* Tried to delete a user data document from fb firestore but failed.
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import * as E from 'fp-ts/Either';
|
|||||||
import * as A from 'fp-ts/Array';
|
import * as A from 'fp-ts/Array';
|
||||||
import { TeamMemberRole } from './team/team.model';
|
import { TeamMemberRole } from './team/team.model';
|
||||||
import { User } from './user/user.model';
|
import { User } from './user/user.model';
|
||||||
import { ENV_EMPTY_AUTH_PROVIDERS, ENV_NOT_FOUND_KEY_AUTH_PROVIDERS, ENV_NOT_SUPPORT_AUTH_PROVIDERS, JSON_INVALID } from './errors';
|
import {
|
||||||
|
ENV_EMPTY_AUTH_PROVIDERS,
|
||||||
|
ENV_NOT_FOUND_KEY_AUTH_PROVIDERS,
|
||||||
|
ENV_NOT_SUPPORT_AUTH_PROVIDERS,
|
||||||
|
JSON_INVALID,
|
||||||
|
} from './errors';
|
||||||
import { AuthProvider } from './auth/helper';
|
import { AuthProvider } from './auth/helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,21 +161,21 @@ export function isValidLength(title: string, length: number) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is called by bootstrap() in main.ts
|
* This function is called by bootstrap() in main.ts
|
||||||
* It checks if the "ALLOWED_AUTH_PROVIDERS" environment variable is properly set or not.
|
* It checks if the "VITE_ALLOWED_AUTH_PROVIDERS" environment variable is properly set or not.
|
||||||
* If not, it throws an error.
|
* If not, it throws an error.
|
||||||
*/
|
*/
|
||||||
export function checkEnvironmentAuthProvider() {
|
export function checkEnvironmentAuthProvider() {
|
||||||
if (!process.env.hasOwnProperty('ALLOWED_AUTH_PROVIDERS')) {
|
if (!process.env.hasOwnProperty('VITE_ALLOWED_AUTH_PROVIDERS')) {
|
||||||
throw new Error(ENV_NOT_FOUND_KEY_AUTH_PROVIDERS);
|
throw new Error(ENV_NOT_FOUND_KEY_AUTH_PROVIDERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.ALLOWED_AUTH_PROVIDERS === '') {
|
if (process.env.VITE_ALLOWED_AUTH_PROVIDERS === '') {
|
||||||
throw new Error(ENV_EMPTY_AUTH_PROVIDERS);
|
throw new Error(ENV_EMPTY_AUTH_PROVIDERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
const givenAuthProviders = process.env.ALLOWED_AUTH_PROVIDERS.split(',').map(
|
const givenAuthProviders = process.env.VITE_ALLOWED_AUTH_PROVIDERS.split(
|
||||||
(provider) => provider.toLocaleUpperCase(),
|
',',
|
||||||
);
|
).map((provider) => provider.toLocaleUpperCase());
|
||||||
const supportedAuthProviders = Object.values(AuthProvider).map(
|
const supportedAuthProviders = Object.values(AuthProvider).map(
|
||||||
(provider: string) => provider.toLocaleUpperCase(),
|
(provider: string) => provider.toLocaleUpperCase(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,8 +29,18 @@ module.exports = {
|
|||||||
"import/named": "off", // because, named import issue with typescript see: https://github.com/typescript-eslint/typescript-eslint/issues/154
|
"import/named": "off", // because, named import issue with typescript see: https://github.com/typescript-eslint/typescript-eslint/issues/154
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
"no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
"prettier/prettier":
|
"prettier/prettier": [
|
||||||
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
semi: false,
|
||||||
|
trailingComma: "es5",
|
||||||
|
singleQuote: false,
|
||||||
|
printWidth: 80,
|
||||||
|
useTabs: false,
|
||||||
|
tabWidth: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
"vue/multi-word-component-names": "off",
|
"vue/multi-word-component-names": "off",
|
||||||
"vue/no-side-effects-in-computed-properties": "off",
|
"vue/no-side-effects-in-computed-properties": "off",
|
||||||
"import/no-named-as-default": "off",
|
"import/no-named-as-default": "off",
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
semi: false
|
semi: false,
|
||||||
|
trailingComma: "es5",
|
||||||
|
singleQuote: false,
|
||||||
|
printWidth: 80,
|
||||||
|
useTabs: false,
|
||||||
|
tabWidth: 2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,10 +190,11 @@ a {
|
|||||||
@apply border-solid border-dividerDark;
|
@apply border-solid border-dividerDark;
|
||||||
@apply rounded;
|
@apply rounded;
|
||||||
@apply shadow-lg;
|
@apply shadow-lg;
|
||||||
|
@apply max-w-[45vw] #{!important};
|
||||||
|
|
||||||
.tippy-content {
|
.tippy-content {
|
||||||
@apply flex flex-col;
|
@apply flex flex-col;
|
||||||
@apply max-h-56;
|
@apply max-h-[45vh];
|
||||||
@apply items-stretch;
|
@apply items-stretch;
|
||||||
@apply overflow-y-auto;
|
@apply overflow-y-auto;
|
||||||
@apply text-secondary text-body;
|
@apply text-secondary text-body;
|
||||||
@@ -201,6 +202,10 @@ a {
|
|||||||
@apply leading-normal;
|
@apply leading-normal;
|
||||||
@apply focus:outline-none;
|
@apply focus:outline-none;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
@apply block #{!important};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tippy-svg-arrow {
|
.tippy-svg-arrow {
|
||||||
@@ -216,6 +221,7 @@ a {
|
|||||||
|
|
||||||
[data-v-tippy] {
|
[data-v-tippy] {
|
||||||
@apply flex flex-1;
|
@apply flex flex-1;
|
||||||
|
@apply truncate;
|
||||||
}
|
}
|
||||||
|
|
||||||
[interactive] > div {
|
[interactive] > div {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@mixin base-theme {
|
@mixin base-theme {
|
||||||
--font-sans: "Inter", sans-serif;
|
--font-sans: "Inter Variable", sans-serif;
|
||||||
--font-mono: "Roboto Mono", monospace;
|
--font-icon: "Material Symbols Rounded Variable";
|
||||||
--font-icon: "Material Icons";
|
--font-mono: "Roboto Mono Variable", monospace;
|
||||||
--font-size-tiny: calc(var(--font-size-body) - 0.062rem);
|
--font-size-tiny: calc(var(--font-size-body) - 0.062rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"open_workspace": "Open workspace",
|
"open_workspace": "Open workspace",
|
||||||
"paste": "Paste",
|
"paste": "Paste",
|
||||||
"prettify": "Prettify",
|
"prettify": "Prettify",
|
||||||
|
"rename": "Rename",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"restore": "Restore",
|
"restore": "Restore",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
@@ -68,6 +69,8 @@
|
|||||||
"invite": "Invite",
|
"invite": "Invite",
|
||||||
"invite_description": "Hoppscotch is an open source API development ecosystem. We designed a simple and intuitive interface for creating and managing your APIs. Hoppscotch is a tool that helps you build, test, document and share your APIs.",
|
"invite_description": "Hoppscotch is an open source API development ecosystem. We designed a simple and intuitive interface for creating and managing your APIs. Hoppscotch is a tool that helps you build, test, document and share your APIs.",
|
||||||
"invite_your_friends": "Invite your friends",
|
"invite_your_friends": "Invite your friends",
|
||||||
|
"social_links": "Social links",
|
||||||
|
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
|
||||||
"join_discord_community": "Join our Discord community",
|
"join_discord_community": "Join our Discord community",
|
||||||
"keyboard_shortcuts": "Keyboard shortcuts",
|
"keyboard_shortcuts": "Keyboard shortcuts",
|
||||||
"name": "Hoppscotch",
|
"name": "Hoppscotch",
|
||||||
@@ -132,6 +135,7 @@
|
|||||||
"renamed": "Collection renamed",
|
"renamed": "Collection renamed",
|
||||||
"request_in_use": "Request in use",
|
"request_in_use": "Request in use",
|
||||||
"save_as": "Save as",
|
"save_as": "Save as",
|
||||||
|
"save_to_collection": "Save to Collection",
|
||||||
"select": "Select a Collection",
|
"select": "Select a Collection",
|
||||||
"select_location": "Select location",
|
"select_location": "Select location",
|
||||||
"select_team": "Select a team",
|
"select_team": "Select a team",
|
||||||
@@ -149,6 +153,7 @@
|
|||||||
"remove_telemetry": "Are you sure you want to opt-out of Telemetry?",
|
"remove_telemetry": "Are you sure you want to opt-out of Telemetry?",
|
||||||
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
||||||
"save_unsaved_tab": "Do you want to save changes made in this tab?",
|
"save_unsaved_tab": "Do you want to save changes made in this tab?",
|
||||||
|
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
|
||||||
"sync": "Would you like to restore your workspace from cloud? This will discard your local progress."
|
"sync": "Would you like to restore your workspace from cloud? This will discard your local progress."
|
||||||
},
|
},
|
||||||
"context_menu": {
|
"context_menu": {
|
||||||
@@ -189,6 +194,7 @@
|
|||||||
"schema": "Connect to a GraphQL endpoint to view schema",
|
"schema": "Connect to a GraphQL endpoint to view schema",
|
||||||
"shortcodes": "Shortcodes are empty",
|
"shortcodes": "Shortcodes are empty",
|
||||||
"subscription": "Subscriptions are empty",
|
"subscription": "Subscriptions are empty",
|
||||||
|
"suggestions": "No matching suggestions found",
|
||||||
"team_name": "Team name empty",
|
"team_name": "Team name empty",
|
||||||
"teams": "You don't belong to any teams",
|
"teams": "You don't belong to any teams",
|
||||||
"tests": "There are no tests for this request"
|
"tests": "There are no tests for this request"
|
||||||
@@ -199,18 +205,25 @@
|
|||||||
"create_new": "Create new environment",
|
"create_new": "Create new environment",
|
||||||
"created": "Environment created",
|
"created": "Environment created",
|
||||||
"deleted": "Environment deletion",
|
"deleted": "Environment deletion",
|
||||||
|
"duplicated": "Environment duplicated",
|
||||||
"edit": "Edit Environment",
|
"edit": "Edit Environment",
|
||||||
"global": "Global",
|
"global": "Global",
|
||||||
|
"empty_variables": "No variables",
|
||||||
|
"global_variables": "Global variables",
|
||||||
"invalid_name": "Please provide a name for the environment",
|
"invalid_name": "Please provide a name for the environment",
|
||||||
|
"list": "Environment variables",
|
||||||
"my_environments": "My Environments",
|
"my_environments": "My Environments",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"nested_overflow": "nested environment variables are limited to 10 levels",
|
"nested_overflow": "nested environment variables are limited to 10 levels",
|
||||||
"new": "New Environment",
|
"new": "New Environment",
|
||||||
|
"no_active_environment": "No active environment",
|
||||||
"no_environment": "No environment",
|
"no_environment": "No environment",
|
||||||
"no_environment_description": "No environments were selected. Choose what to do with the following variables.",
|
"no_environment_description": "No environments were selected. Choose what to do with the following variables.",
|
||||||
|
"quick_peek": "Environment Quick Peek",
|
||||||
"replace_with_variable": "Replace with variable",
|
"replace_with_variable": "Replace with variable",
|
||||||
"scope": "Scope",
|
"scope": "Scope",
|
||||||
"select": "Select environment",
|
"select": "Select environment",
|
||||||
|
"set": "Set environment",
|
||||||
"set_as_environment": "Set as environment",
|
"set_as_environment": "Set as environment",
|
||||||
"team_environments": "Team Environments",
|
"team_environments": "Team Environments",
|
||||||
"title": "Environments",
|
"title": "Environments",
|
||||||
@@ -240,6 +253,7 @@
|
|||||||
"no_duration": "No duration",
|
"no_duration": "No duration",
|
||||||
"no_results_found": "No matches found",
|
"no_results_found": "No matches found",
|
||||||
"page_not_found": "This page could not be found",
|
"page_not_found": "This page could not be found",
|
||||||
|
"proxy_error": "Proxy error",
|
||||||
"script_fail": "Could not execute pre-request script",
|
"script_fail": "Could not execute pre-request script",
|
||||||
"something_went_wrong": "Something went wrong",
|
"something_went_wrong": "Something went wrong",
|
||||||
"test_script_fail": "Could not execute post-request script"
|
"test_script_fail": "Could not execute post-request script"
|
||||||
@@ -296,6 +310,30 @@
|
|||||||
"preview": "Hide Preview",
|
"preview": "Hide Preview",
|
||||||
"sidebar": "Collapse sidebar"
|
"sidebar": "Collapse sidebar"
|
||||||
},
|
},
|
||||||
|
"inspections": {
|
||||||
|
"title": "Inspector",
|
||||||
|
"description": "Inspect possible errors",
|
||||||
|
"environment": {
|
||||||
|
"add_environment": "Add to Environment",
|
||||||
|
"not_found": "Environment variable “{environment}” not found."
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"401_error": "Please check your authentication credentials.",
|
||||||
|
"404_error": "Please check your request URL and method type.",
|
||||||
|
"network_error": "Please check your network connection.",
|
||||||
|
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
|
||||||
|
"default_error": "Please check your request."
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"extension_not_installed": "Extension not installed.",
|
||||||
|
"extention_not_enabled": "Extension not enabled.",
|
||||||
|
"extention_enable_action": "Enable Browser Extension",
|
||||||
|
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list."
|
||||||
|
}
|
||||||
|
},
|
||||||
"import": {
|
"import": {
|
||||||
"collections": "Import collections",
|
"collections": "Import collections",
|
||||||
"curl": "Import cURL",
|
"curl": "Import cURL",
|
||||||
@@ -432,6 +470,7 @@
|
|||||||
"payload": "Payload",
|
"payload": "Payload",
|
||||||
"query": "Query",
|
"query": "Query",
|
||||||
"raw_body": "Raw Request Body",
|
"raw_body": "Raw Request Body",
|
||||||
|
"rename": "Rename Request",
|
||||||
"renamed": "Request renamed",
|
"renamed": "Request renamed",
|
||||||
"run": "Run",
|
"run": "Run",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
@@ -473,9 +512,9 @@
|
|||||||
"account_name_description": "This is your display name.",
|
"account_name_description": "This is your display name.",
|
||||||
"background": "Background",
|
"background": "Background",
|
||||||
"black_mode": "Black",
|
"black_mode": "Black",
|
||||||
|
"dark_mode": "Dark",
|
||||||
"change_font_size": "Change font size",
|
"change_font_size": "Change font size",
|
||||||
"choose_language": "Choose language",
|
"choose_language": "Choose language",
|
||||||
"dark_mode": "Dark",
|
|
||||||
"delete_account": "Delete account",
|
"delete_account": "Delete account",
|
||||||
"delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.",
|
"delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.",
|
||||||
"expand_navigation": "Expand navigation",
|
"expand_navigation": "Expand navigation",
|
||||||
@@ -559,6 +598,9 @@
|
|||||||
"delete_method": "Select DELETE method",
|
"delete_method": "Select DELETE method",
|
||||||
"get_method": "Select GET method",
|
"get_method": "Select GET method",
|
||||||
"head_method": "Select HEAD method",
|
"head_method": "Select HEAD method",
|
||||||
|
"rename": "Rename Current Request",
|
||||||
|
"import_curl": "Import cURL",
|
||||||
|
"show_code": "Show generated code",
|
||||||
"method": "Method",
|
"method": "Method",
|
||||||
"next_method": "Select Next method",
|
"next_method": "Select Next method",
|
||||||
"post_method": "Select POST method",
|
"post_method": "Select POST method",
|
||||||
@@ -567,6 +609,7 @@
|
|||||||
"reset_request": "Reset Request",
|
"reset_request": "Reset Request",
|
||||||
"save_to_collections": "Save to Collections",
|
"save_to_collections": "Save to Collections",
|
||||||
"send_request": "Send Request",
|
"send_request": "Send Request",
|
||||||
|
"save_request": "Save Request",
|
||||||
"title": "Request"
|
"title": "Request"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
@@ -575,10 +618,10 @@
|
|||||||
"title": "Response"
|
"title": "Response"
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"black": "Switch theme to black mode",
|
"black": "Switch theme to Black Mode",
|
||||||
"dark": "Switch theme to dark mode",
|
"dark": "Switch theme to Dark Mode",
|
||||||
"light": "Switch theme to light mode",
|
"light": "Switch theme to Light Mode",
|
||||||
"system": "Switch theme to system mode",
|
"system": "Switch theme to System Mode",
|
||||||
"title": "Theme"
|
"title": "Theme"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -597,8 +640,79 @@
|
|||||||
"url": "URL"
|
"url": "URL"
|
||||||
},
|
},
|
||||||
"spotlight": {
|
"spotlight": {
|
||||||
|
"general": {
|
||||||
|
"help_menu": "Open help and support menu",
|
||||||
|
"chat": "Chat with support",
|
||||||
|
"open_docs": "Read Documentation",
|
||||||
|
"open_keybindings": "Open keyboard shortcuts",
|
||||||
|
"social": "Social links and GitHub",
|
||||||
|
"title": "General"
|
||||||
|
},
|
||||||
|
"miscellaneous": {
|
||||||
|
"invite": "Invite people to Hoppscotch",
|
||||||
|
"title": "Miscellaneous"
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"tab_parameters": "Open parameters tab",
|
||||||
|
"tab_body": "Open body tab",
|
||||||
|
"tab_headers": "Open headers tab",
|
||||||
|
"tab_authorization": "Open authorization tab",
|
||||||
|
"tab_pre_request_script": "Open pre-request script tab",
|
||||||
|
"tab_tests": "Open tests tab"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"copy": "Copy response as JSON",
|
||||||
|
"download": "Download response as file",
|
||||||
|
"title": "Response"
|
||||||
|
},
|
||||||
|
"environments": {
|
||||||
|
"new": "Create new environment",
|
||||||
|
"new_variable": "Create a new environment variable",
|
||||||
|
"edit": "Edit selected environment",
|
||||||
|
"delete": "Delete selected environment",
|
||||||
|
"duplicate": "Duplicate selected environment",
|
||||||
|
"edit_global": "Edit global environment",
|
||||||
|
"duplicate_global": "Duplicate global environment",
|
||||||
|
"title": "Environments"
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"new": "Create new team",
|
||||||
|
"edit": "Edit selected team",
|
||||||
|
"delete": "Delete selected team",
|
||||||
|
"invite": "Invite people to team",
|
||||||
|
"switch_to_personal": "Switch to personal workspace",
|
||||||
|
"title": "Teams"
|
||||||
|
},
|
||||||
|
"tab": {
|
||||||
|
"close_current": "Close current tab",
|
||||||
|
"close_others": "Close others tab",
|
||||||
|
"new_tab": "Open a new tab",
|
||||||
|
"title": "Tabs"
|
||||||
|
},
|
||||||
"section": {
|
"section": {
|
||||||
"user": "User"
|
"user": "User",
|
||||||
|
"theme": "Theme",
|
||||||
|
"interface": "Interface",
|
||||||
|
"interceptor": "Interceptor"
|
||||||
|
},
|
||||||
|
"change_interceptor": "Change Interceptor",
|
||||||
|
"change_language": "Change Language",
|
||||||
|
"install_extension": "Install Browser Extension",
|
||||||
|
"settings": {
|
||||||
|
"theme": {
|
||||||
|
"black": "Black Mode",
|
||||||
|
"dark": "Dark Mode",
|
||||||
|
"light": "Light Mode",
|
||||||
|
"system": "System Mode"
|
||||||
|
},
|
||||||
|
"font": {
|
||||||
|
"size_sm": "Change to Small",
|
||||||
|
"size_md": "Change to Medium",
|
||||||
|
"size_lg": "Change to Large"
|
||||||
|
},
|
||||||
|
"change_interceptor": "Change Interceptor",
|
||||||
|
"change_language": "Change Language",
|
||||||
|
"install_extension": "Install Browser Extension"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sse": {
|
"sse": {
|
||||||
@@ -658,8 +772,11 @@
|
|||||||
"tab": {
|
"tab": {
|
||||||
"authorization": "Authorization",
|
"authorization": "Authorization",
|
||||||
"body": "Body",
|
"body": "Body",
|
||||||
|
"close": "Close Tab",
|
||||||
|
"close_others": "Close other Tabs",
|
||||||
"collections": "Collections",
|
"collections": "Collections",
|
||||||
"documentation": "Documentation",
|
"documentation": "Documentation",
|
||||||
|
"duplicate": "Duplicate Tab",
|
||||||
"environments": "Environments",
|
"environments": "Environments",
|
||||||
"headers": "Headers",
|
"headers": "Headers",
|
||||||
"history": "History",
|
"history": "History",
|
||||||
|
|||||||
@@ -22,137 +22,140 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "^10.1.0",
|
"@apidevtools/swagger-parser": "^10.1.0",
|
||||||
"@codemirror/autocomplete": "^6.0.3",
|
"@codemirror/autocomplete": "^6.9.0",
|
||||||
"@codemirror/commands": "^6.0.1",
|
"@codemirror/commands": "^6.2.4",
|
||||||
"@codemirror/lang-javascript": "^6.0.1",
|
"@codemirror/lang-javascript": "^6.1.9",
|
||||||
"@codemirror/lang-json": "^6.0.0",
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
"@codemirror/lang-xml": "^6.0.0",
|
"@codemirror/lang-xml": "^6.0.2",
|
||||||
"@codemirror/language": "^6.2.0",
|
"@codemirror/language": "^6.9.0",
|
||||||
"@codemirror/legacy-modes": "^6.1.0",
|
"@codemirror/legacy-modes": "^6.3.3",
|
||||||
"@codemirror/lint": "^6.0.0",
|
"@codemirror/lint": "^6.4.0",
|
||||||
"@codemirror/search": "^6.0.0",
|
"@codemirror/search": "^6.5.1",
|
||||||
"@codemirror/state": "^6.1.0",
|
"@codemirror/state": "^6.2.1",
|
||||||
"@codemirror/view": "^6.0.2",
|
"@codemirror/view": "^6.16.0",
|
||||||
|
"@fontsource-variable/inter": "^5.0.8",
|
||||||
|
"@fontsource-variable/material-symbols-rounded": "^5.0.7",
|
||||||
|
"@fontsource-variable/roboto-mono": "^5.0.9",
|
||||||
"@hoppscotch/codemirror-lang-graphql": "workspace:^",
|
"@hoppscotch/codemirror-lang-graphql": "workspace:^",
|
||||||
"@hoppscotch/data": "workspace:^",
|
"@hoppscotch/data": "workspace:^",
|
||||||
"@hoppscotch/js-sandbox": "workspace:^",
|
"@hoppscotch/js-sandbox": "workspace:^",
|
||||||
"@hoppscotch/ui": "workspace:^",
|
"@hoppscotch/ui": "workspace:^",
|
||||||
"@hoppscotch/vue-toasted": "^0.1.0",
|
"@hoppscotch/vue-toasted": "^0.1.0",
|
||||||
"@lezer/highlight": "^1.0.0",
|
"@lezer/highlight": "^1.1.6",
|
||||||
"@sentry/tracing": "^7.13.0",
|
"@sentry/tracing": "^7.64.0",
|
||||||
"@sentry/vue": "^7.13.0",
|
"@sentry/vue": "^7.64.0",
|
||||||
"@urql/core": "^2.5.0",
|
"@urql/core": "^4.1.1",
|
||||||
"@urql/devtools": "^2.0.3",
|
"@urql/devtools": "^2.0.3",
|
||||||
"@urql/exchange-auth": "^0.1.7",
|
"@urql/exchange-auth": "^2.1.6",
|
||||||
"@urql/exchange-graphcache": "^4.4.3",
|
"@urql/exchange-graphcache": "^6.3.2",
|
||||||
"@vitejs/plugin-legacy": "^2.3.0",
|
"@vitejs/plugin-legacy": "^4.1.1",
|
||||||
"@vueuse/core": "^8.9.4",
|
"@vueuse/core": "^10.3.0",
|
||||||
"@vueuse/head": "^0.7.9",
|
"@vueuse/head": "^1.3.1",
|
||||||
"acorn-walk": "^8.2.0",
|
"acorn-walk": "^8.2.0",
|
||||||
"axios": "^0.21.4",
|
"axios": "^1.4.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"dioc": "workspace:^",
|
"dioc": "workspace:^",
|
||||||
"esprima": "^4.0.1",
|
"esprima": "^4.0.1",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"fp-ts": "^2.12.1",
|
"fp-ts": "^2.16.1",
|
||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"globalthis": "^1.0.3",
|
"globalthis": "^1.0.3",
|
||||||
"graphql": "^15.5.0",
|
"graphql": "^16.8.0",
|
||||||
"graphql-language-service-interface": "^2.9.1",
|
"graphql-language-service-interface": "^2.9.1",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"httpsnippet": "^2.0.0",
|
"httpsnippet": "^2.0.0",
|
||||||
"insomnia-importers": "^3.3.0",
|
"insomnia-importers": "^3.6.0",
|
||||||
"io-ts": "^2.2.16",
|
"io-ts": "^2.2.20",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsonpath-plus": "^7.0.0",
|
"jsonpath-plus": "^7.2.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lossless-json": "^2.0.8",
|
"lossless-json": "^2.0.11",
|
||||||
"minisearch": "^6.1.0",
|
"minisearch": "^6.1.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"paho-mqtt": "^1.1.0",
|
"paho-mqtt": "^1.1.0",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"postman-collection": "^4.1.4",
|
"postman-collection": "^4.2.0",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"qs": "^6.10.3",
|
"qs": "^6.11.2",
|
||||||
"rxjs": "^7.5.5",
|
"rxjs": "^7.8.1",
|
||||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||||
"socket.io-client-v3": "npm:socket.io-client@^3.1.3",
|
"socket.io-client-v3": "npm:socket.io-client@^3.1.3",
|
||||||
"socket.io-client-v4": "npm:socket.io-client@^4.4.1",
|
"socket.io-client-v4": "npm:socket.io-client@^4.4.1",
|
||||||
"socketio-wildcard": "^2.0.0",
|
"socketio-wildcard": "^2.0.0",
|
||||||
"splitpanes": "^3.1.1",
|
"splitpanes": "^3.1.5",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"tern": "^0.24.3",
|
"tern": "^0.24.3",
|
||||||
"timers": "^0.1.1",
|
"timers": "^0.1.1",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.1",
|
||||||
"util": "^0.12.4",
|
"util": "^0.12.5",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^9.0.0",
|
||||||
"vue": "^3.2.25",
|
"vue": "^3.3.4",
|
||||||
"vue-i18n": "^9.2.2",
|
"vue-i18n": "^9.2.2",
|
||||||
"vue-pdf-embed": "^1.1.4",
|
"vue-pdf-embed": "^1.1.6",
|
||||||
"vue-router": "^4.0.16",
|
"vue-router": "^4.2.4",
|
||||||
"vue-tippy": "6.0.0-alpha.58",
|
"vue-tippy": "6.3.1",
|
||||||
"vuedraggable-es": "^4.1.1",
|
"vuedraggable-es": "^4.1.1",
|
||||||
"wonka": "^4.0.15",
|
"wonka": "^6.3.4",
|
||||||
"workbox-window": "^6.5.4",
|
"workbox-window": "^7.0.0",
|
||||||
"xml-formatter": "^3.4.1",
|
"xml-formatter": "^3.5.0",
|
||||||
"yargs-parser": "^21.1.1"
|
"yargs-parser": "^21.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
|
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||||
"@esbuild-plugins/node-modules-polyfill": "^0.1.4",
|
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||||
"@graphql-codegen/add": "^3.2.0",
|
"@graphql-codegen/add": "^5.0.0",
|
||||||
"@graphql-codegen/cli": "^2.8.0",
|
"@graphql-codegen/cli": "^5.0.0",
|
||||||
"@graphql-codegen/typed-document-node": "^2.3.1",
|
"@graphql-codegen/typed-document-node": "^5.0.1",
|
||||||
"@graphql-codegen/typescript": "^2.7.1",
|
"@graphql-codegen/typescript": "^4.0.1",
|
||||||
"@graphql-codegen/typescript-operations": "^2.5.1",
|
"@graphql-codegen/typescript-operations": "^4.0.1",
|
||||||
"@graphql-codegen/typescript-urql-graphcache": "^2.3.1",
|
"@graphql-codegen/typescript-urql-graphcache": "^2.4.5",
|
||||||
"@graphql-codegen/urql-introspection": "^2.2.0",
|
"@graphql-codegen/urql-introspection": "^2.2.1",
|
||||||
"@graphql-typed-document-node/core": "^3.1.1",
|
"@graphql-typed-document-node/core": "^3.2.0",
|
||||||
"@iconify-json/lucide": "^1.1.109",
|
"@iconify-json/lucide": "^1.1.119",
|
||||||
"@intlify/vite-plugin-vue-i18n": "^7.0.0",
|
"@intlify/vite-plugin-vue-i18n": "^7.0.0",
|
||||||
"@relmify/jest-fp-ts": "^2.1.1",
|
"@relmify/jest-fp-ts": "^2.1.1",
|
||||||
"@rushstack/eslint-patch": "^1.1.4",
|
"@rushstack/eslint-patch": "^1.3.3",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/lodash-es": "^4.17.6",
|
"@types/lodash-es": "^4.17.8",
|
||||||
"@types/lossless-json": "^1.0.1",
|
"@types/lossless-json": "^1.0.1",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/paho-mqtt": "^1.0.6",
|
"@types/paho-mqtt": "^1.0.7",
|
||||||
"@types/postman-collection": "^3.5.7",
|
"@types/postman-collection": "^3.5.7",
|
||||||
"@types/splitpanes": "^2.2.1",
|
"@types/splitpanes": "^2.2.1",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^9.0.2",
|
||||||
"@types/yargs-parser": "^21.0.0",
|
"@types/yargs-parser": "^21.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.19.0",
|
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
||||||
"@typescript-eslint/parser": "^5.19.0",
|
"@typescript-eslint/parser": "^6.4.0",
|
||||||
"@vitejs/plugin-vue": "^3.1.0",
|
"@vitejs/plugin-vue": "^4.3.1",
|
||||||
"@vue/compiler-sfc": "^3.2.39",
|
"@vue/compiler-sfc": "^3.3.4",
|
||||||
"@vue/eslint-config-typescript": "^11.0.1",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"@vue/runtime-core": "^3.2.39",
|
"@vue/runtime-core": "^3.3.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.3.1",
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.47.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^9.5.1",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"openapi-types": "^12.0.0",
|
"openapi-types": "^12.1.3",
|
||||||
"rollup-plugin-polyfill-node": "^0.10.1",
|
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||||
"sass": "^1.53.0",
|
"sass": "^1.66.0",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^5.1.6",
|
||||||
"unplugin-icons": "^0.14.9",
|
"unplugin-fonts": "^1.0.3",
|
||||||
"unplugin-vue-components": "^0.21.0",
|
"unplugin-icons": "^0.16.5",
|
||||||
"vite": "^3.1.4",
|
"unplugin-vue-components": "^0.25.1",
|
||||||
"vite-plugin-checker": "^0.5.1",
|
"vite": "^4.4.9",
|
||||||
"vite-plugin-fonts": "^0.6.0",
|
"vite-plugin-checker": "^0.6.1",
|
||||||
"vite-plugin-html-config": "^1.0.10",
|
"vite-plugin-html-config": "^1.0.11",
|
||||||
"vite-plugin-inspect": "^0.7.4",
|
"vite-plugin-inspect": "^0.7.38",
|
||||||
"vite-plugin-pages": "^0.26.0",
|
"vite-plugin-pages": "^0.31.0",
|
||||||
"vite-plugin-pages-sitemap": "^1.4.5",
|
"vite-plugin-pages-sitemap": "^1.6.1",
|
||||||
"vite-plugin-pwa": "^0.13.1",
|
"vite-plugin-pwa": "^0.16.4",
|
||||||
"vite-plugin-vue-layouts": "^0.7.0",
|
"vite-plugin-vue-layouts": "^0.8.0",
|
||||||
"vite-plugin-windicss": "^1.8.8",
|
"vite-plugin-windicss": "^1.9.1",
|
||||||
"vitest": "^0.32.2",
|
"vitest": "^0.34.2",
|
||||||
"vue-tsc": "^0.38.2",
|
"vue-tsc": "^1.8.8",
|
||||||
"windicss": "^3.5.6"
|
"windicss": "^3.5.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
packages/hoppscotch-common/src/components.d.ts
vendored
41
packages/hoppscotch-common/src/components.d.ts
vendored
@@ -14,6 +14,7 @@ declare module '@vue/runtime-core' {
|
|||||||
AppFooter: typeof import('./components/app/Footer.vue')['default']
|
AppFooter: typeof import('./components/app/Footer.vue')['default']
|
||||||
AppGitHubStarButton: typeof import('./components/app/GitHubStarButton.vue')['default']
|
AppGitHubStarButton: typeof import('./components/app/GitHubStarButton.vue')['default']
|
||||||
AppHeader: typeof import('./components/app/Header.vue')['default']
|
AppHeader: typeof import('./components/app/Header.vue')['default']
|
||||||
|
AppInspection: typeof import('./components/app/Inspection.vue')['default']
|
||||||
AppInterceptor: typeof import('./components/app/Interceptor.vue')['default']
|
AppInterceptor: typeof import('./components/app/Interceptor.vue')['default']
|
||||||
AppLogo: typeof import('./components/app/Logo.vue')['default']
|
AppLogo: typeof import('./components/app/Logo.vue')['default']
|
||||||
AppOptions: typeof import('./components/app/Options.vue')['default']
|
AppOptions: typeof import('./components/app/Options.vue')['default']
|
||||||
@@ -23,10 +24,13 @@ declare module '@vue/runtime-core' {
|
|||||||
AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default']
|
AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default']
|
||||||
AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default']
|
AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default']
|
||||||
AppSidenav: typeof import('./components/app/Sidenav.vue')['default']
|
AppSidenav: typeof import('./components/app/Sidenav.vue')['default']
|
||||||
|
AppSocial: typeof import('./components/app/Social.vue')['default']
|
||||||
AppSpotlight: typeof import('./components/app/spotlight/index.vue')['default']
|
AppSpotlight: typeof import('./components/app/spotlight/index.vue')['default']
|
||||||
AppSpotlightEntry: typeof import('./components/app/spotlight/Entry.vue')['default']
|
AppSpotlightEntry: typeof import('./components/app/spotlight/Entry.vue')['default']
|
||||||
AppSpotlightEntryGQLHistory: typeof import('./components/app/spotlight/entry/GQLHistory.vue')['default']
|
AppSpotlightEntryGQLHistory: typeof import('./components/app/spotlight/entry/GQLHistory.vue')['default']
|
||||||
|
AppSpotlightEntryGQLRequest: typeof import('./components/app/spotlight/entry/GQLRequest.vue')['default']
|
||||||
AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default']
|
AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default']
|
||||||
|
AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default']
|
||||||
AppSupport: typeof import('./components/app/Support.vue')['default']
|
AppSupport: typeof import('./components/app/Support.vue')['default']
|
||||||
ButtonPrimary: typeof import('./../../hoppscotch-ui/src/components/button/Primary.vue')['default']
|
ButtonPrimary: typeof import('./../../hoppscotch-ui/src/components/button/Primary.vue')['default']
|
||||||
ButtonSecondary: typeof import('./../../hoppscotch-ui/src/components/button/Secondary.vue')['default']
|
ButtonSecondary: typeof import('./../../hoppscotch-ui/src/components/button/Secondary.vue')['default']
|
||||||
@@ -77,6 +81,31 @@ declare module '@vue/runtime-core' {
|
|||||||
History: typeof import('./components/history/index.vue')['default']
|
History: typeof import('./components/history/index.vue')['default']
|
||||||
HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
|
HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
|
||||||
HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
|
HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
|
||||||
|
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
||||||
|
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
||||||
|
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
||||||
|
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
|
||||||
|
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
||||||
|
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
||||||
|
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
||||||
|
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
||||||
|
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
||||||
|
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
|
||||||
|
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||||
|
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||||
|
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||||
|
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
||||||
|
HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder']
|
||||||
|
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
||||||
|
HoppSmartRadio: typeof import('@hoppscotch/ui')['HoppSmartRadio']
|
||||||
|
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
||||||
|
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
||||||
|
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
||||||
|
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
|
||||||
|
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
||||||
|
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
|
||||||
|
HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow']
|
||||||
|
HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows']
|
||||||
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
||||||
HttpAuthorizationApiKey: typeof import('./components/http/authorization/ApiKey.vue')['default']
|
HttpAuthorizationApiKey: typeof import('./components/http/authorization/ApiKey.vue')['default']
|
||||||
HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default']
|
HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default']
|
||||||
@@ -96,14 +125,18 @@ declare module '@vue/runtime-core' {
|
|||||||
HttpResponse: typeof import('./components/http/Response.vue')['default']
|
HttpResponse: typeof import('./components/http/Response.vue')['default']
|
||||||
HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default']
|
HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default']
|
||||||
HttpSidebar: typeof import('./components/http/Sidebar.vue')['default']
|
HttpSidebar: typeof import('./components/http/Sidebar.vue')['default']
|
||||||
|
HttpTabHead: typeof import('./components/http/TabHead.vue')['default']
|
||||||
HttpTestResult: typeof import('./components/http/TestResult.vue')['default']
|
HttpTestResult: typeof import('./components/http/TestResult.vue')['default']
|
||||||
HttpTestResultEntry: typeof import('./components/http/TestResultEntry.vue')['default']
|
HttpTestResultEntry: typeof import('./components/http/TestResultEntry.vue')['default']
|
||||||
HttpTestResultEnv: typeof import('./components/http/TestResultEnv.vue')['default']
|
HttpTestResultEnv: typeof import('./components/http/TestResultEnv.vue')['default']
|
||||||
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
|
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
|
||||||
HttpTests: typeof import('./components/http/Tests.vue')['default']
|
HttpTests: typeof import('./components/http/Tests.vue')['default']
|
||||||
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
|
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
|
||||||
|
IconLucideActivity: typeof import('~icons/lucide/activity')['default']
|
||||||
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
||||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||||
|
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
||||||
|
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
|
||||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||||
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
||||||
@@ -116,6 +149,7 @@ declare module '@vue/runtime-core' {
|
|||||||
IconLucideRss: typeof import('~icons/lucide/rss')['default']
|
IconLucideRss: typeof import('~icons/lucide/rss')['default']
|
||||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||||
|
IconLucideVerified: typeof import('~icons/lucide/verified')['default']
|
||||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||||
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
||||||
LensesRenderersAudioLensRenderer: typeof import('./components/lenses/renderers/AudioLensRenderer.vue')['default']
|
LensesRenderersAudioLensRenderer: typeof import('./components/lenses/renderers/AudioLensRenderer.vue')['default']
|
||||||
@@ -135,6 +169,8 @@ declare module '@vue/runtime-core' {
|
|||||||
RealtimeLog: typeof import('./components/realtime/Log.vue')['default']
|
RealtimeLog: typeof import('./components/realtime/Log.vue')['default']
|
||||||
RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default']
|
RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default']
|
||||||
RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default']
|
RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default']
|
||||||
|
SettingsExtension: typeof import('./components/settings/Extension.vue')['default']
|
||||||
|
SettingsProxy: typeof import('./components/settings/Proxy.vue')['default']
|
||||||
SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default']
|
SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default']
|
||||||
SmartAnchor: typeof import('./../../hoppscotch-ui/src/components/smart/Anchor.vue')['default']
|
SmartAnchor: typeof import('./../../hoppscotch-ui/src/components/smart/Anchor.vue')['default']
|
||||||
SmartAutoComplete: typeof import('./../../hoppscotch-ui/src/components/smart/AutoComplete.vue')['default']
|
SmartAutoComplete: typeof import('./../../hoppscotch-ui/src/components/smart/AutoComplete.vue')['default']
|
||||||
@@ -146,6 +182,7 @@ declare module '@vue/runtime-core' {
|
|||||||
SmartExpand: typeof import('./../../hoppscotch-ui/src/components/smart/Expand.vue')['default']
|
SmartExpand: typeof import('./../../hoppscotch-ui/src/components/smart/Expand.vue')['default']
|
||||||
SmartFileChip: typeof import('./../../hoppscotch-ui/src/components/smart/FileChip.vue')['default']
|
SmartFileChip: typeof import('./../../hoppscotch-ui/src/components/smart/FileChip.vue')['default']
|
||||||
SmartFontSizePicker: typeof import('./components/smart/FontSizePicker.vue')['default']
|
SmartFontSizePicker: typeof import('./components/smart/FontSizePicker.vue')['default']
|
||||||
|
SmartInput: typeof import('./../../hoppscotch-ui/src/components/smart/Input.vue')['default']
|
||||||
SmartIntersection: typeof import('./../../hoppscotch-ui/src/components/smart/Intersection.vue')['default']
|
SmartIntersection: typeof import('./../../hoppscotch-ui/src/components/smart/Intersection.vue')['default']
|
||||||
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
|
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
|
||||||
SmartLink: typeof import('./../../hoppscotch-ui/src/components/smart/Link.vue')['default']
|
SmartLink: typeof import('./../../hoppscotch-ui/src/components/smart/Link.vue')['default']
|
||||||
@@ -160,8 +197,8 @@ declare module '@vue/runtime-core' {
|
|||||||
SmartTab: typeof import('./../../hoppscotch-ui/src/components/smart/Tab.vue')['default']
|
SmartTab: typeof import('./../../hoppscotch-ui/src/components/smart/Tab.vue')['default']
|
||||||
SmartTabs: typeof import('./../../hoppscotch-ui/src/components/smart/Tabs.vue')['default']
|
SmartTabs: typeof import('./../../hoppscotch-ui/src/components/smart/Tabs.vue')['default']
|
||||||
SmartToggle: typeof import('./../../hoppscotch-ui/src/components/smart/Toggle.vue')['default']
|
SmartToggle: typeof import('./../../hoppscotch-ui/src/components/smart/Toggle.vue')['default']
|
||||||
SmartTree: typeof import('./components/smart/Tree.vue')['default']
|
SmartTree: typeof import('./../../hoppscotch-ui/src/components/smart/Tree.vue')['default']
|
||||||
SmartTreeBranch: typeof import('./components/smart/TreeBranch.vue')['default']
|
SmartTreeBranch: typeof import('./../../hoppscotch-ui/src/components/smart/TreeBranch.vue')['default']
|
||||||
SmartWindow: typeof import('./../../hoppscotch-ui/src/components/smart/Window.vue')['default']
|
SmartWindow: typeof import('./../../hoppscotch-ui/src/components/smart/Window.vue')['default']
|
||||||
SmartWindows: typeof import('./../../hoppscotch-ui/src/components/smart/Windows.vue')['default']
|
SmartWindows: typeof import('./../../hoppscotch-ui/src/components/smart/Windows.vue')['default']
|
||||||
TabPrimary: typeof import('./components/tab/Primary.vue')['default']
|
TabPrimary: typeof import('./components/tab/Primary.vue')['default']
|
||||||
|
|||||||
@@ -1,17 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
|
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
|
||||||
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
||||||
|
<AppSocial :show="showSocial" @hide-modal="showSocial = false" />
|
||||||
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
|
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
|
||||||
|
|
||||||
|
<HoppSmartConfirmModal
|
||||||
|
:show="confirmRemove"
|
||||||
|
:title="t('confirm.remove_team')"
|
||||||
|
@hide-modal="confirmRemove = false"
|
||||||
|
@resolve="deleteTeam()"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { pipe } from "fp-ts/function"
|
||||||
|
import * as TE from "fp-ts/TaskEither"
|
||||||
|
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
||||||
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
|
import { showChat } from "~/modules/crisp"
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
const showShortcuts = ref(false)
|
const showShortcuts = ref(false)
|
||||||
const showShare = ref(false)
|
const showShare = ref(false)
|
||||||
|
const showSocial = ref(false)
|
||||||
const showLogin = ref(false)
|
const showLogin = ref(false)
|
||||||
|
|
||||||
|
const confirmRemove = ref(false)
|
||||||
|
|
||||||
|
const teamID = ref<string | null>(null)
|
||||||
|
|
||||||
|
const deleteTeam = () => {
|
||||||
|
if (!teamID.value) return
|
||||||
|
pipe(
|
||||||
|
backendDeleteTeam(teamID.value),
|
||||||
|
TE.match(
|
||||||
|
(err) => {
|
||||||
|
// TODO: Better errors ? We know the possible errors now
|
||||||
|
toast.error(`${t("error.something_went_wrong")}`)
|
||||||
|
console.error(err)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
invokeAction("workspace.switch.personal")
|
||||||
|
toast.success(`${t("team.deleted")}`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)() // Tasks (and TEs) are lazy, so call the function returned
|
||||||
|
}
|
||||||
|
|
||||||
defineActionHandler("flyouts.keybinds.toggle", () => {
|
defineActionHandler("flyouts.keybinds.toggle", () => {
|
||||||
showShortcuts.value = !showShortcuts.value
|
showShortcuts.value = !showShortcuts.value
|
||||||
})
|
})
|
||||||
@@ -20,7 +60,20 @@ defineActionHandler("modals.share.toggle", () => {
|
|||||||
showShare.value = !showShare.value
|
showShare.value = !showShare.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defineActionHandler("modals.social.toggle", () => {
|
||||||
|
showSocial.value = !showSocial.value
|
||||||
|
})
|
||||||
|
|
||||||
defineActionHandler("modals.login.toggle", () => {
|
defineActionHandler("modals.login.toggle", () => {
|
||||||
showLogin.value = !showLogin.value
|
showLogin.value = !showLogin.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defineActionHandler("flyouts.chat.open", () => {
|
||||||
|
showChat()
|
||||||
|
})
|
||||||
|
|
||||||
|
defineActionHandler("modals.team.delete", ({ teamId }) => {
|
||||||
|
teamID.value = teamId
|
||||||
|
confirmRemove.value = true
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,14 +18,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="inline-flex items-center justify-center flex-1 space-x-2">
|
<div class="inline-flex items-center justify-center flex-1 space-x-2">
|
||||||
<button
|
<button
|
||||||
class="flex flex-1 items-center justify-between px-2 py-1 bg-primaryDark transition text-secondaryLight cursor-text rounded border border-dividerDark max-w-xs hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary"
|
class="flex flex-1 items-center justify-between px-2 py-1 self-stretch bg-primaryDark transition text-secondaryLight cursor-text rounded border border-dividerDark max-w-60 hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary"
|
||||||
@click="invokeAction('modals.search.toggle')"
|
@click="invokeAction('modals.search.toggle')"
|
||||||
>
|
>
|
||||||
<span class="inline-flex flex-1 items-center">
|
<span class="inline-flex flex-1 items-center">
|
||||||
<icon-lucide-search class="mr-2 svg-icons" />
|
<icon-lucide-search class="mr-2 svg-icons" />
|
||||||
{{ t("app.search") }}
|
{{ t("app.search") }}
|
||||||
</span>
|
</span>
|
||||||
<span class="flex">
|
<span class="flex space-x-1">
|
||||||
<kbd class="shortcut-key">{{ getPlatformSpecialKey() }}</kbd>
|
<kbd class="shortcut-key">{{ getPlatformSpecialKey() }}</kbd>
|
||||||
<kbd class="shortcut-key">K</kbd>
|
<kbd class="shortcut-key">K</kbd>
|
||||||
</span>
|
</span>
|
||||||
@@ -382,6 +382,22 @@ const settings = ref<any | null>(null)
|
|||||||
const logout = ref<any | null>(null)
|
const logout = ref<any | null>(null)
|
||||||
const accountActions = ref<any | null>(null)
|
const accountActions = ref<any | null>(null)
|
||||||
|
|
||||||
|
defineActionHandler("modals.team.edit", () => {
|
||||||
|
// TODO: Remove this hack
|
||||||
|
setTimeout(() => {
|
||||||
|
handleTeamEdit()
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
defineActionHandler("modals.team.invite", () => {
|
||||||
|
if (
|
||||||
|
selectedTeam.value?.myRole === "OWNER" ||
|
||||||
|
selectedTeam.value?.myRole === "EDITOR"
|
||||||
|
) {
|
||||||
|
inviteTeam({ name: selectedTeam.value.name }, selectedTeam.value.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
defineActionHandler(
|
defineActionHandler(
|
||||||
"user.login",
|
"user.login",
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
112
packages/hoppscotch-common/src/components/app/Inspection.vue
Normal file
112
packages/hoppscotch-common/src/components/app/Inspection.vue
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="inspectionResults && inspectionResults.length > 0">
|
||||||
|
<tippy interactive trigger="click" theme="popover">
|
||||||
|
<div class="flex justify-center items-center flex-1 flex-col">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:icon="IconAlertTriangle"
|
||||||
|
:class="severityColor(getHighestSeverity.severity)"
|
||||||
|
:title="t('inspections.description')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<template #content="{ hide }">
|
||||||
|
<div class="flex flex-col space-y-2 items-start flex-1">
|
||||||
|
<div
|
||||||
|
class="flex justify-between border rounded pl-2 border-divider bg-popover sticky top-0 self-stretch"
|
||||||
|
>
|
||||||
|
<span class="flex items-center flex-1">
|
||||||
|
<icon-lucide-activity class="mr-2 svg-icons text-accent" />
|
||||||
|
<span class="font-bold">
|
||||||
|
{{ t("inspections.title") }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
to="https://docs.hoppscotch.io/documentation/features/inspections"
|
||||||
|
blank
|
||||||
|
:title="t('app.wiki')"
|
||||||
|
:icon="IconHelpCircle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="(inspector, index) in inspectionResults"
|
||||||
|
:key="index"
|
||||||
|
class="flex self-stretch max-w-md w-full"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex flex-col flex-1 rounded border border-dashed border-dividerDark divide-y divide-dashed divide-dividerDark"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="inspector.text.type === 'text'"
|
||||||
|
class="flex-1 px-3 py-2"
|
||||||
|
>
|
||||||
|
{{ inspector.text.text }}
|
||||||
|
<HoppSmartLink
|
||||||
|
blank
|
||||||
|
:to="inspector.doc.link"
|
||||||
|
class="text-accent hover:text-accentDark transition"
|
||||||
|
>
|
||||||
|
{{ inspector.doc.text }}
|
||||||
|
<icon-lucide-arrow-up-right class="svg-icons" />
|
||||||
|
</HoppSmartLink>
|
||||||
|
</span>
|
||||||
|
<span v-if="inspector.action" class="flex p-2 space-x-2">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="inspector.action.text"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
inspector.action?.apply()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { InspectorResult } from "~/services/inspection"
|
||||||
|
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
||||||
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
inspectionResults: InspectorResult[] | undefined
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const getHighestSeverity = computed(() => {
|
||||||
|
if (props.inspectionResults) {
|
||||||
|
return props.inspectionResults.reduce(
|
||||||
|
(prev, curr) => {
|
||||||
|
return prev.severity > curr.severity ? prev : curr
|
||||||
|
},
|
||||||
|
{ severity: 0 }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return { severity: 0 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const severityColor = (severity: number) => {
|
||||||
|
switch (severity) {
|
||||||
|
case 1:
|
||||||
|
return "!text-green-500 hover:!text-green-600"
|
||||||
|
case 2:
|
||||||
|
return "!text-yellow-500 hover:!text-yellow-600"
|
||||||
|
case 3:
|
||||||
|
return "!text-red-500 hover:!text-red-600"
|
||||||
|
default:
|
||||||
|
return "!text-gray-500 hover:!text-gray-600"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -8,91 +8,41 @@
|
|||||||
{{ t("settings.interceptor_description") }}
|
{{ t("settings.interceptor_description") }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<HoppSmartRadioGroup
|
|
||||||
v-model="interceptorSelection"
|
<div>
|
||||||
:radios="interceptors"
|
<div
|
||||||
/>
|
v-for="interceptor in interceptors"
|
||||||
<div
|
:key="interceptor.interceptorID"
|
||||||
v-if="interceptorSelection == 'EXTENSIONS_ENABLED' && !extensionVersion"
|
class="flex flex-col"
|
||||||
class="flex space-x-2"
|
>
|
||||||
>
|
<HoppSmartRadio
|
||||||
<HoppButtonSecondary
|
:value="interceptor.interceptorID"
|
||||||
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
:label="unref(interceptor.name(t))"
|
||||||
blank
|
:selected="interceptorSelection === interceptor.interceptorID"
|
||||||
:icon="IconChrome"
|
@change="interceptorSelection = interceptor.interceptorID"
|
||||||
label="Chrome"
|
/>
|
||||||
outline
|
|
||||||
class="!flex-1"
|
<component
|
||||||
/>
|
:is="interceptor.selectorSubtitle"
|
||||||
<HoppButtonSecondary
|
v-if="interceptor.selectorSubtitle"
|
||||||
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
/>
|
||||||
blank
|
</div>
|
||||||
:icon="IconFirefox"
|
|
||||||
label="Firefox"
|
|
||||||
outline
|
|
||||||
class="!flex-1"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import IconChrome from "~icons/brands/chrome"
|
|
||||||
import IconFirefox from "~icons/brands/firefox"
|
|
||||||
import { computed } from "vue"
|
|
||||||
import { applySetting, toggleSetting } from "~/newstore/settings"
|
|
||||||
import { useSetting } from "@composables/settings"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useService } from "dioc/vue"
|
||||||
import { extensionStatus$ } from "~/newstore/HoppExtension"
|
import { Ref, unref } from "vue"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const PROXY_ENABLED = useSetting("PROXY_ENABLED")
|
const interceptorService = useService(InterceptorService)
|
||||||
const EXTENSIONS_ENABLED = useSetting("EXTENSIONS_ENABLED")
|
|
||||||
|
|
||||||
const currentExtensionStatus = useReadonlyStream(extensionStatus$, null)
|
const interceptorSelection =
|
||||||
|
interceptorService.currentInterceptorID as Ref<string>
|
||||||
|
|
||||||
const extensionVersion = computed(() => {
|
const interceptors = interceptorService.availableInterceptors
|
||||||
return currentExtensionStatus.value === "available"
|
|
||||||
? window.__POSTWOMAN_EXTENSION_HOOK__?.getVersion() ?? null
|
|
||||||
: null
|
|
||||||
})
|
|
||||||
|
|
||||||
const interceptors = computed(() => [
|
|
||||||
{ value: "BROWSER_ENABLED" as const, label: t("state.none") },
|
|
||||||
{ value: "PROXY_ENABLED" as const, label: t("settings.proxy") },
|
|
||||||
{
|
|
||||||
value: "EXTENSIONS_ENABLED" as const,
|
|
||||||
label:
|
|
||||||
`${t("settings.extensions")}: ` +
|
|
||||||
(extensionVersion.value !== null
|
|
||||||
? `v${extensionVersion.value.major}.${extensionVersion.value.minor}`
|
|
||||||
: t("settings.extension_ver_not_reported")),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
type InterceptorMode = (typeof interceptors)["value"][number]["value"]
|
|
||||||
|
|
||||||
const interceptorSelection = computed<InterceptorMode>({
|
|
||||||
get() {
|
|
||||||
if (PROXY_ENABLED.value) return "PROXY_ENABLED"
|
|
||||||
if (EXTENSIONS_ENABLED.value) return "EXTENSIONS_ENABLED"
|
|
||||||
return "BROWSER_ENABLED"
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
if (val === "EXTENSIONS_ENABLED") {
|
|
||||||
applySetting("EXTENSIONS_ENABLED", true)
|
|
||||||
if (PROXY_ENABLED.value) toggleSetting("PROXY_ENABLED")
|
|
||||||
}
|
|
||||||
if (val === "PROXY_ENABLED") {
|
|
||||||
applySetting("PROXY_ENABLED", true)
|
|
||||||
if (EXTENSIONS_ENABLED.value) toggleSetting("EXTENSIONS_ENABLED")
|
|
||||||
}
|
|
||||||
if (val === "BROWSER_ENABLED") {
|
|
||||||
applySetting("PROXY_ENABLED", false)
|
|
||||||
applySetting("EXTENSIONS_ENABLED", false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -130,13 +130,12 @@
|
|||||||
@click="nativeShare()"
|
@click="nativeShare()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartModal>
|
</HoppSmartModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from "vue"
|
import { watch } from "vue"
|
||||||
import IconSidebar from "~icons/lucide/sidebar"
|
import IconSidebar from "~icons/lucide/sidebar"
|
||||||
import IconSidebarOpen from "~icons/lucide/sidebar-open"
|
import IconSidebarOpen from "~icons/lucide/sidebar-open"
|
||||||
import IconBook from "~icons/lucide/book"
|
import IconBook from "~icons/lucide/book"
|
||||||
@@ -151,13 +150,12 @@ import IconUserPlus from "~icons/lucide/user-plus"
|
|||||||
import IconShare2 from "~icons/lucide/share-2"
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
import IconChevronRight from "~icons/lucide/chevron-right"
|
import IconChevronRight from "~icons/lucide/chevron-right"
|
||||||
import { useSetting } from "@composables/settings"
|
import { useSetting } from "@composables/settings"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { invokeAction } from "~/helpers/actions"
|
||||||
import { showChat } from "@modules/crisp"
|
import { showChat } from "@modules/crisp"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const navigatorShare = !!navigator.share
|
const navigatorShare = !!navigator.share
|
||||||
const showShare = ref(false)
|
|
||||||
|
|
||||||
const ZEN_MODE = useSetting("ZEN_MODE")
|
const ZEN_MODE = useSetting("ZEN_MODE")
|
||||||
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
||||||
@@ -174,10 +172,6 @@ defineProps<{
|
|||||||
show: boolean
|
show: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineActionHandler("modals.share.toggle", () => {
|
|
||||||
showShare.value = !showShare.value
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "hide-modal"): void
|
(e: "hide-modal"): void
|
||||||
}>()
|
}>()
|
||||||
@@ -198,7 +192,7 @@ const expandCollection = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const expandInvite = () => {
|
const expandInvite = () => {
|
||||||
showShare.value = true
|
invokeAction("modals.share.toggle")
|
||||||
}
|
}
|
||||||
|
|
||||||
const nativeShare = () => {
|
const nativeShare = () => {
|
||||||
|
|||||||
@@ -4,15 +4,13 @@
|
|||||||
<div
|
<div
|
||||||
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
|
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col px-6 py-4 border-b border-dividerLight">
|
<HoppSmartInput
|
||||||
<input
|
v-model="filterText"
|
||||||
v-model="filterText"
|
type="search"
|
||||||
type="search"
|
styles="px-6 py-4 border-b border-dividerLight"
|
||||||
autocomplete="off"
|
:placeholder="`${t('action.search')}`"
|
||||||
class="flex px-4 py-2 border rounded bg-primaryContrast border-divider hover:border-dividerDark focus-visible:border-dividerDark"
|
input-styles="flex px-4 py-2 border rounded bg-primaryContrast border-divider hover:border-dividerDark focus-visible:border-dividerDark"
|
||||||
:placeholder="`${t('action.search')}`"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col divide-y divide-dividerLight">
|
<div class="flex flex-col divide-y divide-dividerLight">
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
@@ -21,6 +19,7 @@
|
|||||||
>
|
>
|
||||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
|
|
||||||
<details
|
<details
|
||||||
v-for="(sectionResults, sectionTitle) in shortcutsResults"
|
v-for="(sectionResults, sectionTitle) in shortcutsResults"
|
||||||
v-else
|
v-else
|
||||||
|
|||||||
135
packages/hoppscotch-common/src/components/app/Social.vue
Normal file
135
packages/hoppscotch-common/src/components/app/Social.vue
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<HoppSmartModal
|
||||||
|
v-if="show"
|
||||||
|
dialog
|
||||||
|
:title="t('app.social_links')"
|
||||||
|
@close="hideModal"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<div class="flex flex-col space-y-2">
|
||||||
|
<div class="grid grid-cols-3 gap-4">
|
||||||
|
<a
|
||||||
|
v-for="(platform, index) in platforms"
|
||||||
|
:key="`platform-${index}`"
|
||||||
|
:href="platform.link"
|
||||||
|
target="_blank"
|
||||||
|
class="social-link"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<component :is="platform.icon" class="w-6 h-6" />
|
||||||
|
<span class="mt-3">
|
||||||
|
{{ platform.name }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<button class="social-link" @click="copyAppLink">
|
||||||
|
<component :is="copyIcon" class="w-6 h-6 text-xl" />
|
||||||
|
<span class="mt-3">
|
||||||
|
{{ t("app.copy") }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<p class="text-secondaryLight">
|
||||||
|
{{ t("app.social_description") }}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</HoppSmartModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { useToast } from "@composables/toast"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
|
import IconFacebook from "~icons/brands/facebook"
|
||||||
|
import IconLinkedIn from "~icons/brands/linkedin"
|
||||||
|
import IconReddit from "~icons/brands/reddit"
|
||||||
|
import IconTwitter from "~icons/brands/twitter"
|
||||||
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
import IconGitHub from "~icons/lucide/github"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
show: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "hide-modal"): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const url = "https://hoppscotch.io"
|
||||||
|
|
||||||
|
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||||
|
IconCopy,
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
|
||||||
|
const platforms = [
|
||||||
|
{
|
||||||
|
name: "GitHub",
|
||||||
|
icon: IconGitHub,
|
||||||
|
link: `https://hoppscotch.io/github`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Twitter",
|
||||||
|
icon: IconTwitter,
|
||||||
|
link: `https://twitter.com/hoppscotch_io`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Facebook",
|
||||||
|
icon: IconFacebook,
|
||||||
|
link: `https://www.facebook.com/hoppscotch.io`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Reddit",
|
||||||
|
icon: IconReddit,
|
||||||
|
link: `https://www.reddit.com/r/hoppscotch`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "LinkedIn",
|
||||||
|
icon: IconLinkedIn,
|
||||||
|
link: `https://www.linkedin.com/company/hoppscotch/`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const copyAppLink = () => {
|
||||||
|
copyToClipboard(url)
|
||||||
|
copyIcon.value = IconCheck
|
||||||
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideModal = () => {
|
||||||
|
emit("hide-modal")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.social-link {
|
||||||
|
@apply border border-dividerLight;
|
||||||
|
@apply rounded;
|
||||||
|
@apply flex-col flex;
|
||||||
|
@apply p-4;
|
||||||
|
@apply items-center;
|
||||||
|
@apply justify-center;
|
||||||
|
@apply font-semibold;
|
||||||
|
@apply hover: (bg-primaryLight text-secondaryDark);
|
||||||
|
@apply focus: outline-none;
|
||||||
|
@apply focus-visible: border-divider;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
@apply opacity-80;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
svg {
|
||||||
|
@apply opacity-100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -80,10 +80,11 @@ const props = defineProps<{
|
|||||||
active: boolean
|
active: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const formattedShortcutKeys = computed(() =>
|
const formattedShortcutKeys = computed(
|
||||||
props.entry.meta?.keyboardShortcut?.map((key) => {
|
() =>
|
||||||
return SPECIAL_KEY_CHARS[key] ?? capitalize(key)
|
props.entry.meta?.keyboardShortcut?.map((key) => {
|
||||||
})
|
return SPECIAL_KEY_CHARS[key] ?? capitalize(key)
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<span class="flex flex-1 space-x-2 items-center">
|
||||||
|
<template v-for="(folder, index) in pathFolders" :key="index">
|
||||||
|
<span class="block" :class="{ truncate: index !== 0 }">
|
||||||
|
{{ folder.name }}
|
||||||
|
</span>
|
||||||
|
<icon-lucide-chevron-right class="flex flex-shrink-0" />
|
||||||
|
</template>
|
||||||
|
<span v-if="request" class="block">
|
||||||
|
{{ request.name }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { HoppCollection, HoppGQLRequest } from "@hoppscotch/data"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { graphqlCollectionStore } from "~/newstore/collections"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
folderPath: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const pathFolders = computed(() => {
|
||||||
|
try {
|
||||||
|
const folderIndicies = props.folderPath
|
||||||
|
.split("/")
|
||||||
|
.slice(0, -1)
|
||||||
|
.map((x) => parseInt(x))
|
||||||
|
|
||||||
|
const pathItems: HoppCollection<HoppGQLRequest>[] = []
|
||||||
|
|
||||||
|
let currentFolder =
|
||||||
|
graphqlCollectionStore.value.state[folderIndicies.shift()!]
|
||||||
|
|
||||||
|
pathItems.push(currentFolder)
|
||||||
|
|
||||||
|
while (folderIndicies.length > 0) {
|
||||||
|
const folderIndex = folderIndicies.shift()!
|
||||||
|
|
||||||
|
const folder = currentFolder.folders[folderIndex]
|
||||||
|
pathItems.push(folder)
|
||||||
|
|
||||||
|
currentFolder = folder
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathItems
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const request = computed(() => {
|
||||||
|
try {
|
||||||
|
const requestIndex = parseInt(props.folderPath.split("/").at(-1)!)
|
||||||
|
|
||||||
|
return pathFolders.value[pathFolders.value.length - 1].requests[
|
||||||
|
requestIndex
|
||||||
|
]
|
||||||
|
} catch (e) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<span class="flex flex-1 items-center space-x-2">
|
||||||
|
<template v-for="(folder, index) in pathFolders" :key="index">
|
||||||
|
<span class="block" :class="{ truncate: index !== 0 }">
|
||||||
|
{{ folder.name }}
|
||||||
|
</span>
|
||||||
|
<icon-lucide-chevron-right class="flex flex-shrink-0" />
|
||||||
|
</template>
|
||||||
|
<span
|
||||||
|
v-if="request"
|
||||||
|
class="font-semibold truncate text-tiny flex flex-shrink-0 border border-dividerDark rounded-md px-1"
|
||||||
|
:class="getMethodLabelColorClassOf(request)"
|
||||||
|
>
|
||||||
|
{{ request.method.toUpperCase() }}
|
||||||
|
</span>
|
||||||
|
<span v-if="request" class="block">
|
||||||
|
{{ request.name }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { restCollectionStore } from "~/newstore/collections"
|
||||||
|
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
folderPath: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const pathFolders = computed(() => {
|
||||||
|
try {
|
||||||
|
const folderIndicies = props.folderPath
|
||||||
|
.split("/")
|
||||||
|
.slice(0, -1)
|
||||||
|
.map((x) => parseInt(x))
|
||||||
|
|
||||||
|
const pathItems: HoppCollection<HoppRESTRequest>[] = []
|
||||||
|
|
||||||
|
let currentFolder = restCollectionStore.value.state[folderIndicies.shift()!]
|
||||||
|
pathItems.push(currentFolder)
|
||||||
|
|
||||||
|
while (folderIndicies.length > 0) {
|
||||||
|
const folderIndex = folderIndicies.shift()!
|
||||||
|
|
||||||
|
const folder = currentFolder.folders[folderIndex]
|
||||||
|
pathItems.push(folder)
|
||||||
|
|
||||||
|
currentFolder = folder
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathItems
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const request = computed(() => {
|
||||||
|
try {
|
||||||
|
const requestIndex = parseInt(props.folderPath.split("/").at(-1)!)
|
||||||
|
|
||||||
|
return pathFolders.value[pathFolders.value.length - 1].requests[
|
||||||
|
requestIndex
|
||||||
|
]
|
||||||
|
} catch (e) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -95,6 +95,22 @@ import {
|
|||||||
import { isEqual } from "lodash-es"
|
import { isEqual } from "lodash-es"
|
||||||
import { HistorySpotlightSearcherService } from "~/services/spotlight/searchers/history.searcher"
|
import { HistorySpotlightSearcherService } from "~/services/spotlight/searchers/history.searcher"
|
||||||
import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/user.searcher"
|
import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/user.searcher"
|
||||||
|
import { NavigationSpotlightSearcherService } from "~/services/spotlight/searchers/navigation.searcher"
|
||||||
|
import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.searcher"
|
||||||
|
import { CollectionsSpotlightSearcherService } from "~/services/spotlight/searchers/collections.searcher"
|
||||||
|
import { MiscellaneousSpotlightSearcherService } from "~/services/spotlight/searchers/miscellaneous.searcher"
|
||||||
|
import { TabSpotlightSearcherService } from "~/services/spotlight/searchers/tab.searcher"
|
||||||
|
import { GeneralSpotlightSearcherService } from "~/services/spotlight/searchers/general.searcher"
|
||||||
|
import { ResponseSpotlightSearcherService } from "~/services/spotlight/searchers/response.searcher"
|
||||||
|
import { RequestSpotlightSearcherService } from "~/services/spotlight/searchers/request.searcher"
|
||||||
|
import {
|
||||||
|
EnvironmentsSpotlightSearcherService,
|
||||||
|
SwitchEnvSpotlightSearcherService,
|
||||||
|
} from "~/services/spotlight/searchers/environment.searcher"
|
||||||
|
import {
|
||||||
|
SwitchWorkspaceSpotlightSearcherService,
|
||||||
|
WorkspaceSpotlightSearcherService,
|
||||||
|
} from "~/services/spotlight/searchers/workspace.searcher"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -110,6 +126,18 @@ const spotlightService = useService(SpotlightService)
|
|||||||
|
|
||||||
useService(HistorySpotlightSearcherService)
|
useService(HistorySpotlightSearcherService)
|
||||||
useService(UserSpotlightSearcherService)
|
useService(UserSpotlightSearcherService)
|
||||||
|
useService(NavigationSpotlightSearcherService)
|
||||||
|
useService(SettingsSpotlightSearcherService)
|
||||||
|
useService(CollectionsSpotlightSearcherService)
|
||||||
|
useService(MiscellaneousSpotlightSearcherService)
|
||||||
|
useService(TabSpotlightSearcherService)
|
||||||
|
useService(GeneralSpotlightSearcherService)
|
||||||
|
useService(ResponseSpotlightSearcherService)
|
||||||
|
useService(RequestSpotlightSearcherService)
|
||||||
|
useService(EnvironmentsSpotlightSearcherService)
|
||||||
|
useService(SwitchEnvSpotlightSearcherService)
|
||||||
|
useService(WorkspaceSpotlightSearcherService)
|
||||||
|
useService(SwitchWorkspaceSpotlightSearcherService)
|
||||||
|
|
||||||
const search = ref("")
|
const search = ref("")
|
||||||
|
|
||||||
@@ -236,3 +264,4 @@ function newUseArrowKeysForNavigation() {
|
|||||||
return { selectedEntry }
|
return { selectedEntry }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
~/services/spotlight/searchers/workspace.searcher
|
||||||
|
|||||||
@@ -6,21 +6,13 @@
|
|||||||
@close="hideModal"
|
@close="hideModal"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelAdd"
|
placeholder=" "
|
||||||
v-model="name"
|
:label="t('action.label')"
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
@submit="addNewCollection"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="addNewCollection"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelAdd">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
@@ -65,28 +57,28 @@ const emit = defineEmits<{
|
|||||||
(e: "hide-modal"): void
|
(e: "hide-modal"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const name = ref("")
|
const editingName = ref("")
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (!show) {
|
if (!show) {
|
||||||
name.value = ""
|
editingName.value = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const addNewCollection = () => {
|
const addNewCollection = () => {
|
||||||
if (!name.value) {
|
if (!editingName.value) {
|
||||||
toast.error(t("collection.invalid_name"))
|
toast.error(t("collection.invalid_name"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
emit("submit", name.value)
|
emit("submit", editingName.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
name.value = ""
|
editingName.value = ""
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,21 +6,13 @@
|
|||||||
@close="emit('hide-modal')"
|
@close="emit('hide-modal')"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelAddFolder"
|
placeholder=" "
|
||||||
v-model="name"
|
input-styles="floating-input"
|
||||||
v-focus
|
:label="t('action.label')"
|
||||||
class="input floating-input"
|
@submit="addFolder"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="addFolder"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelAddFolder">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
@@ -65,27 +57,27 @@ const emit = defineEmits<{
|
|||||||
(e: "add-folder", name: string): void
|
(e: "add-folder", name: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const name = ref("")
|
const editingName = ref("")
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (!show) {
|
if (!show) {
|
||||||
name.value = ""
|
editingName.value = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const addFolder = () => {
|
const addFolder = () => {
|
||||||
if (name.value.trim() === "") {
|
if (editingName.value.trim() === "") {
|
||||||
toast.error(t("folder.invalid_name"))
|
toast.error(t("folder.invalid_name"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
emit("add-folder", name.value)
|
emit("add-folder", editingName.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
name.value = ""
|
editingName.value = ""
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,19 +6,13 @@
|
|||||||
@close="$emit('hide-modal')"
|
@close="$emit('hide-modal')"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelAddRequest"
|
placeholder=" "
|
||||||
v-model="name"
|
:label="t('action.label')"
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
@submit="addRequest"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="addRequest"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelAddRequest">{{ t("action.label") }}</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
@@ -64,23 +58,23 @@ const emit = defineEmits<{
|
|||||||
(event: "add-request", name: string): void
|
(event: "add-request", name: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const name = ref("")
|
const editingName = ref("")
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
name.value = currentActiveTab.value.document.request.name
|
editingName.value = currentActiveTab.value.document.request.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const addRequest = () => {
|
const addRequest = () => {
|
||||||
if (name.value.trim() === "") {
|
if (editingName.value.trim() === "") {
|
||||||
toast.error(`${t("error.empty_req_name")}`)
|
toast.error(`${t("error.empty_req_name")}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
emit("add-request", name.value)
|
emit("add-request", editingName.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
|
|||||||
@@ -6,21 +6,13 @@
|
|||||||
@close="hideModal"
|
@close="hideModal"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelEdit"
|
placeholder=" "
|
||||||
v-model="name"
|
input-styles="floating-input"
|
||||||
v-focus
|
:label="t('action.label')"
|
||||||
class="input floating-input"
|
@submit="saveCollection"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="saveCollection"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelEdit">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
@@ -67,26 +59,26 @@ const emit = defineEmits<{
|
|||||||
(e: "hide-modal"): void
|
(e: "hide-modal"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const name = ref("")
|
const editingName = ref("")
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.editingCollectionName,
|
() => props.editingCollectionName,
|
||||||
(newName) => {
|
(newName) => {
|
||||||
name.value = newName
|
editingName.value = newName
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const saveCollection = () => {
|
const saveCollection = () => {
|
||||||
if (name.value.trim() === "") {
|
if (editingName.value.trim() === "") {
|
||||||
toast.error(t("collection.invalid_name"))
|
toast.error(t("collection.invalid_name"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
emit("submit", name.value)
|
emit("submit", editingName.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
name.value = ""
|
editingName.value = ""
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,21 +6,13 @@
|
|||||||
@close="emit('hide-modal')"
|
@close="emit('hide-modal')"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelEditFolder"
|
placeholder=" "
|
||||||
v-model="name"
|
:label="t('action.label')"
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
@submit="editFolder"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="editFolder"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelEditFolder">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
@@ -67,26 +59,26 @@ const emit = defineEmits<{
|
|||||||
(e: "hide-modal"): void
|
(e: "hide-modal"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const name = ref("")
|
const editingName = ref("")
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.editingFolderName,
|
() => props.editingFolderName,
|
||||||
(newName) => {
|
(newName) => {
|
||||||
name.value = newName
|
editingName.value = newName
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const editFolder = () => {
|
const editFolder = () => {
|
||||||
if (name.value.trim() === "") {
|
if (editingName.value.trim() === "") {
|
||||||
toast.error(t("folder.invalid_name"))
|
toast.error(t("folder.invalid_name"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
emit("submit", name.value)
|
emit("submit", editingName.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
name.value = ""
|
editingName.value = ""
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,21 +6,13 @@
|
|||||||
@close="hideModal"
|
@close="hideModal"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelEditReq"
|
placeholder=" "
|
||||||
v-model="name"
|
:label="t('action.label')"
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
@submit="editRequest"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="editRequest"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelEditReq">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
@@ -68,19 +60,19 @@ const emit = defineEmits<{
|
|||||||
(e: "update:modelValue", value: string): void
|
(e: "update:modelValue", value: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const name = useVModel(props, "modelValue")
|
const editingName = useVModel(props, "modelValue")
|
||||||
|
|
||||||
const editRequest = () => {
|
const editRequest = () => {
|
||||||
if (name.value.trim() === "") {
|
if (editingName.value.trim() === "") {
|
||||||
toast.error(t("request.invalid_name"))
|
toast.error(t("request.invalid_name"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
emit("submit", name.value)
|
emit("submit", editingName.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
name.value = ""
|
editingName.value = ""
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col flex-1">
|
<div class="flex flex-col flex-1">
|
||||||
<SmartTree :adapter="myAdapter">
|
<HoppSmartTree :adapter="myAdapter">
|
||||||
<template
|
<template
|
||||||
#content="{ node, toggleChildren, isOpen, highlightChildren }"
|
#content="{ node, toggleChildren, isOpen, highlightChildren }"
|
||||||
>
|
>
|
||||||
@@ -291,7 +291,7 @@
|
|||||||
>
|
>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</template>
|
</template>
|
||||||
</SmartTree>
|
</HoppSmartTree>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -303,7 +303,10 @@ import IconHelpCircle from "~icons/lucide/help-circle"
|
|||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { computed, PropType, Ref, toRef } from "vue"
|
import { computed, PropType, Ref, toRef } from "vue"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
import { ChildrenResult, SmartTreeAdapter } from "~/helpers/treeAdapter"
|
import {
|
||||||
|
ChildrenResult,
|
||||||
|
SmartTreeAdapter,
|
||||||
|
} from "@hoppscotch/ui/dist/helpers/treeAdapter"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
|
|||||||
@@ -8,21 +8,15 @@
|
|||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="relative flex">
|
<HoppSmartInput
|
||||||
<input
|
v-model="requestName"
|
||||||
id="selectLabelSaveReq"
|
styles="relative flex"
|
||||||
v-model="requestName"
|
placeholder=" "
|
||||||
v-focus
|
:label="t('request.name')"
|
||||||
class="input floating-input"
|
input-styles="floating-input"
|
||||||
placeholder=" "
|
@submit="saveRequestAs"
|
||||||
type="text"
|
/>
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="saveRequestAs"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelSaveReq">
|
|
||||||
{{ t("request.name") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<label class="p-4">
|
<label class="p-4">
|
||||||
{{ t("collection.select_location") }}
|
{{ t("collection.select_location") }}
|
||||||
</label>
|
</label>
|
||||||
@@ -62,7 +56,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { nextTick, reactive, ref, watch } from "vue"
|
import { computed, nextTick, reactive, ref, watch } from "vue"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import {
|
import {
|
||||||
HoppGQLRequest,
|
HoppGQLRequest,
|
||||||
@@ -107,10 +101,12 @@ const props = withDefaults(
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
mode: "rest" | "graphql"
|
mode: "rest" | "graphql"
|
||||||
|
request?: HoppRESTRequest | HoppGQLRequest | null
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
show: false,
|
show: false,
|
||||||
mode: "rest",
|
mode: "rest",
|
||||||
|
request: null,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -132,9 +128,17 @@ const restRequestName = computedWithControl(
|
|||||||
() => currentActiveTab.value.document.request.name
|
() => currentActiveTab.value.document.request.name
|
||||||
)
|
)
|
||||||
|
|
||||||
const requestName = ref(
|
const reqName = computed(() => {
|
||||||
props.mode === "rest" ? restRequestName.value : gqlRequestName.value
|
if (props.request) {
|
||||||
)
|
return props.request.name
|
||||||
|
} else if (props.mode === "rest") {
|
||||||
|
return restRequestName.value
|
||||||
|
} else {
|
||||||
|
return gqlRequestName.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const requestName = ref(reqName.value)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [currentActiveTab.value, gqlRequestName.value],
|
() => [currentActiveTab.value, gqlRequestName.value],
|
||||||
@@ -198,10 +202,15 @@ const saveRequestAs = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestUpdated =
|
let requestUpdated
|
||||||
props.mode === "rest"
|
|
||||||
? cloneDeep(currentActiveTab.value.document.request)
|
if (props.request) {
|
||||||
: cloneDeep(getGQLSession().request)
|
requestUpdated = cloneDeep(props.request)
|
||||||
|
} else if (props.mode === "rest") {
|
||||||
|
requestUpdated = cloneDeep(currentActiveTab.value.document.request)
|
||||||
|
} else {
|
||||||
|
requestUpdated = cloneDeep(getGQLSession().request)
|
||||||
|
}
|
||||||
|
|
||||||
requestUpdated.name = requestName.value
|
requestUpdated.name = requestName.value
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col overflow-hidden">
|
<div class="flex flex-col overflow-hidden">
|
||||||
<SmartTree :adapter="teamAdapter">
|
<HoppSmartTree :adapter="teamAdapter">
|
||||||
<template
|
<template
|
||||||
#content="{ node, toggleChildren, isOpen, highlightChildren }"
|
#content="{ node, toggleChildren, isOpen, highlightChildren }"
|
||||||
>
|
>
|
||||||
@@ -311,7 +311,7 @@
|
|||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</SmartTree>
|
</HoppSmartTree>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -326,7 +326,10 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
||||||
import { TeamRequest } from "~/helpers/teams/TeamRequest"
|
import { TeamRequest } from "~/helpers/teams/TeamRequest"
|
||||||
import { ChildrenResult, SmartTreeAdapter } from "~/helpers/treeAdapter"
|
import {
|
||||||
|
ChildrenResult,
|
||||||
|
SmartTreeAdapter,
|
||||||
|
} from "@hoppscotch/ui/dist/helpers/treeAdapter"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
|
|||||||
@@ -6,21 +6,13 @@
|
|||||||
@close="hideModal"
|
@close="hideModal"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="name"
|
||||||
id="selectLabelGqlAdd"
|
placeholder=" "
|
||||||
v-model="name"
|
input-styles="floating-input"
|
||||||
v-focus
|
:label="t('action.label')"
|
||||||
class="input floating-input"
|
@submit="addNewCollection"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="addNewCollection"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelGqlAdd">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
|
|||||||
@@ -6,21 +6,13 @@
|
|||||||
@close="$emit('hide-modal')"
|
@close="$emit('hide-modal')"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="name"
|
||||||
id="selectLabelGqlAddFolder"
|
placeholder=" "
|
||||||
v-model="name"
|
:label="t('action.label')"
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
@submit="addFolder"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="addFolder"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelGqlAddFolder">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
|
|||||||
@@ -6,21 +6,13 @@
|
|||||||
@close="emit('hide-modal')"
|
@close="emit('hide-modal')"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelGqlAddRequest"
|
placeholder=" "
|
||||||
v-model="name"
|
:label="t('action.label')"
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
@submit="addRequest"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="addRequest"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelGqlAddRequest">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
@@ -65,24 +57,24 @@ const emit = defineEmits<{
|
|||||||
): void
|
): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const name = ref("")
|
const editingName = ref("")
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
name.value = getGQLSession().request.name
|
editingName.value = getGQLSession().request.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const addRequest = () => {
|
const addRequest = () => {
|
||||||
if (!name.value) {
|
if (!editingName.value) {
|
||||||
toast.error(`${t("error.empty_req_name")}`)
|
toast.error(`${t("error.empty_req_name")}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
emit("add-request", {
|
emit("add-request", {
|
||||||
name: name.value,
|
name: editingName.value,
|
||||||
path: props.folderPath,
|
path: props.folderPath,
|
||||||
})
|
})
|
||||||
hideModal()
|
hideModal()
|
||||||
|
|||||||
@@ -6,21 +6,13 @@
|
|||||||
@close="hideModal"
|
@close="hideModal"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelGqlEdit"
|
placeholder=" "
|
||||||
v-model="name"
|
:label="t('action.label')"
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
@submit="saveCollection"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="saveCollection"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelGqlEdit">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
@@ -60,17 +52,17 @@ const emit = defineEmits<{
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const name = ref<string | null>()
|
const editingName = ref<string | null>()
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.editingCollectionName,
|
() => props.editingCollectionName,
|
||||||
(val) => {
|
(val) => {
|
||||||
name.value = val
|
editingName.value = val
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const saveCollection = () => {
|
const saveCollection = () => {
|
||||||
if (!name.value) {
|
if (!editingName.value) {
|
||||||
toast.error(`${t("collection.invalid_name")}`)
|
toast.error(`${t("collection.invalid_name")}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -78,7 +70,7 @@ const saveCollection = () => {
|
|||||||
// TODO: Better typechecking here ?
|
// TODO: Better typechecking here ?
|
||||||
const collectionUpdated = {
|
const collectionUpdated = {
|
||||||
...(props.editingCollection as any),
|
...(props.editingCollection as any),
|
||||||
name: name.value,
|
name: editingName.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
editGraphqlCollection(props.editingCollectionIndex, collectionUpdated)
|
editGraphqlCollection(props.editingCollectionIndex, collectionUpdated)
|
||||||
@@ -86,7 +78,7 @@ const saveCollection = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
name.value = null
|
editingName.value = null
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,21 +6,13 @@
|
|||||||
@close="$emit('hide-modal')"
|
@close="$emit('hide-modal')"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="name"
|
||||||
id="selectLabelGqlEditFolder"
|
placeholder=" "
|
||||||
v-model="name"
|
:label="t('action.label')"
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
@submit="editFolder"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="editFolder"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelGqlEditFolder">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
|
|||||||
@@ -6,21 +6,13 @@
|
|||||||
@close="hideModal"
|
@close="hideModal"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="requestUpdateData.name"
|
||||||
id="selectLabelGqlEditReq"
|
placeholder=" "
|
||||||
v-model="requestUpdateData.name"
|
:label="t('action.label')"
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
@submit="saveRequest"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="saveRequest"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelGqlEditReq">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
type="search"
|
type="search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
:placeholder="t('action.search')"
|
:placeholder="t('action.search')"
|
||||||
class="py-2 pl-4 pr-2 bg-transparent"
|
class="py-2 pl-4 pr-2 bg-transparent !border-0"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="flex justify-between flex-1 flex-shrink-0 border-y bg-primary border-dividerLight"
|
class="flex justify-between flex-1 flex-shrink-0 border-y bg-primary border-dividerLight"
|
||||||
|
|||||||
@@ -18,12 +18,12 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<WorkspaceCurrent :section="t('tab.collections')" />
|
<WorkspaceCurrent :section="t('tab.collections')" />
|
||||||
|
|
||||||
<input
|
<input
|
||||||
v-model="filterTexts"
|
v-model="filterTexts"
|
||||||
type="search"
|
|
||||||
autocomplete="off"
|
|
||||||
:placeholder="t('action.search')"
|
:placeholder="t('action.search')"
|
||||||
class="py-2 pl-4 pr-2 bg-transparent"
|
class="py-2 pl-4 pr-2 bg-transparent !border-0"
|
||||||
|
type="search"
|
||||||
:disabled="collectionsType.type === 'team-collections'"
|
:disabled="collectionsType.type === 'team-collections'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
t("environment.name")
|
t("environment.name")
|
||||||
}}</label>
|
}}</label>
|
||||||
<input
|
<input
|
||||||
v-model="name"
|
v-model="editingName"
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="t('environment.variable')"
|
:placeholder="t('environment.variable')"
|
||||||
class="input"
|
class="input"
|
||||||
@@ -88,7 +88,6 @@ const props = defineProps<{
|
|||||||
position: { top: number; left: number }
|
position: { top: number; left: number }
|
||||||
name: string
|
name: string
|
||||||
value: string
|
value: string
|
||||||
replaceWithVariable: boolean
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -106,7 +105,7 @@ watch(
|
|||||||
scope.value = {
|
scope.value = {
|
||||||
type: "global",
|
type: "global",
|
||||||
}
|
}
|
||||||
name.value = ""
|
editingName.value = ""
|
||||||
replaceWithVariable.value = false
|
replaceWithVariable.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,22 +131,22 @@ const scope = ref<Scope>({
|
|||||||
|
|
||||||
const replaceWithVariable = ref(false)
|
const replaceWithVariable = ref(false)
|
||||||
|
|
||||||
const name = ref("")
|
const editingName = ref(props.name)
|
||||||
|
|
||||||
const addEnvironment = async () => {
|
const addEnvironment = async () => {
|
||||||
if (!name.value) {
|
if (!editingName.value) {
|
||||||
toast.error(`${t("environment.invalid_name")}`)
|
toast.error(`${t("environment.invalid_name")}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (scope.value.type === "global") {
|
if (scope.value.type === "global") {
|
||||||
addGlobalEnvVariable({
|
addGlobalEnvVariable({
|
||||||
key: name.value,
|
key: editingName.value,
|
||||||
value: props.value,
|
value: props.value,
|
||||||
})
|
})
|
||||||
toast.success(`${t("environment.updated")}`)
|
toast.success(`${t("environment.updated")}`)
|
||||||
} else if (scope.value.type === "my-environment") {
|
} else if (scope.value.type === "my-environment") {
|
||||||
addEnvironmentVariable(scope.value.index, {
|
addEnvironmentVariable(scope.value.index, {
|
||||||
key: name.value,
|
key: editingName.value,
|
||||||
value: props.value,
|
value: props.value,
|
||||||
})
|
})
|
||||||
toast.success(`${t("environment.updated")}`)
|
toast.success(`${t("environment.updated")}`)
|
||||||
@@ -155,7 +154,7 @@ const addEnvironment = async () => {
|
|||||||
const newVariables = [
|
const newVariables = [
|
||||||
...scope.value.environment.environment.variables,
|
...scope.value.environment.environment.variables,
|
||||||
{
|
{
|
||||||
key: name.value,
|
key: editingName.value,
|
||||||
value: props.value,
|
value: props.value,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -179,7 +178,7 @@ const addEnvironment = async () => {
|
|||||||
}
|
}
|
||||||
if (replaceWithVariable.value) {
|
if (replaceWithVariable.value) {
|
||||||
//replace the current tab endpoint with the variable name with << and >>
|
//replace the current tab endpoint with the variable name with << and >>
|
||||||
const variableName = `<<${name.value}>>`
|
const variableName = `<<${editingName.value}>>`
|
||||||
//replace the currenttab endpoint containing the value in the text with variablename
|
//replace the currenttab endpoint containing the value in the text with variablename
|
||||||
currentActiveTab.value.document.request.endpoint =
|
currentActiveTab.value.document.request.endpoint =
|
||||||
currentActiveTab.value.document.request.endpoint.replace(
|
currentActiveTab.value.document.request.endpoint.replace(
|
||||||
|
|||||||
@@ -1,203 +1,305 @@
|
|||||||
<template>
|
<template>
|
||||||
<tippy
|
<div class="flex divide-x divide-dividerLight">
|
||||||
interactive
|
<tippy
|
||||||
trigger="click"
|
interactive
|
||||||
theme="popover"
|
trigger="click"
|
||||||
:on-shown="() => tippyActions!.focus()"
|
theme="popover"
|
||||||
>
|
:on-shown="() => envSelectorActions!.focus()"
|
||||||
<span
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="`${t('environment.select')}`"
|
|
||||||
class="bg-transparent select-wrapper"
|
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<span
|
||||||
:icon="IconLayers"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:label="
|
:title="`${t('environment.select')}`"
|
||||||
mdAndLarger
|
class="select-wrapper"
|
||||||
? selectedEnv.type !== 'NO_ENV_SELECTED'
|
|
||||||
? selectedEnv.name
|
|
||||||
: `${t('environment.select')}`
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
class="flex-1 !justify-start pr-8 rounded-none"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<template #content="{ hide }">
|
|
||||||
<div
|
|
||||||
ref="tippyActions"
|
|
||||||
role="menu"
|
|
||||||
class="flex flex-col focus:outline-none"
|
|
||||||
tabindex="0"
|
|
||||||
@keyup.escape="hide()"
|
|
||||||
>
|
>
|
||||||
<HoppSmartItem
|
<HoppButtonSecondary
|
||||||
v-if="!isScopeSelector"
|
:icon="IconLayers"
|
||||||
:label="`${t('environment.no_environment')}`"
|
:label="
|
||||||
:info-icon="
|
mdAndLarger
|
||||||
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
|
? selectedEnv.type !== 'NO_ENV_SELECTED'
|
||||||
? IconCheck
|
? selectedEnv.name
|
||||||
: undefined
|
: `${t('environment.select')}`
|
||||||
"
|
: ''
|
||||||
:active-info-icon="
|
|
||||||
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
selectedEnvironmentIndex = { type: 'NO_ENV_SELECTED' }
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
"
|
||||||
|
class="flex-1 !justify-start pr-8 rounded-none"
|
||||||
/>
|
/>
|
||||||
<HoppSmartItem
|
</span>
|
||||||
v-else-if="isScopeSelector && modelValue"
|
<template #content="{ hide }">
|
||||||
:label="t('environment.global')"
|
<div
|
||||||
:icon="IconGlobe"
|
ref="envSelectorActions"
|
||||||
:info-icon="modelValue.type === 'global' ? IconCheck : undefined"
|
role="menu"
|
||||||
:active-info-icon="modelValue.type === 'global'"
|
class="flex flex-col focus:outline-none"
|
||||||
@click="
|
tabindex="0"
|
||||||
() => {
|
@keyup.escape="hide()"
|
||||||
$emit('update:modelValue', {
|
|
||||||
type: 'global',
|
|
||||||
})
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartTabs
|
|
||||||
v-model="selectedEnvTab"
|
|
||||||
styles="sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-10 top-0 bg-primary"
|
|
||||||
render-inactive-tabs
|
|
||||||
>
|
>
|
||||||
<HoppSmartTab
|
<HoppSmartItem
|
||||||
:id="'my-environments'"
|
:label="`${t('environment.no_environment')}`"
|
||||||
:label="`${t('environment.my_environments')}`"
|
:info-icon="
|
||||||
|
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
|
||||||
|
? IconCheck
|
||||||
|
: undefined
|
||||||
|
"
|
||||||
|
:active-info-icon="
|
||||||
|
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
|
||||||
|
"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
selectedEnvironmentIndex = { type: 'NO_ENV_SELECTED' }
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartTabs
|
||||||
|
v-model="selectedEnvTab"
|
||||||
|
:styles="`sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-0 top-0 bg-primary ${
|
||||||
|
!isTeamSelected || workspace.type === 'personal'
|
||||||
|
? 'bg-primaryLight'
|
||||||
|
: ''
|
||||||
|
}`"
|
||||||
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<HoppSmartItem
|
<HoppSmartTab
|
||||||
v-for="(gen, index) in myEnvironments"
|
:id="'my-environments'"
|
||||||
:key="`gen-${index}`"
|
:label="`${t('environment.my_environments')}`"
|
||||||
:icon="IconLayers"
|
|
||||||
:label="gen.name"
|
|
||||||
:info-icon="isEnvActive(index) ? IconCheck : undefined"
|
|
||||||
:active-info-icon="isEnvActive(index)"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
handleEnvironmentChange(index, {
|
|
||||||
type: 'my-environment',
|
|
||||||
environment: gen,
|
|
||||||
})
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartPlaceholder
|
|
||||||
v-if="myEnvironments.length === 0"
|
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
|
||||||
:alt="`${t('empty.environments')}`"
|
|
||||||
:text="t('empty.environments')"
|
|
||||||
>
|
>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
</HoppSmartTab>
|
|
||||||
<HoppSmartTab
|
|
||||||
:id="'team-environments'"
|
|
||||||
:label="`${t('environment.team_environments')}`"
|
|
||||||
:disabled="!isTeamSelected || workspace.type === 'personal'"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="teamListLoading"
|
|
||||||
class="flex flex-col items-center justify-center p-4"
|
|
||||||
>
|
|
||||||
<HoppSmartSpinner class="my-4" />
|
|
||||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="isTeamSelected" class="flex flex-col">
|
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
v-for="(gen, index) in teamEnvironmentList"
|
v-for="(gen, index) in myEnvironments"
|
||||||
:key="`gen-team-${index}`"
|
:key="`gen-${index}`"
|
||||||
:icon="IconLayers"
|
:icon="IconLayers"
|
||||||
:label="gen.environment.name"
|
:label="gen.name"
|
||||||
:info-icon="isEnvActive(gen.id) ? IconCheck : undefined"
|
:info-icon="index === selectedEnv.index ? IconCheck : undefined"
|
||||||
:active-info-icon="isEnvActive(gen.id)"
|
:active-info-icon="index === selectedEnv.index"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
handleEnvironmentChange(index, {
|
selectedEnvironmentIndex = {
|
||||||
type: 'team-environment',
|
type: 'MY_ENV',
|
||||||
environment: gen,
|
index: index,
|
||||||
})
|
}
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
<HoppSmartPlaceholder
|
v-if="myEnvironments.length === 0"
|
||||||
v-if="teamEnvironmentList.length === 0"
|
class="flex flex-col items-center justify-center text-secondaryLight"
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
|
||||||
:alt="`${t('empty.environments')}`"
|
|
||||||
:text="t('empty.environments')"
|
|
||||||
>
|
>
|
||||||
</HoppSmartPlaceholder>
|
<img
|
||||||
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
|
loading="lazy"
|
||||||
|
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-2"
|
||||||
|
:alt="`${t('empty.environments')}`"
|
||||||
|
/>
|
||||||
|
<span class="pb-2 text-center">
|
||||||
|
{{ t("empty.environments") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</HoppSmartTab>
|
||||||
|
<HoppSmartTab
|
||||||
|
:id="'team-environments'"
|
||||||
|
:label="`${t('environment.team_environments')}`"
|
||||||
|
:disabled="!isTeamSelected || workspace.type === 'personal'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="teamListLoading"
|
||||||
|
class="flex flex-col items-center justify-center p-4"
|
||||||
|
>
|
||||||
|
<HoppSmartSpinner class="my-4" />
|
||||||
|
<span class="text-secondaryLight">
|
||||||
|
{{ t("state.loading") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="isTeamSelected" class="flex flex-col">
|
||||||
|
<HoppSmartItem
|
||||||
|
v-for="(gen, index) in teamEnvironmentList"
|
||||||
|
:key="`gen-team-${index}`"
|
||||||
|
:icon="IconLayers"
|
||||||
|
:label="gen.environment.name"
|
||||||
|
:info-icon="
|
||||||
|
gen.id === selectedEnv.teamEnvID ? IconCheck : undefined
|
||||||
|
"
|
||||||
|
:active-info-icon="gen.id === selectedEnv.teamEnvID"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
selectedEnvironmentIndex = {
|
||||||
|
type: 'TEAM_ENV',
|
||||||
|
teamEnvID: gen.id,
|
||||||
|
teamID: gen.teamID,
|
||||||
|
environment: gen.environment,
|
||||||
|
}
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="teamEnvironmentList.length === 0"
|
||||||
|
class="flex flex-col items-center justify-center text-secondaryLight"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
|
loading="lazy"
|
||||||
|
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-2"
|
||||||
|
:alt="`${t('empty.environments')}`"
|
||||||
|
/>
|
||||||
|
<span class="pb-2 text-center">
|
||||||
|
{{ t("empty.environments") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!teamListLoading && teamAdapterError"
|
||||||
|
class="flex flex-col items-center py-4"
|
||||||
|
>
|
||||||
|
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
||||||
|
{{ getErrorMessage(teamAdapterError) }}
|
||||||
|
</div>
|
||||||
|
</HoppSmartTab>
|
||||||
|
</HoppSmartTabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
<span class="flex">
|
||||||
|
<tippy
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
:on-shown="() => envQuickPeekActions!.focus()"
|
||||||
|
>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="`${t('environment.quick_peek')}`"
|
||||||
|
:icon="IconEye"
|
||||||
|
class="!px-4"
|
||||||
|
/>
|
||||||
|
<template #content="{ hide }">
|
||||||
|
<div
|
||||||
|
ref="envQuickPeekActions"
|
||||||
|
role="menu"
|
||||||
|
class="flex flex-col focus:outline-none"
|
||||||
|
tabindex="0"
|
||||||
|
@keyup.escape="hide()"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="sticky top-0 font-semibold truncate flex items-center justify-between text-secondaryDark bg-primary border border-divider rounded pl-4"
|
||||||
|
>
|
||||||
|
{{ t("environment.global_variables") }}
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('action.edit')"
|
||||||
|
:icon="IconEdit"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
editGlobalEnv()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="my-2 flex flex-col flex-1 space-y-2 pl-4 pr-2">
|
||||||
|
<div class="flex flex-1 space-x-4">
|
||||||
|
<span class="w-1/4 min-w-32 truncate text-tiny font-semibold">
|
||||||
|
{{ t("environment.name") }}
|
||||||
|
</span>
|
||||||
|
<span class="w-full min-w-32 truncate text-tiny font-semibold">
|
||||||
|
{{ t("environment.value") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="(variable, index) in globalEnvs"
|
||||||
|
:key="index"
|
||||||
|
class="flex flex-1 space-x-4"
|
||||||
|
>
|
||||||
|
<span class="text-secondaryLight w-1/4 min-w-32 truncate">
|
||||||
|
{{ variable.key }}
|
||||||
|
</span>
|
||||||
|
<span class="text-secondaryLight w-full min-w-32 truncate">
|
||||||
|
{{ variable.value }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="globalEnvs.length === 0" class="text-secondaryLight">
|
||||||
|
{{ t("environment.empty_variables") }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!teamListLoading && teamAdapterError"
|
class="sticky top-0 mt-2 font-semibold truncate flex items-center justify-between text-secondaryDark bg-primary border border-divider rounded pl-4"
|
||||||
class="flex flex-col items-center py-4"
|
:class="{
|
||||||
|
'bg-primaryLight': !selectedEnv.variables,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
{{ t("environment.list") }}
|
||||||
{{ getErrorMessage(teamAdapterError) }}
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:disabled="!selectedEnv.variables"
|
||||||
|
:title="t('action.edit')"
|
||||||
|
:icon="IconEdit"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
editEnv()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</HoppSmartTab>
|
<div
|
||||||
</HoppSmartTabs>
|
v-if="selectedEnv.type === 'NO_ENV_SELECTED'"
|
||||||
</div>
|
class="text-secondaryLight my-2 flex flex-col flex-1 pl-4"
|
||||||
</template>
|
>
|
||||||
</tippy>
|
{{ t("environment.no_active_environment") }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="my-2 flex flex-col flex-1 space-y-2 pl-4 pr-2">
|
||||||
|
<div class="flex flex-1 space-x-4">
|
||||||
|
<span class="w-1/4 min-w-32 truncate text-tiny font-semibold">
|
||||||
|
{{ t("environment.name") }}
|
||||||
|
</span>
|
||||||
|
<span class="w-full min-w-32 truncate text-tiny font-semibold">
|
||||||
|
{{ t("environment.value") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="(variable, index) in environmentVariables"
|
||||||
|
:key="index"
|
||||||
|
class="flex flex-1 space-x-4"
|
||||||
|
>
|
||||||
|
<span class="text-secondaryLight w-1/4 min-w-32 truncate">
|
||||||
|
{{ variable.key }}
|
||||||
|
</span>
|
||||||
|
<span class="text-secondaryLight w-full min-w-32 truncate">
|
||||||
|
{{ variable.value }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="environmentVariables.length === 0"
|
||||||
|
class="text-secondaryLight"
|
||||||
|
>
|
||||||
|
{{ t("environment.empty_variables") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref, watch } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
import IconLayers from "~icons/lucide/layers"
|
import IconLayers from "~icons/lucide/layers"
|
||||||
import IconGlobe from "~icons/lucide/globe"
|
import IconEye from "~icons/lucide/eye"
|
||||||
|
import IconEdit from "~icons/lucide/edit"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { useReadonlyStream, useStream } from "~/composables/stream"
|
import { useReadonlyStream, useStream } from "~/composables/stream"
|
||||||
import {
|
import {
|
||||||
environments$,
|
environments$,
|
||||||
|
globalEnv$,
|
||||||
selectedEnvironmentIndex$,
|
selectedEnvironmentIndex$,
|
||||||
setSelectedEnvironmentIndex,
|
setSelectedEnvironmentIndex,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import { changeWorkspace, workspaceStatus$ } from "~/newstore/workspace"
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
|
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
|
||||||
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
import { invokeAction } from "~/helpers/actions"
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
|
||||||
import { onLoggedIn } from "~/composables/auth"
|
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
|
||||||
import { Environment } from "@hoppscotch/data"
|
|
||||||
|
|
||||||
type Scope =
|
|
||||||
| {
|
|
||||||
type: "global"
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "my-environment"
|
|
||||||
environment: Environment
|
|
||||||
index: number
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "team-environment"
|
|
||||||
environment: TeamEnvironment
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
isScopeSelector?: boolean
|
|
||||||
modelValue?: Scope
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: "update:modelValue", data: Scope): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||||
const mdAndLarger = breakpoints.greater("md")
|
const mdAndLarger = breakpoints.greater("md")
|
||||||
@@ -212,39 +314,6 @@ const myEnvironments = useReadonlyStream(environments$, [])
|
|||||||
|
|
||||||
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
// TeamList-Adapter
|
|
||||||
const teamListAdapter = new TeamListAdapter(true)
|
|
||||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
|
||||||
const teamListFetched = ref(false)
|
|
||||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
|
||||||
|
|
||||||
onLoggedIn(() => {
|
|
||||||
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
|
||||||
})
|
|
||||||
|
|
||||||
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
|
||||||
REMEMBERED_TEAM_ID.value = team.id
|
|
||||||
changeWorkspace({
|
|
||||||
teamID: team.id,
|
|
||||||
teamName: team.name,
|
|
||||||
type: "team",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => myTeams.value,
|
|
||||||
(newTeams) => {
|
|
||||||
if (newTeams && !teamListFetched.value) {
|
|
||||||
teamListFetched.value = true
|
|
||||||
if (REMEMBERED_TEAM_ID.value) {
|
|
||||||
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
|
||||||
if (team) switchToTeamWorkspace(team)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// TeamEnv List Adapter
|
|
||||||
const teamEnvListAdapter = new TeamEnvironmentAdapter(undefined)
|
const teamEnvListAdapter = new TeamEnvironmentAdapter(undefined)
|
||||||
const teamListLoading = useReadonlyStream(teamEnvListAdapter.loading$, false)
|
const teamListLoading = useReadonlyStream(teamEnvListAdapter.loading$, false)
|
||||||
const teamAdapterError = useReadonlyStream(teamEnvListAdapter.error$, null)
|
const teamAdapterError = useReadonlyStream(teamEnvListAdapter.error$, null)
|
||||||
@@ -279,157 +348,41 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleEnvironmentChange = (
|
|
||||||
index: number,
|
|
||||||
env?:
|
|
||||||
| {
|
|
||||||
type: "my-environment"
|
|
||||||
environment: Environment
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "team-environment"
|
|
||||||
environment: TeamEnvironment
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
if (props.isScopeSelector && env) {
|
|
||||||
if (env.type === "my-environment") {
|
|
||||||
emit("update:modelValue", {
|
|
||||||
type: "my-environment",
|
|
||||||
environment: env.environment,
|
|
||||||
index,
|
|
||||||
})
|
|
||||||
} else if (env.type === "team-environment") {
|
|
||||||
emit("update:modelValue", {
|
|
||||||
type: "team-environment",
|
|
||||||
environment: env.environment,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (env && env.type === "my-environment") {
|
|
||||||
selectedEnvironmentIndex.value = {
|
|
||||||
type: "MY_ENV",
|
|
||||||
index,
|
|
||||||
}
|
|
||||||
} else if (env && env.type === "team-environment") {
|
|
||||||
selectedEnvironmentIndex.value = {
|
|
||||||
type: "TEAM_ENV",
|
|
||||||
teamEnvID: env.environment.id,
|
|
||||||
teamID: env.environment.teamID,
|
|
||||||
environment: env.environment.environment,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isEnvActive = (id: string | number) => {
|
|
||||||
if (props.isScopeSelector) {
|
|
||||||
if (props.modelValue?.type === "my-environment") {
|
|
||||||
return props.modelValue.index === id
|
|
||||||
} else if (props.modelValue?.type === "team-environment") {
|
|
||||||
return (
|
|
||||||
props.modelValue?.type === "team-environment" &&
|
|
||||||
props.modelValue.environment &&
|
|
||||||
props.modelValue.environment.id === id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
|
||||||
return selectedEnv.value.index === id
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
|
||||||
selectedEnv.value.teamEnvID === id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedEnv = computed(() => {
|
const selectedEnv = computed(() => {
|
||||||
if (props.isScopeSelector) {
|
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
||||||
if (props.modelValue?.type === "my-environment") {
|
const environment =
|
||||||
return {
|
myEnvironments.value[selectedEnvironmentIndex.value.index]
|
||||||
type: "MY_ENV",
|
return {
|
||||||
index: props.modelValue.index,
|
type: "MY_ENV",
|
||||||
name: props.modelValue.environment?.name,
|
index: selectedEnvironmentIndex.value.index,
|
||||||
}
|
name: environment.name,
|
||||||
} else if (props.modelValue?.type === "team-environment") {
|
variables: environment.variables,
|
||||||
|
}
|
||||||
|
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
||||||
|
const teamEnv = teamEnvironmentList.value.find(
|
||||||
|
(env) =>
|
||||||
|
env.id ===
|
||||||
|
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||||
|
selectedEnvironmentIndex.value.teamEnvID)
|
||||||
|
)
|
||||||
|
if (teamEnv) {
|
||||||
return {
|
return {
|
||||||
type: "TEAM_ENV",
|
type: "TEAM_ENV",
|
||||||
name: props.modelValue.environment.environment.name,
|
name: teamEnv.environment.name,
|
||||||
teamEnvID: props.modelValue.environment.id,
|
teamEnvID: selectedEnvironmentIndex.value.teamEnvID,
|
||||||
}
|
variables: teamEnv.environment.variables,
|
||||||
} else {
|
|
||||||
return { type: "global", name: "Global" }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
|
||||||
return {
|
|
||||||
type: "MY_ENV",
|
|
||||||
index: selectedEnvironmentIndex.value.index,
|
|
||||||
name: myEnvironments.value[selectedEnvironmentIndex.value.index].name,
|
|
||||||
}
|
|
||||||
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
|
||||||
const teamEnv = teamEnvironmentList.value.find(
|
|
||||||
(env) =>
|
|
||||||
env.id ===
|
|
||||||
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
|
||||||
selectedEnvironmentIndex.value.teamEnvID)
|
|
||||||
)
|
|
||||||
if (teamEnv) {
|
|
||||||
return {
|
|
||||||
type: "TEAM_ENV",
|
|
||||||
name: teamEnv.environment.name,
|
|
||||||
teamEnvID: selectedEnvironmentIndex.value.teamEnvID,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return { type: "NO_ENV_SELECTED" }
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return { type: "NO_ENV_SELECTED" }
|
return { type: "NO_ENV_SELECTED" }
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
})
|
return { type: "NO_ENV_SELECTED" }
|
||||||
|
|
||||||
// Set the selected environment as initial scope value
|
|
||||||
onMounted(() => {
|
|
||||||
if (props.isScopeSelector) {
|
|
||||||
if (
|
|
||||||
selectedEnvironmentIndex.value.type === "MY_ENV" &&
|
|
||||||
selectedEnvironmentIndex.value.index !== undefined
|
|
||||||
) {
|
|
||||||
emit("update:modelValue", {
|
|
||||||
type: "my-environment",
|
|
||||||
environment: myEnvironments.value[selectedEnvironmentIndex.value.index],
|
|
||||||
index: selectedEnvironmentIndex.value.index,
|
|
||||||
})
|
|
||||||
} else if (
|
|
||||||
selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
|
||||||
selectedEnvironmentIndex.value.teamEnvID &&
|
|
||||||
teamEnvironmentList.value &&
|
|
||||||
teamEnvironmentList.value.length > 0
|
|
||||||
) {
|
|
||||||
const teamEnv = teamEnvironmentList.value.find(
|
|
||||||
(env) =>
|
|
||||||
env.id ===
|
|
||||||
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
|
||||||
selectedEnvironmentIndex.value.teamEnvID)
|
|
||||||
)
|
|
||||||
if (teamEnv) {
|
|
||||||
emit("update:modelValue", {
|
|
||||||
type: "team-environment",
|
|
||||||
environment: teamEnv,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
emit("update:modelValue", {
|
|
||||||
type: "global",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Template refs
|
// Template refs
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const envSelectorActions = ref<TippyComponent | null>(null)
|
||||||
|
const envQuickPeekActions = ref<TippyComponent | null>(null)
|
||||||
|
|
||||||
const getErrorMessage = (err: GQLError<string>) => {
|
const getErrorMessage = (err: GQLError<string>) => {
|
||||||
if (err.type === "network_error") {
|
if (err.type === "network_error") {
|
||||||
@@ -443,4 +396,32 @@ const getErrorMessage = (err: GQLError<string>) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const globalEnvs = useReadonlyStream(globalEnv$, [])
|
||||||
|
|
||||||
|
const environmentVariables = computed(() => {
|
||||||
|
if (selectedEnv.value.variables) {
|
||||||
|
return selectedEnv.value.variables
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const editGlobalEnv = () => {
|
||||||
|
invokeAction("modals.my.environment.edit", {
|
||||||
|
envName: "Global",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const editEnv = () => {
|
||||||
|
if (selectedEnv.value.type === "MY_ENV" && selectedEnv.value.name) {
|
||||||
|
invokeAction("modals.my.environment.edit", {
|
||||||
|
envName: selectedEnv.value.name,
|
||||||
|
})
|
||||||
|
} else if (selectedEnv.value.type === "TEAM_ENV" && selectedEnv.value.name) {
|
||||||
|
invokeAction("modals.team.environment.edit", {
|
||||||
|
envName: selectedEnv.value.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
@edit-environment="editEnvironment('Global')"
|
@edit-environment="editEnvironment('Global')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<EnvironmentsMy v-if="environmentType.type === 'my-environments'" />
|
<EnvironmentsMy v-show="environmentType.type === 'my-environments'" />
|
||||||
<EnvironmentsTeams
|
<EnvironmentsTeams
|
||||||
v-if="environmentType.type === 'team-environments'"
|
v-show="environmentType.type === 'team-environments'"
|
||||||
:team="environmentType.selectedTeam"
|
:team="environmentType.selectedTeam"
|
||||||
:team-environments="teamEnvironmentList"
|
:team-environments="teamEnvironmentList"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -198,10 +198,15 @@ const resetSelectedData = () => {
|
|||||||
editingEnvironmentIndex.value = null
|
editingEnvironmentIndex.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineActionHandler("modals.environment.new", () => {
|
||||||
|
action.value = "new"
|
||||||
|
showModalDetails.value = true
|
||||||
|
})
|
||||||
|
|
||||||
defineActionHandler(
|
defineActionHandler(
|
||||||
"modals.my.environment.edit",
|
"modals.my.environment.edit",
|
||||||
({ envName, variableName }) => {
|
({ envName, variableName }) => {
|
||||||
editingVariableName.value = variableName
|
if (variableName) editingVariableName.value = variableName
|
||||||
envName === "Global" && editEnvironment("Global")
|
envName === "Global" && editEnvironment("Global")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,22 +7,15 @@
|
|||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="relative flex">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelEnvEdit"
|
placeholder=" "
|
||||||
v-model="name"
|
:label="t('action.label')"
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
:disabled="editingEnvironmentIndex === 'Global'"
|
||||||
placeholder=" "
|
@submit="saveEnvironment"
|
||||||
type="text"
|
/>
|
||||||
autocomplete="off"
|
|
||||||
:disabled="editingEnvironmentIndex === 'Global'"
|
|
||||||
@keyup.enter="saveEnvironment"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelEnvEdit">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between flex-1">
|
<div class="flex items-center justify-between flex-1">
|
||||||
<label for="variableList" class="p-4">
|
<label for="variableList" class="p-4">
|
||||||
{{ t("environment.variable_list") }}
|
{{ t("environment.variable_list") }}
|
||||||
@@ -178,7 +171,7 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const idTicker = ref(0)
|
const idTicker = ref(0)
|
||||||
|
|
||||||
const name = ref<string | null>(null)
|
const editingName = ref<string | null>(null)
|
||||||
const vars = ref<EnvironmentVariable[]>([
|
const vars = ref<EnvironmentVariable[]>([
|
||||||
{ id: idTicker.value++, env: { key: "", value: "" } },
|
{ id: idTicker.value++, env: { key: "", value: "" } },
|
||||||
])
|
])
|
||||||
@@ -231,10 +224,12 @@ const liveEnvs = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (props.editingEnvironmentIndex === "Global") {
|
if (props.editingEnvironmentIndex === "Global") {
|
||||||
return [...vars.value.map((x) => ({ ...x.env, source: name.value! }))]
|
return [
|
||||||
|
...vars.value.map((x) => ({ ...x.env, source: editingName.value! })),
|
||||||
|
]
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
...vars.value.map((x) => ({ ...x.env, source: name.value! })),
|
...vars.value.map((x) => ({ ...x.env, source: editingName.value! })),
|
||||||
...globalVars.value.map((x) => ({ ...x, source: "Global" })),
|
...globalVars.value.map((x) => ({ ...x, source: "Global" })),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -244,7 +239,7 @@ watch(
|
|||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
name.value = workingEnv.value?.name ?? null
|
editingName.value = workingEnv.value?.name ?? null
|
||||||
vars.value = pipe(
|
vars.value = pipe(
|
||||||
workingEnv.value?.variables ?? [],
|
workingEnv.value?.variables ?? [],
|
||||||
A.map((e) => ({
|
A.map((e) => ({
|
||||||
@@ -277,7 +272,7 @@ const removeEnvironmentVariable = (index: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const saveEnvironment = () => {
|
const saveEnvironment = () => {
|
||||||
if (!name.value) {
|
if (!editingName.value) {
|
||||||
toast.error(`${t("environment.invalid_name")}`)
|
toast.error(`${t("environment.invalid_name")}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -293,13 +288,13 @@ const saveEnvironment = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const environmentUpdated: Environment = {
|
const environmentUpdated: Environment = {
|
||||||
name: name.value,
|
name: editingName.value,
|
||||||
variables: filterdVariables,
|
variables: filterdVariables,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.action === "new") {
|
if (props.action === "new") {
|
||||||
// Creating a new environment
|
// Creating a new environment
|
||||||
createEnvironment(name.value, environmentUpdated.variables)
|
createEnvironment(editingName.value, environmentUpdated.variables)
|
||||||
setSelectedEnvironmentIndex({
|
setSelectedEnvironmentIndex({
|
||||||
type: "MY_ENV",
|
type: "MY_ENV",
|
||||||
index: envList.value.length - 1,
|
index: envList.value.length - 1,
|
||||||
@@ -337,7 +332,7 @@ const saveEnvironment = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
name.value = null
|
editingName.value = null
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -158,5 +158,7 @@ const duplicateEnvironments = () => {
|
|||||||
cloneDeep(getGlobalVariables())
|
cloneDeep(getGlobalVariables())
|
||||||
)
|
)
|
||||||
} else duplicateEnvironment(props.environmentIndex)
|
} else duplicateEnvironment(props.environmentIndex)
|
||||||
|
|
||||||
|
toast.success(`${t("environment.duplicated")}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ const resetSelectedData = () => {
|
|||||||
defineActionHandler(
|
defineActionHandler(
|
||||||
"modals.my.environment.edit",
|
"modals.my.environment.edit",
|
||||||
({ envName, variableName }) => {
|
({ envName, variableName }) => {
|
||||||
editingVariableName.value = variableName
|
if (variableName) editingVariableName.value = variableName
|
||||||
const envIndex: number = environments.value.findIndex(
|
const envIndex: number = environments.value.findIndex(
|
||||||
(environment: Environment) => {
|
(environment: Environment) => {
|
||||||
return environment.name === envName
|
return environment.name === envName
|
||||||
|
|||||||
@@ -7,23 +7,15 @@
|
|||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col px-2">
|
<div class="flex flex-col px-2">
|
||||||
<div class="relative flex">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelEnvEdit"
|
placeholder=" "
|
||||||
v-model="name"
|
:input-styles="['floating-input', isViewer && 'opacity-25']"
|
||||||
v-focus
|
:label="t('action.label')"
|
||||||
class="input floating-input"
|
:disabled="isViewer"
|
||||||
:class="isViewer && 'opacity-25'"
|
@submit="saveEnvironment"
|
||||||
placeholder=""
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
:disabled="isViewer"
|
|
||||||
@keyup.enter="saveEnvironment"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelEnvEdit">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between flex-1">
|
<div class="flex items-center justify-between flex-1">
|
||||||
<label for="variableList" class="p-4">
|
<label for="variableList" class="p-4">
|
||||||
{{ t("environment.variable_list") }}
|
{{ t("environment.variable_list") }}
|
||||||
@@ -190,7 +182,7 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const idTicker = ref(0)
|
const idTicker = ref(0)
|
||||||
|
|
||||||
const name = ref<string | null>(null)
|
const editingName = ref<string | null>(null)
|
||||||
const vars = ref<EnvironmentVariable[]>([
|
const vars = ref<EnvironmentVariable[]>([
|
||||||
{ id: idTicker.value++, env: { key: "", value: "" } },
|
{ id: idTicker.value++, env: { key: "", value: "" } },
|
||||||
])
|
])
|
||||||
@@ -216,7 +208,9 @@ const liveEnvs = computed(() => {
|
|||||||
if (evnExpandError.value) {
|
if (evnExpandError.value) {
|
||||||
return []
|
return []
|
||||||
} else {
|
} else {
|
||||||
return [...vars.value.map((x) => ({ ...x.env, source: name.value! }))]
|
return [
|
||||||
|
...vars.value.map((x) => ({ ...x.env, source: editingName.value! })),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -225,7 +219,7 @@ watch(
|
|||||||
(show) => {
|
(show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
if (props.action === "new") {
|
if (props.action === "new") {
|
||||||
name.value = null
|
editingName.value = null
|
||||||
vars.value = pipe(
|
vars.value = pipe(
|
||||||
props.envVars() ?? [],
|
props.envVars() ?? [],
|
||||||
A.map((e: { key: string; value: string }) => ({
|
A.map((e: { key: string; value: string }) => ({
|
||||||
@@ -234,7 +228,7 @@ watch(
|
|||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
} else if (props.editingEnvironment !== null) {
|
} else if (props.editingEnvironment !== null) {
|
||||||
name.value = props.editingEnvironment.environment.name ?? null
|
editingName.value = props.editingEnvironment.environment.name ?? null
|
||||||
vars.value = pipe(
|
vars.value = pipe(
|
||||||
props.editingEnvironment.environment.variables ?? [],
|
props.editingEnvironment.environment.variables ?? [],
|
||||||
A.map((e: { key: string; value: string }) => ({
|
A.map((e: { key: string; value: string }) => ({
|
||||||
@@ -272,7 +266,7 @@ const isLoading = ref(false)
|
|||||||
const saveEnvironment = async () => {
|
const saveEnvironment = async () => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|
||||||
if (!name.value) {
|
if (!editingName.value) {
|
||||||
toast.error(`${t("environment.invalid_name")}`)
|
toast.error(`${t("environment.invalid_name")}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -297,7 +291,7 @@ const saveEnvironment = async () => {
|
|||||||
createTeamEnvironment(
|
createTeamEnvironment(
|
||||||
JSON.stringify(filterdVariables),
|
JSON.stringify(filterdVariables),
|
||||||
props.editingTeamId,
|
props.editingTeamId,
|
||||||
name.value
|
editingName.value
|
||||||
),
|
),
|
||||||
TE.match(
|
TE.match(
|
||||||
(err: GQLError<string>) => {
|
(err: GQLError<string>) => {
|
||||||
@@ -320,7 +314,7 @@ const saveEnvironment = async () => {
|
|||||||
updateTeamEnvironment(
|
updateTeamEnvironment(
|
||||||
JSON.stringify(filterdVariables),
|
JSON.stringify(filterdVariables),
|
||||||
props.editingEnvironment.id,
|
props.editingEnvironment.id,
|
||||||
name.value
|
editingName.value
|
||||||
),
|
),
|
||||||
TE.match(
|
TE.match(
|
||||||
(err: GQLError<string>) => {
|
(err: GQLError<string>) => {
|
||||||
@@ -339,7 +333,7 @@ const saveEnvironment = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
name.value = null
|
editingName.value = null
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ const duplicateEnvironments = () => {
|
|||||||
toast.error(`${getErrorMessage(err)}`)
|
toast.error(`${getErrorMessage(err)}`)
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
toast.success(`${t("team_environment.duplicate")}`)
|
toast.success(`${t("environment.duplicated")}`)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)()
|
)()
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ const getErrorMessage = (err: GQLError<string>) => {
|
|||||||
defineActionHandler(
|
defineActionHandler(
|
||||||
"modals.team.environment.edit",
|
"modals.team.environment.edit",
|
||||||
({ envName, variableName }) => {
|
({ envName, variableName }) => {
|
||||||
editingVariableName.value = variableName
|
if (variableName) editingVariableName.value = variableName
|
||||||
const teamEnvToEdit = props.teamEnvironments.find(
|
const teamEnvToEdit = props.teamEnvironments.find(
|
||||||
(environment) => environment.environment.name === envName
|
(environment) => environment.environment.name === envName
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,27 +9,22 @@
|
|||||||
<template #body>
|
<template #body>
|
||||||
<div v-if="mode === 'sign-in'" class="flex flex-col space-y-2">
|
<div v-if="mode === 'sign-in'" class="flex flex-col space-y-2">
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
:loading="signingInWithGitHub"
|
v-for="provider in allowedAuthProviders"
|
||||||
:icon="IconGithub"
|
:key="provider.id"
|
||||||
:label="`${t('auth.continue_with_github')}`"
|
:loading="provider.isLoading.value"
|
||||||
@click="signInWithGithub"
|
:icon="provider.icon"
|
||||||
|
:label="provider.label"
|
||||||
|
@click="provider.action"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<hr v-if="additonalLoginItems.length > 0" />
|
||||||
|
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
:loading="signingInWithGoogle"
|
v-for="loginItem in additonalLoginItems"
|
||||||
:icon="IconGoogle"
|
:key="loginItem.id"
|
||||||
:label="`${t('auth.continue_with_google')}`"
|
:icon="loginItem.icon"
|
||||||
@click="signInWithGoogle"
|
:label="loginItem.text(t)"
|
||||||
/>
|
@click="doAdditionalLoginItemClickAction(loginItem)"
|
||||||
<HoppSmartItem
|
|
||||||
:loading="signingInWithMicrosoft"
|
|
||||||
:icon="IconMicrosoft"
|
|
||||||
:label="`${t('auth.continue_with_microsoft')}`"
|
|
||||||
@click="signInWithMicrosoft"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
:icon="IconEmail"
|
|
||||||
:label="`${t('auth.continue_with_email')}`"
|
|
||||||
@click="mode = 'email'"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<form
|
<form
|
||||||
@@ -37,24 +32,14 @@
|
|||||||
class="flex flex-col space-y-2"
|
class="flex flex-col space-y-2"
|
||||||
@submit.prevent="signInWithEmail"
|
@submit.prevent="signInWithEmail"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="form.email"
|
||||||
id="email"
|
type="email"
|
||||||
v-model="form.email"
|
placeholder=" "
|
||||||
v-focus
|
:label="t('auth.email')"
|
||||||
class="input floating-input"
|
input-styles="floating-input"
|
||||||
placeholder=" "
|
/>
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
autocomplete="off"
|
|
||||||
required
|
|
||||||
spellcheck="false"
|
|
||||||
autofocus
|
|
||||||
/>
|
|
||||||
<label for="email">
|
|
||||||
{{ t("auth.email") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
:loading="signingInWithEmail"
|
:loading="signingInWithEmail"
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -123,124 +108,138 @@
|
|||||||
</HoppSmartModal>
|
</HoppSmartModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from "vue"
|
import { Ref, computed, onMounted, ref } from "vue"
|
||||||
|
|
||||||
|
import { useStreamSubscriber } from "@composables/stream"
|
||||||
|
import { useToast } from "@composables/toast"
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { setLocalConfig } from "~/newstore/localpersistence"
|
||||||
|
|
||||||
import IconGithub from "~icons/auth/github"
|
import IconGithub from "~icons/auth/github"
|
||||||
import IconGoogle from "~icons/auth/google"
|
import IconGoogle from "~icons/auth/google"
|
||||||
import IconEmail from "~icons/auth/email"
|
import IconEmail from "~icons/auth/email"
|
||||||
import IconMicrosoft from "~icons/auth/microsoft"
|
import IconMicrosoft from "~icons/auth/microsoft"
|
||||||
import IconArrowLeft from "~icons/lucide/arrow-left"
|
import IconArrowLeft from "~icons/lucide/arrow-left"
|
||||||
import { setLocalConfig } from "~/newstore/localpersistence"
|
|
||||||
import { useStreamSubscriber } from "@composables/stream"
|
|
||||||
import { useToast } from "@composables/toast"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
|
|
||||||
export default defineComponent({
|
import { LoginItemDef } from "~/platform/auth"
|
||||||
props: {
|
|
||||||
show: Boolean,
|
|
||||||
},
|
|
||||||
emits: ["hide-modal"],
|
|
||||||
setup() {
|
|
||||||
const { subscribeToStream } = useStreamSubscriber()
|
|
||||||
|
|
||||||
const tosLink = import.meta.env.VITE_APP_TOS_LINK
|
defineProps<{
|
||||||
const privacyPolicyLink = import.meta.env.VITE_APP_PRIVACY_POLICY_LINK
|
show: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
return {
|
const emit = defineEmits<{
|
||||||
subscribeToStream,
|
(e: "hide-modal"): void
|
||||||
t: useI18n(),
|
}>()
|
||||||
toast: useToast(),
|
|
||||||
IconGithub,
|
|
||||||
IconGoogle,
|
|
||||||
IconEmail,
|
|
||||||
IconMicrosoft,
|
|
||||||
IconArrowLeft,
|
|
||||||
tosLink,
|
|
||||||
privacyPolicyLink,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
form: {
|
|
||||||
email: "",
|
|
||||||
},
|
|
||||||
signingInWithGoogle: false,
|
|
||||||
signingInWithGitHub: false,
|
|
||||||
signingInWithMicrosoft: false,
|
|
||||||
signingInWithEmail: false,
|
|
||||||
mode: "sign-in",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const currentUser$ = platform.auth.getCurrentUserStream()
|
|
||||||
|
|
||||||
this.subscribeToStream(currentUser$, (user) => {
|
const { subscribeToStream } = useStreamSubscriber()
|
||||||
if (user) this.hideModal()
|
const t = useI18n()
|
||||||
})
|
const toast = useToast()
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
showLoginSuccess() {
|
|
||||||
this.toast.success(`${this.t("auth.login_success")}`)
|
|
||||||
},
|
|
||||||
async signInWithGoogle() {
|
|
||||||
this.signingInWithGoogle = true
|
|
||||||
|
|
||||||
try {
|
const form = {
|
||||||
await platform.auth.signInUserWithGoogle()
|
email: "",
|
||||||
} catch (e) {
|
}
|
||||||
console.error(e)
|
|
||||||
/*
|
const signingInWithGoogle = ref(false)
|
||||||
|
const signingInWithGitHub = ref(false)
|
||||||
|
const signingInWithMicrosoft = ref(false)
|
||||||
|
const signingInWithEmail = ref(false)
|
||||||
|
const mode = ref("sign-in")
|
||||||
|
|
||||||
|
const tosLink = import.meta.env.VITE_APP_TOS_LINK
|
||||||
|
const privacyPolicyLink = import.meta.env.VITE_APP_PRIVACY_POLICY_LINK
|
||||||
|
|
||||||
|
type AuthProviderItem = {
|
||||||
|
id: string
|
||||||
|
icon: typeof IconGithub
|
||||||
|
label: string
|
||||||
|
action: (...args: any[]) => any
|
||||||
|
isLoading: Ref<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
const additonalLoginItems = computed(
|
||||||
|
() => platform.auth.additionalLoginItems ?? []
|
||||||
|
)
|
||||||
|
|
||||||
|
const doAdditionalLoginItemClickAction = async (item: LoginItemDef) => {
|
||||||
|
await item.onClick()
|
||||||
|
emit("hide-modal")
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const currentUser$ = platform.auth.getCurrentUserStream()
|
||||||
|
|
||||||
|
subscribeToStream(currentUser$, (user) => {
|
||||||
|
if (user) hideModal()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const showLoginSuccess = () => {
|
||||||
|
toast.success(`${t("auth.login_success")}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const signInWithGoogle = async () => {
|
||||||
|
signingInWithGoogle.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await platform.auth.signInUserWithGoogle()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
/*
|
||||||
A auth/account-exists-with-different-credential Firebase error wont happen between Google and any other providers
|
A auth/account-exists-with-different-credential Firebase error wont happen between Google and any other providers
|
||||||
Seems Google account overwrites accounts of other providers https://github.com/firebase/firebase-android-sdk/issues/25
|
Seems Google account overwrites accounts of other providers https://github.com/firebase/firebase-android-sdk/issues/25
|
||||||
*/
|
*/
|
||||||
this.toast.error(`${this.t("error.something_went_wrong")}`)
|
toast.error(`${t("error.something_went_wrong")}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.signingInWithGoogle = false
|
signingInWithGoogle.value = false
|
||||||
},
|
}
|
||||||
async signInWithGithub() {
|
|
||||||
this.signingInWithGitHub = true
|
|
||||||
|
|
||||||
const result = await platform.auth.signInUserWithGithub()
|
const signInWithGithub = async () => {
|
||||||
|
signingInWithGitHub.value = true
|
||||||
|
|
||||||
if (!result) {
|
const result = await platform.auth.signInUserWithGithub()
|
||||||
this.signingInWithGitHub = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.type === "success") {
|
if (!result) {
|
||||||
// this.showLoginSuccess()
|
signingInWithGitHub.value = false
|
||||||
} else if (result.type === "account-exists-with-different-cred") {
|
return
|
||||||
this.toast.info(`${this.t("auth.account_exists")}`, {
|
}
|
||||||
duration: 0,
|
|
||||||
closeOnSwipe: false,
|
|
||||||
action: {
|
|
||||||
text: `${this.t("action.yes")}`,
|
|
||||||
onClick: async (_, toastObject) => {
|
|
||||||
await result.link()
|
|
||||||
this.showLoginSuccess()
|
|
||||||
|
|
||||||
toastObject.goAway(0)
|
if (result.type === "success") {
|
||||||
},
|
// this.showLoginSuccess()
|
||||||
},
|
} else if (result.type === "account-exists-with-different-cred") {
|
||||||
})
|
toast.info(`${t("auth.account_exists")}`, {
|
||||||
} else {
|
duration: 0,
|
||||||
console.log("error logging into github", result.err)
|
closeOnSwipe: false,
|
||||||
this.toast.error(`${this.t("error.something_went_wrong")}`)
|
action: {
|
||||||
}
|
text: `${t("action.yes")}`,
|
||||||
|
onClick: async (_, toastObject) => {
|
||||||
|
await result.link()
|
||||||
|
showLoginSuccess()
|
||||||
|
|
||||||
this.signingInWithGitHub = false
|
toastObject.goAway(0)
|
||||||
},
|
},
|
||||||
async signInWithMicrosoft() {
|
},
|
||||||
this.signingInWithMicrosoft = true
|
})
|
||||||
|
} else {
|
||||||
|
console.log("error logging into github", result.err)
|
||||||
|
toast.error(`${t("error.something_went_wrong")}`)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
signingInWithGitHub.value = false
|
||||||
await platform.auth.signInUserWithMicrosoft()
|
}
|
||||||
// this.showLoginSuccess()
|
|
||||||
} catch (e) {
|
const signInWithMicrosoft = async () => {
|
||||||
console.error(e)
|
signingInWithMicrosoft.value = true
|
||||||
/*
|
|
||||||
|
try {
|
||||||
|
await platform.auth.signInUserWithMicrosoft()
|
||||||
|
// this.showLoginSuccess()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
/*
|
||||||
A auth/account-exists-with-different-credential Firebase error wont happen between MS with Google or Github
|
A auth/account-exists-with-different-credential Firebase error wont happen between MS with Google or Github
|
||||||
If a Github account exists and user then logs in with MS email we get a "Something went wrong toast" and console errors and MS replaces GH as only provider.
|
If a Github account exists and user then logs in with MS email we get a "Something went wrong toast" and console errors and MS replaces GH as only provider.
|
||||||
The error messages are as follows:
|
The error messages are as follows:
|
||||||
@@ -248,34 +247,82 @@ export default defineComponent({
|
|||||||
@firebase/auth: Auth (9.6.11): INTERNAL ASSERTION FAILED: Pending promise was never set
|
@firebase/auth: Auth (9.6.11): INTERNAL ASSERTION FAILED: Pending promise was never set
|
||||||
They may be related to https://github.com/firebase/firebaseui-web/issues/947
|
They may be related to https://github.com/firebase/firebaseui-web/issues/947
|
||||||
*/
|
*/
|
||||||
this.toast.error(`${this.t("error.something_went_wrong")}`)
|
toast.error(`${t("error.something_went_wrong")}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.signingInWithMicrosoft = false
|
signingInWithMicrosoft.value = false
|
||||||
},
|
}
|
||||||
async signInWithEmail() {
|
|
||||||
this.signingInWithEmail = true
|
|
||||||
|
|
||||||
await platform.auth
|
const signInWithEmail = async () => {
|
||||||
.signInWithEmail(this.form.email)
|
signingInWithEmail.value = true
|
||||||
.then(() => {
|
|
||||||
this.mode = "email-sent"
|
await platform.auth
|
||||||
setLocalConfig("emailForSignIn", this.form.email)
|
.signInWithEmail(form.email)
|
||||||
})
|
.then(() => {
|
||||||
.catch((e) => {
|
mode.value = "email-sent"
|
||||||
console.error(e)
|
setLocalConfig("emailForSignIn", form.email)
|
||||||
this.toast.error(e.message)
|
})
|
||||||
this.signingInWithEmail = false
|
.catch((e) => {
|
||||||
})
|
console.error(e)
|
||||||
.finally(() => {
|
toast.error(e.message)
|
||||||
this.signingInWithEmail = false
|
signingInWithEmail.value = false
|
||||||
})
|
})
|
||||||
},
|
.finally(() => {
|
||||||
hideModal() {
|
signingInWithEmail.value = false
|
||||||
this.mode = "sign-in"
|
})
|
||||||
this.toast.clear()
|
}
|
||||||
this.$emit("hide-modal")
|
|
||||||
},
|
const hideModal = () => {
|
||||||
|
mode.value = "sign-in"
|
||||||
|
toast.clear()
|
||||||
|
|
||||||
|
emit("hide-modal")
|
||||||
|
}
|
||||||
|
|
||||||
|
const authProviders: AuthProviderItem[] = [
|
||||||
|
{
|
||||||
|
id: "GITHUB",
|
||||||
|
icon: IconGithub,
|
||||||
|
label: t("auth.continue_with_github"),
|
||||||
|
action: signInWithGithub,
|
||||||
|
isLoading: signingInWithGitHub,
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
|
id: "GOOGLE",
|
||||||
|
icon: IconGoogle,
|
||||||
|
label: t("auth.continue_with_google"),
|
||||||
|
action: signInWithGoogle,
|
||||||
|
isLoading: signingInWithGoogle,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "MICROSOFT",
|
||||||
|
icon: IconMicrosoft,
|
||||||
|
label: t("auth.continue_with_microsoft"),
|
||||||
|
action: signInWithMicrosoft,
|
||||||
|
isLoading: signingInWithMicrosoft,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "EMAIL",
|
||||||
|
icon: IconEmail,
|
||||||
|
label: t("auth.continue_with_email"),
|
||||||
|
action: () => {
|
||||||
|
mode.value = "email"
|
||||||
|
},
|
||||||
|
isLoading: signingInWithEmail,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const allowedAuthProvidersIDsString: string | undefined = import.meta.env
|
||||||
|
.VITE_ALLOWED_AUTH_PROVIDERS
|
||||||
|
|
||||||
|
const allowedAuthProvidersIDs = allowedAuthProvidersIDsString
|
||||||
|
? allowedAuthProvidersIDsString.split(",")
|
||||||
|
: []
|
||||||
|
|
||||||
|
const allowedAuthProviders =
|
||||||
|
allowedAuthProvidersIDs.length > 0
|
||||||
|
? authProviders.filter((provider) =>
|
||||||
|
allowedAuthProvidersIDs.includes(provider.id)
|
||||||
|
)
|
||||||
|
: authProviders
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { GQLConnection } from "~/helpers/GQLConnection"
|
import { GQLConnection } from "~/helpers/GQLConnection"
|
||||||
import { getCurrentStrategyID } from "~/helpers/network"
|
|
||||||
import { useReadonlyStream, useStream } from "@composables/stream"
|
import { useReadonlyStream, useStream } from "@composables/stream"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import {
|
import {
|
||||||
@@ -38,9 +37,13 @@ import {
|
|||||||
gqlURL$,
|
gqlURL$,
|
||||||
setGQLURL,
|
setGQLURL,
|
||||||
} from "~/newstore/GQLSession"
|
} from "~/newstore/GQLSession"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
const interceptorService = useService(InterceptorService)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
conn: GQLConnection
|
conn: GQLConnection
|
||||||
}>()
|
}>()
|
||||||
@@ -62,7 +65,7 @@ const onConnectClick = () => {
|
|||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_REQUEST_RUN",
|
type: "HOPP_REQUEST_RUN",
|
||||||
platform: "graphql-schema",
|
platform: "graphql-schema",
|
||||||
strategy: getCurrentStrategyID(),
|
strategy: interceptorService.currentInterceptorID.value!,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
props.conn.disconnect()
|
props.conn.disconnect()
|
||||||
|
|||||||
@@ -373,7 +373,6 @@ import { commonHeaders } from "~/helpers/headers"
|
|||||||
import { GQLConnection } from "~/helpers/GQLConnection"
|
import { GQLConnection } from "~/helpers/GQLConnection"
|
||||||
import { makeGQLHistoryEntry, addGraphqlHistoryEntry } from "~/newstore/history"
|
import { makeGQLHistoryEntry, addGraphqlHistoryEntry } from "~/newstore/history"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { getCurrentStrategyID } from "~/helpers/network"
|
|
||||||
import { useCodemirror } from "@composables/codemirror"
|
import { useCodemirror } from "@composables/codemirror"
|
||||||
import jsonLinter from "~/helpers/editor/linting/json"
|
import jsonLinter from "~/helpers/editor/linting/json"
|
||||||
import { createGQLQueryLinter } from "~/helpers/editor/linting/gqlQuery"
|
import { createGQLQueryLinter } from "~/helpers/editor/linting/gqlQuery"
|
||||||
@@ -381,6 +380,8 @@ import queryCompleter from "~/helpers/editor/completion/gqlQuery"
|
|||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
import { objRemoveKey } from "~/helpers/functional/object"
|
import { objRemoveKey } from "~/helpers/functional/object"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
|
||||||
type OptionTabs = "query" | "headers" | "variables" | "authorization"
|
type OptionTabs = "query" | "headers" | "variables" | "authorization"
|
||||||
|
|
||||||
@@ -390,6 +391,8 @@ const selectedOptionTab = ref<OptionTabs>("query")
|
|||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
const interceptorService = useService(InterceptorService)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
conn: GQLConnection
|
conn: GQLConnection
|
||||||
}>()
|
}>()
|
||||||
@@ -744,7 +747,7 @@ const runQuery = async () => {
|
|||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_REQUEST_RUN",
|
type: "HOPP_REQUEST_RUN",
|
||||||
platform: "graphql-query",
|
platform: "graphql-query",
|
||||||
strategy: getCurrentStrategyID(),
|
strategy: interceptorService.currentInterceptorID.value!,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -754,7 +757,11 @@ const hideRequestModal = () => {
|
|||||||
|
|
||||||
const prettifyQuery = () => {
|
const prettifyQuery = () => {
|
||||||
try {
|
try {
|
||||||
gqlQueryString.value = gql.print(gql.parse(gqlQueryString.value))
|
gqlQueryString.value = gql.print(
|
||||||
|
gql.parse(gqlQueryString.value, {
|
||||||
|
allowLegacyFragmentVariables: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
prettifyQueryIcon.value = IconCheck
|
prettifyQueryIcon.value = IconCheck
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(`${t("error.gql_prettify_invalid_query")}`)
|
toast.error(`${t("error.gql_prettify_invalid_query")}`)
|
||||||
|
|||||||
@@ -105,6 +105,7 @@
|
|||||||
@toggle-star="toggleStar(entry.entry)"
|
@toggle-star="toggleStar(entry.entry)"
|
||||||
@delete-entry="deleteHistory(entry.entry)"
|
@delete-entry="deleteHistory(entry.entry)"
|
||||||
@use-entry="useHistory(toRaw(entry.entry))"
|
@use-entry="useHistory(toRaw(entry.entry))"
|
||||||
|
@add-to-collection="addToCollection(entry.entry)"
|
||||||
/>
|
/>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,7 +177,7 @@ import {
|
|||||||
import HistoryRestCard from "./rest/Card.vue"
|
import HistoryRestCard from "./rest/Card.vue"
|
||||||
import HistoryGraphqlCard from "./graphql/Card.vue"
|
import HistoryGraphqlCard from "./graphql/Card.vue"
|
||||||
import { createNewTab } from "~/helpers/rest/tab"
|
import { createNewTab } from "~/helpers/rest/tab"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
|
|
||||||
type HistoryEntry = GQLHistoryEntry | RESTHistoryEntry
|
type HistoryEntry = GQLHistoryEntry | RESTHistoryEntry
|
||||||
|
|
||||||
@@ -324,6 +325,14 @@ const deleteHistory = (entry: HistoryEntry) => {
|
|||||||
toast.success(`${t("state.deleted")}`)
|
toast.success(`${t("state.deleted")}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addToCollection = (entry: HistoryEntry) => {
|
||||||
|
if (props.page === "rest") {
|
||||||
|
invokeAction("request.save-as", {
|
||||||
|
request: entry.request,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toggleStar = (entry: HistoryEntry) => {
|
const toggleStar = (entry: HistoryEntry) => {
|
||||||
// History entry type specified because function does not know the type
|
// History entry type specified because function does not know the type
|
||||||
if (props.page === "rest")
|
if (props.page === "rest")
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-stretch group">
|
<div
|
||||||
|
class="flex items-stretch group"
|
||||||
|
@contextmenu.prevent="options!.tippy.show()"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||||
class="flex items-center justify-center w-16 px-2 truncate cursor-pointer"
|
class="flex items-center justify-center w-16 px-2 truncate cursor-pointer"
|
||||||
@@ -26,6 +29,39 @@
|
|||||||
{{ entry.request.endpoint }}
|
{{ entry.request.endpoint }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span>
|
||||||
|
<tippy
|
||||||
|
ref="options"
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
:on-shown="() => tippyActions!.focus()"
|
||||||
|
>
|
||||||
|
<template #content="{ hide }">
|
||||||
|
<div
|
||||||
|
ref="tippyActions"
|
||||||
|
class="flex flex-col focus:outline-none"
|
||||||
|
tabindex="0"
|
||||||
|
role="menu"
|
||||||
|
@keyup.s="addToCollectionAction?.$el.click()"
|
||||||
|
@keyup.escape="hide()"
|
||||||
|
>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="addToCollectionAction"
|
||||||
|
:icon="IconSave"
|
||||||
|
:label="`${t('collection.save_to_collection')}`"
|
||||||
|
:shortcut="['S']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('add-to-collection')
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
</span>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconTrash"
|
:icon="IconTrash"
|
||||||
@@ -48,15 +84,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue"
|
import { computed, ref } from "vue"
|
||||||
import findStatusGroup from "~/helpers/findStatusGroup"
|
import findStatusGroup from "~/helpers/findStatusGroup"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { RESTHistoryEntry } from "~/newstore/history"
|
import { RESTHistoryEntry } from "~/newstore/history"
|
||||||
import { shortDateTime } from "~/helpers/utils/date"
|
import { shortDateTime } from "~/helpers/utils/date"
|
||||||
|
import IconSave from "~icons/lucide/save"
|
||||||
import IconStar from "~icons/lucide/star"
|
import IconStar from "~icons/lucide/star"
|
||||||
import IconStarOff from "~icons/hopp/star-off"
|
import IconStarOff from "~icons/hopp/star-off"
|
||||||
import IconTrash from "~icons/lucide/trash"
|
import IconTrash from "~icons/lucide/trash"
|
||||||
|
import { TippyComponent } from "vue-tippy"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
entry: RESTHistoryEntry
|
entry: RESTHistoryEntry
|
||||||
@@ -67,8 +104,13 @@ const emit = defineEmits<{
|
|||||||
(e: "use-entry"): void
|
(e: "use-entry"): void
|
||||||
(e: "delete-entry"): void
|
(e: "delete-entry"): void
|
||||||
(e: "toggle-star"): void
|
(e: "toggle-star"): void
|
||||||
|
(e: "add-to-collection"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
|
const options = ref<TippyComponent | null>(null)
|
||||||
|
const addToCollectionAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const duration = computed(() => {
|
const duration = computed(() => {
|
||||||
|
|||||||
@@ -28,7 +28,9 @@
|
|||||||
>
|
>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
:label="t('state.none')"
|
:label="t('state.none')"
|
||||||
:info-icon="(body.contentType === null ? IconDone : null) as any"
|
:info-icon="
|
||||||
|
(body.contentType === null ? IconDone : null) as any
|
||||||
|
"
|
||||||
:active-info-icon="body.contentType === null"
|
:active-info-icon="body.contentType === null"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<span class="select-wrapper">
|
<span class="select-wrapper">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="
|
:label="
|
||||||
CodegenDefinitions.find((x) => x.name === codegenType).caption
|
CodegenDefinitions.find((x) => x.name === codegenType)!.caption
|
||||||
"
|
"
|
||||||
outline
|
outline
|
||||||
class="flex-1 pr-8"
|
class="flex-1 pr-8"
|
||||||
@@ -57,12 +57,7 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="
|
v-if="filteredCodegenDefinitions.length === 0"
|
||||||
!(
|
|
||||||
filteredCodegenDefinitions.length !== 0 ||
|
|
||||||
CodegenDefinitions.length === 0
|
|
||||||
)
|
|
||||||
"
|
|
||||||
:text="`${t('state.nothing_found')} ‟${searchQuery}”`"
|
:text="`${t('state.nothing_found')} ‟${searchQuery}”`"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
|||||||
@@ -79,16 +79,13 @@
|
|||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<HoppSmartAutoComplete
|
<SmartEnvInput
|
||||||
|
v-model="header.key"
|
||||||
:placeholder="`${t('count.header', { count: index + 1 })}`"
|
:placeholder="`${t('count.header', { count: index + 1 })}`"
|
||||||
:source="commonHeaders"
|
:auto-complete-source="commonHeaders"
|
||||||
:spellcheck="false"
|
:env-index="index"
|
||||||
:value="header.key"
|
:inspection-results="getInspectorResult(headerKeyResults, index)"
|
||||||
autofocus
|
@change="
|
||||||
styles=" bg-transparent flex flex-1
|
|
||||||
py-1 px-4 truncate "
|
|
||||||
class="flex-1 !flex"
|
|
||||||
@input="
|
|
||||||
updateHeader(index, {
|
updateHeader(index, {
|
||||||
id: header.id,
|
id: header.id,
|
||||||
key: $event,
|
key: $event,
|
||||||
@@ -100,6 +97,10 @@
|
|||||||
<SmartEnvInput
|
<SmartEnvInput
|
||||||
v-model="header.value"
|
v-model="header.value"
|
||||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||||
|
:inspection-results="
|
||||||
|
getInspectorResult(headerValueResults, index)
|
||||||
|
"
|
||||||
|
:env-index="index"
|
||||||
@change="
|
@change="
|
||||||
updateHeader(index, {
|
updateHeader(index, {
|
||||||
id: header.id,
|
id: header.id,
|
||||||
@@ -265,6 +266,9 @@ import {
|
|||||||
} from "~/helpers/utils/EffectiveURL"
|
} from "~/helpers/utils/EffectiveURL"
|
||||||
import { aggregateEnvs$, getAggregateEnvs } from "~/newstore/environments"
|
import { aggregateEnvs$, getAggregateEnvs } from "~/newstore/environments"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||||
|
import { currentTabID } from "~/helpers/rest/tab"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -502,4 +506,39 @@ const changeTab = (tab: ComputedHeader["source"]) => {
|
|||||||
if (tab === "auth") emit("change-tab", "authorization")
|
if (tab === "auth") emit("change-tab", "authorization")
|
||||||
else emit("change-tab", "bodyParams")
|
else emit("change-tab", "bodyParams")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
|
const allTabResults = inspectionService.tabs
|
||||||
|
|
||||||
|
const headerKeyResults = computed(() => {
|
||||||
|
return (
|
||||||
|
allTabResults.value
|
||||||
|
.get(currentTabID.value)
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.locations.type === "header" &&
|
||||||
|
result.locations.position === "key"
|
||||||
|
) ?? []
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const headerValueResults = computed(() => {
|
||||||
|
return (
|
||||||
|
allTabResults.value
|
||||||
|
.get(currentTabID.value)
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.locations.type === "header" &&
|
||||||
|
result.locations.position === "value"
|
||||||
|
) ?? []
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const getInspectorResult = (results: InspectorResult[], index: number) => {
|
||||||
|
return results.filter((result) => {
|
||||||
|
if (result.locations.type === "url" || result.locations.type === "response")
|
||||||
|
return
|
||||||
|
return result.locations.index === index
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -82,6 +82,9 @@
|
|||||||
<SmartEnvInput
|
<SmartEnvInput
|
||||||
v-model="param.key"
|
v-model="param.key"
|
||||||
:placeholder="`${t('count.parameter', { count: index + 1 })}`"
|
:placeholder="`${t('count.parameter', { count: index + 1 })}`"
|
||||||
|
:inspection-results="
|
||||||
|
getInspectorResult(parameterKeyResults, index)
|
||||||
|
"
|
||||||
@change="
|
@change="
|
||||||
updateParam(index, {
|
updateParam(index, {
|
||||||
id: param.id,
|
id: param.id,
|
||||||
@@ -94,6 +97,9 @@
|
|||||||
<SmartEnvInput
|
<SmartEnvInput
|
||||||
v-model="param.value"
|
v-model="param.value"
|
||||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||||
|
:inspection-results="
|
||||||
|
getInspectorResult(parameterValueResults, index)
|
||||||
|
"
|
||||||
@change="
|
@change="
|
||||||
updateParam(index, {
|
updateParam(index, {
|
||||||
id: param.id,
|
id: param.id,
|
||||||
@@ -173,7 +179,7 @@ import IconCheckCircle from "~icons/lucide/check-circle"
|
|||||||
import IconCircle from "~icons/lucide/circle"
|
import IconCircle from "~icons/lucide/circle"
|
||||||
import IconTrash from "~icons/lucide/trash"
|
import IconTrash from "~icons/lucide/trash"
|
||||||
import IconWrapText from "~icons/lucide/wrap-text"
|
import IconWrapText from "~icons/lucide/wrap-text"
|
||||||
import { reactive, ref, watch } from "vue"
|
import { computed, reactive, ref, watch } from "vue"
|
||||||
import { flow, pipe } from "fp-ts/function"
|
import { flow, pipe } from "fp-ts/function"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import * as A from "fp-ts/Array"
|
import * as A from "fp-ts/Array"
|
||||||
@@ -195,6 +201,9 @@ import { useToast } from "@composables/toast"
|
|||||||
import { throwError } from "@functional/error"
|
import { throwError } from "@functional/error"
|
||||||
import { objRemoveKey } from "@functional/object"
|
import { objRemoveKey } from "@functional/object"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||||
|
import { currentTabID } from "~/helpers/rest/tab"
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
@@ -398,4 +407,39 @@ const clearContent = () => {
|
|||||||
|
|
||||||
bulkParams.value = ""
|
bulkParams.value = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
|
const allTabResults = inspectionService.tabs
|
||||||
|
|
||||||
|
const parameterKeyResults = computed(() => {
|
||||||
|
return (
|
||||||
|
allTabResults.value
|
||||||
|
.get(currentTabID.value)
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.locations.type === "parameter" &&
|
||||||
|
result.locations.position === "key"
|
||||||
|
) ?? []
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const parameterValueResults = computed(() => {
|
||||||
|
return (
|
||||||
|
allTabResults.value
|
||||||
|
.get(currentTabID.value)
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.locations.type === "parameter" &&
|
||||||
|
result.locations.position === "value"
|
||||||
|
) ?? []
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const getInspectorResult = (results: InspectorResult[], index: number) => {
|
||||||
|
return results.filter((result) => {
|
||||||
|
if (result.locations.type === "url" || result.locations.type === "response")
|
||||||
|
return
|
||||||
|
return result.locations.index === index
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -53,16 +53,25 @@
|
|||||||
v-model="tab.document.request.endpoint"
|
v-model="tab.document.request.endpoint"
|
||||||
:placeholder="`${t('request.url')}`"
|
:placeholder="`${t('request.url')}`"
|
||||||
:auto-complete-source="userHistories"
|
:auto-complete-source="userHistories"
|
||||||
|
:inspection-results="tabResults"
|
||||||
@paste="onPasteUrl($event)"
|
@paste="onPasteUrl($event)"
|
||||||
@enter="newSendRequest"
|
@enter="newSendRequest"
|
||||||
/>
|
>
|
||||||
|
<template #empty>
|
||||||
|
<span>
|
||||||
|
{{ t("empty.history_suggestions") }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</SmartEnvInput>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mt-2 sm:mt-0">
|
<div class="flex mt-2 sm:mt-0">
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
id="send"
|
id="send"
|
||||||
v-tippy="{ theme: 'tooltip', delay: [500, 20], allowHTML: true }"
|
v-tippy="{ theme: 'tooltip', delay: [500, 20], allowHTML: true }"
|
||||||
:title="`${t('action.send')} <kbd>${getSpecialKey()}</kbd><kbd>↩</kbd>`"
|
:title="`${t(
|
||||||
|
'action.send'
|
||||||
|
)} <kbd>${getSpecialKey()}</kbd><kbd>↩</kbd>`"
|
||||||
:label="`${!loading ? t('action.send') : t('action.cancel')}`"
|
:label="`${!loading ? t('action.send') : t('action.cancel')}`"
|
||||||
class="flex-1 rounded-r-none min-w-20"
|
class="flex-1 rounded-r-none min-w-20"
|
||||||
@click="!loading ? newSendRequest() : cancelRequest()"
|
@click="!loading ? newSendRequest() : cancelRequest()"
|
||||||
@@ -221,6 +230,7 @@
|
|||||||
v-if="showSaveRequestModal"
|
v-if="showSaveRequestModal"
|
||||||
mode="rest"
|
mode="rest"
|
||||||
:show="showSaveRequestModal"
|
:show="showSaveRequestModal"
|
||||||
|
:request="request"
|
||||||
@hide-modal="showSaveRequestModal = false"
|
@hide-modal="showSaveRequestModal = false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -233,17 +243,12 @@ import { useReadonlyStream, useStreamSubscriber } from "@composables/stream"
|
|||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { refAutoReset, useVModel } from "@vueuse/core"
|
import { refAutoReset, useVModel } from "@vueuse/core"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { isLeft, isRight } from "fp-ts/lib/Either"
|
import { Ref, computed, onBeforeUnmount, ref } from "vue"
|
||||||
import { computed, onBeforeUnmount, ref } from "vue"
|
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { runMutation } from "~/helpers/backend/GQLClient"
|
import { runMutation } from "~/helpers/backend/GQLClient"
|
||||||
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
|
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
|
||||||
import { createShortcode } from "~/helpers/backend/mutations/Shortcode"
|
import { createShortcode } from "~/helpers/backend/mutations/Shortcode"
|
||||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
import {
|
|
||||||
cancelRunningExtensionRequest,
|
|
||||||
hasExtensionInstalled,
|
|
||||||
} from "~/helpers/strategies/ExtensionStrategy"
|
|
||||||
import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
||||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
@@ -258,13 +263,17 @@ import IconLink2 from "~icons/lucide/link-2"
|
|||||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||||
import IconSave from "~icons/lucide/save"
|
import IconSave from "~icons/lucide/save"
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
import { HoppRESTTab, currentTabID } from "~/helpers/rest/tab"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { RESTHistoryEntry, restHistory$ } from "~/newstore/history"
|
import { RESTHistoryEntry, restHistory$ } from "~/newstore/history"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { getCurrentStrategyID } from "~/helpers/network"
|
import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { InspectionService } from "~/services/inspection"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
const interceptorService = useService(InterceptorService)
|
||||||
|
|
||||||
const methods = [
|
const methods = [
|
||||||
"GET",
|
"GET",
|
||||||
@@ -317,6 +326,8 @@ const saveRequestAction = ref<any | null>(null)
|
|||||||
|
|
||||||
const history = useReadonlyStream<RESTHistoryEntry[]>(restHistory$, [])
|
const history = useReadonlyStream<RESTHistoryEntry[]>(restHistory$, [])
|
||||||
|
|
||||||
|
const requestCancelFunc: Ref<(() => void) | null> = ref(null)
|
||||||
|
|
||||||
const userHistories = computed(() => {
|
const userHistories = computed(() => {
|
||||||
return history.value.map((history) => history.request.endpoint).slice(0, 10)
|
return history.value.map((history) => history.request.endpoint).slice(0, 10)
|
||||||
})
|
})
|
||||||
@@ -335,13 +346,15 @@ const newSendRequest = async () => {
|
|||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_REQUEST_RUN",
|
type: "HOPP_REQUEST_RUN",
|
||||||
platform: "rest",
|
platform: "rest",
|
||||||
strategy: getCurrentStrategyID(),
|
strategy: interceptorService.currentInterceptorID.value!,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Double calling is because the function returns a TaskEither than should be executed
|
const [cancel, streamPromise] = runRESTRequest$(tab)
|
||||||
const streamResult = await runRESTRequest$(tab)()
|
const streamResult = await streamPromise
|
||||||
|
|
||||||
if (isRight(streamResult)) {
|
requestCancelFunc.value = cancel
|
||||||
|
|
||||||
|
if (E.isRight(streamResult)) {
|
||||||
subscribeToStream(
|
subscribeToStream(
|
||||||
streamResult.right,
|
streamResult.right,
|
||||||
(responseState) => {
|
(responseState) => {
|
||||||
@@ -358,7 +371,7 @@ const newSendRequest = async () => {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else if (isLeft(streamResult)) {
|
} else {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
toast.error(`${t("error.script_fail")}`)
|
toast.error(`${t("error.script_fail")}`)
|
||||||
let error: Error
|
let error: Error
|
||||||
@@ -408,9 +421,8 @@ function isCURL(curl: string) {
|
|||||||
|
|
||||||
const cancelRequest = () => {
|
const cancelRequest = () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
if (hasExtensionInstalled()) {
|
requestCancelFunc.value?.()
|
||||||
cancelRunningExtensionRequest()
|
|
||||||
}
|
|
||||||
updateRESTResponse(null)
|
updateRESTResponse(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,6 +590,8 @@ const saveRequest = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const request = ref<HoppRESTRequest | null>(null)
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (loading.value) cancelRequest()
|
if (loading.value) cancelRequest()
|
||||||
})
|
})
|
||||||
@@ -593,7 +607,22 @@ defineActionHandler("request.method.prev", cycleUpMethod)
|
|||||||
defineActionHandler("request.save", saveRequest)
|
defineActionHandler("request.save", saveRequest)
|
||||||
defineActionHandler(
|
defineActionHandler(
|
||||||
"request.save-as",
|
"request.save-as",
|
||||||
() => (showSaveRequestModal.value = true)
|
(
|
||||||
|
req:
|
||||||
|
| {
|
||||||
|
requestType: "rest"
|
||||||
|
request: HoppRESTRequest
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
requestType: "gql"
|
||||||
|
request: HoppGQLRequest
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
showSaveRequestModal.value = true
|
||||||
|
if (req && req.requestType === "rest") {
|
||||||
|
request.value = req.request
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
defineActionHandler("request.method.get", () => updateMethod("GET"))
|
defineActionHandler("request.method.get", () => updateMethod("GET"))
|
||||||
defineActionHandler("request.method.post", () => updateMethod("POST"))
|
defineActionHandler("request.method.post", () => updateMethod("POST"))
|
||||||
@@ -601,6 +630,13 @@ defineActionHandler("request.method.put", () => updateMethod("PUT"))
|
|||||||
defineActionHandler("request.method.delete", () => updateMethod("DELETE"))
|
defineActionHandler("request.method.delete", () => updateMethod("DELETE"))
|
||||||
defineActionHandler("request.method.head", () => updateMethod("HEAD"))
|
defineActionHandler("request.method.head", () => updateMethod("HEAD"))
|
||||||
|
|
||||||
|
defineActionHandler("request.import-curl", () => {
|
||||||
|
showCurlImportModal.value = true
|
||||||
|
})
|
||||||
|
defineActionHandler("request.show-code", () => {
|
||||||
|
showCodegenModal.value = true
|
||||||
|
})
|
||||||
|
|
||||||
const isCustomMethod = computed(() => {
|
const isCustomMethod = computed(() => {
|
||||||
return (
|
return (
|
||||||
tab.value.document.request.method === "CUSTOM" ||
|
tab.value.document.request.method === "CUSTOM" ||
|
||||||
@@ -609,4 +645,12 @@ const isCustomMethod = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const COLUMN_LAYOUT = useSetting("COLUMN_LAYOUT")
|
const COLUMN_LAYOUT = useSetting("COLUMN_LAYOUT")
|
||||||
|
|
||||||
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
|
const allTabResults = inspectionService.tabs
|
||||||
|
|
||||||
|
const tabResults = computed(() => {
|
||||||
|
return allTabResults.value.get(currentTabID.value) ?? []
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<HoppSmartTabs
|
<HoppSmartTabs
|
||||||
v-model="selectedRealtimeTab"
|
v-model="selectedOptionsTab"
|
||||||
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-upperMobilePrimaryStickyFold sm:top-upperPrimaryStickyFold z-10"
|
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-upperMobilePrimaryStickyFold sm:top-upperPrimaryStickyFold z-10"
|
||||||
render-inactive-tabs
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
@@ -56,12 +56,15 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { computed, ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
|
||||||
export type RequestOptionTabs =
|
export type RequestOptionTabs =
|
||||||
| "params"
|
| "params"
|
||||||
| "bodyParams"
|
| "bodyParams"
|
||||||
| "headers"
|
| "headers"
|
||||||
| "authorization"
|
| "authorization"
|
||||||
|
| "preRequestScript"
|
||||||
|
| "tests"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -73,10 +76,10 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const request = useVModel(props, "modelValue", emit)
|
const request = useVModel(props, "modelValue", emit)
|
||||||
|
|
||||||
const selectedRealtimeTab = ref<RequestOptionTabs>("params")
|
const selectedOptionsTab = ref<RequestOptionTabs>("params")
|
||||||
|
|
||||||
const changeTab = (e: RequestOptionTabs) => {
|
const changeTab = (e: RequestOptionTabs) => {
|
||||||
selectedRealtimeTab.value = e
|
selectedOptionsTab.value = e
|
||||||
}
|
}
|
||||||
|
|
||||||
const newActiveParamsCount$ = computed(() => {
|
const newActiveParamsCount$ = computed(() => {
|
||||||
@@ -96,4 +99,8 @@ const newActiveHeadersCount$ = computed(() => {
|
|||||||
if (e === 0) return null
|
if (e === 0) return null
|
||||||
return `${e}`
|
return `${e}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defineActionHandler("request.open-tab", ({ tab }) => {
|
||||||
|
selectedOptionsTab.value = tab
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col flex-1">
|
<div class="flex flex-col flex-1 relative">
|
||||||
<HttpResponseMeta :response="tab.response" />
|
<HttpResponseMeta :response="tab.response" />
|
||||||
<LensesResponseBodyRenderer
|
<LensesResponseBodyRenderer
|
||||||
v-if="!loading && hasResponse"
|
v-if="!loading && hasResponse"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="sticky top-0 z-10 flex items-start justify-center flex-shrink-0 p-4 overflow-auto overflow-x-auto bg-primary whitespace-nowrap"
|
class="sticky top-0 z-10 flex items-center justify-center flex-shrink-0 p-4 overflow-auto overflow-x-auto bg-primary whitespace-nowrap"
|
||||||
>
|
>
|
||||||
<AppShortcutsPrompt v-if="response == null" class="flex-1" />
|
<AppShortcutsPrompt v-if="response == null" class="flex-1" />
|
||||||
<div v-else class="flex flex-col flex-1">
|
<div v-else class="flex flex-col flex-1">
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
:alt="`${t('error.network_fail')}`"
|
:alt="`${t('error.network_fail')}`"
|
||||||
:heading="t('error.network_fail')"
|
:heading="t('error.network_fail')"
|
||||||
:text="t('helpers.network_fail')"
|
:text="t('helpers.network_fail')"
|
||||||
|
large
|
||||||
>
|
>
|
||||||
<AppInterceptor class="p-2 border rounded border-dividerLight" />
|
<AppInterceptor class="p-2 border rounded border-dividerLight" />
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
:alt="`${t('error.script_fail')}`"
|
:alt="`${t('error.script_fail')}`"
|
||||||
:label="t('error.script_fail')"
|
:label="t('error.script_fail')"
|
||||||
:text="t('helpers.script_fail')"
|
:text="t('helpers.script_fail')"
|
||||||
|
large
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mt-2 w-full px-4 py-2 overflow-auto font-mono text-red-400 whitespace-normal rounded bg-primaryLight"
|
class="mt-2 w-full px-4 py-2 overflow-auto font-mono text-red-400 whitespace-normal rounded bg-primaryLight"
|
||||||
@@ -70,6 +72,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<AppInspection
|
||||||
|
v-if="response?.type !== 'loading'"
|
||||||
|
:inspection-results="tabResults"
|
||||||
|
:class="[
|
||||||
|
response === null || response?.type === 'network_fail'
|
||||||
|
? 'absolute right-2 top-2'
|
||||||
|
: 'ml-2 -m-2',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -80,6 +91,9 @@ import type { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
|||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { InspectionService } from "~/services/inspection"
|
||||||
|
import { currentTabID } from "~/helpers/rest/tab"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
@@ -128,4 +142,16 @@ const statusCategory = computed(() => {
|
|||||||
}
|
}
|
||||||
return findStatusGroup(props.response.statusCode)
|
return findStatusGroup(props.response.statusCode)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
|
const allTabResults = inspectionService.tabs
|
||||||
|
|
||||||
|
const tabResults = computed(() => {
|
||||||
|
return (
|
||||||
|
allTabResults.value
|
||||||
|
.get(currentTabID.value)
|
||||||
|
?.filter((result) => result.locations.type === "response") ?? []
|
||||||
|
)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
126
packages/hoppscotch-common/src/components/http/TabHead.vue
Normal file
126
packages/hoppscotch-common/src/components/http/TabHead.vue
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||||
|
:title="tab.document.request.name"
|
||||||
|
class="truncate px-2 flex items-center"
|
||||||
|
@dblclick="emit('open-rename-modal')"
|
||||||
|
@contextmenu.prevent="options?.tippy.show()"
|
||||||
|
@click.middle="emit('close-tab')"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="font-semibold text-tiny"
|
||||||
|
:class="getMethodLabelColorClassOf(tab.document.request)"
|
||||||
|
>
|
||||||
|
{{ tab.document.request.method }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<tippy
|
||||||
|
ref="options"
|
||||||
|
trigger="manual"
|
||||||
|
interactive
|
||||||
|
theme="popover"
|
||||||
|
:on-shown="() => tippyActions!.focus()"
|
||||||
|
>
|
||||||
|
<span class="leading-8 px-2 truncate">
|
||||||
|
{{ tab.document.request.name }}
|
||||||
|
</span>
|
||||||
|
<template #content="{ hide }">
|
||||||
|
<div
|
||||||
|
ref="tippyActions"
|
||||||
|
class="flex flex-col focus:outline-none"
|
||||||
|
tabindex="0"
|
||||||
|
@keyup.r="renameAction?.$el.click()"
|
||||||
|
@keyup.d="duplicateAction?.$el.click()"
|
||||||
|
@keyup.w="closeAction?.$el.click()"
|
||||||
|
@keyup.x="closeOthersAction?.$el.click()"
|
||||||
|
@keyup.escape="hide()"
|
||||||
|
>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="renameAction"
|
||||||
|
:icon="IconFileEdit"
|
||||||
|
:label="t('request.rename')"
|
||||||
|
:shortcut="['R']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('open-rename-modal')
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="duplicateAction"
|
||||||
|
:icon="IconCopy"
|
||||||
|
:label="t('tab.duplicate')"
|
||||||
|
:shortcut="['D']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('duplicate-tab')
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
v-if="isRemovable"
|
||||||
|
ref="closeAction"
|
||||||
|
:icon="IconXCircle"
|
||||||
|
:label="t('tab.close')"
|
||||||
|
:shortcut="['W']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('close-tab')
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
v-if="isRemovable"
|
||||||
|
ref="closeOthersAction"
|
||||||
|
:icon="IconXSquare"
|
||||||
|
:label="t('tab.close_others')"
|
||||||
|
:shortcut="['X']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('close-other-tabs')
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue"
|
||||||
|
import { TippyComponent } from "vue-tippy"
|
||||||
|
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import { HoppRESTTab } from "~/helpers/rest/tab"
|
||||||
|
import IconXCircle from "~icons/lucide/x-circle"
|
||||||
|
import IconXSquare from "~icons/lucide/x-square"
|
||||||
|
import IconFileEdit from "~icons/lucide/file-edit"
|
||||||
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
tab: HoppRESTTab
|
||||||
|
isRemovable: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "open-rename-modal"): void
|
||||||
|
(event: "close-tab"): void
|
||||||
|
(event: "close-other-tabs"): void
|
||||||
|
(event: "duplicate-tab"): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
|
const options = ref<TippyComponent | null>(null)
|
||||||
|
|
||||||
|
const renameAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const closeAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const closeOthersAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const duplicateAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
interceptorSelection === extensionService.interceptorID &&
|
||||||
|
extensionService.extensionStatus.value !== 'available'
|
||||||
|
"
|
||||||
|
class="flex space-x-2"
|
||||||
|
>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
||||||
|
blank
|
||||||
|
:icon="IconChrome"
|
||||||
|
label="Chrome"
|
||||||
|
outline
|
||||||
|
class="!flex-1"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
||||||
|
blank
|
||||||
|
:icon="IconFirefox"
|
||||||
|
label="Firefox"
|
||||||
|
outline
|
||||||
|
class="!flex-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import IconChrome from "~icons/brands/chrome"
|
||||||
|
import IconFirefox from "~icons/brands/firefox"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension"
|
||||||
|
|
||||||
|
const interceptorService = useService(InterceptorService)
|
||||||
|
const extensionService = useService(ExtensionInterceptorService)
|
||||||
|
|
||||||
|
const interceptorSelection = interceptorService.currentInterceptorID
|
||||||
|
</script>
|
||||||
@@ -131,6 +131,7 @@ const fetchMyTeams = async () => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
const result = await runGQLQuery({
|
const result = await runGQLQuery({
|
||||||
query: GetMyTeamsDocument,
|
query: GetMyTeamsDocument,
|
||||||
|
variables: {},
|
||||||
})
|
})
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<div class="relative flex flex-col">
|
<div class="relative flex flex-col">
|
||||||
<input
|
<input
|
||||||
id="selectLabelAdd"
|
id="selectLabelAdd"
|
||||||
v-model="name"
|
v-model="editingName"
|
||||||
v-focus
|
v-focus
|
||||||
class="input floating-input"
|
class="input floating-input"
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
@@ -113,13 +113,13 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const QoS = ref<(typeof QOS_VALUES)[number]>(2)
|
const QoS = ref<(typeof QOS_VALUES)[number]>(2)
|
||||||
const name = ref("")
|
const editingName = ref("")
|
||||||
const color = ref("#f58290")
|
const color = ref("#f58290")
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
() => {
|
() => {
|
||||||
name.value = ""
|
editingName.value = ""
|
||||||
QoS.value = 2
|
QoS.value = 2
|
||||||
const randomColor = Math.floor(Math.random() * 16777215).toString(16)
|
const randomColor = Math.floor(Math.random() * 16777215).toString(16)
|
||||||
color.value = `#${randomColor}`
|
color.value = `#${randomColor}`
|
||||||
@@ -127,18 +127,18 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const addNewSubscription = () => {
|
const addNewSubscription = () => {
|
||||||
if (!name.value) {
|
if (!editingName.value) {
|
||||||
toastr.error(t("mqtt.invalid_topic").toString())
|
toastr.error(t("mqtt.invalid_topic").toString())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
emit("submit", {
|
emit("submit", {
|
||||||
name: name.value,
|
name: editingName.value,
|
||||||
qos: QoS.value,
|
qos: QoS.value,
|
||||||
color: color.value,
|
color: color.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
name.value = ""
|
editingName.value = ""
|
||||||
QoS.value = 2
|
QoS.value = 2
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<div class="my-1 text-secondaryLight">
|
||||||
|
<span v-if="extensionVersion != null">
|
||||||
|
{{
|
||||||
|
`${t("settings.extension_version")}: v${extensionVersion.major}.${
|
||||||
|
extensionVersion.minor
|
||||||
|
}`
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ t("settings.extension_version") }}:
|
||||||
|
{{ t("settings.extension_ver_not_reported") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col py-4 space-y-2">
|
||||||
|
<span>
|
||||||
|
<HoppSmartItem
|
||||||
|
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
||||||
|
blank
|
||||||
|
:icon="IconChrome"
|
||||||
|
label="Chrome"
|
||||||
|
:info-icon="hasChromeExtInstalled ? IconCheckCircle : null"
|
||||||
|
:active-info-icon="hasChromeExtInstalled"
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<HoppSmartItem
|
||||||
|
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
||||||
|
blank
|
||||||
|
:icon="IconFirefox"
|
||||||
|
label="Firefox"
|
||||||
|
:info-icon="hasFirefoxExtInstalled ? IconCheckCircle : null"
|
||||||
|
:active-info-icon="hasFirefoxExtInstalled"
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="py-4 space-y-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<HoppSmartToggle
|
||||||
|
:on="extensionEnabled"
|
||||||
|
@change="extensionEnabled = !extensionEnabled"
|
||||||
|
>
|
||||||
|
{{ t("settings.extensions_use_toggle") }}
|
||||||
|
</HoppSmartToggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import IconChrome from "~icons/brands/chrome"
|
||||||
|
import IconFirefox from "~icons/brands/firefox"
|
||||||
|
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const interceptorService = useService(InterceptorService)
|
||||||
|
const extensionService = useService(ExtensionInterceptorService)
|
||||||
|
|
||||||
|
const extensionVersion = extensionService.extensionVersion
|
||||||
|
const hasChromeExtInstalled = extensionService.chromeExtensionInstalled
|
||||||
|
const hasFirefoxExtInstalled = extensionService.firefoxExtensionInstalled
|
||||||
|
|
||||||
|
const extensionEnabled = computed({
|
||||||
|
get() {
|
||||||
|
return (
|
||||||
|
interceptorService.currentInterceptorID.value ===
|
||||||
|
extensionService.interceptorID
|
||||||
|
)
|
||||||
|
},
|
||||||
|
set(active) {
|
||||||
|
if (active) {
|
||||||
|
interceptorService.currentInterceptorID.value =
|
||||||
|
extensionService.interceptorID
|
||||||
|
} else {
|
||||||
|
interceptorService.currentInterceptorID.value =
|
||||||
|
platform.interceptors.default
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
94
packages/hoppscotch-common/src/components/settings/Proxy.vue
Normal file
94
packages/hoppscotch-common/src/components/settings/Proxy.vue
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div class="my-1 text-secondaryLight">
|
||||||
|
{{ `${t("settings.official_proxy_hosting")} ${t("settings.read_the")}` }}
|
||||||
|
<HoppSmartAnchor
|
||||||
|
class="link"
|
||||||
|
to="https://docs.hoppscotch.io/support/privacy"
|
||||||
|
blank
|
||||||
|
:label="t('app.proxy_privacy_policy')"
|
||||||
|
/>.
|
||||||
|
</div>
|
||||||
|
<div class="py-4 space-y-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<HoppSmartToggle
|
||||||
|
:on="proxyEnabled"
|
||||||
|
@change="proxyEnabled = !proxyEnabled"
|
||||||
|
>
|
||||||
|
{{ t("settings.proxy_use_toggle") }}
|
||||||
|
</HoppSmartToggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center py-4 space-x-2">
|
||||||
|
<HoppSmartInput
|
||||||
|
v-model="PROXY_URL"
|
||||||
|
styles="flex-1"
|
||||||
|
placeholder=" "
|
||||||
|
input-styles="input floating-input"
|
||||||
|
:disabled="!proxyEnabled"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<label for="url">
|
||||||
|
{{ t("settings.proxy_url") }}
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
</HoppSmartInput>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('settings.reset_default')"
|
||||||
|
:icon="clearIcon"
|
||||||
|
outline
|
||||||
|
class="rounded"
|
||||||
|
@click="resetProxy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import { useSetting } from "~/composables/settings"
|
||||||
|
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||||
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { proxyInterceptor } from "~/platform/std/interceptors/proxy"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const interceptorService = useService(InterceptorService)
|
||||||
|
|
||||||
|
const PROXY_URL = useSetting("PROXY_URL")
|
||||||
|
|
||||||
|
const proxyEnabled = computed({
|
||||||
|
get() {
|
||||||
|
return (
|
||||||
|
interceptorService.currentInterceptorID.value ===
|
||||||
|
proxyInterceptor.interceptorID
|
||||||
|
)
|
||||||
|
},
|
||||||
|
set(active) {
|
||||||
|
if (active) {
|
||||||
|
interceptorService.currentInterceptorID.value =
|
||||||
|
proxyInterceptor.interceptorID
|
||||||
|
} else {
|
||||||
|
interceptorService.currentInterceptorID.value =
|
||||||
|
platform.interceptors.default
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const clearIcon = refAutoReset<typeof IconRotateCCW | typeof IconCheck>(
|
||||||
|
IconRotateCCW,
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
|
||||||
|
const resetProxy = () => {
|
||||||
|
PROXY_URL.value = "https://proxy.hoppscotch.io/"
|
||||||
|
clearIcon.value = IconCheck
|
||||||
|
toast.success(`${t("state.cleared")}`)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -18,15 +18,13 @@
|
|||||||
</span>
|
</span>
|
||||||
<template #content="{ hide }">
|
<template #content="{ hide }">
|
||||||
<div class="flex flex-col space-y-2">
|
<div class="flex flex-col space-y-2">
|
||||||
<div class="sticky z-10 top-0 flex-shrink-0 overflow-x-auto">
|
<HoppSmartInput
|
||||||
<input
|
v-model="searchQuery"
|
||||||
v-model="searchQuery"
|
styles="ticky z-10 top-0 flex-shrink-0 overflow-x-auto"
|
||||||
type="search"
|
:placeholder="`${t('action.search')}`"
|
||||||
autocomplete="off"
|
type="search"
|
||||||
class="flex w-full p-4 py-2 input !bg-primaryContrast"
|
input-styles="flex w-full p-4 py-2 input !bg-primaryContrast"
|
||||||
:placeholder="`${t('action.search')}`"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
ref="tippyActions"
|
ref="tippyActions"
|
||||||
class="flex flex-col focus:outline-none"
|
class="flex flex-col focus:outline-none"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="autocomplete-wrapper">
|
<div class="autocomplete-wrapper">
|
||||||
<div class="absolute inset-0 flex flex-1 overflow-x-auto">
|
<div
|
||||||
|
class="absolute inset-0 flex flex-1 divide-x divide-dividerLight overflow-x-auto"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
ref="editor"
|
ref="editor"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@@ -10,6 +12,10 @@
|
|||||||
@keydown="handleKeystroke"
|
@keydown="handleKeystroke"
|
||||||
@focusin="showSuggestionPopover = true"
|
@focusin="showSuggestionPopover = true"
|
||||||
></div>
|
></div>
|
||||||
|
<AppInspection
|
||||||
|
:inspection-results="inspectionResults"
|
||||||
|
class="sticky inset-y-0 right-0 bg-primary rounded-r"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
v-if="showSuggestionPopover && autoCompleteSource"
|
v-if="showSuggestionPopover && autoCompleteSource"
|
||||||
@@ -34,8 +40,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="suggestions.length === 0" class="pointer-events-none">
|
<li v-if="suggestions.length === 0" class="pointer-events-none">
|
||||||
<span class="truncate py-0.5">
|
<div v-if="slots.empty" class="truncate py-0.5">
|
||||||
{{ t("empty.history_suggestions") }}
|
<slot name="empty"></slot>
|
||||||
|
</div>
|
||||||
|
<span v-else class="truncate py-0.5">
|
||||||
|
{{ t("empty.suggestions") }}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -43,7 +52,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch, nextTick, computed, Ref } from "vue"
|
import { ref, onMounted, watch, nextTick, computed, Ref, useSlots } from "vue"
|
||||||
import {
|
import {
|
||||||
EditorView,
|
EditorView,
|
||||||
placeholder as placeholderExt,
|
placeholder as placeholderExt,
|
||||||
@@ -62,6 +71,7 @@ import { AggregateEnvironment, aggregateEnvs$ } from "~/newstore/environments"
|
|||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { onClickOutside, useDebounceFn } from "@vueuse/core"
|
import { onClickOutside, useDebounceFn } from "@vueuse/core"
|
||||||
|
import { InspectorResult } from "~/services/inspection"
|
||||||
import { invokeAction } from "~/helpers/actions"
|
import { invokeAction } from "~/helpers/actions"
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -75,6 +85,7 @@ const props = withDefaults(
|
|||||||
environmentHighlights?: boolean
|
environmentHighlights?: boolean
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
autoCompleteSource?: string[]
|
autoCompleteSource?: string[]
|
||||||
|
inspectionResults?: InspectorResult[] | undefined
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
modelValue: "",
|
modelValue: "",
|
||||||
@@ -85,6 +96,8 @@ const props = withDefaults(
|
|||||||
readonly: false,
|
readonly: false,
|
||||||
environmentHighlights: true,
|
environmentHighlights: true,
|
||||||
autoCompleteSource: undefined,
|
autoCompleteSource: undefined,
|
||||||
|
inspectionResult: undefined,
|
||||||
|
inspectionResults: undefined,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -98,6 +111,8 @@ const emit = defineEmits<{
|
|||||||
(e: "click", ev: any): void
|
(e: "click", ev: any): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const cachedValue = ref(props.modelValue)
|
const cachedValue = ref(props.modelValue)
|
||||||
@@ -142,7 +157,9 @@ const suggestions = computed(() => {
|
|||||||
const updateModelValue = (value: string) => {
|
const updateModelValue = (value: string) => {
|
||||||
emit("update:modelValue", value)
|
emit("update:modelValue", value)
|
||||||
emit("change", value)
|
emit("change", value)
|
||||||
showSuggestionPopover.value = false
|
nextTick(() => {
|
||||||
|
showSuggestionPopover.value = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeystroke = (ev: KeyboardEvent) => {
|
const handleKeystroke = (ev: KeyboardEvent) => {
|
||||||
@@ -342,7 +359,6 @@ const initView = (el: any) => {
|
|||||||
el.addEventListener("keyup", debounceFn)
|
el.addEventListener("keyup", debounceFn)
|
||||||
|
|
||||||
const extensions: Extension = [
|
const extensions: Extension = [
|
||||||
EditorView.lineWrapping,
|
|
||||||
EditorView.contentAttributes.of({ "aria-label": props.placeholder }),
|
EditorView.contentAttributes.of({ "aria-label": props.placeholder }),
|
||||||
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
|
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
|
||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
|
|||||||
@@ -1,21 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<HoppSmartModal v-if="show" dialog :title="t('team.new')" @close="hideModal">
|
<HoppSmartModal v-if="show" dialog :title="t('team.new')" @close="hideModal">
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelTeamAdd"
|
:label="t('action.label')"
|
||||||
v-model="name"
|
placeholder=" "
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
@submit="addNewTeam"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="addNewTeam"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelTeamAdd">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
@@ -58,14 +50,14 @@ const emit = defineEmits<{
|
|||||||
(e: "hide-modal"): void
|
(e: "hide-modal"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const name = ref<string | null>(null)
|
const editingName = ref<string | null>(null)
|
||||||
|
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
|
||||||
const addNewTeam = async () => {
|
const addNewTeam = async () => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
await pipe(
|
await pipe(
|
||||||
TeamNameCodec.decode(name.value),
|
TeamNameCodec.decode(editingName.value),
|
||||||
TE.fromEither,
|
TE.fromEither,
|
||||||
TE.mapLeft(() => "invalid_name" as const),
|
TE.mapLeft(() => "invalid_name" as const),
|
||||||
TE.chainW(createTeam),
|
TE.chainW(createTeam),
|
||||||
@@ -94,7 +86,7 @@ const addNewTeam = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
name.value = null
|
editingName.value = null
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,21 +2,13 @@
|
|||||||
<HoppSmartModal v-if="show" dialog :title="t('team.edit')" @close="hideModal">
|
<HoppSmartModal v-if="show" dialog :title="t('team.edit')" @close="hideModal">
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="relative flex">
|
<HoppSmartInput
|
||||||
<input
|
v-model="editingName"
|
||||||
id="selectLabelTeamEdit"
|
placeholder=" "
|
||||||
v-model="name"
|
:label="t('action.label')"
|
||||||
v-focus
|
input-styles="floating-input"
|
||||||
class="input floating-input"
|
@submit="saveTeam"
|
||||||
placeholder=" "
|
/>
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="saveTeam"
|
|
||||||
/>
|
|
||||||
<label for="selectLabelTeamEdit">
|
|
||||||
{{ t("action.label") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between flex-1 pt-4">
|
<div class="flex items-center justify-between flex-1 pt-4">
|
||||||
<label for="memberList" class="p-4">
|
<label for="memberList" class="p-4">
|
||||||
{{ t("team.members") }}
|
{{ t("team.members") }}
|
||||||
@@ -185,7 +177,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, toRef, watch } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import {
|
import {
|
||||||
GetTeamDocument,
|
GetTeamDocument,
|
||||||
@@ -236,12 +228,12 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const name = toRef(props.editingTeam, "name")
|
const editingName = ref(props.editingTeam.name)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.editingTeam.name,
|
() => props.editingTeam.name,
|
||||||
(newName: string) => {
|
(newName: string) => {
|
||||||
name.value = newName
|
editingName.value = newName
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -389,11 +381,11 @@ const isLoading = ref(false)
|
|||||||
|
|
||||||
const saveTeam = async () => {
|
const saveTeam = async () => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
if (name.value !== "") {
|
if (editingName.value !== "") {
|
||||||
if (TeamNameCodec.is(name.value)) {
|
if (TeamNameCodec.is(editingName.value)) {
|
||||||
const updateTeamNameResult = await renameTeam(
|
const updateTeamNameResult = await renameTeam(
|
||||||
props.editingTeamID,
|
props.editingTeamID,
|
||||||
name.value
|
editingName.value
|
||||||
)()
|
)()
|
||||||
if (E.isLeft(updateTeamNameResult)) {
|
if (E.isLeft(updateTeamNameResult)) {
|
||||||
toast.error(`${t("error.something_went_wrong")}`)
|
toast.error(`${t("error.something_went_wrong")}`)
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ import { changeWorkspace, workspaceStatus$ } from "~/newstore/workspace"
|
|||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
import IconDone from "~icons/lucide/check"
|
import IconDone from "~icons/lucide/check"
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
@@ -154,4 +155,14 @@ const displayModalAdd = (shouldDisplay: boolean) => {
|
|||||||
showModalAdd.value = shouldDisplay
|
showModalAdd.value = shouldDisplay
|
||||||
teamListadapter.fetchList()
|
teamListadapter.fetchList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineActionHandler("modals.team.new", () => {
|
||||||
|
displayModalAdd(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
defineActionHandler("workspace.switch.personal", switchToPersonalWorkspace)
|
||||||
|
defineActionHandler("workspace.switch", ({ teamId }) => {
|
||||||
|
const team = myTeams.value.find((t) => t.id === teamId)
|
||||||
|
if (team) switchToTeamWorkspace(team)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
parseGQLErrorString,
|
parseGQLErrorString,
|
||||||
} from "@helpers/backend/GQLClient"
|
} from "@helpers/backend/GQLClient"
|
||||||
import {
|
import {
|
||||||
|
AnyVariables,
|
||||||
createRequest,
|
createRequest,
|
||||||
GraphQLRequest,
|
GraphQLRequest,
|
||||||
OperationResult,
|
OperationResult,
|
||||||
@@ -35,7 +36,11 @@ type UseQueryOptions<T = any, V = object> = {
|
|||||||
pollDuration?: number | undefined
|
pollDuration?: number | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGQLQuery = <DocType, DocVarType, DocErrorType extends string>(
|
export const useGQLQuery = <
|
||||||
|
DocType,
|
||||||
|
DocVarType extends AnyVariables,
|
||||||
|
DocErrorType extends string,
|
||||||
|
>(
|
||||||
_args: UseQueryOptions<DocType, DocVarType>
|
_args: UseQueryOptions<DocType, DocVarType>
|
||||||
) => {
|
) => {
|
||||||
const stops: WatchStopHandle[] = []
|
const stops: WatchStopHandle[] = []
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import * as E from "fp-ts/Either"
|
||||||
import { BehaviorSubject } from "rxjs"
|
import { BehaviorSubject } from "rxjs"
|
||||||
import {
|
import {
|
||||||
getIntrospectionQuery,
|
getIntrospectionQuery,
|
||||||
@@ -11,7 +12,8 @@ import {
|
|||||||
} from "graphql"
|
} from "graphql"
|
||||||
import { distinctUntilChanged, map } from "rxjs/operators"
|
import { distinctUntilChanged, map } from "rxjs/operators"
|
||||||
import { GQLHeader, HoppGQLAuth } from "@hoppscotch/data"
|
import { GQLHeader, HoppGQLAuth } from "@hoppscotch/data"
|
||||||
import { sendNetworkRequest } from "./network"
|
import { getService } from "~/modules/dioc"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
|
||||||
const GQL_SCHEMA_POLL_INTERVAL = 7000
|
const GQL_SCHEMA_POLL_INTERVAL = 7000
|
||||||
|
|
||||||
@@ -29,9 +31,7 @@ export class GQLConnection {
|
|||||||
map((schema) => {
|
map((schema) => {
|
||||||
if (!schema) return null
|
if (!schema) return null
|
||||||
|
|
||||||
return printSchema(schema, {
|
return printSchema(schema)
|
||||||
commentDescriptions: true,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ export class GQLConnection {
|
|||||||
headers.forEach((x) => (finalHeaders[x.key] = x.value))
|
headers.forEach((x) => (finalHeaders[x.key] = x.value))
|
||||||
|
|
||||||
const reqOptions = {
|
const reqOptions = {
|
||||||
method: "POST",
|
method: "POST" as const,
|
||||||
url,
|
url,
|
||||||
headers: {
|
headers: {
|
||||||
...finalHeaders,
|
...finalHeaders,
|
||||||
@@ -190,11 +190,20 @@ export class GQLConnection {
|
|||||||
data: introspectionQuery,
|
data: introspectionQuery,
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await sendNetworkRequest(reqOptions)
|
const interceptorService = getService(InterceptorService)
|
||||||
|
|
||||||
|
const res = await interceptorService.runRequest(reqOptions).response
|
||||||
|
|
||||||
|
if (E.isLeft(res)) {
|
||||||
|
console.error(res.left)
|
||||||
|
throw new Error(res.left.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = res.right
|
||||||
|
|
||||||
// HACK : Temporary trailing null character issue from the extension fix
|
// HACK : Temporary trailing null character issue from the extension fix
|
||||||
const response = new TextDecoder("utf-8")
|
const response = new TextDecoder("utf-8")
|
||||||
.decode(data.data)
|
.decode(data.data as any)
|
||||||
.replace(/\0+$/, "")
|
.replace(/\0+$/, "")
|
||||||
|
|
||||||
const introspectResponse = JSON.parse(response)
|
const introspectResponse = JSON.parse(response)
|
||||||
@@ -245,7 +254,7 @@ export class GQLConnection {
|
|||||||
.forEach(({ key, value }) => (finalHeaders[key] = value))
|
.forEach(({ key, value }) => (finalHeaders[key] = value))
|
||||||
|
|
||||||
const reqOptions = {
|
const reqOptions = {
|
||||||
method: "POST",
|
method: "POST" as const,
|
||||||
url,
|
url,
|
||||||
headers: {
|
headers: {
|
||||||
...finalHeaders,
|
...finalHeaders,
|
||||||
@@ -260,11 +269,19 @@ export class GQLConnection {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await sendNetworkRequest(reqOptions)
|
const interceptorService = getService(InterceptorService)
|
||||||
|
const result = await interceptorService.runRequest(reqOptions).response
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
console.error(result.left)
|
||||||
|
throw new Error(result.left.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = result.right
|
||||||
|
|
||||||
// HACK: Temporary trailing null character issue from the extension fix
|
// HACK: Temporary trailing null character issue from the extension fix
|
||||||
const responseText = new TextDecoder("utf-8")
|
const responseText = new TextDecoder("utf-8")
|
||||||
.decode(res.data)
|
.decode(res.data as any)
|
||||||
.replace(/\0+$/, "")
|
.replace(/\0+$/, "")
|
||||||
|
|
||||||
return responseText
|
return responseText
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Observable, Subject } from "rxjs"
|
import { Observable, Subject } from "rxjs"
|
||||||
import { filter } from "rxjs/operators"
|
import { filter } from "rxjs/operators"
|
||||||
import * as TE from "fp-ts/lib/TaskEither"
|
|
||||||
import { flow, pipe } from "fp-ts/function"
|
import { flow, pipe } from "fp-ts/function"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import * as A from "fp-ts/Array"
|
import * as A from "fp-ts/Array"
|
||||||
@@ -10,7 +9,7 @@ import {
|
|||||||
runTestScript,
|
runTestScript,
|
||||||
TestDescriptor,
|
TestDescriptor,
|
||||||
} from "@hoppscotch/js-sandbox"
|
} from "@hoppscotch/js-sandbox"
|
||||||
import { isRight } from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import {
|
import {
|
||||||
getCombinedEnvVariables,
|
getCombinedEnvVariables,
|
||||||
@@ -69,106 +68,130 @@ export const executedResponses$ = new Subject<
|
|||||||
HoppRESTResponse & { type: "success" | "fail " }
|
HoppRESTResponse & { type: "success" | "fail " }
|
||||||
>()
|
>()
|
||||||
|
|
||||||
export const runRESTRequest$ = (
|
export function runRESTRequest$(
|
||||||
tab: Ref<HoppRESTTab>
|
tab: Ref<HoppRESTTab>
|
||||||
): TE.TaskEither<string | Error, Observable<HoppRESTResponse>> =>
|
): [
|
||||||
pipe(
|
() => void,
|
||||||
getFinalEnvsFromPreRequest(
|
Promise<
|
||||||
tab.value.document.request.preRequestScript,
|
| E.Left<"script_fail" | "cancellation">
|
||||||
getCombinedEnvVariables()
|
| E.Right<Observable<HoppRESTResponse>>
|
||||||
),
|
>,
|
||||||
TE.chain((envs) => {
|
] {
|
||||||
const effectiveRequest = getEffectiveRESTRequest(
|
let cancelCalled = false
|
||||||
tab.value.document.request,
|
let cancelFunc: (() => void) | null = null
|
||||||
{
|
|
||||||
name: "Env",
|
|
||||||
variables: combineEnvVariables(envs),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const stream = createRESTNetworkRequestStream(effectiveRequest)
|
const cancel = () => {
|
||||||
|
cancelCalled = true
|
||||||
|
cancelFunc?.()
|
||||||
|
}
|
||||||
|
|
||||||
// Run Test Script when request ran successfully
|
const res = getFinalEnvsFromPreRequest(
|
||||||
const subscription = stream
|
tab.value.document.request.preRequestScript,
|
||||||
.pipe(filter((res) => res.type === "success" || res.type === "fail"))
|
getCombinedEnvVariables()
|
||||||
.subscribe(async (res) => {
|
)().then((envs) => {
|
||||||
if (res.type === "success" || res.type === "fail") {
|
if (cancelCalled) return E.left("cancellation" as const)
|
||||||
executedResponses$.next(
|
|
||||||
// @ts-expect-error Typescript can't figure out this inference for some reason
|
|
||||||
res
|
|
||||||
)
|
|
||||||
|
|
||||||
const runResult = await runTestScript(res.req.testScript, envs, {
|
if (E.isLeft(envs)) {
|
||||||
|
console.error(envs.left)
|
||||||
|
return E.left("script_fail" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectiveRequest = getEffectiveRESTRequest(
|
||||||
|
tab.value.document.request,
|
||||||
|
{
|
||||||
|
name: "Env",
|
||||||
|
variables: combineEnvVariables(envs.right),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const [stream, cancelRun] = createRESTNetworkRequestStream(effectiveRequest)
|
||||||
|
cancelFunc = cancelRun
|
||||||
|
|
||||||
|
const subscription = stream
|
||||||
|
.pipe(filter((res) => res.type === "success" || res.type === "fail"))
|
||||||
|
.subscribe(async (res) => {
|
||||||
|
if (res.type === "success" || res.type === "fail") {
|
||||||
|
executedResponses$.next(
|
||||||
|
// @ts-expect-error Typescript can't figure out this inference for some reason
|
||||||
|
res
|
||||||
|
)
|
||||||
|
|
||||||
|
const runResult = await runTestScript(
|
||||||
|
res.req.testScript,
|
||||||
|
envs.right,
|
||||||
|
{
|
||||||
status: res.statusCode,
|
status: res.statusCode,
|
||||||
body: getTestableBody(res),
|
body: getTestableBody(res),
|
||||||
headers: res.headers,
|
headers: res.headers,
|
||||||
})()
|
|
||||||
|
|
||||||
if (isRight(runResult)) {
|
|
||||||
tab.value.testResults = translateToSandboxTestResults(
|
|
||||||
runResult.right
|
|
||||||
)
|
|
||||||
|
|
||||||
setGlobalEnvVariables(runResult.right.envs.global)
|
|
||||||
|
|
||||||
if (
|
|
||||||
environmentsStore.value.selectedEnvironmentIndex.type ===
|
|
||||||
"MY_ENV"
|
|
||||||
) {
|
|
||||||
const env = getEnvironment({
|
|
||||||
type: "MY_ENV",
|
|
||||||
index: environmentsStore.value.selectedEnvironmentIndex.index,
|
|
||||||
})
|
|
||||||
updateEnvironment(
|
|
||||||
environmentsStore.value.selectedEnvironmentIndex.index,
|
|
||||||
{
|
|
||||||
...env,
|
|
||||||
variables: runResult.right.envs.selected,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else if (
|
|
||||||
environmentsStore.value.selectedEnvironmentIndex.type ===
|
|
||||||
"TEAM_ENV"
|
|
||||||
) {
|
|
||||||
const env = getEnvironment({
|
|
||||||
type: "TEAM_ENV",
|
|
||||||
})
|
|
||||||
pipe(
|
|
||||||
updateTeamEnvironment(
|
|
||||||
JSON.stringify(runResult.right.envs.selected),
|
|
||||||
environmentsStore.value.selectedEnvironmentIndex.teamEnvID,
|
|
||||||
env.name
|
|
||||||
)
|
|
||||||
)()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tab.value.testResults = {
|
|
||||||
description: "",
|
|
||||||
expectResults: [],
|
|
||||||
tests: [],
|
|
||||||
envDiff: {
|
|
||||||
global: {
|
|
||||||
additions: [],
|
|
||||||
deletions: [],
|
|
||||||
updations: [],
|
|
||||||
},
|
|
||||||
selected: {
|
|
||||||
additions: [],
|
|
||||||
deletions: [],
|
|
||||||
updations: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scriptError: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
)()
|
||||||
|
|
||||||
subscription.unsubscribe()
|
if (E.isRight(runResult)) {
|
||||||
|
tab.value.testResults = translateToSandboxTestResults(
|
||||||
|
runResult.right
|
||||||
|
)
|
||||||
|
|
||||||
|
setGlobalEnvVariables(runResult.right.envs.global)
|
||||||
|
|
||||||
|
if (
|
||||||
|
environmentsStore.value.selectedEnvironmentIndex.type === "MY_ENV"
|
||||||
|
) {
|
||||||
|
const env = getEnvironment({
|
||||||
|
type: "MY_ENV",
|
||||||
|
index: environmentsStore.value.selectedEnvironmentIndex.index,
|
||||||
|
})
|
||||||
|
updateEnvironment(
|
||||||
|
environmentsStore.value.selectedEnvironmentIndex.index,
|
||||||
|
{
|
||||||
|
...env,
|
||||||
|
variables: runResult.right.envs.selected,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
environmentsStore.value.selectedEnvironmentIndex.type ===
|
||||||
|
"TEAM_ENV"
|
||||||
|
) {
|
||||||
|
const env = getEnvironment({
|
||||||
|
type: "TEAM_ENV",
|
||||||
|
})
|
||||||
|
pipe(
|
||||||
|
updateTeamEnvironment(
|
||||||
|
JSON.stringify(runResult.right.envs.selected),
|
||||||
|
environmentsStore.value.selectedEnvironmentIndex.teamEnvID,
|
||||||
|
env.name
|
||||||
|
)
|
||||||
|
)()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tab.value.testResults = {
|
||||||
|
description: "",
|
||||||
|
expectResults: [],
|
||||||
|
tests: [],
|
||||||
|
envDiff: {
|
||||||
|
global: {
|
||||||
|
additions: [],
|
||||||
|
deletions: [],
|
||||||
|
updations: [],
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
additions: [],
|
||||||
|
deletions: [],
|
||||||
|
updations: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scriptError: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
return TE.right(stream)
|
subscription.unsubscribe()
|
||||||
})
|
}
|
||||||
)
|
})
|
||||||
|
|
||||||
|
return E.right(stream)
|
||||||
|
})
|
||||||
|
|
||||||
|
return [cancel, res]
|
||||||
|
}
|
||||||
|
|
||||||
const getAddedEnvVariables = (
|
const getAddedEnvVariables = (
|
||||||
current: Environment["variables"],
|
current: Environment["variables"],
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
import { Ref, onBeforeUnmount, onMounted, watch } from "vue"
|
import { Ref, onBeforeUnmount, onMounted, watch } from "vue"
|
||||||
import { BehaviorSubject } from "rxjs"
|
import { BehaviorSubject } from "rxjs"
|
||||||
import { HoppRESTDocument } from "./rest/document"
|
import { HoppRESTDocument } from "./rest/document"
|
||||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { RequestOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||||
|
|
||||||
export type HoppAction =
|
export type HoppAction =
|
||||||
| "contextmenu.open" // Send/Cancel a Hoppscotch Request
|
| "contextmenu.open" // Send/Cancel a Hoppscotch Request
|
||||||
@@ -14,6 +15,7 @@ export type HoppAction =
|
|||||||
| "request.copy-link" // Copy Request Link
|
| "request.copy-link" // Copy Request Link
|
||||||
| "request.save" // Save to Collections
|
| "request.save" // Save to Collections
|
||||||
| "request.save-as" // Save As
|
| "request.save-as" // Save As
|
||||||
|
| "rest.request.rename" // Rename
|
||||||
| "request.method.next" // Select Next Method
|
| "request.method.next" // Select Next Method
|
||||||
| "request.method.prev" // Select Previous Method
|
| "request.method.prev" // Select Previous Method
|
||||||
| "request.method.get" // Select GET Method
|
| "request.method.get" // Select GET Method
|
||||||
@@ -21,13 +23,22 @@ export type HoppAction =
|
|||||||
| "request.method.post" // Select POST Method
|
| "request.method.post" // Select POST Method
|
||||||
| "request.method.put" // Select PUT Method
|
| "request.method.put" // Select PUT Method
|
||||||
| "request.method.delete" // Select DELETE Method
|
| "request.method.delete" // Select DELETE Method
|
||||||
|
| "request.import-curl" // Import cURL
|
||||||
|
| "request.show-code" // Show generated code
|
||||||
|
| "flyouts.chat.open" // Shows the keybinds flyout
|
||||||
| "flyouts.keybinds.toggle" // Shows the keybinds flyout
|
| "flyouts.keybinds.toggle" // Shows the keybinds flyout
|
||||||
| "modals.search.toggle" // Shows the search modal
|
| "modals.search.toggle" // Shows the search modal
|
||||||
| "modals.support.toggle" // Shows the support modal
|
| "modals.support.toggle" // Shows the support modal
|
||||||
| "modals.share.toggle" // Shows the share modal
|
| "modals.share.toggle" // Shows the share modal
|
||||||
|
| "modals.social.toggle" // Shows the social links modal
|
||||||
| "modals.environment.add" // Show add environment modal via context menu
|
| "modals.environment.add" // Show add environment modal via context menu
|
||||||
|
| "modals.environment.new" // Add new environment
|
||||||
| "modals.my.environment.edit" // Edit current personal environment
|
| "modals.my.environment.edit" // Edit current personal environment
|
||||||
| "modals.team.environment.edit" // Edit current team environment
|
| "modals.team.environment.edit" // Edit current team environment
|
||||||
|
| "modals.team.new" // Add new team
|
||||||
|
| "modals.team.edit" // Edit selected team
|
||||||
|
| "modals.team.invite" // Invite selected team
|
||||||
|
| "workspace.switch.personal" // Switch to personal workspace
|
||||||
| "navigation.jump.rest" // Jump to REST page
|
| "navigation.jump.rest" // Jump to REST page
|
||||||
| "navigation.jump.graphql" // Jump to GraphQL page
|
| "navigation.jump.graphql" // Jump to GraphQL page
|
||||||
| "navigation.jump.realtime" // Jump to realtime page
|
| "navigation.jump.realtime" // Jump to realtime page
|
||||||
@@ -67,15 +78,34 @@ type HoppActionArgsMap = {
|
|||||||
}
|
}
|
||||||
"modals.my.environment.edit": {
|
"modals.my.environment.edit": {
|
||||||
envName: string
|
envName: string
|
||||||
variableName: string
|
variableName?: string
|
||||||
}
|
}
|
||||||
"modals.team.environment.edit": {
|
"modals.team.environment.edit": {
|
||||||
envName: string
|
envName: string
|
||||||
variableName: string
|
variableName?: string
|
||||||
|
}
|
||||||
|
"modals.team.delete": {
|
||||||
|
teamId: string
|
||||||
|
}
|
||||||
|
"workspace.switch": {
|
||||||
|
teamId: string
|
||||||
}
|
}
|
||||||
"rest.request.open": {
|
"rest.request.open": {
|
||||||
doc: HoppRESTDocument
|
doc: HoppRESTDocument
|
||||||
}
|
}
|
||||||
|
"request.save-as":
|
||||||
|
| {
|
||||||
|
requestType: "rest"
|
||||||
|
request: HoppRESTRequest
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
requestType: "gql"
|
||||||
|
request: HoppGQLRequest
|
||||||
|
}
|
||||||
|
"request.open-tab": {
|
||||||
|
tab: RequestOptionTabs
|
||||||
|
}
|
||||||
|
|
||||||
"gql.request.open": {
|
"gql.request.open": {
|
||||||
request: HoppGQLRequest
|
request: HoppGQLRequest
|
||||||
}
|
}
|
||||||
@@ -144,7 +174,7 @@ type InvokeActionFunc = {
|
|||||||
* @param args The argument passed to the action handler. Optional if action has no args required
|
* @param args The argument passed to the action handler. Optional if action has no args required
|
||||||
*/
|
*/
|
||||||
export const invokeAction: InvokeActionFunc = <
|
export const invokeAction: InvokeActionFunc = <
|
||||||
A extends HoppAction | HoppActionWithArgs
|
A extends HoppAction | HoppActionWithArgs,
|
||||||
>(
|
>(
|
||||||
action: A,
|
action: A,
|
||||||
args: ArgOfHoppAction<A>
|
args: ArgOfHoppAction<A>
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ import {
|
|||||||
Operation,
|
Operation,
|
||||||
OperationResult,
|
OperationResult,
|
||||||
Client,
|
Client,
|
||||||
|
AnyVariables,
|
||||||
} from "@urql/core"
|
} from "@urql/core"
|
||||||
import { authExchange } from "@urql/exchange-auth"
|
import { AuthConfig, authExchange } from "@urql/exchange-auth"
|
||||||
import { devtoolsExchange } from "@urql/devtools"
|
// import { devtoolsExchange } from "@urql/devtools"
|
||||||
import { SubscriptionClient } from "subscriptions-transport-ws"
|
import { SubscriptionClient } from "subscriptions-transport-ws"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
@@ -67,43 +68,43 @@ const createSubscriptionClient = () => {
|
|||||||
|
|
||||||
const createHoppClient = () => {
|
const createHoppClient = () => {
|
||||||
const exchanges = [
|
const exchanges = [
|
||||||
devtoolsExchange,
|
// devtoolsExchange,
|
||||||
dedupExchange,
|
dedupExchange,
|
||||||
authExchange({
|
authExchange(async (): Promise<AuthConfig> => {
|
||||||
addAuthToOperation({ authState, operation }) {
|
const probableUser = platform.auth.getProbableUser()
|
||||||
if (!authState) {
|
if (probableUser !== null)
|
||||||
return operation
|
await platform.auth.waitProbableLoginToConfirm()
|
||||||
}
|
|
||||||
|
|
||||||
const fetchOptions =
|
return {
|
||||||
typeof operation.context.fetchOptions === "function"
|
addAuthToOperation(operation) {
|
||||||
? operation.context.fetchOptions()
|
const fetchOptions =
|
||||||
: operation.context.fetchOptions || {}
|
typeof operation.context.fetchOptions === "function"
|
||||||
|
? operation.context.fetchOptions()
|
||||||
|
: operation.context.fetchOptions || {}
|
||||||
|
|
||||||
const authHeaders = platform.auth.getBackendHeaders()
|
const authHeaders = platform.auth.getBackendHeaders()
|
||||||
|
|
||||||
return makeOperation(operation.kind, operation, {
|
return makeOperation(operation.kind, operation, {
|
||||||
...operation.context,
|
...operation.context,
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
...fetchOptions,
|
...fetchOptions,
|
||||||
headers: {
|
headers: {
|
||||||
...fetchOptions.headers,
|
...fetchOptions.headers,
|
||||||
...authHeaders,
|
...authHeaders,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
})
|
},
|
||||||
},
|
willAuthError() {
|
||||||
willAuthError() {
|
return platform.auth.willBackendHaveAuthError()
|
||||||
return platform.auth.willBackendHaveAuthError()
|
},
|
||||||
},
|
didAuthError() {
|
||||||
getAuth: async () => {
|
return false
|
||||||
const probableUser = platform.auth.getProbableUser()
|
},
|
||||||
|
async refreshAuth() {
|
||||||
if (probableUser !== null)
|
// TODO
|
||||||
await platform.auth.waitProbableLoginToConfirm()
|
},
|
||||||
|
}
|
||||||
return {}
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
fetchExchange,
|
fetchExchange,
|
||||||
errorExchange({
|
errorExchange({
|
||||||
@@ -165,9 +166,9 @@ export function initBackendGQLClient() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunQueryOptions<T = any, V = object> = {
|
type RunQueryOptions<T = any, V = AnyVariables> = {
|
||||||
query: TypedDocumentNode<T, V>
|
query: TypedDocumentNode<T, V>
|
||||||
variables?: V
|
variables: V
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -183,7 +184,11 @@ export type GQLError<T extends string> =
|
|||||||
error: T
|
error: T
|
||||||
}
|
}
|
||||||
|
|
||||||
export const runGQLQuery = <DocType, DocVarType, DocErrorType extends string>(
|
export const runGQLQuery = <
|
||||||
|
DocType,
|
||||||
|
DocVarType extends AnyVariables,
|
||||||
|
DocErrorType extends string,
|
||||||
|
>(
|
||||||
args: RunQueryOptions<DocType, DocVarType>
|
args: RunQueryOptions<DocType, DocVarType>
|
||||||
): Promise<E.Either<GQLError<DocErrorType>, DocType>> => {
|
): Promise<E.Either<GQLError<DocErrorType>, DocType>> => {
|
||||||
const request = createRequest<DocType, DocVarType>(args.query, args.variables)
|
const request = createRequest<DocType, DocVarType>(args.query, args.variables)
|
||||||
@@ -245,8 +250,8 @@ export const runGQLQuery = <DocType, DocVarType, DocErrorType extends string>(
|
|||||||
// Make sure to handle cases if the subscription fires with the same update multiple times
|
// Make sure to handle cases if the subscription fires with the same update multiple times
|
||||||
export const runGQLSubscription = <
|
export const runGQLSubscription = <
|
||||||
DocType,
|
DocType,
|
||||||
DocVarType,
|
DocVarType extends AnyVariables,
|
||||||
DocErrorType extends string
|
DocErrorType extends string,
|
||||||
>(
|
>(
|
||||||
args: RunQueryOptions<DocType, DocVarType>
|
args: RunQueryOptions<DocType, DocVarType>
|
||||||
) => {
|
) => {
|
||||||
@@ -335,10 +340,10 @@ export const parseGQLErrorString = (s: string) =>
|
|||||||
export const runMutation = <
|
export const runMutation = <
|
||||||
DocType,
|
DocType,
|
||||||
DocVariables extends object | undefined,
|
DocVariables extends object | undefined,
|
||||||
DocErrors extends string
|
DocErrors extends string,
|
||||||
>(
|
>(
|
||||||
mutation: TypedDocumentNode<DocType, DocVariables>,
|
mutation: TypedDocumentNode<DocType, DocVariables>,
|
||||||
variables?: DocVariables,
|
variables: DocVariables,
|
||||||
additionalConfig?: Partial<OperationContext>
|
additionalConfig?: Partial<OperationContext>
|
||||||
): TE.TaskEither<GQLError<DocErrors>, DocType> =>
|
): TE.TaskEither<GQLError<DocErrors>, DocType> =>
|
||||||
pipe(
|
pipe(
|
||||||
|
|||||||
@@ -73,22 +73,22 @@ describe("detect content type", () => {
|
|||||||
// })
|
// })
|
||||||
|
|
||||||
describe("text/html", () => {
|
describe("text/html", () => {
|
||||||
test("should return text/html for valid HTML data", () => {
|
// test("should return text/html for valid HTML data", () => {
|
||||||
expect(
|
// expect(
|
||||||
detectContentType(`
|
// detectContentType(`
|
||||||
<!DOCTYPE html>
|
// <!DOCTYPE html>
|
||||||
<html>
|
// <html>
|
||||||
<head>
|
// <head>
|
||||||
<title>Page Title</title>
|
// <title>Page Title</title>
|
||||||
</head>
|
// </head>
|
||||||
<body>
|
// <body>
|
||||||
<h1>This is a Heading</h1>
|
// <h1>This is a Heading</h1>
|
||||||
<p>This is a paragraph.</p>
|
// <p>This is a paragraph.</p>
|
||||||
</body>
|
// </body>
|
||||||
</html>
|
// </html>
|
||||||
`)
|
// `)
|
||||||
).toBe("text/html")
|
// ).toBe("text/html")
|
||||||
})
|
// })
|
||||||
|
|
||||||
// TODO: Figure this test situation
|
// TODO: Figure this test situation
|
||||||
// test("should return text/html for invalid HTML data", () => {
|
// test("should return text/html for invalid HTML data", () => {
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ const multipartFunctions = {
|
|||||||
(nameArr) =>
|
(nameArr) =>
|
||||||
[nameArr[1], pair[0].includes("filename") ? "" : pair[1]] as [
|
[nameArr[1], pair[0].includes("filename") ? "" : pair[1]] as [
|
||||||
string,
|
string,
|
||||||
string
|
string,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||||||
|
|
||||||
const selectedEnvType = getSelectedEnvironmentType()
|
const selectedEnvType = getSelectedEnvironmentType()
|
||||||
|
|
||||||
const envTypeIcon = `<i class="inline-flex -my-1 -mx-0.5 opacity-65 items-center text-base material-icons border-secondary">${
|
const envTypeIcon = `<span class="inline-flex -my-2 -mx-0.5 opacity-65 items-center text-base font-icon">${
|
||||||
selectedEnvType === "TEAM_ENV" ? "people" : "person"
|
selectedEnvType === "TEAM_ENV" ? "group" : "person"
|
||||||
}</i>`
|
}</span>`
|
||||||
|
|
||||||
const appendEditAction = (tooltip: HTMLElement) => {
|
const appendEditAction = (tooltip: HTMLElement) => {
|
||||||
const editIcon = document.createElement("span")
|
const editIcon = document.createElement("span")
|
||||||
@@ -88,7 +88,7 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||||||
variableName: parsedEnvKey,
|
variableName: parsedEnvKey,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
editIcon.innerHTML = `<i class="inline-flex items-center px-1 -mx-1 -my-1 text-base material-icons border-secondary">drive_file_rename_outline</i>`
|
editIcon.innerHTML = `<span class="inline-flex items-center px-1 -mx-1 -my-2 text-base font-icon">edit</span>`
|
||||||
tooltip.appendChild(editIcon)
|
tooltip.appendChild(editIcon)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
export const tupleToRecord = <
|
export const tupleToRecord = <
|
||||||
KeyType extends string | number | symbol,
|
KeyType extends string | number | symbol,
|
||||||
ValueType
|
ValueType,
|
||||||
>(
|
>(
|
||||||
tuples: [KeyType, ValueType][]
|
tuples: [KeyType, ValueType][]
|
||||||
): Record<KeyType, ValueType> =>
|
): Record<KeyType, ValueType> =>
|
||||||
@@ -25,7 +25,7 @@ export const tupleToRecord = <
|
|||||||
*/
|
*/
|
||||||
export const tupleWithSameKeysToRecord = <
|
export const tupleWithSameKeysToRecord = <
|
||||||
KeyType extends string | number | symbol,
|
KeyType extends string | number | symbol,
|
||||||
ValueType
|
ValueType,
|
||||||
>(
|
>(
|
||||||
tuples: [KeyType, ValueType][]
|
tuples: [KeyType, ValueType][]
|
||||||
): Record<KeyType, ValueType[]> => {
|
): Record<KeyType, ValueType[]> => {
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ const parseOpenAPIV3Body = (
|
|||||||
// We only take the first definition
|
// We only take the first definition
|
||||||
const [contentType, media]: [
|
const [contentType, media]: [
|
||||||
string,
|
string,
|
||||||
OpenAPIV3.MediaTypeObject | OpenAPIV31.MediaTypeObject
|
OpenAPIV3.MediaTypeObject | OpenAPIV31.MediaTypeObject,
|
||||||
] = objs[0]
|
] = objs[0]
|
||||||
|
|
||||||
return contentType in knownContentTypes
|
return contentType in knownContentTypes
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ const getHoppReqParams = (item: Item): HoppRESTParam[] => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PMRequestAuthDef<
|
type PMRequestAuthDef<
|
||||||
AuthType extends RequestAuthDefinition["type"] = RequestAuthDefinition["type"]
|
AuthType extends
|
||||||
|
RequestAuthDefinition["type"] = RequestAuthDefinition["type"],
|
||||||
> = AuthType extends RequestAuthDefinition["type"] & string
|
> = AuthType extends RequestAuthDefinition["type"] & string
|
||||||
? // eslint-disable-next-line no-unused-vars
|
? // eslint-disable-next-line no-unused-vars
|
||||||
{ type: AuthType } & { [x in AuthType]: VariableDefinition[] }
|
{ type: AuthType } & { [x in AuthType]: VariableDefinition[] }
|
||||||
|
|||||||
@@ -1,189 +1,107 @@
|
|||||||
import { AxiosResponse, AxiosRequestConfig } from "axios"
|
import { AxiosRequestConfig } from "axios"
|
||||||
import { BehaviorSubject, Observable } from "rxjs"
|
import { BehaviorSubject, Observable } from "rxjs"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import * as T from "fp-ts/Task"
|
import * as E from "fp-ts/Either"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
|
||||||
import AxiosStrategy, {
|
|
||||||
cancelRunningAxiosRequest,
|
|
||||||
} from "./strategies/AxiosStrategy"
|
|
||||||
import ExtensionStrategy, {
|
|
||||||
cancelRunningExtensionRequest,
|
|
||||||
hasExtensionInstalled,
|
|
||||||
} from "./strategies/ExtensionStrategy"
|
|
||||||
import { HoppRESTResponse } from "./types/HoppRESTResponse"
|
import { HoppRESTResponse } from "./types/HoppRESTResponse"
|
||||||
import { EffectiveHoppRESTRequest } from "./utils/EffectiveURL"
|
import { EffectiveHoppRESTRequest } from "./utils/EffectiveURL"
|
||||||
import { settingsStore } from "~/newstore/settings"
|
import { getService } from "~/modules/dioc"
|
||||||
|
import {
|
||||||
export type NetworkResponse = AxiosResponse<any> & {
|
InterceptorService,
|
||||||
config?: {
|
NetworkResponse,
|
||||||
timeData?: {
|
} from "~/services/interceptor.service"
|
||||||
startTime: number
|
|
||||||
endTime: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NetworkStrategy = (
|
export type NetworkStrategy = (
|
||||||
req: AxiosRequestConfig
|
req: AxiosRequestConfig
|
||||||
) => TE.TaskEither<any, NetworkResponse>
|
) => TE.TaskEither<any, NetworkResponse>
|
||||||
|
|
||||||
export const cancelRunningRequest = () => {
|
export const cancelRunningRequest = () => {
|
||||||
if (isExtensionsAllowed() && hasExtensionInstalled()) {
|
// TODO: Implement
|
||||||
cancelRunningExtensionRequest()
|
|
||||||
} else {
|
|
||||||
cancelRunningAxiosRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isExtensionsAllowed = () => settingsStore.value.EXTENSIONS_ENABLED
|
function processResponse(
|
||||||
|
|
||||||
const runAppropriateStrategy = (req: AxiosRequestConfig) => {
|
|
||||||
if (isExtensionsAllowed() && hasExtensionInstalled()) {
|
|
||||||
return ExtensionStrategy(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
return AxiosStrategy(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an identifier for how a request will be ran
|
|
||||||
* if the system is asked to fire a request
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export function getCurrentStrategyID() {
|
|
||||||
if (isExtensionsAllowed() && hasExtensionInstalled()) {
|
|
||||||
return "extension" as const
|
|
||||||
} else if (settingsStore.value.PROXY_ENABLED) {
|
|
||||||
return "proxy" as const
|
|
||||||
} else {
|
|
||||||
return "normal" as const
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sendNetworkRequest = (req: any) =>
|
|
||||||
pipe(
|
|
||||||
runAppropriateStrategy(req),
|
|
||||||
TE.getOrElse((e) => {
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
)()
|
|
||||||
|
|
||||||
const processResponse = (
|
|
||||||
res: NetworkResponse,
|
res: NetworkResponse,
|
||||||
req: EffectiveHoppRESTRequest,
|
req: EffectiveHoppRESTRequest,
|
||||||
backupTimeStart: number,
|
backupTimeStart: number,
|
||||||
backupTimeEnd: number,
|
backupTimeEnd: number,
|
||||||
successState: HoppRESTResponse["type"]
|
successState: HoppRESTResponse["type"]
|
||||||
) =>
|
) {
|
||||||
pipe(
|
const contentLength = res.headers["content-length"]
|
||||||
TE.Do,
|
? parseInt(res.headers["content-length"])
|
||||||
|
: (res.data as ArrayBuffer).byteLength
|
||||||
|
|
||||||
// Calculate the content length
|
return <HoppRESTResponse>{
|
||||||
TE.bind("contentLength", () =>
|
type: successState,
|
||||||
TE.of(
|
statusCode: res.status,
|
||||||
res.headers["content-length"]
|
body: res.data,
|
||||||
? parseInt(res.headers["content-length"])
|
headers: Object.keys(res.headers).map((x) => ({
|
||||||
: (res.data as ArrayBuffer).byteLength
|
key: x,
|
||||||
)
|
value: res.headers[x],
|
||||||
),
|
})),
|
||||||
|
meta: {
|
||||||
// Building the final response object
|
responseSize: contentLength,
|
||||||
TE.map(
|
responseDuration: backupTimeEnd - backupTimeStart,
|
||||||
({ contentLength }) =>
|
},
|
||||||
<HoppRESTResponse>{
|
req,
|
||||||
type: successState,
|
}
|
||||||
statusCode: res.status,
|
}
|
||||||
body: res.data,
|
|
||||||
headers: Object.keys(res.headers).map((x) => ({
|
|
||||||
key: x,
|
|
||||||
value: res.headers[x],
|
|
||||||
})),
|
|
||||||
meta: {
|
|
||||||
responseSize: contentLength,
|
|
||||||
responseDuration: backupTimeEnd - backupTimeStart,
|
|
||||||
},
|
|
||||||
req,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export function createRESTNetworkRequestStream(
|
export function createRESTNetworkRequestStream(
|
||||||
request: EffectiveHoppRESTRequest
|
request: EffectiveHoppRESTRequest
|
||||||
): Observable<HoppRESTResponse> {
|
): [Observable<HoppRESTResponse>, () => void] {
|
||||||
const response = new BehaviorSubject<HoppRESTResponse>({
|
const response = new BehaviorSubject<HoppRESTResponse>({
|
||||||
type: "loading",
|
type: "loading",
|
||||||
req: request,
|
req: request,
|
||||||
})
|
})
|
||||||
|
|
||||||
pipe(
|
const req = cloneDeep(request)
|
||||||
TE.Do,
|
|
||||||
|
|
||||||
// Get a deep clone of the request
|
const headers = req.effectiveFinalHeaders.reduce((acc, { key, value }) => {
|
||||||
TE.bind("req", () => TE.of(cloneDeep(request))),
|
return Object.assign(acc, { [key]: value })
|
||||||
|
}, {})
|
||||||
|
|
||||||
// Assembling headers object
|
const params = new URLSearchParams()
|
||||||
TE.bind("headers", ({ req }) =>
|
for (const param of req.effectiveFinalParams) {
|
||||||
TE.of(
|
params.append(param.key, param.value)
|
||||||
req.effectiveFinalHeaders.reduce((acc, { key, value }) => {
|
}
|
||||||
return Object.assign(acc, { [key]: value })
|
|
||||||
}, {})
|
const backupTimeStart = Date.now()
|
||||||
|
|
||||||
|
const service = getService(InterceptorService)
|
||||||
|
|
||||||
|
const res = service.runRequest({
|
||||||
|
method: req.method as any,
|
||||||
|
url: req.effectiveFinalURL.trim(),
|
||||||
|
headers,
|
||||||
|
params,
|
||||||
|
data: req.effectiveFinalBody,
|
||||||
|
})
|
||||||
|
|
||||||
|
res.response.then((res) => {
|
||||||
|
const backupTimeEnd = Date.now()
|
||||||
|
|
||||||
|
if (E.isRight(res)) {
|
||||||
|
const processedRes = processResponse(
|
||||||
|
res.right,
|
||||||
|
req,
|
||||||
|
backupTimeStart,
|
||||||
|
backupTimeEnd,
|
||||||
|
"success"
|
||||||
)
|
)
|
||||||
),
|
|
||||||
|
|
||||||
// Assembling params object
|
response.next(processedRes)
|
||||||
TE.bind("params", ({ req }) => {
|
|
||||||
const params = new URLSearchParams()
|
|
||||||
req.effectiveFinalParams.forEach((x) => {
|
|
||||||
params.append(x.key, x.value)
|
|
||||||
})
|
|
||||||
return TE.of(params)
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Keeping the backup start time
|
|
||||||
TE.bind("backupTimeStart", () => TE.of(Date.now())),
|
|
||||||
|
|
||||||
// Running the request and getting the response
|
|
||||||
TE.bind("res", ({ req, headers, params }) =>
|
|
||||||
runAppropriateStrategy({
|
|
||||||
method: req.method as any,
|
|
||||||
url: req.effectiveFinalURL.trim(),
|
|
||||||
headers,
|
|
||||||
params,
|
|
||||||
data: req.effectiveFinalBody,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
|
|
||||||
// Getting the backup end time
|
|
||||||
TE.bind("backupTimeEnd", () => TE.of(Date.now())),
|
|
||||||
|
|
||||||
// Assemble the final response object
|
|
||||||
TE.chainW(({ req, res, backupTimeEnd, backupTimeStart }) =>
|
|
||||||
processResponse(res, req, backupTimeStart, backupTimeEnd, "success")
|
|
||||||
),
|
|
||||||
|
|
||||||
// Writing success state to the stream
|
|
||||||
TE.chain((res) => {
|
|
||||||
response.next(res)
|
|
||||||
response.complete()
|
response.complete()
|
||||||
|
|
||||||
return TE.of(res)
|
return
|
||||||
}),
|
}
|
||||||
|
|
||||||
// Package the error type
|
response.next({
|
||||||
TE.getOrElseW((e) => {
|
type: "network_fail",
|
||||||
const obj: HoppRESTResponse = {
|
req,
|
||||||
type: "network_fail",
|
error: res.left,
|
||||||
error: e,
|
|
||||||
req: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
response.next(obj)
|
|
||||||
response.complete()
|
|
||||||
|
|
||||||
return T.of(obj)
|
|
||||||
})
|
})
|
||||||
)()
|
response.complete()
|
||||||
|
})
|
||||||
|
|
||||||
return response
|
return [response, () => res.cancel()]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import HTTPSnippet from "httpsnippet"
|
import { HTTPSnippet } from "httpsnippet"
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
|
|||||||
@@ -181,6 +181,33 @@ export function closeTab(tabID: string) {
|
|||||||
tabMap.delete(tabID)
|
tabMap.delete(tabID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function closeOtherTabs(tabID: string) {
|
||||||
|
if (!tabMap.has(tabID)) {
|
||||||
|
console.warn(
|
||||||
|
`The tab to close other tabs does not exist (tab id: ${tabID})`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tabOrdering.value = [tabID]
|
||||||
|
|
||||||
|
tabMap.forEach((_, id) => {
|
||||||
|
if (id !== tabID) tabMap.delete(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
currentTabID.value = tabID
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDirtyTabsCount() {
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
for (const tab of tabMap.values()) {
|
||||||
|
if (tab.document.isDirty) count++
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
export function getTabRefWithSaveContext(ctx: HoppRESTSaveContext) {
|
export function getTabRefWithSaveContext(ctx: HoppRESTSaveContext) {
|
||||||
for (const tab of tabMap.values()) {
|
for (const tab of tabMap.values()) {
|
||||||
// For `team-collection` request id can be considered unique
|
// For `team-collection` request id can be considered unique
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ type BodyType<T extends ValidContentTypes | null | ANY_TYPE> =
|
|||||||
|
|
||||||
type TransitionDefinition<
|
type TransitionDefinition<
|
||||||
FromType extends ValidContentTypes | null | ANY_TYPE,
|
FromType extends ValidContentTypes | null | ANY_TYPE,
|
||||||
ToType extends ValidContentTypes | null | ANY_TYPE
|
ToType extends ValidContentTypes | null | ANY_TYPE,
|
||||||
> = {
|
> = {
|
||||||
from: FromType
|
from: FromType
|
||||||
to: ToType
|
to: ToType
|
||||||
@@ -37,7 +37,7 @@ type TransitionDefinition<
|
|||||||
|
|
||||||
const rule = <
|
const rule = <
|
||||||
FromType extends ValidContentTypes | null | ANY_TYPE,
|
FromType extends ValidContentTypes | null | ANY_TYPE,
|
||||||
ToType extends ValidContentTypes | null | ANY_TYPE
|
ToType extends ValidContentTypes | null | ANY_TYPE,
|
||||||
>(
|
>(
|
||||||
input: TransitionDefinition<FromType, ToType>
|
input: TransitionDefinition<FromType, ToType>
|
||||||
) => input
|
) => input
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
import * as TE from "fp-ts/TaskEither"
|
|
||||||
import * as O from "fp-ts/Option"
|
|
||||||
import { pipe } from "fp-ts/function"
|
|
||||||
import { AxiosRequestConfig } from "axios"
|
|
||||||
import { cloneDeep } from "lodash-es"
|
|
||||||
import { NetworkResponse, NetworkStrategy } from "../network"
|
|
||||||
import { browserIsChrome, browserIsFirefox } from "../utils/userAgent"
|
|
||||||
|
|
||||||
export const hasExtensionInstalled = () =>
|
|
||||||
typeof window.__POSTWOMAN_EXTENSION_HOOK__ !== "undefined"
|
|
||||||
|
|
||||||
export const hasChromeExtensionInstalled = () =>
|
|
||||||
hasExtensionInstalled() && browserIsChrome()
|
|
||||||
|
|
||||||
export const hasFirefoxExtensionInstalled = () =>
|
|
||||||
hasExtensionInstalled() && browserIsFirefox()
|
|
||||||
|
|
||||||
export const cancelRunningExtensionRequest = () => {
|
|
||||||
window.__POSTWOMAN_EXTENSION_HOOK__?.cancelRequest()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defineSubscribableObject = <T extends object>(obj: T) => {
|
|
||||||
const proxyObject = {
|
|
||||||
...obj,
|
|
||||||
_subscribers: {} as {
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
[key in keyof T]?: ((...args: any[]) => any)[]
|
|
||||||
},
|
|
||||||
subscribe(prop: keyof T, func: (...args: any[]) => any): void {
|
|
||||||
if (Array.isArray(this._subscribers[prop])) {
|
|
||||||
this._subscribers[prop]?.push(func)
|
|
||||||
} else {
|
|
||||||
this._subscribers[prop] = [func]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type SubscribableProxyObject = typeof proxyObject
|
|
||||||
|
|
||||||
return new Proxy(proxyObject, {
|
|
||||||
set(obj, prop, newVal) {
|
|
||||||
obj[prop as keyof SubscribableProxyObject] = newVal
|
|
||||||
|
|
||||||
const currentSubscribers = obj._subscribers[prop as keyof T]
|
|
||||||
|
|
||||||
if (Array.isArray(currentSubscribers)) {
|
|
||||||
for (const subscriber of currentSubscribers) {
|
|
||||||
subscriber(newVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const preProcessRequest = (req: AxiosRequestConfig): AxiosRequestConfig => {
|
|
||||||
const reqClone = cloneDeep(req)
|
|
||||||
|
|
||||||
// If the parameters are URLSearchParams, inject them to URL instead
|
|
||||||
// This prevents marshalling issues with structured cloning of URLSearchParams
|
|
||||||
if (reqClone.params instanceof URLSearchParams) {
|
|
||||||
try {
|
|
||||||
const url = new URL(reqClone.url ?? "")
|
|
||||||
|
|
||||||
for (const [key, value] of reqClone.params.entries()) {
|
|
||||||
url.searchParams.append(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
reqClone.url = url.toString()
|
|
||||||
} catch (e) {
|
|
||||||
// making this a non-empty block, so we can make the linter happy.
|
|
||||||
// we should probably use, allowEmptyCatch, or take the time to do something with the caught errors :)
|
|
||||||
}
|
|
||||||
|
|
||||||
reqClone.params = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return reqClone
|
|
||||||
}
|
|
||||||
|
|
||||||
const extensionStrategy: NetworkStrategy = (req) =>
|
|
||||||
pipe(
|
|
||||||
TE.Do,
|
|
||||||
|
|
||||||
TE.bind("processedReq", () => TE.of(preProcessRequest(req))),
|
|
||||||
|
|
||||||
// Storeing backup timing data in case the extension does not have that info
|
|
||||||
TE.bind("backupTimeDataStart", () => TE.of(new Date().getTime())),
|
|
||||||
|
|
||||||
// Run the request
|
|
||||||
TE.bind("response", ({ processedReq }) =>
|
|
||||||
pipe(
|
|
||||||
window.__POSTWOMAN_EXTENSION_HOOK__,
|
|
||||||
O.fromNullable,
|
|
||||||
TE.fromOption(() => "NO_PW_EXT_HOOK" as const),
|
|
||||||
TE.chain((extensionHook) =>
|
|
||||||
TE.tryCatch(
|
|
||||||
() =>
|
|
||||||
extensionHook.sendRequest({
|
|
||||||
...processedReq,
|
|
||||||
wantsBinary: true,
|
|
||||||
}),
|
|
||||||
(err) => err as any
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
// Inject backup time data if not present
|
|
||||||
TE.map(({ backupTimeDataStart, response }) => ({
|
|
||||||
...response,
|
|
||||||
config: {
|
|
||||||
timeData: {
|
|
||||||
startTime: backupTimeDataStart,
|
|
||||||
endTime: new Date().getTime(),
|
|
||||||
},
|
|
||||||
...response.config,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
TE.orElse((e) =>
|
|
||||||
e !== "cancellation" && e.response
|
|
||||||
? TE.right(e.response as NetworkResponse)
|
|
||||||
: TE.left(e)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export default extensionStrategy
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import { vi, describe, expect, test } from "vitest"
|
|
||||||
import axios from "axios"
|
|
||||||
import axiosStrategy from "../AxiosStrategy"
|
|
||||||
|
|
||||||
vi.mock("axios")
|
|
||||||
vi.mock("~/newstore/settings", () => {
|
|
||||||
return {
|
|
||||||
__esModule: true,
|
|
||||||
settingsStore: {
|
|
||||||
value: {
|
|
||||||
PROXY_ENABLED: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
axios.CancelToken.source.mockReturnValue({ token: "test" })
|
|
||||||
axios.mockResolvedValue({})
|
|
||||||
|
|
||||||
describe("axiosStrategy", () => {
|
|
||||||
describe("No-Proxy Requests", () => {
|
|
||||||
test("sends request to the actual sender if proxy disabled", async () => {
|
|
||||||
await axiosStrategy({ url: "test" })()
|
|
||||||
|
|
||||||
expect(axios).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
url: "test",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("asks axios to return data as arraybuffer", async () => {
|
|
||||||
await axiosStrategy({ url: "test" })()
|
|
||||||
|
|
||||||
expect(axios).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
responseType: "arraybuffer",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("resolves successful requests", async () => {
|
|
||||||
expect(await axiosStrategy({})()).toBeRight()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("rejects cancel errors with text 'cancellation'", async () => {
|
|
||||||
axios.isCancel.mockReturnValueOnce(true)
|
|
||||||
axios.mockRejectedValue("err")
|
|
||||||
|
|
||||||
expect(await axiosStrategy({})()).toEqualLeft("cancellation")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("rejects non-cancellation errors as-is", async () => {
|
|
||||||
axios.isCancel.mockReturnValueOnce(false)
|
|
||||||
axios.mockRejectedValue("err")
|
|
||||||
|
|
||||||
expect(await axiosStrategy({})()).toEqualLeft("err")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("non-cancellation errors that have response data are right", async () => {
|
|
||||||
const errorResponse = { error: "errr" }
|
|
||||||
axios.isCancel.mockReturnValueOnce(false)
|
|
||||||
axios.mockRejectedValue({
|
|
||||||
response: {
|
|
||||||
data: Buffer.from(JSON.stringify(errorResponse), "utf8").toString(
|
|
||||||
"base64"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(await axiosStrategy({})()).toSubsetEqualRight({
|
|
||||||
data: "eyJlcnJvciI6ImVycnIifQ==",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
import { describe, test, expect, vi } from "vitest"
|
|
||||||
import axios from "axios"
|
|
||||||
import axiosStrategy from "../AxiosStrategy"
|
|
||||||
|
|
||||||
vi.mock("../../utils/b64", () => ({
|
|
||||||
__esModule: true,
|
|
||||||
decodeB64StringToArrayBuffer: vi.fn((data) => `${data}-converted`),
|
|
||||||
}))
|
|
||||||
vi.mock("~/newstore/settings", () => {
|
|
||||||
return {
|
|
||||||
__esModule: true,
|
|
||||||
settingsStore: {
|
|
||||||
value: {
|
|
||||||
PROXY_ENABLED: true,
|
|
||||||
PROXY_URL: "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("axiosStrategy", () => {
|
|
||||||
describe("Proxy Requests", () => {
|
|
||||||
test("sends POST request to proxy if proxy is enabled", async () => {
|
|
||||||
let passedURL
|
|
||||||
|
|
||||||
vi.spyOn(axios, "post").mockImplementation((url) => {
|
|
||||||
passedURL = url
|
|
||||||
return Promise.resolve({ data: { success: true, isBinary: false } })
|
|
||||||
})
|
|
||||||
|
|
||||||
await axiosStrategy({})()
|
|
||||||
|
|
||||||
expect(passedURL).toEqual("test")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("passes request fields to axios properly", async () => {
|
|
||||||
const reqFields = {
|
|
||||||
testA: "testA",
|
|
||||||
testB: "testB",
|
|
||||||
testC: "testC",
|
|
||||||
}
|
|
||||||
|
|
||||||
let passedFields
|
|
||||||
|
|
||||||
vi.spyOn(axios, "post").mockImplementation((_url, req) => {
|
|
||||||
passedFields = req
|
|
||||||
return Promise.resolve({ data: { success: true, isBinary: false } })
|
|
||||||
})
|
|
||||||
|
|
||||||
await axiosStrategy(reqFields)()
|
|
||||||
|
|
||||||
expect(passedFields).toMatchObject(reqFields)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("passes wantsBinary field", async () => {
|
|
||||||
let passedFields
|
|
||||||
|
|
||||||
vi.spyOn(axios, "post").mockImplementation((_url, req) => {
|
|
||||||
passedFields = req
|
|
||||||
return Promise.resolve({ data: { success: true, isBinary: false } })
|
|
||||||
})
|
|
||||||
|
|
||||||
await axiosStrategy({})()
|
|
||||||
|
|
||||||
expect(passedFields).toHaveProperty("wantsBinary")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("checks for proxy response success field and throws error message for non-success", async () => {
|
|
||||||
vi.spyOn(axios, "post").mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
success: false,
|
|
||||||
data: {
|
|
||||||
message: "test message",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(await axiosStrategy({})()).toEqualLeft("test message")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("checks for proxy response success field and throws error 'Proxy Error' for non-success", async () => {
|
|
||||||
vi.spyOn(axios, "post").mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
success: false,
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(await axiosStrategy({})()).toBeLeft("Proxy Error")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("checks for proxy response success and doesn't left for success", async () => {
|
|
||||||
vi.spyOn(axios, "post").mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
success: true,
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(await axiosStrategy({})()).toBeRight()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("checks isBinary response field and right with the converted value if so", async () => {
|
|
||||||
vi.spyOn(axios, "post").mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
success: true,
|
|
||||||
isBinary: true,
|
|
||||||
data: "testdata",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(await axiosStrategy({})()).toSubsetEqualRight({
|
|
||||||
data: "testdata-converted",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test("checks isBinary response field and right with the actual value if not so", async () => {
|
|
||||||
vi.spyOn(axios, "post").mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
success: true,
|
|
||||||
isBinary: false,
|
|
||||||
data: "testdata",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(await axiosStrategy({})()).toSubsetEqualRight({
|
|
||||||
data: "testdata",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test("cancel errors are returned a left with the string 'cancellation'", async () => {
|
|
||||||
vi.spyOn(axios, "post").mockRejectedValue("errr")
|
|
||||||
vi.spyOn(axios, "isCancel").mockReturnValueOnce(true)
|
|
||||||
|
|
||||||
expect(await axiosStrategy({})()).toEqualLeft("cancellation")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("non-cancellation errors return a left", async () => {
|
|
||||||
vi.spyOn(axios, "post").mockRejectedValue("errr")
|
|
||||||
vi.spyOn(axios, "isCancel").mockReturnValueOnce(false)
|
|
||||||
|
|
||||||
expect(await axiosStrategy({})()).toEqualLeft("errr")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
import { vi, describe, expect, test, beforeEach } from "vitest"
|
|
||||||
import extensionStrategy, {
|
|
||||||
hasExtensionInstalled,
|
|
||||||
hasChromeExtensionInstalled,
|
|
||||||
hasFirefoxExtensionInstalled,
|
|
||||||
cancelRunningExtensionRequest,
|
|
||||||
} from "../ExtensionStrategy"
|
|
||||||
|
|
||||||
vi.mock("../../utils/b64", () => ({
|
|
||||||
__esModule: true,
|
|
||||||
decodeB64StringToArrayBuffer: vi.fn((data) => `${data}-converted`),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock("~/newstore/settings", () => {
|
|
||||||
return {
|
|
||||||
__esModule: true,
|
|
||||||
settingsStore: {
|
|
||||||
value: {
|
|
||||||
EXTENSIONS_ENABLED: true,
|
|
||||||
PROXY_ENABLED: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("hasExtensionInstalled", () => {
|
|
||||||
test("returns true if extension is present and hooked", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
|
||||||
|
|
||||||
expect(hasExtensionInstalled()).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("returns false if extension not present or not hooked", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
|
||||||
|
|
||||||
expect(hasExtensionInstalled()).toEqual(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("hasChromeExtensionInstalled", () => {
|
|
||||||
test("returns true if extension is hooked and browser is chrome", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
|
||||||
vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
|
|
||||||
vi.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
|
|
||||||
|
|
||||||
expect(hasChromeExtensionInstalled()).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("returns false if extension is hooked and browser is not chrome", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
|
||||||
vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
|
|
||||||
vi.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
|
|
||||||
|
|
||||||
expect(hasChromeExtensionInstalled()).toEqual(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("returns false if extension not installed and browser is chrome", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
|
||||||
vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
|
|
||||||
vi.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
|
|
||||||
|
|
||||||
expect(hasChromeExtensionInstalled()).toEqual(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("returns false if extension not installed and browser is not chrome", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
|
||||||
vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
|
|
||||||
vi.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
|
|
||||||
|
|
||||||
expect(hasChromeExtensionInstalled()).toEqual(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("hasFirefoxExtensionInstalled", () => {
|
|
||||||
test("returns true if extension is hooked and browser is firefox", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
|
||||||
vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
|
|
||||||
|
|
||||||
expect(hasFirefoxExtensionInstalled()).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("returns false if extension is hooked and browser is not firefox", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
|
||||||
vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
|
|
||||||
|
|
||||||
expect(hasFirefoxExtensionInstalled()).toEqual(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("returns false if extension not installed and browser is firefox", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
|
||||||
vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
|
|
||||||
|
|
||||||
expect(hasFirefoxExtensionInstalled()).toEqual(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("returns false if extension not installed and browser is not firefox", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
|
||||||
vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
|
|
||||||
|
|
||||||
expect(hasFirefoxExtensionInstalled()).toEqual(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("cancelRunningExtensionRequest", () => {
|
|
||||||
const cancelFunc = vi.fn()
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
cancelFunc.mockClear()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("cancels request if extension installed and function present in hook", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
|
||||||
cancelRequest: cancelFunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelRunningExtensionRequest()
|
|
||||||
expect(cancelFunc).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("does not cancel request if extension not installed", () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
|
||||||
|
|
||||||
cancelRunningExtensionRequest()
|
|
||||||
expect(cancelFunc).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("extensionStrategy", () => {
|
|
||||||
const sendReqFunc = vi.fn()
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
sendReqFunc.mockClear()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("Non-Proxy Requests", () => {
|
|
||||||
test("ask extension to send request", async () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
|
||||||
sendRequest: sendReqFunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
sendReqFunc.mockResolvedValue({
|
|
||||||
data: '{"success":true,"data":""}',
|
|
||||||
})
|
|
||||||
|
|
||||||
await extensionStrategy({})()
|
|
||||||
|
|
||||||
expect(sendReqFunc).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("sends request to the actual sender if proxy disabled", async () => {
|
|
||||||
let passedUrl
|
|
||||||
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
|
||||||
sendRequest: sendReqFunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
sendReqFunc.mockImplementation(({ url }) => {
|
|
||||||
passedUrl = url
|
|
||||||
|
|
||||||
return Promise.resolve({
|
|
||||||
data: '{"success":true,"data":""}',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await extensionStrategy({ url: "test" })()
|
|
||||||
|
|
||||||
expect(passedUrl).toEqual("test")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("asks extension to get binary data", async () => {
|
|
||||||
let passedFields
|
|
||||||
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
|
||||||
sendRequest: sendReqFunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
sendReqFunc.mockImplementation((fields) => {
|
|
||||||
passedFields = fields
|
|
||||||
|
|
||||||
return Promise.resolve({
|
|
||||||
data: '{"success":true,"data":""}',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await extensionStrategy({})()
|
|
||||||
|
|
||||||
expect(passedFields).toHaveProperty("wantsBinary")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("rights successful requests", async () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
|
||||||
sendRequest: sendReqFunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
sendReqFunc.mockResolvedValue({
|
|
||||||
data: '{"success":true,"data":""}',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(await extensionStrategy({})()).toBeRight()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("rejects errors as-is", async () => {
|
|
||||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
|
||||||
sendRequest: sendReqFunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
sendReqFunc.mockRejectedValue("err")
|
|
||||||
|
|
||||||
expect(await extensionStrategy({})()).toEqualLeft("err")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user