Compare commits
43 Commits
release/20
...
fix/nodema
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d35688f27 | ||
|
|
173c456eb7 | ||
|
|
53644de851 | ||
|
|
feabd00d30 | ||
|
|
5428a73811 | ||
|
|
4a154e6569 | ||
|
|
0aa5825d8b | ||
|
|
bdb63e99d5 | ||
|
|
8175ec640a | ||
|
|
b5307e4a89 | ||
|
|
19294802be | ||
|
|
cbe3e14b47 | ||
|
|
01df1663ad | ||
|
|
abd5288da8 | ||
|
|
a89bc473f6 | ||
|
|
57cb59027b | ||
|
|
7a9f0c8756 | ||
|
|
46caf9b198 | ||
|
|
f5db54484c | ||
|
|
8deb6471b9 | ||
|
|
73b3ff8e41 | ||
|
|
016a18d3b2 | ||
|
|
ba31cdabea | ||
|
|
51510566bc | ||
|
|
cabee0ecc8 | ||
|
|
2c2b39a236 | ||
|
|
78450c9316 | ||
|
|
b18fd90b64 | ||
|
|
0188a8d7db | ||
|
|
6c63a8dc28 | ||
|
|
17d6ae15a5 | ||
|
|
40f72278a9 | ||
|
|
f717704731 | ||
|
|
185c225297 | ||
|
|
2694731c36 | ||
|
|
ae89af9978 | ||
|
|
87d617012f | ||
|
|
2420b3fa42 | ||
|
|
175a991ec4 | ||
|
|
0301649aff | ||
|
|
544b045300 | ||
|
|
65884293be | ||
|
|
3cb4861bac |
@@ -5,5 +5,5 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/NicoVIII/devcontainer-features/pnpm:1": {}
|
"ghcr.io/NicoVIII/devcontainer-features/pnpm:1": {}
|
||||||
},
|
},
|
||||||
"postCreateCommand": "mv .env.example .env && pnpm i"
|
"postCreateCommand": "cp .env.example .env && pnpm i"
|
||||||
}
|
}
|
||||||
|
|||||||
3
.github/workflows/release-push-docker.yml
vendored
3
.github/workflows/release-push-docker.yml
vendored
@@ -18,6 +18,9 @@ jobs:
|
|||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/ui.yml
vendored
2
.github/workflows/ui.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
# Deploy the ui site with netlify-cli
|
# Deploy the ui site with netlify-cli
|
||||||
- name: Deploy to Netlify (ui)
|
- name: Deploy to Netlify (ui)
|
||||||
run: npx netlify-cli deploy --dir=packages/hoppscotch-ui/.histoire/dist --prod
|
run: npx netlify-cli@15.11.0 deploy --dir=packages/hoppscotch-ui/.histoire/dist --prod
|
||||||
env:
|
env:
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_UI_SITE_ID }}
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_UI_SITE_ID }}
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -22,16 +22,14 @@
|
|||||||
"workspaces": [
|
"workspaces": [
|
||||||
"./packages/*"
|
"./packages/*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
|
||||||
"husky": "^7.0.4",
|
|
||||||
"lint-staged": "^12.3.8"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^16.2.3",
|
"@commitlint/cli": "^16.2.3",
|
||||||
"@commitlint/config-conventional": "^16.2.1",
|
"@commitlint/config-conventional": "^16.2.1",
|
||||||
"@types/node": "^17.0.24",
|
"@types/node": "17.0.27",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"http-server": "^14.1.1"
|
"http-server": "^14.1.1",
|
||||||
|
"husky": "^7.0.4",
|
||||||
|
"lint-staged": "12.4.0"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"packageExtensions": {
|
"packageExtensions": {
|
||||||
|
|||||||
@@ -17,16 +17,16 @@
|
|||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/language": "^6.9.0",
|
"@codemirror/language": "^6.9.2",
|
||||||
"@lezer/highlight": "^1.1.6",
|
"@lezer/highlight": "1.1.4",
|
||||||
"@lezer/lr": "^1.3.10"
|
"@lezer/lr": "^1.3.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lezer/generator": "^1.5.0",
|
"@lezer/generator": "^1.5.1",
|
||||||
"mocha": "^9.2.2",
|
"mocha": "^9.2.2",
|
||||||
"rollup": "^2.70.2",
|
"rollup": "^3.29.3",
|
||||||
"rollup-plugin-dts": "^4.2.1",
|
"rollup-plugin-dts": "^6.0.2",
|
||||||
"rollup-plugin-ts": "^2.0.7",
|
"rollup-plugin-ts": "^3.4.5",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-backend",
|
"name": "hoppscotch-backend",
|
||||||
"version": "2023.8.1",
|
"version": "2023.8.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -24,18 +24,17 @@
|
|||||||
"do-test": "pnpm run test"
|
"do-test": "pnpm run test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs-modules/mailer": "^1.8.1",
|
"@apollo/server": "^4.9.4",
|
||||||
"@nestjs/apollo": "^10.1.6",
|
"@nestjs-modules/mailer": "^1.9.1",
|
||||||
"@nestjs/common": "^9.2.1",
|
"@nestjs/apollo": "^12.0.9",
|
||||||
"@nestjs/core": "^9.2.1",
|
"@nestjs/common": "^10.2.6",
|
||||||
"@nestjs/graphql": "^10.1.6",
|
"@nestjs/core": "^10.2.6",
|
||||||
"@nestjs/jwt": "^10.0.1",
|
"@nestjs/graphql": "^12.0.9",
|
||||||
"@nestjs/passport": "^9.0.0",
|
"@nestjs/jwt": "^10.1.1",
|
||||||
"@nestjs/platform-express": "^9.2.1",
|
"@nestjs/passport": "^10.0.2",
|
||||||
"@nestjs/throttler": "^4.0.0",
|
"@nestjs/platform-express": "^10.2.6",
|
||||||
|
"@nestjs/throttler": "^5.0.0",
|
||||||
"@prisma/client": "^4.16.2",
|
"@prisma/client": "^4.16.2",
|
||||||
"apollo-server-express": "^3.11.1",
|
|
||||||
"apollo-server-plugin-base": "^3.7.1",
|
|
||||||
"argon2": "^0.30.3",
|
"argon2": "^0.30.3",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
"cookie": "^0.5.0",
|
"cookie": "^0.5.0",
|
||||||
@@ -43,9 +42,9 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"fp-ts": "^2.13.1",
|
"fp-ts": "^2.13.1",
|
||||||
"graphql": "^15.5.0",
|
"graphql": "^16.8.1",
|
||||||
"graphql-query-complexity": "^0.12.0",
|
"graphql-query-complexity": "^0.12.0",
|
||||||
"graphql-redis-subscriptions": "^2.5.0",
|
"graphql-redis-subscriptions": "^2.6.0",
|
||||||
"graphql-subscriptions": "^2.0.0",
|
"graphql-subscriptions": "^2.0.0",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"io-ts": "^2.2.16",
|
"io-ts": "^2.2.16",
|
||||||
@@ -63,9 +62,9 @@
|
|||||||
"rxjs": "^7.6.0"
|
"rxjs": "^7.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^9.1.5",
|
"@nestjs/cli": "^10.1.18",
|
||||||
"@nestjs/schematics": "^9.0.3",
|
"@nestjs/schematics": "^10.0.2",
|
||||||
"@nestjs/testing": "^9.2.1",
|
"@nestjs/testing": "^10.2.6",
|
||||||
"@relmify/jest-fp-ts": "^2.0.2",
|
"@relmify/jest-fp-ts": "^2.0.2",
|
||||||
"@types/argon2": "^0.15.0",
|
"@types/argon2": "^0.15.0",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export class AdminService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.mailerService.sendUserInvitationEmail(inviteeEmail, {
|
await this.mailerService.sendUserInvitationEmail(inviteeEmail, {
|
||||||
template: 'code-your-own',
|
template: 'user-invitation',
|
||||||
variables: {
|
variables: {
|
||||||
inviteeEmail: inviteeEmail,
|
inviteeEmail: inviteeEmail,
|
||||||
magicLink: `${process.env.VITE_BASE_URL}`,
|
magicLink: `${process.env.VITE_BASE_URL}`,
|
||||||
|
|||||||
@@ -27,12 +27,7 @@ import { AppController } from './app.controller';
|
|||||||
buildSchemaOptions: {
|
buildSchemaOptions: {
|
||||||
numberScalarMode: 'integer',
|
numberScalarMode: 'integer',
|
||||||
},
|
},
|
||||||
cors: {
|
|
||||||
origin: process.env.WHITELISTED_ORIGINS.split(','),
|
|
||||||
credentials: true,
|
|
||||||
},
|
|
||||||
playground: process.env.PRODUCTION !== 'true',
|
playground: process.env.PRODUCTION !== 'true',
|
||||||
debug: process.env.PRODUCTION !== 'true',
|
|
||||||
autoSchemaFile: true,
|
autoSchemaFile: true,
|
||||||
installSubscriptionHandlers: true,
|
installSubscriptionHandlers: true,
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
@@ -62,10 +57,12 @@ import { AppController } from './app.controller';
|
|||||||
}),
|
}),
|
||||||
driver: ApolloDriver,
|
driver: ApolloDriver,
|
||||||
}),
|
}),
|
||||||
ThrottlerModule.forRoot({
|
ThrottlerModule.forRoot([
|
||||||
ttl: +process.env.RATE_LIMIT_TTL,
|
{
|
||||||
limit: +process.env.RATE_LIMIT_MAX,
|
ttl: +process.env.RATE_LIMIT_TTL,
|
||||||
}),
|
limit: +process.env.RATE_LIMIT_MAX,
|
||||||
|
},
|
||||||
|
]),
|
||||||
UserModule,
|
UserModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
AdminModule,
|
AdminModule,
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.mailerService.sendEmail(email, {
|
await this.mailerService.sendEmail(email, {
|
||||||
template: 'code-your-own',
|
template: 'user-invitation',
|
||||||
variables: {
|
variables: {
|
||||||
inviteeEmail: email,
|
inviteeEmail: email,
|
||||||
magicLink: `${url}/enter?token=${generatedTokens.token}`,
|
magicLink: `${url}/enter?token=${generatedTokens.token}`,
|
||||||
|
|||||||
@@ -93,9 +93,7 @@ export async function emitGQLSchemaFile() {
|
|||||||
numberScalarMode: 'integer',
|
numberScalarMode: 'integer',
|
||||||
});
|
});
|
||||||
|
|
||||||
const schemaString = printSchema(schema, {
|
const schemaString = printSchema(schema);
|
||||||
commentDescriptions: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.log(`Writing schema to GQL_SCHEMA_EMIT_LOCATION (${destination})`);
|
logger.log(`Writing schema to GQL_SCHEMA_EMIT_LOCATION (${destination})`);
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
|
export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
|
||||||
protected getTracker(req: Record<string, any>): string {
|
protected async getTracker(req: Record<string, any>): Promise<string> {
|
||||||
return req.ips.length ? req.ips[0] : req.ip; // individualize IP extraction to meet your own needs
|
return req.ips.length ? req.ips[0] : req.ip; // individualize IP extraction to meet your own needs
|
||||||
// learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#directives
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export type MailDescription = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UserMagicLinkMailDescription = {
|
export type UserMagicLinkMailDescription = {
|
||||||
template: 'code-your-own';
|
template: 'user-invitation';
|
||||||
variables: {
|
variables: {
|
||||||
inviteeEmail: string;
|
inviteeEmail: string;
|
||||||
magicLink: string;
|
magicLink: string;
|
||||||
@@ -16,7 +16,7 @@ export type UserMagicLinkMailDescription = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type AdminUserInvitationMailDescription = {
|
export type AdminUserInvitationMailDescription = {
|
||||||
template: 'code-your-own';
|
template: 'user-invitation';
|
||||||
variables: {
|
variables: {
|
||||||
inviteeEmail: string;
|
inviteeEmail: string;
|
||||||
magicLink: string;
|
magicLink: string;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class MailerService {
|
|||||||
case 'team-invitation':
|
case 'team-invitation':
|
||||||
return `${mailDesc.variables.invitee} invited you to join ${mailDesc.variables.invite_team_name} in Hoppscotch`;
|
return `${mailDesc.variables.invitee} invited you to join ${mailDesc.variables.invite_team_name} in Hoppscotch`;
|
||||||
|
|
||||||
case 'code-your-own':
|
case 'user-invitation':
|
||||||
return 'Sign in to Hoppscotch';
|
return 'Sign in to Hoppscotch';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<style type="text/css" rel="stylesheet" media="all">
|
<style type="text/css" rel="stylesheet" media="all">
|
||||||
/* Base ------------------------------ */
|
/* Base ------------------------------ */
|
||||||
|
|
||||||
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
|
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
|
||||||
body {
|
body {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
@@ -22,19 +22,19 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
-webkit-text-size-adjust: none;
|
-webkit-text-size-adjust: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #3869D4;
|
color: #3869D4;
|
||||||
}
|
}
|
||||||
|
|
||||||
a img {
|
a img {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preheader {
|
.preheader {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@@ -47,13 +47,13 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
/* Type ------------------------------ */
|
/* Type ------------------------------ */
|
||||||
|
|
||||||
body,
|
body,
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
|
font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
@@ -77,12 +77,12 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p,
|
p,
|
||||||
ul,
|
ul,
|
||||||
ol,
|
ol,
|
||||||
@@ -91,25 +91,25 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.625;
|
line-height: 1.625;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.sub {
|
p.sub {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
/* Utilities ------------------------------ */
|
/* Utilities ------------------------------ */
|
||||||
|
|
||||||
.align-right {
|
.align-right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-left {
|
.align-left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-center {
|
.align-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
/* Buttons ------------------------------ */
|
/* Buttons ------------------------------ */
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
background-color: #3869D4;
|
background-color: #3869D4;
|
||||||
border-top: 10px solid #3869D4;
|
border-top: 10px solid #3869D4;
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
-webkit-text-size-adjust: none;
|
-webkit-text-size-adjust: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--green {
|
.button--green {
|
||||||
background-color: #22BC66;
|
background-color: #22BC66;
|
||||||
border-top: 10px solid #22BC66;
|
border-top: 10px solid #22BC66;
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
border-bottom: 10px solid #22BC66;
|
border-bottom: 10px solid #22BC66;
|
||||||
border-left: 18px solid #22BC66;
|
border-left: 18px solid #22BC66;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--red {
|
.button--red {
|
||||||
background-color: #FF6136;
|
background-color: #FF6136;
|
||||||
border-top: 10px solid #FF6136;
|
border-top: 10px solid #FF6136;
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
border-bottom: 10px solid #FF6136;
|
border-bottom: 10px solid #FF6136;
|
||||||
border-left: 18px solid #FF6136;
|
border-left: 18px solid #FF6136;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 500px) {
|
@media only screen and (max-width: 500px) {
|
||||||
.button {
|
.button {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
@@ -148,21 +148,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Attribute list ------------------------------ */
|
/* Attribute list ------------------------------ */
|
||||||
|
|
||||||
.attributes {
|
.attributes {
|
||||||
margin: 0 0 21px;
|
margin: 0 0 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attributes_content {
|
.attributes_content {
|
||||||
background-color: #F4F4F7;
|
background-color: #F4F4F7;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attributes_item {
|
.attributes_item {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
/* Related Items ------------------------------ */
|
/* Related Items ------------------------------ */
|
||||||
|
|
||||||
.related {
|
.related {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -171,31 +171,31 @@
|
|||||||
-premailer-cellpadding: 0;
|
-premailer-cellpadding: 0;
|
||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related_item {
|
.related_item {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
color: #CBCCCF;
|
color: #CBCCCF;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related_item-title {
|
.related_item-title {
|
||||||
display: block;
|
display: block;
|
||||||
margin: .5em 0 0;
|
margin: .5em 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related_item-thumb {
|
.related_item-thumb {
|
||||||
display: block;
|
display: block;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related_heading {
|
.related_heading {
|
||||||
border-top: 1px solid #CBCCCF;
|
border-top: 1px solid #CBCCCF;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 25px 0 10px;
|
padding: 25px 0 10px;
|
||||||
}
|
}
|
||||||
/* Discount Code ------------------------------ */
|
/* Discount Code ------------------------------ */
|
||||||
|
|
||||||
.discount {
|
.discount {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -206,33 +206,33 @@
|
|||||||
background-color: #F4F4F7;
|
background-color: #F4F4F7;
|
||||||
border: 2px dashed #CBCCCF;
|
border: 2px dashed #CBCCCF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discount_heading {
|
.discount_heading {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discount_body {
|
.discount_body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
/* Social Icons ------------------------------ */
|
/* Social Icons ------------------------------ */
|
||||||
|
|
||||||
.social {
|
.social {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.social td {
|
.social td {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.social_icon {
|
.social_icon {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin: 0 8px 10px 8px;
|
margin: 0 8px 10px 8px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
/* Data table ------------------------------ */
|
/* Data table ------------------------------ */
|
||||||
|
|
||||||
.purchase {
|
.purchase {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
-premailer-cellpadding: 0;
|
-premailer-cellpadding: 0;
|
||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_content {
|
.purchase_content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -250,50 +250,50 @@
|
|||||||
-premailer-cellpadding: 0;
|
-premailer-cellpadding: 0;
|
||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_item {
|
.purchase_item {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
color: #51545E;
|
color: #51545E;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_heading {
|
.purchase_heading {
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
border-bottom: 1px solid #EAEAEC;
|
border-bottom: 1px solid #EAEAEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_heading p {
|
.purchase_heading p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #85878E;
|
color: #85878E;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_footer {
|
.purchase_footer {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
border-top: 1px solid #EAEAEC;
|
border-top: 1px solid #EAEAEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_total {
|
.purchase_total {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_total--label {
|
.purchase_total--label {
|
||||||
padding: 0 15px 0 0;
|
padding: 0 15px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #F2F4F6;
|
background-color: #F2F4F6;
|
||||||
color: #51545E;
|
color: #51545E;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: #51545E;
|
color: #51545E;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-wrapper {
|
.email-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -303,7 +303,7 @@
|
|||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
background-color: #F2F4F6;
|
background-color: #F2F4F6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-content {
|
.email-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -313,16 +313,16 @@
|
|||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
}
|
}
|
||||||
/* Masthead ----------------------- */
|
/* Masthead ----------------------- */
|
||||||
|
|
||||||
.email-masthead {
|
.email-masthead {
|
||||||
padding: 25px 0;
|
padding: 25px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-masthead_logo {
|
.email-masthead_logo {
|
||||||
width: 94px;
|
width: 94px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-masthead_name {
|
.email-masthead_name {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -331,7 +331,7 @@
|
|||||||
text-shadow: 0 1px 0 white;
|
text-shadow: 0 1px 0 white;
|
||||||
}
|
}
|
||||||
/* Body ------------------------------ */
|
/* Body ------------------------------ */
|
||||||
|
|
||||||
.email-body {
|
.email-body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -340,7 +340,7 @@
|
|||||||
-premailer-cellpadding: 0;
|
-premailer-cellpadding: 0;
|
||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-body_inner {
|
.email-body_inner {
|
||||||
width: 570px;
|
width: 570px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -350,7 +350,7 @@
|
|||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-footer {
|
.email-footer {
|
||||||
width: 570px;
|
width: 570px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -360,11 +360,11 @@
|
|||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-footer p {
|
.email-footer p {
|
||||||
color: #A8AAAF;
|
color: #A8AAAF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body-action {
|
.body-action {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 30px auto;
|
margin: 30px auto;
|
||||||
@@ -374,25 +374,25 @@
|
|||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body-sub {
|
.body-sub {
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
padding-top: 25px;
|
padding-top: 25px;
|
||||||
border-top: 1px solid #EAEAEC;
|
border-top: 1px solid #EAEAEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-cell {
|
.content-cell {
|
||||||
padding: 45px;
|
padding: 45px;
|
||||||
}
|
}
|
||||||
/*Media Queries ------------------------------ */
|
/*Media Queries ------------------------------ */
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
@media only screen and (max-width: 600px) {
|
||||||
.email-body_inner,
|
.email-body_inner,
|
||||||
.email-footer {
|
.email-footer {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body,
|
body,
|
||||||
.email-body,
|
.email-body,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { GraphQLSchemaHost } from '@nestjs/graphql';
|
import { GraphQLSchemaHost } from '@nestjs/graphql';
|
||||||
import {
|
import {
|
||||||
ApolloServerPlugin,
|
ApolloServerPlugin,
|
||||||
|
BaseContext,
|
||||||
GraphQLRequestListener,
|
GraphQLRequestListener,
|
||||||
} from 'apollo-server-plugin-base';
|
} from '@apollo/server';
|
||||||
import { Plugin } from '@nestjs/apollo';
|
import { Plugin } from '@nestjs/apollo';
|
||||||
import { GraphQLError } from 'graphql';
|
import { GraphQLError } from 'graphql';
|
||||||
import {
|
import {
|
||||||
@@ -17,7 +18,7 @@ const COMPLEXITY_LIMIT = 50;
|
|||||||
export class GQLComplexityPlugin implements ApolloServerPlugin {
|
export class GQLComplexityPlugin implements ApolloServerPlugin {
|
||||||
constructor(private gqlSchemaHost: GraphQLSchemaHost) {}
|
constructor(private gqlSchemaHost: GraphQLSchemaHost) {}
|
||||||
|
|
||||||
async requestDidStart(): Promise<GraphQLRequestListener> {
|
async requestDidStart(): Promise<GraphQLRequestListener<BaseContext>> {
|
||||||
const { schema } = this.gqlSchemaHost;
|
const { schema } = this.gqlSchemaHost;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Team, TeamCollection as DBTeamCollection } from '@prisma/client';
|
import { Team, TeamCollection as DBTeamCollection } from '@prisma/client';
|
||||||
import { mock, mockDeep, mockReset } from 'jest-mock-extended';
|
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||||
import {
|
import {
|
||||||
TEAM_COLL_DEST_SAME,
|
TEAM_COLL_DEST_SAME,
|
||||||
TEAM_COLL_INVALID_JSON,
|
TEAM_COLL_INVALID_JSON,
|
||||||
@@ -17,9 +17,6 @@ import { PrismaService } from 'src/prisma/prisma.service';
|
|||||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||||
import { AuthUser } from 'src/types/AuthUser';
|
import { AuthUser } from 'src/types/AuthUser';
|
||||||
import { TeamCollectionService } from './team-collection.service';
|
import { TeamCollectionService } from './team-collection.service';
|
||||||
import { TeamCollection } from './team-collection.model';
|
|
||||||
import { TeamCollectionModule } from './team-collection.module';
|
|
||||||
import * as E from 'fp-ts/Either';
|
|
||||||
|
|
||||||
const mockPrisma = mockDeep<PrismaService>();
|
const mockPrisma = mockDeep<PrismaService>();
|
||||||
const mockPubSub = mockDeep<PubSubService>();
|
const mockPubSub = mockDeep<PubSubService>();
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
|
|
||||||
describe('createDuplicateEnvironment', () => {
|
describe('createDuplicateEnvironment', () => {
|
||||||
test('should successfully duplicate an existing team environment', async () => {
|
test('should successfully duplicate an existing team environment', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce(
|
||||||
teamEnvironment,
|
teamEnvironment,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -322,7 +322,9 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirst.mockRejectedValue('NotFoundError');
|
mockPrisma.teamEnvironment.findFirstOrThrow.mockRejectedValue(
|
||||||
|
'NotFoundError',
|
||||||
|
);
|
||||||
|
|
||||||
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
@@ -332,7 +334,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should send pubsub message to "team_environment/<teamID>/created" if team environment is updated successfully', async () => {
|
test('should send pubsub message to "team_environment/<teamID>/created" if team environment is updated successfully', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce(
|
||||||
teamEnvironment,
|
teamEnvironment,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -183,11 +183,10 @@ export class TeamEnvironmentsService {
|
|||||||
*/
|
*/
|
||||||
async createDuplicateEnvironment(id: string) {
|
async createDuplicateEnvironment(id: string) {
|
||||||
try {
|
try {
|
||||||
const environment = await this.prisma.teamEnvironment.findFirst({
|
const environment = await this.prisma.teamEnvironment.findFirstOrThrow({
|
||||||
where: {
|
where: {
|
||||||
id: id,
|
id: id,
|
||||||
},
|
},
|
||||||
rejectOnNotFound: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await this.prisma.teamEnvironment.create({
|
const result = await this.prisma.teamEnvironment.create({
|
||||||
|
|||||||
@@ -142,13 +142,15 @@ describe('UserHistoryService', () => {
|
|||||||
});
|
});
|
||||||
describe('createUserHistory', () => {
|
describe('createUserHistory', () => {
|
||||||
test('Should resolve right and create a REST request to users history and return a `UserHistory` object', async () => {
|
test('Should resolve right and create a REST request to users history and return a `UserHistory` object', async () => {
|
||||||
|
const executedOn = new Date();
|
||||||
|
|
||||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||||
userUid: 'abc',
|
userUid: 'abc',
|
||||||
id: '1',
|
id: '1',
|
||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -158,7 +160,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: JSON.stringify([{}]),
|
request: JSON.stringify([{}]),
|
||||||
responseMetadata: JSON.stringify([{}]),
|
responseMetadata: JSON.stringify([{}]),
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -172,13 +174,15 @@ describe('UserHistoryService', () => {
|
|||||||
).toEqualRight(userHistory);
|
).toEqualRight(userHistory);
|
||||||
});
|
});
|
||||||
test('Should resolve right and create a GQL request to users history and return a `UserHistory` object', async () => {
|
test('Should resolve right and create a GQL request to users history and return a `UserHistory` object', async () => {
|
||||||
|
const executedOn = new Date();
|
||||||
|
|
||||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||||
userUid: 'abc',
|
userUid: 'abc',
|
||||||
id: '1',
|
id: '1',
|
||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.GQL,
|
reqType: ReqType.GQL,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -188,7 +192,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: JSON.stringify([{}]),
|
request: JSON.stringify([{}]),
|
||||||
responseMetadata: JSON.stringify([{}]),
|
responseMetadata: JSON.stringify([{}]),
|
||||||
reqType: ReqType.GQL,
|
reqType: ReqType.GQL,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -212,13 +216,15 @@ describe('UserHistoryService', () => {
|
|||||||
).toEqualLeft(USER_HISTORY_INVALID_REQ_TYPE);
|
).toEqualLeft(USER_HISTORY_INVALID_REQ_TYPE);
|
||||||
});
|
});
|
||||||
test('Should create a GQL request to users history and publish a created subscription', async () => {
|
test('Should create a GQL request to users history and publish a created subscription', async () => {
|
||||||
|
const executedOn = new Date();
|
||||||
|
|
||||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||||
userUid: 'abc',
|
userUid: 'abc',
|
||||||
id: '1',
|
id: '1',
|
||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.GQL,
|
reqType: ReqType.GQL,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -228,7 +234,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: JSON.stringify([{}]),
|
request: JSON.stringify([{}]),
|
||||||
responseMetadata: JSON.stringify([{}]),
|
responseMetadata: JSON.stringify([{}]),
|
||||||
reqType: ReqType.GQL,
|
reqType: ReqType.GQL,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -245,13 +251,15 @@ describe('UserHistoryService', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('Should create a REST request to users history and publish a created subscription', async () => {
|
test('Should create a REST request to users history and publish a created subscription', async () => {
|
||||||
|
const executedOn = new Date();
|
||||||
|
|
||||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||||
userUid: 'abc',
|
userUid: 'abc',
|
||||||
id: '1',
|
id: '1',
|
||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -261,7 +269,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: JSON.stringify([{}]),
|
request: JSON.stringify([{}]),
|
||||||
responseMetadata: JSON.stringify([{}]),
|
responseMetadata: JSON.stringify([{}]),
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -323,13 +331,15 @@ describe('UserHistoryService', () => {
|
|||||||
).toEqualLeft(USER_HISTORY_NOT_FOUND);
|
).toEqualLeft(USER_HISTORY_NOT_FOUND);
|
||||||
});
|
});
|
||||||
test('Should star/unstar a request in the history and publish a updated subscription', async () => {
|
test('Should star/unstar a request in the history and publish a updated subscription', async () => {
|
||||||
|
const executedOn = new Date();
|
||||||
|
|
||||||
mockPrisma.userHistory.findFirst.mockResolvedValueOnce({
|
mockPrisma.userHistory.findFirst.mockResolvedValueOnce({
|
||||||
userUid: 'abc',
|
userUid: 'abc',
|
||||||
id: '1',
|
id: '1',
|
||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -339,7 +349,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: true,
|
isStarred: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -349,7 +359,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: JSON.stringify([{}]),
|
request: JSON.stringify([{}]),
|
||||||
responseMetadata: JSON.stringify([{}]),
|
responseMetadata: JSON.stringify([{}]),
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: true,
|
isStarred: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/cli",
|
"name": "@hoppscotch/cli",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
||||||
"homepage": "https://hoppscotch.io",
|
"homepage": "https://hoppscotch.io",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -40,9 +40,6 @@
|
|||||||
"@hoppscotch/js-sandbox": "workspace:^",
|
"@hoppscotch/js-sandbox": "workspace:^",
|
||||||
"@relmify/jest-fp-ts": "^2.0.2",
|
"@relmify/jest-fp-ts": "^2.0.2",
|
||||||
"@swc/core": "^1.2.181",
|
"@swc/core": "^1.2.181",
|
||||||
"@types/axios": "^0.14.0",
|
|
||||||
"@types/chalk": "^2.2.0",
|
|
||||||
"@types/commander": "^2.12.2",
|
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^27.4.1",
|
||||||
"@types/lodash": "^4.14.181",
|
"@types/lodash": "^4.14.181",
|
||||||
"@types/qs": "^6.9.7",
|
"@types/qs": "^6.9.7",
|
||||||
@@ -58,6 +55,7 @@
|
|||||||
"qs": "^6.10.3",
|
"qs": "^6.10.3",
|
||||||
"ts-jest": "^27.1.4",
|
"ts-jest": "^27.1.4",
|
||||||
"tsup": "^5.12.7",
|
"tsup": "^5.12.7",
|
||||||
"typescript": "^4.6.4"
|
"typescript": "^4.6.4",
|
||||||
|
"zod": "^3.22.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
|
|||||||
ERROR_MSG = `Unavailable command: ${error.command}`;
|
ERROR_MSG = `Unavailable command: ${error.command}`;
|
||||||
break;
|
break;
|
||||||
case "MALFORMED_ENV_FILE":
|
case "MALFORMED_ENV_FILE":
|
||||||
|
ERROR_MSG = `The environment file is not of the correct format.`;
|
||||||
|
break;
|
||||||
|
case "BULK_ENV_FILE":
|
||||||
|
ERROR_MSG = `CLI doesn't support bulk environments export.`;
|
||||||
|
break;
|
||||||
case "MALFORMED_COLLECTION":
|
case "MALFORMED_COLLECTION":
|
||||||
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
|
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
|
||||||
break;
|
break;
|
||||||
@@ -82,4 +87,4 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
|
|||||||
if (!S.isEmpty(ERROR_MSG)) {
|
if (!S.isEmpty(ERROR_MSG)) {
|
||||||
console.error(ERROR_CODE, ERROR_MSG);
|
console.error(ERROR_CODE, ERROR_MSG);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,27 +1,45 @@
|
|||||||
import { error } from "../../types/errors";
|
import { error } from "../../types/errors";
|
||||||
import { HoppEnvs, HoppEnvPair } from "../../types/request";
|
import {
|
||||||
|
HoppEnvs,
|
||||||
|
HoppEnvPair,
|
||||||
|
HoppEnvKeyPairObject,
|
||||||
|
HoppEnvExportObject,
|
||||||
|
HoppBulkEnvExportObject,
|
||||||
|
} from "../../types/request";
|
||||||
import { readJsonFile } from "../../utils/mutators";
|
import { readJsonFile } from "../../utils/mutators";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses env json file for given path and validates the parsed env json object.
|
* Parses env json file for given path and validates the parsed env json object.
|
||||||
* @param path Path of env.json file to be parsed.
|
* @param path Path of env.json file to be parsed.
|
||||||
* @returns For successful parsing we get HoppEnvs object.
|
* @returns For successful parsing we get HoppEnvs object.
|
||||||
*/
|
*/
|
||||||
export async function parseEnvsData(path: string) {
|
export async function parseEnvsData(path: string) {
|
||||||
const contents = await readJsonFile(path)
|
const contents = await readJsonFile(path);
|
||||||
|
const envPairs: Array<HoppEnvPair> = [];
|
||||||
|
const HoppEnvKeyPairResult = HoppEnvKeyPairObject.safeParse(contents);
|
||||||
|
const HoppEnvExportObjectResult = HoppEnvExportObject.safeParse(contents);
|
||||||
|
const HoppBulkEnvExportObjectResult =
|
||||||
|
HoppBulkEnvExportObject.safeParse(contents);
|
||||||
|
|
||||||
if(!(contents && typeof contents === "object" && !Array.isArray(contents))) {
|
// CLI doesnt support bulk environments export.
|
||||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: null })
|
// Hence we check for this case and throw an error if it matches the format.
|
||||||
|
if (HoppBulkEnvExportObjectResult.success) {
|
||||||
|
throw error({ code: "BULK_ENV_FILE", path, data: error });
|
||||||
}
|
}
|
||||||
|
|
||||||
const envPairs: Array<HoppEnvPair> = []
|
// Checks if the environment file is of the correct format.
|
||||||
|
// If it doesnt match either of them, we throw an error.
|
||||||
|
if (!(HoppEnvKeyPairResult.success || HoppEnvExportObjectResult.success)) {
|
||||||
|
throw error({ code: "MALFORMED_ENV_FILE", path, data: error });
|
||||||
|
}
|
||||||
|
|
||||||
for( const [key,value] of Object.entries(contents)) {
|
if (HoppEnvKeyPairResult.success) {
|
||||||
if(typeof value !== "string") {
|
for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) {
|
||||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: {value: value} })
|
envPairs.push({ key, value });
|
||||||
}
|
}
|
||||||
|
} else if (HoppEnvExportObjectResult.success) {
|
||||||
envPairs.push({key, value})
|
const { key, value } = HoppEnvExportObjectResult.data.variables[0];
|
||||||
|
envPairs.push({ key, value });
|
||||||
}
|
}
|
||||||
return <HoppEnvs>{ global: [], selected: envPairs }
|
|
||||||
|
return <HoppEnvs>{ global: [], selected: envPairs };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type HoppErrors = {
|
|||||||
REQUEST_ERROR: HoppErrorData;
|
REQUEST_ERROR: HoppErrorData;
|
||||||
INVALID_ARGUMENT: HoppErrorData;
|
INVALID_ARGUMENT: HoppErrorData;
|
||||||
MALFORMED_ENV_FILE: HoppErrorPath & HoppErrorData;
|
MALFORMED_ENV_FILE: HoppErrorPath & HoppErrorData;
|
||||||
|
BULK_ENV_FILE: HoppErrorPath & HoppErrorData;
|
||||||
INVALID_FILE_TYPE: HoppErrorData;
|
INVALID_FILE_TYPE: HoppErrorData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||||
import { TestReport } from "../interfaces/response";
|
import { TestReport } from "../interfaces/response";
|
||||||
import { HoppCLIError } from "./errors";
|
import { HoppCLIError } from "./errors";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export type FormDataEntry = {
|
export type FormDataEntry = {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -9,6 +10,22 @@ export type FormDataEntry = {
|
|||||||
|
|
||||||
export type HoppEnvPair = { key: string; value: string };
|
export type HoppEnvPair = { key: string; value: string };
|
||||||
|
|
||||||
|
export const HoppEnvKeyPairObject = z.record(z.string(), z.string());
|
||||||
|
|
||||||
|
// Shape of the single environment export object that is exported from the app.
|
||||||
|
export const HoppEnvExportObject = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
variables: z.array(
|
||||||
|
z.object({
|
||||||
|
key: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shape of the bulk environment export object that is exported from the app.
|
||||||
|
export const HoppBulkEnvExportObject = z.array(HoppEnvExportObject);
|
||||||
|
|
||||||
export type HoppEnvs = {
|
export type HoppEnvs = {
|
||||||
global: HoppEnvPair[];
|
global: HoppEnvPair[];
|
||||||
selected: HoppEnvPair[];
|
selected: HoppEnvPair[];
|
||||||
|
|||||||
@@ -137,10 +137,10 @@ a {
|
|||||||
|
|
||||||
.cm-tooltip {
|
.cm-tooltip {
|
||||||
.tippy-box {
|
.tippy-box {
|
||||||
@apply shadow-none;
|
@apply shadow-none #{!important};
|
||||||
@apply fixed;
|
@apply fixed;
|
||||||
@apply inline-flex;
|
@apply inline-flex;
|
||||||
@apply -mt-8;
|
@apply -mt-7.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,7 @@
|
|||||||
},
|
},
|
||||||
"authorization": {
|
"authorization": {
|
||||||
"generate_token": "Generate Token",
|
"generate_token": "Generate Token",
|
||||||
|
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init",
|
||||||
"include_in_url": "Include in URL",
|
"include_in_url": "Include in URL",
|
||||||
"learn": "Learn how",
|
"learn": "Learn how",
|
||||||
"pass_key_by": "Pass by",
|
"pass_key_by": "Pass by",
|
||||||
@@ -124,6 +125,7 @@
|
|||||||
"created": "Collection created",
|
"created": "Collection created",
|
||||||
"different_parent": "Cannot reorder collection with different parent",
|
"different_parent": "Cannot reorder collection with different parent",
|
||||||
"edit": "Edit Collection",
|
"edit": "Edit Collection",
|
||||||
|
"import_or_create": "Import or create a collection",
|
||||||
"invalid_name": "Please provide a name for the collection",
|
"invalid_name": "Please provide a name for the collection",
|
||||||
"invalid_root_move": "Collection already in the root",
|
"invalid_root_move": "Collection already in the root",
|
||||||
"moved": "Moved Successfully",
|
"moved": "Moved Successfully",
|
||||||
@@ -209,6 +211,7 @@
|
|||||||
"empty_variables": "No variables",
|
"empty_variables": "No variables",
|
||||||
"global": "Global",
|
"global": "Global",
|
||||||
"global_variables": "Global variables",
|
"global_variables": "Global variables",
|
||||||
|
"import_or_create": "Import or create a environment",
|
||||||
"invalid_name": "Please provide a name for the environment",
|
"invalid_name": "Please provide a name for the environment",
|
||||||
"list": "Environment variables",
|
"list": "Environment variables",
|
||||||
"my_environments": "My Environments",
|
"my_environments": "My Environments",
|
||||||
@@ -249,7 +252,9 @@
|
|||||||
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
|
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
|
||||||
"network_error": "There seems to be a network error. Please try again.",
|
"network_error": "There seems to be a network error. Please try again.",
|
||||||
"network_fail": "Could not send request",
|
"network_fail": "Could not send request",
|
||||||
|
"no_collections_to_export": "No collections to export. Please create a collection to get started.",
|
||||||
"no_duration": "No duration",
|
"no_duration": "No duration",
|
||||||
|
"no_environments_to_export": "No environments to export. Please create an environment to get started.",
|
||||||
"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",
|
"proxy_error": "Proxy error",
|
||||||
@@ -456,6 +461,7 @@
|
|||||||
"enter_curl": "Enter cURL command",
|
"enter_curl": "Enter cURL command",
|
||||||
"generate_code": "Generate code",
|
"generate_code": "Generate code",
|
||||||
"generated_code": "Generated code",
|
"generated_code": "Generated code",
|
||||||
|
"go_to_authorization_tab": "Go to Authorization",
|
||||||
"header_list": "Header List",
|
"header_list": "Header List",
|
||||||
"invalid_name": "Please provide a name for the request",
|
"invalid_name": "Please provide a name for the request",
|
||||||
"method": "Method",
|
"method": "Method",
|
||||||
@@ -743,9 +749,11 @@
|
|||||||
"disconnected_from": "Disconnected from {name}",
|
"disconnected_from": "Disconnected from {name}",
|
||||||
"docs_generated": "Documentation generated",
|
"docs_generated": "Documentation generated",
|
||||||
"download_started": "Download started",
|
"download_started": "Download started",
|
||||||
|
"download_failed": "Download failed",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"file_imported": "File imported",
|
"file_imported": "File imported",
|
||||||
"finished_in": "Finished in {duration} ms",
|
"finished_in": "Finished in {duration} ms",
|
||||||
|
"hide": "Hide",
|
||||||
"history_deleted": "History deleted",
|
"history_deleted": "History deleted",
|
||||||
"linewrap": "Wrap lines",
|
"linewrap": "Wrap lines",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
@@ -756,6 +764,7 @@
|
|||||||
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||||
"published_message": "Published message: {message} to topic: {topic}",
|
"published_message": "Published message: {message} to topic: {topic}",
|
||||||
"reconnection_error": "Failed to reconnect",
|
"reconnection_error": "Failed to reconnect",
|
||||||
|
"show":"Show",
|
||||||
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
||||||
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
||||||
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
||||||
@@ -837,7 +846,7 @@
|
|||||||
"new": "New Team",
|
"new": "New Team",
|
||||||
"new_created": "New team created",
|
"new_created": "New team created",
|
||||||
"new_name": "My New Team",
|
"new_name": "My New Team",
|
||||||
"no_access": "You do not have edit access to these collections",
|
"no_access": "You do not have edit access to this team",
|
||||||
"no_invite_found": "Invitation not found. Contact your team owner.",
|
"no_invite_found": "Invitation not found. Contact your team owner.",
|
||||||
"no_request_found": "Request not found.",
|
"no_request_found": "Request not found.",
|
||||||
"not_found": "Team not found. Contact your team owner.",
|
"not_found": "Team not found. Contact your team owner.",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"choose_file": "選擇一個檔案",
|
"choose_file": "選擇一個檔案",
|
||||||
"clear": "清除",
|
"clear": "清除",
|
||||||
"clear_all": "全部清除",
|
"clear_all": "全部清除",
|
||||||
"clear_history": "Clear all History",
|
"clear_history": "清除所有歷史記錄",
|
||||||
"close": "關閉",
|
"close": "關閉",
|
||||||
"connect": "連線",
|
"connect": "連線",
|
||||||
"connecting": "正在連接",
|
"connecting": "正在連接",
|
||||||
@@ -79,8 +79,8 @@
|
|||||||
"search": "搜尋",
|
"search": "搜尋",
|
||||||
"share": "分享",
|
"share": "分享",
|
||||||
"shortcuts": "快捷方式",
|
"shortcuts": "快捷方式",
|
||||||
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
|
"social_description": "在社交媒體上追蹤我們即可在第一時間得知新聞、更新、以及新版本的消息。",
|
||||||
"social_links": "Social links",
|
"social_links": "社群連結",
|
||||||
"spotlight": "聚光燈",
|
"spotlight": "聚光燈",
|
||||||
"status": "狀態",
|
"status": "狀態",
|
||||||
"status_description": "檢查網站狀態",
|
"status_description": "檢查網站狀態",
|
||||||
@@ -135,15 +135,15 @@
|
|||||||
"renamed": "集合已重新命名",
|
"renamed": "集合已重新命名",
|
||||||
"request_in_use": "請求正在使用中",
|
"request_in_use": "請求正在使用中",
|
||||||
"save_as": "另存為",
|
"save_as": "另存為",
|
||||||
"save_to_collection": "Save to Collection",
|
"save_to_collection": "儲存到集合",
|
||||||
"select": "選擇一個集合",
|
"select": "選擇一個集合",
|
||||||
"select_location": "選擇位置",
|
"select_location": "選擇位置",
|
||||||
"select_team": "選擇一個團隊",
|
"select_team": "選擇一個團隊",
|
||||||
"team_collections": "團隊集合"
|
"team_collections": "團隊集合"
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"close_unsaved_tab": "Are you sure you want to close this tab?",
|
"close_unsaved_tab": "您確定要關閉此分頁嗎?",
|
||||||
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
|
"close_unsaved_tabs": "您確定要關閉所有分頁嗎?{count} 個未儲存的分頁將會遺失。",
|
||||||
"exit_team": "您確定要離開此團隊嗎?",
|
"exit_team": "您確定要離開此團隊嗎?",
|
||||||
"logout": "您確定要登出嗎?",
|
"logout": "您確定要登出嗎?",
|
||||||
"remove_collection": "您確定要永久刪除該集合嗎?",
|
"remove_collection": "您確定要永久刪除該集合嗎?",
|
||||||
@@ -158,9 +158,9 @@
|
|||||||
"sync": "您想從雲端恢復您的工作區嗎?這將丟棄您的本地進度。"
|
"sync": "您想從雲端恢復您的工作區嗎?這將丟棄您的本地進度。"
|
||||||
},
|
},
|
||||||
"context_menu": {
|
"context_menu": {
|
||||||
"add_parameters": "Add to parameters",
|
"add_parameters": "新增至參數",
|
||||||
"open_request_in_new_tab": "Open request in new tab",
|
"open_request_in_new_tab": "在新分頁開啟請求",
|
||||||
"set_environment_variable": "Set as variable"
|
"set_environment_variable": "設為變數"
|
||||||
},
|
},
|
||||||
"count": {
|
"count": {
|
||||||
"header": "請求標頭 {count}",
|
"header": "請求標頭 {count}",
|
||||||
@@ -204,31 +204,31 @@
|
|||||||
"create_new": "建立新環境",
|
"create_new": "建立新環境",
|
||||||
"created": "已建立環境",
|
"created": "已建立環境",
|
||||||
"deleted": "刪除環境",
|
"deleted": "刪除環境",
|
||||||
"duplicated": "Environment duplicated",
|
"duplicated": "已複製環境",
|
||||||
"edit": "編輯環境",
|
"edit": "編輯環境",
|
||||||
"empty_variables": "No variables",
|
"empty_variables": "無變數",
|
||||||
"global": "Global",
|
"global": "全域",
|
||||||
"global_variables": "Global variables",
|
"global_variables": "全域變數",
|
||||||
"invalid_name": "請提供有效的環境名稱",
|
"invalid_name": "請提供有效的環境名稱",
|
||||||
"list": "Environment variables",
|
"list": "環境變數",
|
||||||
"my_environments": "我的環境",
|
"my_environments": "我的環境",
|
||||||
"name": "Name",
|
"name": "名稱",
|
||||||
"nested_overflow": "巢狀環境變數不得大於 10 層",
|
"nested_overflow": "巢狀環境變數不得大於 10 層",
|
||||||
"new": "建立環境",
|
"new": "建立環境",
|
||||||
"no_active_environment": "No active environment",
|
"no_active_environment": "無使用中的環境",
|
||||||
"no_environment": "無環境",
|
"no_environment": "無環境",
|
||||||
"no_environment_description": "未選取任何環境。請選擇要對以下變數進行的動作。",
|
"no_environment_description": "未選取任何環境。請選擇要對以下變數進行的動作。",
|
||||||
"quick_peek": "Environment Quick Peek",
|
"quick_peek": "快速預覽環境",
|
||||||
"replace_with_variable": "Replace with variable",
|
"replace_with_variable": "以變數替代",
|
||||||
"scope": "Scope",
|
"scope": "範圍",
|
||||||
"select": "選擇環境",
|
"select": "選擇環境",
|
||||||
"set": "Set environment",
|
"set": "設定環境",
|
||||||
"set_as_environment": "Set as environment",
|
"set_as_environment": "設為環境",
|
||||||
"team_environments": "團隊環境",
|
"team_environments": "團隊環境",
|
||||||
"title": "環境",
|
"title": "環境",
|
||||||
"updated": "更新環境",
|
"updated": "更新環境",
|
||||||
"value": "Value",
|
"value": "數值",
|
||||||
"variable": "Variable",
|
"variable": "變數",
|
||||||
"variable_list": "變數列表"
|
"variable_list": "變數列表"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
"no_duration": "無持續時間",
|
"no_duration": "無持續時間",
|
||||||
"no_results_found": "找不到結果",
|
"no_results_found": "找不到結果",
|
||||||
"page_not_found": "找不到此頁面",
|
"page_not_found": "找不到此頁面",
|
||||||
"proxy_error": "Proxy error",
|
"proxy_error": "Proxy 錯誤",
|
||||||
"script_fail": "無法執行預請求指令碼",
|
"script_fail": "無法執行預請求指令碼",
|
||||||
"something_went_wrong": "發生了一些錯誤",
|
"something_went_wrong": "發生了一些錯誤",
|
||||||
"test_script_fail": "無法執行測試指令碼"
|
"test_script_fail": "無法執行測試指令碼"
|
||||||
@@ -278,13 +278,13 @@
|
|||||||
"renamed": "資料夾已重新命名"
|
"renamed": "資料夾已重新命名"
|
||||||
},
|
},
|
||||||
"graphql": {
|
"graphql": {
|
||||||
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
|
"connection_switch_confirm": "您要使用最新的 GraphQL 端點連線嗎?",
|
||||||
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
|
"connection_switch_new_url": "切換至分頁將斷開使用中的 GraphQL 連線。新的連線網址為 ",
|
||||||
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
|
"connection_switch_url": "您已連接至 GraphQL 端點。連線網址為 ",
|
||||||
"mutations": "變體",
|
"mutations": "變體",
|
||||||
"schema": "綱要",
|
"schema": "綱要",
|
||||||
"subscriptions": "訂閱",
|
"subscriptions": "訂閱",
|
||||||
"switch_connection": "Switch connection"
|
"switch_connection": "切換連線"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"time": "時間",
|
"time": "時間",
|
||||||
@@ -339,27 +339,27 @@
|
|||||||
"title": "匯入"
|
"title": "匯入"
|
||||||
},
|
},
|
||||||
"inspections": {
|
"inspections": {
|
||||||
"description": "Inspect possible errors",
|
"description": "檢查潛在錯誤",
|
||||||
"environment": {
|
"environment": {
|
||||||
"add_environment": "Add to Environment",
|
"add_environment": "新增至環境",
|
||||||
"not_found": "Environment variable “{environment}” not found."
|
"not_found": "找不到環境變數 “{environment}”。"
|
||||||
},
|
},
|
||||||
"header": {
|
"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."
|
"cookie": "瀏覽器不允許 Hoppscotch 設定 Cookie 標頭。在我們推出 Hoppscotch 桌面版前,請先使用 Authorization 標頭。"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"401_error": "Please check your authentication credentials.",
|
"401_error": "請檢查您的授權認證。",
|
||||||
"404_error": "Please check your request URL and method type.",
|
"404_error": "請檢查您的請求網址和方式類型。",
|
||||||
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
|
"cors_error": "請檢查您的跨來源資源共用設定。",
|
||||||
"default_error": "Please check your request.",
|
"default_error": "請檢查您的請求。",
|
||||||
"network_error": "Please check your network connection."
|
"network_error": "請檢查您的網路連線。"
|
||||||
},
|
},
|
||||||
"title": "Inspector",
|
"title": "檢查工具",
|
||||||
"url": {
|
"url": {
|
||||||
"extension_not_installed": "Extension not installed.",
|
"extension_not_installed": "未安裝擴充套件。",
|
||||||
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
|
"extension_unknown_origin": "請確認您是否已將 API 端點的來源加入 Hoppscotch 擴充套件的清單。",
|
||||||
"extention_enable_action": "Enable Browser Extension",
|
"extention_enable_action": "啟用瀏覽器擴充套件",
|
||||||
"extention_not_enabled": "Extension not enabled."
|
"extention_not_enabled": "未啟用擴充套件。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
@@ -472,7 +472,7 @@
|
|||||||
"payload": "負載",
|
"payload": "負載",
|
||||||
"query": "查詢",
|
"query": "查詢",
|
||||||
"raw_body": "原始請求本體",
|
"raw_body": "原始請求本體",
|
||||||
"rename": "Rename Request",
|
"rename": "重新命名請求",
|
||||||
"renamed": "請求已重新命名",
|
"renamed": "請求已重新命名",
|
||||||
"run": "執行",
|
"run": "執行",
|
||||||
"save": "儲存",
|
"save": "儲存",
|
||||||
@@ -510,7 +510,7 @@
|
|||||||
"accent_color": "強調色",
|
"accent_color": "強調色",
|
||||||
"account": "帳號",
|
"account": "帳號",
|
||||||
"account_deleted": "已刪除您的帳號",
|
"account_deleted": "已刪除您的帳號",
|
||||||
"account_description": "自定義您的帳號設定。",
|
"account_description": "自訂您的帳號設定。",
|
||||||
"account_email_description": "您的主要電子郵件地址。",
|
"account_email_description": "您的主要電子郵件地址。",
|
||||||
"account_name_description": "這是您的顯示名稱。",
|
"account_name_description": "這是您的顯示名稱。",
|
||||||
"background": "背景",
|
"background": "背景",
|
||||||
@@ -542,7 +542,7 @@
|
|||||||
"read_the": "閱讀",
|
"read_the": "閱讀",
|
||||||
"reset_default": "重置為預設",
|
"reset_default": "重置為預設",
|
||||||
"short_codes": "快捷碼",
|
"short_codes": "快捷碼",
|
||||||
"short_codes_description": "我們為您打造的快捷碼。",
|
"short_codes_description": "您建立的快捷碼。",
|
||||||
"sidebar_on_left": "左側邊欄",
|
"sidebar_on_left": "左側邊欄",
|
||||||
"sync": "同步",
|
"sync": "同步",
|
||||||
"sync_collections": "集合",
|
"sync_collections": "集合",
|
||||||
@@ -551,9 +551,9 @@
|
|||||||
"sync_history": "歷史",
|
"sync_history": "歷史",
|
||||||
"system_mode": "系統",
|
"system_mode": "系統",
|
||||||
"telemetry": "遙測服務",
|
"telemetry": "遙測服務",
|
||||||
"telemetry_helps_us": "遙測服務幫助我們進行個性化操作,為您提供最佳體驗。",
|
"telemetry_helps_us": "遙測服務能夠幫助我們進行個人化操作,為您提供最佳體驗。",
|
||||||
"theme": "主題",
|
"theme": "主題",
|
||||||
"theme_description": "自定義您的應用程式主題。",
|
"theme_description": "自訂您的應用程式主題。",
|
||||||
"use_experimental_url_bar": "使用帶有環境醒目標示的實驗性網址欄",
|
"use_experimental_url_bar": "使用帶有環境醒目標示的實驗性網址欄",
|
||||||
"user": "使用者",
|
"user": "使用者",
|
||||||
"verified_email": "已確認電子郵件地址",
|
"verified_email": "已確認電子郵件地址",
|
||||||
@@ -592,26 +592,26 @@
|
|||||||
"title": "導航"
|
"title": "導航"
|
||||||
},
|
},
|
||||||
"others": {
|
"others": {
|
||||||
"prettify": "Prettify Editor's Content",
|
"prettify": "美化編輯器的內容",
|
||||||
"title": "Others"
|
"title": "其他"
|
||||||
},
|
},
|
||||||
"request": {
|
"request": {
|
||||||
"copy_request_link": "複製請求連結",
|
"copy_request_link": "複製請求連結",
|
||||||
"delete_method": "選擇 DELETE 方法",
|
"delete_method": "選擇 DELETE 方法",
|
||||||
"get_method": "選擇 GET 方法",
|
"get_method": "選擇 GET 方法",
|
||||||
"head_method": "選擇 HEAD 方法",
|
"head_method": "選擇 HEAD 方法",
|
||||||
"import_curl": "Import cURL",
|
"import_curl": "匯入 cURL",
|
||||||
"method": "方法",
|
"method": "方法",
|
||||||
"next_method": "選擇下一個方法",
|
"next_method": "選擇下一個方法",
|
||||||
"post_method": "選擇 POST 方法",
|
"post_method": "選擇 POST 方法",
|
||||||
"previous_method": "選擇上一個方法",
|
"previous_method": "選擇上一個方法",
|
||||||
"put_method": "選擇 PUT 方法",
|
"put_method": "選擇 PUT 方法",
|
||||||
"rename": "Rename Request",
|
"rename": "重新命名請求",
|
||||||
"reset_request": "重置請求",
|
"reset_request": "重置請求",
|
||||||
"save_request": "Save Request",
|
"save_request": "儲存請求",
|
||||||
"save_to_collections": "儲存到集合",
|
"save_to_collections": "儲存到集合",
|
||||||
"send_request": "傳送請求",
|
"send_request": "傳送請求",
|
||||||
"show_code": "Generate code snippet",
|
"show_code": "產生程式碼片段",
|
||||||
"title": "請求"
|
"title": "請求"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
@@ -642,82 +642,82 @@
|
|||||||
"url": "網址"
|
"url": "網址"
|
||||||
},
|
},
|
||||||
"spotlight": {
|
"spotlight": {
|
||||||
"change_language": "Change Language",
|
"change_language": "變更語言",
|
||||||
"environments": {
|
"environments": {
|
||||||
"delete": "Delete current environment",
|
"delete": "刪除目前環境",
|
||||||
"duplicate": "Duplicate current environment",
|
"duplicate": "複製目前環境",
|
||||||
"duplicate_global": "Duplicate global environment",
|
"duplicate_global": "複製全域環境",
|
||||||
"edit": "Edit current environment",
|
"edit": "編輯目前環境",
|
||||||
"edit_global": "Edit global environment",
|
"edit_global": "編輯全域環境",
|
||||||
"new": "Create new environment",
|
"new": "建立新環境",
|
||||||
"new_variable": "Create a new environment variable",
|
"new_variable": "建立新環境變數",
|
||||||
"title": "Environments"
|
"title": "環境"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"chat": "Chat with support",
|
"chat": "與客服對話",
|
||||||
"help_menu": "Help and support",
|
"help_menu": "幫助與支援",
|
||||||
"open_docs": "Read Documentation",
|
"open_docs": "閱讀說明文件",
|
||||||
"open_github": "Open GitHub repository",
|
"open_github": "開啟 GitHub 儲存庫",
|
||||||
"open_keybindings": "Keyboard shortcuts",
|
"open_keybindings": "鍵盤快捷鍵",
|
||||||
"social": "Social",
|
"social": "社交",
|
||||||
"title": "General"
|
"title": "一般"
|
||||||
},
|
},
|
||||||
"graphql": {
|
"graphql": {
|
||||||
"connect": "Connect to server",
|
"connect": "連接至伺服器",
|
||||||
"disconnect": "Disconnect from server"
|
"disconnect": "斷開與伺服器的連線"
|
||||||
},
|
},
|
||||||
"miscellaneous": {
|
"miscellaneous": {
|
||||||
"invite": "Invite your friends to Hoppscotch",
|
"invite": "邀請您的朋友使用 Hoppscotch",
|
||||||
"title": "Miscellaneous"
|
"title": "雜項"
|
||||||
},
|
},
|
||||||
"request": {
|
"request": {
|
||||||
"save_as_new": "Save as new request",
|
"save_as_new": "儲存為新請求",
|
||||||
"select_method": "Select method",
|
"select_method": "選擇方法",
|
||||||
"switch_to": "Switch to",
|
"switch_to": "切換至",
|
||||||
"tab_authorization": "Authorization tab",
|
"tab_authorization": "授權分頁",
|
||||||
"tab_body": "Body tab",
|
"tab_body": "本體分頁",
|
||||||
"tab_headers": "Headers tab",
|
"tab_headers": "標頭分頁",
|
||||||
"tab_parameters": "Parameters tab",
|
"tab_parameters": "參數分頁",
|
||||||
"tab_pre_request_script": "Pre-request script tab",
|
"tab_pre_request_script": "預請求腳本分頁",
|
||||||
"tab_query": "Query tab",
|
"tab_query": "查詢分頁",
|
||||||
"tab_tests": "Tests tab",
|
"tab_tests": "測試分頁",
|
||||||
"tab_variables": "Variables tab"
|
"tab_variables": "變數分頁"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"copy": "Copy response",
|
"copy": "複製回應",
|
||||||
"download": "Download response as file",
|
"download": "下載回應",
|
||||||
"title": "Response"
|
"title": "回應"
|
||||||
},
|
},
|
||||||
"section": {
|
"section": {
|
||||||
"interceptor": "Interceptor",
|
"interceptor": "攔截器",
|
||||||
"interface": "Interface",
|
"interface": "介面",
|
||||||
"theme": "Theme",
|
"theme": "主題",
|
||||||
"user": "User"
|
"user": "使用者"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"change_interceptor": "Change Interceptor",
|
"change_interceptor": "變更攔截器",
|
||||||
"change_language": "Change Language",
|
"change_language": "變更語言",
|
||||||
"theme": {
|
"theme": {
|
||||||
"black": "Black",
|
"black": "黑色",
|
||||||
"dark": "Dark",
|
"dark": "暗色",
|
||||||
"light": "Light",
|
"light": "亮色",
|
||||||
"system": "System preference"
|
"system": "跟隨系統"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tab": {
|
"tab": {
|
||||||
"close_current": "Close current tab",
|
"close_current": "關閉目前分頁",
|
||||||
"close_others": "Close all other tabs",
|
"close_others": "關閉所有其他分頁",
|
||||||
"duplicate": "Duplicate current tab",
|
"duplicate": "複製目前分頁",
|
||||||
"new_tab": "Open a new tab",
|
"new_tab": "開啟新分頁",
|
||||||
"title": "Tabs"
|
"title": "分頁"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"delete": "Delete current team",
|
"delete": "刪除目前團隊",
|
||||||
"edit": "Edit current team",
|
"edit": "編輯目前團隊",
|
||||||
"invite": "Invite people to team",
|
"invite": "邀請他人加入團隊",
|
||||||
"new": "Create new team",
|
"new": "建立新團隊",
|
||||||
"switch_to_personal": "Switch to your personal workspace",
|
"switch_to_personal": "切換至您的個人工作區",
|
||||||
"title": "Teams"
|
"title": "團隊"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sse": {
|
"sse": {
|
||||||
@@ -777,11 +777,11 @@
|
|||||||
"tab": {
|
"tab": {
|
||||||
"authorization": "授權",
|
"authorization": "授權",
|
||||||
"body": "請求本體",
|
"body": "請求本體",
|
||||||
"close": "Close Tab",
|
"close": "關閉分頁",
|
||||||
"close_others": "Close other Tabs",
|
"close_others": "關閉其他分頁",
|
||||||
"collections": "集合",
|
"collections": "集合",
|
||||||
"documentation": "幫助文件",
|
"documentation": "幫助文件",
|
||||||
"duplicate": "Duplicate Tab",
|
"duplicate": "複製分頁",
|
||||||
"environments": "環境",
|
"environments": "環境",
|
||||||
"headers": "請求標頭",
|
"headers": "請求標頭",
|
||||||
"history": "歷史記錄",
|
"history": "歷史記錄",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/common",
|
"name": "@hoppscotch/common",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.8.1",
|
"version": "2023.8.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
"test": "vitest --run",
|
"test": "vitest --run",
|
||||||
@@ -17,22 +17,22 @@
|
|||||||
"postinstall": "pnpm run gql-codegen",
|
"postinstall": "pnpm run gql-codegen",
|
||||||
"do-test": "pnpm run test",
|
"do-test": "pnpm run test",
|
||||||
"do-lint": "pnpm run prod-lint",
|
"do-lint": "pnpm run prod-lint",
|
||||||
"do-typecheck": "pnpm run lint",
|
"do-typecheck": "node type-check.mjs",
|
||||||
"do-lintfix": "pnpm run lintfix"
|
"do-lintfix": "pnpm run lintfix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "^10.1.0",
|
"@apidevtools/swagger-parser": "^10.1.0",
|
||||||
"@codemirror/autocomplete": "^6.9.0",
|
"@codemirror/autocomplete": "^6.10.2",
|
||||||
"@codemirror/commands": "^6.2.4",
|
"@codemirror/commands": "^6.3.0",
|
||||||
"@codemirror/lang-javascript": "^6.1.9",
|
"@codemirror/lang-javascript": "^6.2.1",
|
||||||
"@codemirror/lang-json": "^6.0.1",
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
"@codemirror/lang-xml": "^6.0.2",
|
"@codemirror/lang-xml": "^6.0.2",
|
||||||
"@codemirror/language": "^6.9.0",
|
"@codemirror/language": "^6.9.2",
|
||||||
"@codemirror/legacy-modes": "^6.3.3",
|
"@codemirror/legacy-modes": "^6.3.3",
|
||||||
"@codemirror/lint": "^6.4.0",
|
"@codemirror/lint": "^6.4.2",
|
||||||
"@codemirror/search": "^6.5.1",
|
"@codemirror/search": "^6.5.4",
|
||||||
"@codemirror/state": "^6.2.1",
|
"@codemirror/state": "^6.3.1",
|
||||||
"@codemirror/view": "^6.16.0",
|
"@codemirror/view": "^6.22.0",
|
||||||
"@fontsource-variable/inter": "^5.0.8",
|
"@fontsource-variable/inter": "^5.0.8",
|
||||||
"@fontsource-variable/material-symbols-rounded": "^5.0.7",
|
"@fontsource-variable/material-symbols-rounded": "^5.0.7",
|
||||||
"@fontsource-variable/roboto-mono": "^5.0.9",
|
"@fontsource-variable/roboto-mono": "^5.0.9",
|
||||||
@@ -41,9 +41,7 @@
|
|||||||
"@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.1.6",
|
"@lezer/highlight": "1.1.4",
|
||||||
"@sentry/tracing": "^7.64.0",
|
|
||||||
"@sentry/vue": "^7.64.0",
|
|
||||||
"@urql/core": "^4.1.1",
|
"@urql/core": "^4.1.1",
|
||||||
"@urql/devtools": "^2.0.3",
|
"@urql/devtools": "^2.0.3",
|
||||||
"@urql/exchange-auth": "^2.1.6",
|
"@urql/exchange-auth": "^2.1.6",
|
||||||
@@ -138,6 +136,7 @@
|
|||||||
"eslint": "^8.47.0",
|
"eslint": "^8.47.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
|
"glob": "^10.3.10",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
"rollup-plugin-polyfill-node": "^0.12.0",
|
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||||
|
|||||||
@@ -2,53 +2,16 @@
|
|||||||
<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" />
|
||||||
<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 { pipe } from "fp-ts/function"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
|
||||||
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
|
||||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
|
||||||
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 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
|
||||||
})
|
})
|
||||||
@@ -60,9 +23,4 @@ defineActionHandler("modals.share.toggle", () => {
|
|||||||
defineActionHandler("modals.login.toggle", () => {
|
defineActionHandler("modals.login.toggle", () => {
|
||||||
showLogin.value = !showLogin.value
|
showLogin.value = !showLogin.value
|
||||||
})
|
})
|
||||||
|
|
||||||
defineActionHandler("modals.team.delete", ({ teamId }) => {
|
|
||||||
teamID.value = teamId
|
|
||||||
confirmRemove.value = true
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -231,29 +231,39 @@
|
|||||||
@invite-team="inviteTeam(editingTeamName, editingTeamID)"
|
@invite-team="inviteTeam(editingTeamName, editingTeamID)"
|
||||||
@refetch-teams="refetchTeams"
|
@refetch-teams="refetchTeams"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<HoppSmartConfirmModal
|
||||||
|
:show="confirmRemove"
|
||||||
|
:title="t('confirm.remove_team')"
|
||||||
|
@hide-modal="confirmRemove = false"
|
||||||
|
@resolve="deleteTeam"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive, ref, watch } from "vue"
|
|
||||||
import IconUser from "~icons/lucide/user"
|
|
||||||
import IconUsers from "~icons/lucide/users"
|
|
||||||
import IconSettings from "~icons/lucide/settings"
|
|
||||||
import IconDownload from "~icons/lucide/download"
|
|
||||||
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
|
||||||
import IconUserPlus from "~icons/lucide/user-plus"
|
|
||||||
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
|
||||||
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
|
||||||
import { pwaDefferedPrompt, installPWA } from "@modules/pwa"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
|
||||||
import { useToast } from "~/composables/toast"
|
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
|
import { installPWA, pwaDefferedPrompt } from "@modules/pwa"
|
||||||
|
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
||||||
|
import { computed, reactive, ref, watch } from "vue"
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql"
|
||||||
|
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
import IconDownload from "~icons/lucide/download"
|
||||||
|
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
||||||
|
import IconSettings from "~icons/lucide/settings"
|
||||||
|
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
||||||
|
import IconUser from "~icons/lucide/user"
|
||||||
|
import IconUserPlus from "~icons/lucide/user-plus"
|
||||||
|
import IconUsers from "~icons/lucide/users"
|
||||||
|
import { pipe } from "fp-ts/function"
|
||||||
|
import * as TE from "fp-ts/TaskEither"
|
||||||
|
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -278,6 +288,9 @@ const currentUser = useReadonlyStream(
|
|||||||
platform.auth.getProbableUser()
|
platform.auth.getProbableUser()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const confirmRemove = ref(false)
|
||||||
|
const teamID = ref<string | null>(null)
|
||||||
|
|
||||||
const selectedTeam = ref<GetMyTeamsQuery["myTeams"][number] | undefined>()
|
const selectedTeam = ref<GetMyTeamsQuery["myTeams"][number] | undefined>()
|
||||||
|
|
||||||
// TeamList-Adapter
|
// TeamList-Adapter
|
||||||
@@ -377,6 +390,24 @@ const handleTeamEdit = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// Template refs
|
// Template refs
|
||||||
const tippyActions = ref<any | null>(null)
|
const tippyActions = ref<any | null>(null)
|
||||||
const profile = ref<any | null>(null)
|
const profile = ref<any | null>(null)
|
||||||
@@ -405,6 +436,12 @@ defineActionHandler(
|
|||||||
computed(() => !currentUser.value)
|
computed(() => !currentUser.value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
defineActionHandler("modals.team.delete", ({ teamId }) => {
|
||||||
|
if (selectedTeam.value?.myRole !== TeamMemberRole.Owner) return noPermission()
|
||||||
|
teamID.value = teamId
|
||||||
|
confirmRemove.value = true
|
||||||
|
})
|
||||||
|
|
||||||
const noPermission = () => {
|
const noPermission = () => {
|
||||||
toast.error(`${t("profile.no_permission")}`)
|
toast.error(`${t("profile.no_permission")}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,8 @@
|
|||||||
import { ref, watch } from "vue"
|
import { ref, watch } from "vue"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@@ -60,11 +61,12 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const editingName = ref("")
|
const editingName = ref("")
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
editingName.value = currentActiveTab.value.document.request.name
|
editingName.value = tabs.currentActiveTab.value.document.request.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="!saveRequest"
|
v-if="!saveRequest"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
@click="emit('display-modal-import-export')"
|
@click="emit('display-modal-import-export')"
|
||||||
/>
|
/>
|
||||||
@@ -257,12 +257,27 @@
|
|||||||
:alt="`${t('empty.collections')}`"
|
:alt="`${t('empty.collections')}`"
|
||||||
:text="t('empty.collections')"
|
:text="t('empty.collections')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div class="flex flex-col items-center space-y-4">
|
||||||
:label="t('add.new')"
|
<span class="text-secondaryLight text-center">
|
||||||
filled
|
{{ t("collection.import_or_create") }}
|
||||||
outline
|
</span>
|
||||||
@click="emit('display-modal-add')"
|
<div class="flex gap-4 flex-col items-stretch">
|
||||||
/>
|
<HoppButtonPrimary
|
||||||
|
:icon="IconImport"
|
||||||
|
:label="t('import.title')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="emit('display-modal-import-export')"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:icon="IconPlus"
|
||||||
|
:label="t('add.new')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="emit('display-modal-add')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-else-if="node.data.type === 'collections'"
|
v-else-if="node.data.type === 'collections'"
|
||||||
@@ -288,8 +303,7 @@
|
|||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
:alt="`${t('empty.folder')}`"
|
:alt="`${t('empty.folder')}`"
|
||||||
:text="t('empty.folder')"
|
:text="t('empty.folder')"
|
||||||
>
|
/>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartTree>
|
</HoppSmartTree>
|
||||||
</div>
|
</div>
|
||||||
@@ -297,9 +311,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import IconArchive from "~icons/lucide/archive"
|
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
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"
|
||||||
@@ -312,7 +326,8 @@ import { useColorMode } from "@composables/theming"
|
|||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked.js"
|
import { Picked } from "~/helpers/types/HoppPicked.js"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
export type Collection = {
|
export type Collection = {
|
||||||
type: "collections"
|
type: "collections"
|
||||||
@@ -520,7 +535,8 @@ const isSelected = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const active = computed(() => currentActiveTab.value.document.saveContext)
|
const tabs = useService(RESTTabService)
|
||||||
|
const active = computed(() => tabs.currentActiveTab.value.document.saveContext)
|
||||||
|
|
||||||
const isActiveRequest = (folderPath: string, requestIndex: number) => {
|
const isActiveRequest = (folderPath: string, requestIndex: number) => {
|
||||||
return pipe(
|
return pipe(
|
||||||
|
|||||||
@@ -82,12 +82,16 @@ import {
|
|||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { computedWithControl } from "@vueuse/core"
|
import { computedWithControl } from "@vueuse/core"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { currentActiveTab as activeRESTTab } from "~/helpers/rest/tab"
|
import { useService } from "dioc/vue"
|
||||||
import { currentActiveTab as activeGQLTab } from "~/helpers/graphql/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const RESTTabs = useService(RESTTabService)
|
||||||
|
const GQLTabs = useService(GQLTabService)
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
||||||
|
|
||||||
type CollectionType =
|
type CollectionType =
|
||||||
@@ -123,13 +127,13 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const gqlRequestName = computedWithControl(
|
const gqlRequestName = computedWithControl(
|
||||||
() => activeGQLTab.value,
|
() => GQLTabs.currentActiveTab.value,
|
||||||
() => activeGQLTab.value.document.request.name
|
() => GQLTabs.currentActiveTab.value.document.request.name
|
||||||
)
|
)
|
||||||
|
|
||||||
const restRequestName = computedWithControl(
|
const restRequestName = computedWithControl(
|
||||||
() => activeRESTTab.value,
|
() => RESTTabs.currentActiveTab.value,
|
||||||
() => activeRESTTab.value.document.request.name
|
() => RESTTabs.currentActiveTab.value.document.request.name
|
||||||
)
|
)
|
||||||
|
|
||||||
const reqName = computed(() => {
|
const reqName = computed(() => {
|
||||||
@@ -145,12 +149,14 @@ const reqName = computed(() => {
|
|||||||
const requestName = ref(reqName.value)
|
const requestName = ref(reqName.value)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [activeRESTTab.value, activeGQLTab.value],
|
() => [RESTTabs.currentActiveTab.value, GQLTabs.currentActiveTab.value],
|
||||||
() => {
|
() => {
|
||||||
if (props.mode === "rest") {
|
if (props.mode === "rest") {
|
||||||
requestName.value = activeRESTTab.value?.document.request.name ?? ""
|
requestName.value =
|
||||||
|
RESTTabs.currentActiveTab.value?.document.request.name ?? ""
|
||||||
} else {
|
} else {
|
||||||
requestName.value = activeGQLTab.value?.document.request.name ?? ""
|
requestName.value =
|
||||||
|
GQLTabs.currentActiveTab.value?.document.request.name ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -210,8 +216,8 @@ const saveRequestAs = async () => {
|
|||||||
|
|
||||||
const requestUpdated =
|
const requestUpdated =
|
||||||
props.mode === "rest"
|
props.mode === "rest"
|
||||||
? cloneDeep(activeRESTTab.value.document.request)
|
? cloneDeep(RESTTabs.currentActiveTab.value.document.request)
|
||||||
: cloneDeep(activeGQLTab.value.document.request)
|
: cloneDeep(GQLTabs.currentActiveTab.value.document.request)
|
||||||
|
|
||||||
requestUpdated.name = requestName.value
|
requestUpdated.name = requestName.value
|
||||||
|
|
||||||
@@ -224,7 +230,7 @@ const saveRequestAs = async () => {
|
|||||||
requestUpdated
|
requestUpdated
|
||||||
)
|
)
|
||||||
|
|
||||||
activeRESTTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: requestUpdated,
|
request: requestUpdated,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -251,7 +257,7 @@ const saveRequestAs = async () => {
|
|||||||
requestUpdated
|
requestUpdated
|
||||||
)
|
)
|
||||||
|
|
||||||
activeRESTTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: requestUpdated,
|
request: requestUpdated,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -279,7 +285,7 @@ const saveRequestAs = async () => {
|
|||||||
requestUpdated
|
requestUpdated
|
||||||
)
|
)
|
||||||
|
|
||||||
activeRESTTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: requestUpdated,
|
request: requestUpdated,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -439,7 +445,7 @@ const updateTeamCollectionOrFolder = (
|
|||||||
(result) => {
|
(result) => {
|
||||||
const { createRequestInCollection } = result
|
const { createRequestInCollection } = result
|
||||||
|
|
||||||
activeRESTTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: requestUpdated,
|
request: requestUpdated,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -460,7 +466,7 @@ const updateTeamCollectionOrFolder = (
|
|||||||
const requestSaved = () => {
|
const requestSaved = () => {
|
||||||
toast.success(`${t("request.added")}`)
|
toast.success(`${t("request.added")}`)
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
activeRESTTab.value.document.isDirty = false
|
RESTTabs.currentActiveTab.value.document.isDirty = false
|
||||||
})
|
})
|
||||||
hideModal()
|
hideModal()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
class="!rounded-none"
|
class="!rounded-none"
|
||||||
:icon="IconPlus"
|
:icon="IconPlus"
|
||||||
:title="t('team.no_access')"
|
:title="t('team.no_access')"
|
||||||
:label="t('action.new')"
|
:label="t('add.new')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-else
|
v-else
|
||||||
:icon="IconPlus"
|
:icon="IconPlus"
|
||||||
:label="t('action.new')"
|
:label="t('add.new')"
|
||||||
class="!rounded-none"
|
class="!rounded-none"
|
||||||
@click="emit('display-modal-add')"
|
@click="emit('display-modal-add')"
|
||||||
/>
|
/>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
collectionsType.type === 'team-collections' &&
|
collectionsType.type === 'team-collections' &&
|
||||||
collectionsType.selectedTeam === undefined
|
collectionsType.selectedTeam === undefined
|
||||||
"
|
"
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
@click="emit('display-modal-import-export')"
|
@click="emit('display-modal-import-export')"
|
||||||
/>
|
/>
|
||||||
@@ -261,55 +261,68 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #emptyNode="{ node }">
|
<template #emptyNode="{ node }">
|
||||||
<div v-if="node === null">
|
<HoppSmartPlaceholder
|
||||||
<div @drop="(e) => e.stopPropagation()">
|
v-if="node === null"
|
||||||
<HoppSmartPlaceholder
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:alt="`${t('empty.collections')}`"
|
||||||
:alt="`${t('empty.collections')}`"
|
:text="t('empty.collections')"
|
||||||
:text="t('empty.collections')"
|
@drop.stop
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div class="flex flex-col items-center space-y-4">
|
||||||
v-if="hasNoTeamAccess"
|
<span class="text-secondaryLight text-center">
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
{{ t("collection.import_or_create") }}
|
||||||
disabled
|
</span>
|
||||||
|
<div class="flex gap-4 flex-col items-stretch">
|
||||||
|
<HoppButtonPrimary
|
||||||
|
:icon="IconImport"
|
||||||
|
:label="t('import.title')"
|
||||||
filled
|
filled
|
||||||
outline
|
outline
|
||||||
:title="t('team.no_access')"
|
:disabled="hasNoTeamAccess"
|
||||||
:label="t('action.new')"
|
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||||
|
@click="
|
||||||
|
hasNoTeamAccess ? null : emit('display-modal-import-export')
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-else
|
|
||||||
:icon="IconPlus"
|
:icon="IconPlus"
|
||||||
:label="t('action.new')"
|
:label="t('add.new')"
|
||||||
filled
|
filled
|
||||||
outline
|
outline
|
||||||
@click="emit('display-modal-add')"
|
:disabled="hasNoTeamAccess"
|
||||||
|
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||||
|
@click="hasNoTeamAccess ? null : emit('display-modal-add')"
|
||||||
/>
|
/>
|
||||||
</HoppSmartPlaceholder>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</HoppSmartPlaceholder>
|
||||||
<div
|
<HoppSmartPlaceholder
|
||||||
v-else-if="node.data.type === 'collections'"
|
v-else-if="node.data.type === 'collections'"
|
||||||
@drop="(e) => e.stopPropagation()"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
|
:alt="`${t('empty.collections')}`"
|
||||||
|
:text="t('empty.collections')"
|
||||||
|
@drop.stop
|
||||||
>
|
>
|
||||||
<HoppSmartPlaceholder
|
<HoppButtonSecondary
|
||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:label="t('add.new')"
|
||||||
:alt="`${t('empty.collections')}`"
|
filled
|
||||||
:text="t('empty.collections')"
|
outline
|
||||||
>
|
@click="
|
||||||
</HoppSmartPlaceholder>
|
node.data.type === 'collections' &&
|
||||||
</div>
|
emit('add-folder', {
|
||||||
<div
|
path: node.id,
|
||||||
|
folder: node.data.data.data,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</HoppSmartPlaceholder>
|
||||||
|
<HoppSmartPlaceholder
|
||||||
v-else-if="node.data.type === 'folders'"
|
v-else-if="node.data.type === 'folders'"
|
||||||
@drop="(e) => e.stopPropagation()"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
>
|
:alt="`${t('empty.folder')}`"
|
||||||
<HoppSmartPlaceholder
|
:text="t('empty.folder')"
|
||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
@drop.stop
|
||||||
:alt="`${t('empty.folder')}`"
|
/>
|
||||||
:text="t('empty.folder')"
|
|
||||||
>
|
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartTree>
|
</HoppSmartTree>
|
||||||
</div>
|
</div>
|
||||||
@@ -317,9 +330,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import IconArchive from "~icons/lucide/archive"
|
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
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 { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
@@ -335,10 +348,12 @@ import { HoppRESTRequest } from "@hoppscotch/data"
|
|||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked.js"
|
import { Picked } from "~/helpers/types/HoppPicked.js"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
||||||
|
|
||||||
@@ -536,7 +551,7 @@ const isSelected = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const active = computed(() => currentActiveTab.value.document.saveContext)
|
const active = computed(() => tabs.currentActiveTab.value.document.saveContext)
|
||||||
|
|
||||||
const isActiveRequest = (requestID: string) => {
|
const isActiveRequest = (requestID: string) => {
|
||||||
return pipe(
|
return pipe(
|
||||||
|
|||||||
@@ -36,11 +36,14 @@
|
|||||||
import { ref, watch } from "vue"
|
import { ref, watch } from "vue"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { currentActiveTab } from "~/helpers/graphql/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
folderPath?: string
|
folderPath?: string
|
||||||
@@ -63,7 +66,7 @@ watch(
|
|||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
editingName.value = currentActiveTab.value?.document.request.name
|
editingName.value = tabs.currentActiveTab.value?.document.request.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -220,7 +220,8 @@ import {
|
|||||||
moveGraphqlRequest,
|
moveGraphqlRequest,
|
||||||
} from "~/newstore/collections"
|
} from "~/newstore/collections"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked"
|
import { Picked } from "~/helpers/types/HoppPicked"
|
||||||
import { getTabsRefTo } from "~/helpers/graphql/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
picked: { type: Object, default: null },
|
picked: { type: Object, default: null },
|
||||||
@@ -235,6 +236,8 @@ const colorMode = useColorMode()
|
|||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
// TODO: improve types plz
|
// TODO: improve types plz
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "select", i: Picked | null): void
|
(e: "select", i: Picked | null): void
|
||||||
@@ -295,7 +298,7 @@ const removeCollection = () => {
|
|||||||
emit("select", null)
|
emit("select", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const possibleTabs = getTabsRefTo((tab) => {
|
const possibleTabs = tabs.getTabsRefTo((tab) => {
|
||||||
const ctx = tab.document.saveContext
|
const ctx = tab.document.saveContext
|
||||||
|
|
||||||
if (!ctx) return false
|
if (!ctx) return false
|
||||||
|
|||||||
@@ -203,12 +203,15 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { removeGraphqlFolder, moveGraphqlRequest } from "~/newstore/collections"
|
import { removeGraphqlFolder, moveGraphqlRequest } from "~/newstore/collections"
|
||||||
import { computed, ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
import { getTabsRefTo } from "~/helpers/graphql/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
picked: { type: Object, default: null },
|
picked: { type: Object, default: null },
|
||||||
// Whether the request is in a selectable mode (activates 'select' event)
|
// Whether the request is in a selectable mode (activates 'select' event)
|
||||||
@@ -277,7 +280,7 @@ const removeFolder = () => {
|
|||||||
emit("select", { picked: null })
|
emit("select", { picked: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
const possibleTabs = getTabsRefTo((tab) => {
|
const possibleTabs = tabs.getTabsRefTo((tab) => {
|
||||||
const ctx = tab.document.saveContext
|
const ctx = tab.document.saveContext
|
||||||
|
|
||||||
if (!ctx) return false
|
if (!ctx) return false
|
||||||
|
|||||||
@@ -260,6 +260,13 @@ const importFromJSON = () => {
|
|||||||
|
|
||||||
const exportJSON = () => {
|
const exportJSON = () => {
|
||||||
const dataToWrite = collectionJson.value
|
const dataToWrite = collectionJson.value
|
||||||
|
|
||||||
|
const parsedCollections = JSON.parse(dataToWrite)
|
||||||
|
|
||||||
|
if (!parsedCollections.length) {
|
||||||
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
|
}
|
||||||
|
|
||||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
const a = document.createElement("a")
|
const a = document.createElement("a")
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
|||||||
@@ -137,12 +137,8 @@ import { useToast } from "@composables/toast"
|
|||||||
import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
|
import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { removeGraphqlRequest } from "~/newstore/collections"
|
import { removeGraphqlRequest } from "~/newstore/collections"
|
||||||
import {
|
import { useService } from "dioc/vue"
|
||||||
createNewTab,
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
getTabRefWithSaveContext,
|
|
||||||
currentTabID,
|
|
||||||
currentActiveTab,
|
|
||||||
} from "~/helpers/graphql/tab"
|
|
||||||
|
|
||||||
// Template refs
|
// Template refs
|
||||||
const tippyActions = ref<any | null>(null)
|
const tippyActions = ref<any | null>(null)
|
||||||
@@ -154,6 +150,8 @@ const deleteAction = ref<any | null>(null)
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// Whether the object is selected (show the tick mark)
|
// Whether the object is selected (show the tick mark)
|
||||||
picked: { type: Object, default: null },
|
picked: { type: Object, default: null },
|
||||||
@@ -165,7 +163,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isActive = computed(() => {
|
const isActive = computed(() => {
|
||||||
const saveCtx = currentActiveTab.value?.document.saveContext
|
const saveCtx = tabs.currentActiveTab.value?.document.saveContext
|
||||||
|
|
||||||
if (!saveCtx) return false
|
if (!saveCtx) return false
|
||||||
|
|
||||||
@@ -201,7 +199,7 @@ const selectRequest = () => {
|
|||||||
if (props.saveRequest) {
|
if (props.saveRequest) {
|
||||||
pick()
|
pick()
|
||||||
} else {
|
} else {
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: props.folderPath,
|
folderPath: props.folderPath,
|
||||||
requestIndex: props.requestIndex,
|
requestIndex: props.requestIndex,
|
||||||
@@ -209,11 +207,11 @@ const selectRequest = () => {
|
|||||||
|
|
||||||
// Switch to that request if that request is open
|
// Switch to that request if that request is open
|
||||||
if (possibleTab) {
|
if (possibleTab) {
|
||||||
currentTabID.value = possibleTab.value.id
|
tabs.setActiveTab(possibleTab.value.id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
saveContext: {
|
saveContext: {
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: props.folderPath,
|
folderPath: props.folderPath,
|
||||||
@@ -253,7 +251,7 @@ const removeRequest = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Detach the request from any of the tabs
|
// Detach the request from any of the tabs
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: props.folderPath,
|
folderPath: props.folderPath,
|
||||||
requestIndex: props.requestIndex,
|
requestIndex: props.requestIndex,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
v-if="!saveRequest"
|
v-if="!saveRequest"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
@click="displayModalImportExport(true)"
|
@click="displayModalImportExport(true)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,12 +66,27 @@
|
|||||||
:alt="`${t('empty.collections')}`"
|
:alt="`${t('empty.collections')}`"
|
||||||
:text="t('empty.collections')"
|
:text="t('empty.collections')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div class="flex flex-col items-center space-y-4">
|
||||||
:label="t('add.new')"
|
<span class="text-secondaryLight text-center">
|
||||||
filled
|
{{ t("collection.import_or_create") }}
|
||||||
outline
|
</span>
|
||||||
@click="displayModalAdd(true)"
|
<div class="flex gap-4 flex-col items-stretch">
|
||||||
/>
|
<HoppButtonPrimary
|
||||||
|
:icon="IconImport"
|
||||||
|
:label="t('import.title')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="displayModalImportExport(true)"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="t('add.new')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
:icon="IconPlus"
|
||||||
|
@click="displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
|
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
|
||||||
@@ -140,12 +155,13 @@ import {
|
|||||||
|
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
import IconArchive from "~icons/lucide/archive"
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { createNewTab, currentActiveTab } from "~/helpers/graphql/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -158,14 +174,16 @@ export default defineComponent({
|
|||||||
const collections = useReadonlyStream(graphqlCollections$, [], "deep")
|
const collections = useReadonlyStream(graphqlCollections$, [], "deep")
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
collections,
|
collections,
|
||||||
colorMode,
|
colorMode,
|
||||||
t,
|
t,
|
||||||
|
tabs,
|
||||||
IconPlus,
|
IconPlus,
|
||||||
IconHelpCircle,
|
IconHelpCircle,
|
||||||
IconArchive,
|
IconImport,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -267,13 +285,13 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
onAddRequest({ name, path, index }) {
|
onAddRequest({ name, path, index }) {
|
||||||
const newRequest = {
|
const newRequest = {
|
||||||
...currentActiveTab.value.document.request,
|
...this.tabs.currentActiveTab.value.document.request,
|
||||||
name,
|
name,
|
||||||
}
|
}
|
||||||
|
|
||||||
saveGraphqlRequestAs(path, newRequest)
|
saveGraphqlRequestAs(path, newRequest)
|
||||||
|
|
||||||
createNewTab({
|
this.tabs.createNewTab({
|
||||||
saveContext: {
|
saveContext: {
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: path,
|
folderPath: path,
|
||||||
|
|||||||
@@ -18,13 +18,12 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<WorkspaceCurrent :section="t('tab.collections')" />
|
<WorkspaceCurrent :section="t('tab.collections')" />
|
||||||
|
<input
|
||||||
<HoppSmartInput
|
|
||||||
v-model="filterTexts"
|
v-model="filterTexts"
|
||||||
:placeholder="t('action.search')"
|
|
||||||
input-styles="py-2 pl-4 pr-2 bg-transparent !border-0"
|
|
||||||
type="search"
|
type="search"
|
||||||
:autofocus="false"
|
autocomplete="off"
|
||||||
|
class="flex w-full p-4 py-2 bg-transparent h-8"
|
||||||
|
:placeholder="t('action.search')"
|
||||||
:disabled="collectionsType.type === 'team-collections'"
|
:disabled="collectionsType.type === 'team-collections'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -219,12 +218,6 @@ import {
|
|||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { createCollectionGists } from "~/helpers/gist"
|
import { createCollectionGists } from "~/helpers/gist"
|
||||||
import {
|
|
||||||
createNewTab,
|
|
||||||
currentActiveTab,
|
|
||||||
currentTabID,
|
|
||||||
getTabRefWithSaveContext,
|
|
||||||
} from "~/helpers/rest/tab"
|
|
||||||
import {
|
import {
|
||||||
getRequestsByPath,
|
getRequestsByPath,
|
||||||
resolveSaveContextOnRequestReorder,
|
resolveSaveContextOnRequestReorder,
|
||||||
@@ -239,9 +232,11 @@ import { currentReorderingStatus$ } from "~/newstore/reordering"
|
|||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
saveRequest: {
|
saveRequest: {
|
||||||
@@ -377,22 +372,26 @@ const updateSelectedTeam = (team: SelectedTeam) => {
|
|||||||
const workspace = workspaceService.currentWorkspace
|
const workspace = workspaceService.currentWorkspace
|
||||||
|
|
||||||
// Used to switch collection type and team when user switch workspace in the global workspace switcher
|
// Used to switch collection type and team when user switch workspace in the global workspace switcher
|
||||||
// Check if there is a teamID in the workspace, if yes, switch to team collection and select the team
|
// Check if there is a teamID in the workspace, if yes, switch to team collections and select the team
|
||||||
// If there is no teamID, switch to my environment
|
// If there is no teamID, switch to my collections
|
||||||
watch(
|
watch(
|
||||||
() => {
|
() => {
|
||||||
const space = workspace.value
|
const space = workspace.value
|
||||||
|
return space.type === "personal" ? undefined : space.teamID
|
||||||
if (space.type === "personal") return undefined
|
|
||||||
else return space.teamID
|
|
||||||
},
|
},
|
||||||
(teamID) => {
|
(teamID) => {
|
||||||
if (!teamID) {
|
if (teamID) {
|
||||||
switchToMyCollections()
|
|
||||||
} else if (teamID) {
|
|
||||||
const team = myTeams.value?.find((t) => t.id === teamID)
|
const team = myTeams.value?.find((t) => t.id === teamID)
|
||||||
if (team) updateSelectedTeam(team)
|
if (team) {
|
||||||
|
updateSelectedTeam(team)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return switchToMyCollections()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -650,7 +649,7 @@ const addRequest = (payload: {
|
|||||||
|
|
||||||
const onAddRequest = (requestName: string) => {
|
const onAddRequest = (requestName: string) => {
|
||||||
const newRequest = {
|
const newRequest = {
|
||||||
...cloneDeep(currentActiveTab.value.document.request),
|
...cloneDeep(tabs.currentActiveTab.value.document.request),
|
||||||
name: requestName,
|
name: requestName,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,7 +658,7 @@ const onAddRequest = (requestName: string) => {
|
|||||||
if (!path) return
|
if (!path) return
|
||||||
const insertionIndex = saveRESTRequestAs(path, newRequest)
|
const insertionIndex = saveRESTRequestAs(path, newRequest)
|
||||||
|
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: newRequest,
|
request: newRequest,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -708,7 +707,7 @@ const onAddRequest = (requestName: string) => {
|
|||||||
(result) => {
|
(result) => {
|
||||||
const { createRequestInCollection } = result
|
const { createRequestInCollection } = result
|
||||||
|
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: newRequest,
|
request: newRequest,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -931,7 +930,7 @@ const updateEditingRequest = (newName: string) => {
|
|||||||
|
|
||||||
if (folderPath === null || requestIndex === null) return
|
if (folderPath === null || requestIndex === null) return
|
||||||
|
|
||||||
const possibleActiveTab = getTabRefWithSaveContext({
|
const possibleActiveTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
requestIndex,
|
requestIndex,
|
||||||
folderPath,
|
folderPath,
|
||||||
@@ -975,7 +974,7 @@ const updateEditingRequest = (newName: string) => {
|
|||||||
)
|
)
|
||||||
)()
|
)()
|
||||||
|
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID,
|
requestID,
|
||||||
})
|
})
|
||||||
@@ -1211,7 +1210,7 @@ const onRemoveRequest = () => {
|
|||||||
emit("select", null)
|
emit("select", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath,
|
folderPath,
|
||||||
requestIndex,
|
requestIndex,
|
||||||
@@ -1271,7 +1270,7 @@ const onRemoveRequest = () => {
|
|||||||
)()
|
)()
|
||||||
|
|
||||||
// If there is a tab attached to this request, dissociate its state and mark it dirty
|
// If there is a tab attached to this request, dissociate its state and mark it dirty
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID,
|
requestID,
|
||||||
})
|
})
|
||||||
@@ -1304,14 +1303,14 @@ const selectRequest = (selectedRequest: {
|
|||||||
let possibleTab = null
|
let possibleTab = null
|
||||||
|
|
||||||
if (collectionsType.value.type === "team-collections") {
|
if (collectionsType.value.type === "team-collections") {
|
||||||
possibleTab = getTabRefWithSaveContext({
|
possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID: requestIndex,
|
requestID: requestIndex,
|
||||||
})
|
})
|
||||||
if (possibleTab) {
|
if (possibleTab) {
|
||||||
currentTabID.value = possibleTab.value.id
|
tabs.setActiveTab(possibleTab.value.id)
|
||||||
} else {
|
} else {
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: cloneDeep(request),
|
request: cloneDeep(request),
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -1321,16 +1320,16 @@ const selectRequest = (selectedRequest: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
possibleTab = getTabRefWithSaveContext({
|
possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
requestIndex: parseInt(requestIndex),
|
requestIndex: parseInt(requestIndex),
|
||||||
folderPath: folderPath!,
|
folderPath: folderPath!,
|
||||||
})
|
})
|
||||||
if (possibleTab) {
|
if (possibleTab) {
|
||||||
currentTabID.value = possibleTab.value.id
|
tabs.setActiveTab(possibleTab.value.id)
|
||||||
} else {
|
} else {
|
||||||
// If not, open the request in a new tab
|
// If not, open the request in a new tab
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: cloneDeep(request),
|
request: cloneDeep(request),
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -1373,7 +1372,7 @@ const dropRequest = (payload: {
|
|||||||
destinationCollectionIndex
|
destinationCollectionIndex
|
||||||
)
|
)
|
||||||
|
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath,
|
folderPath,
|
||||||
requestIndex: pathToLastIndex(requestIndex),
|
requestIndex: pathToLastIndex(requestIndex),
|
||||||
@@ -1422,7 +1421,7 @@ const dropRequest = (payload: {
|
|||||||
1
|
1
|
||||||
)
|
)
|
||||||
|
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID: requestIndex,
|
requestID: requestIndex,
|
||||||
})
|
})
|
||||||
@@ -1938,6 +1937,12 @@ const exportJSONCollection = async () => {
|
|||||||
|
|
||||||
await getJSONCollection()
|
await getJSONCollection()
|
||||||
|
|
||||||
|
const parsedCollections = JSON.parse(collectionJSON.value)
|
||||||
|
|
||||||
|
if (!parsedCollections.length) {
|
||||||
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
|
}
|
||||||
|
|
||||||
initializeDownloadCollection(collectionJSON.value, null)
|
initializeDownloadCollection(collectionJSON.value, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,11 +83,14 @@ import {
|
|||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import { updateTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
import { updateTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
position: { top: number; left: number }
|
position: { top: number; left: number }
|
||||||
@@ -189,8 +192,8 @@ const addEnvironment = async () => {
|
|||||||
//replace the current tab endpoint with the variable name with << and >>
|
//replace the current tab endpoint with the variable name with << and >>
|
||||||
const variableName = `<<${editingName.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 =
|
tabs.currentActiveTab.value.document.request.endpoint =
|
||||||
currentActiveTab.value.document.request.endpoint.replace(
|
tabs.currentActiveTab.value.document.request.endpoint.replace(
|
||||||
editingValue.value,
|
editingValue.value,
|
||||||
variableName
|
variableName
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -377,6 +377,13 @@ const importFromPostman = ({
|
|||||||
|
|
||||||
const exportJSON = () => {
|
const exportJSON = () => {
|
||||||
const dataToWrite = environmentJson.value
|
const dataToWrite = environmentJson.value
|
||||||
|
|
||||||
|
const parsedCollections = JSON.parse(dataToWrite)
|
||||||
|
|
||||||
|
if (!parsedCollections.length) {
|
||||||
|
return toast.error(t("error.no_environments_to_export"))
|
||||||
|
}
|
||||||
|
|
||||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
const a = document.createElement("a")
|
const a = document.createElement("a")
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
/>
|
/>
|
||||||
<HoppSmartTabs
|
<HoppSmartTabs
|
||||||
v-model="selectedEnvTab"
|
v-model="selectedEnvTab"
|
||||||
:styles="`sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-0 top-0 bg-primary ${
|
:styles="`sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-10 top-0 bg-primary ${
|
||||||
!isTeamSelected || workspace.type === 'personal'
|
!isTeamSelected || workspace.type === 'personal'
|
||||||
? 'bg-primaryLight'
|
? 'bg-primaryLight'
|
||||||
: ''
|
: ''
|
||||||
@@ -478,7 +478,8 @@ watch(
|
|||||||
teamEnvListAdapter.changeTeamID(newVal.teamID)
|
teamEnvListAdapter.changeTeamID(newVal.teamID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const selectedEnv = computed(() => {
|
const selectedEnv = computed(() => {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
role="menu"
|
role="menu"
|
||||||
@keyup.e="edit!.$el.click()"
|
@keyup.e="edit!.$el.click()"
|
||||||
@keyup.d="duplicate!.$el.click()"
|
@keyup.d="duplicate!.$el.click()"
|
||||||
|
@keyup.j="exportAsJsonEl!.$el.click()"
|
||||||
@keyup.delete="
|
@keyup.delete="
|
||||||
!(environmentIndex === 'Global')
|
!(environmentIndex === 'Global')
|
||||||
? deleteAction!.$el.click()
|
? deleteAction!.$el.click()
|
||||||
@@ -77,6 +78,18 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="exportAsJsonEl"
|
||||||
|
:icon="IconEdit"
|
||||||
|
:label="`${t('export.as_json')}`"
|
||||||
|
:shortcut="['J']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
exportEnvironmentAsJSON()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
v-if="environmentIndex !== 'Global'"
|
v-if="environmentIndex !== 'Global'"
|
||||||
ref="deleteAction"
|
ref="deleteAction"
|
||||||
@@ -121,6 +134,7 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||||
|
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -136,10 +150,18 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const confirmRemove = ref(false)
|
const confirmRemove = ref(false)
|
||||||
|
|
||||||
|
const exportEnvironmentAsJSON = () => {
|
||||||
|
const { environment, environmentIndex } = props
|
||||||
|
exportAsJSON(environment, environmentIndex)
|
||||||
|
? toast.success(t("state.download_started"))
|
||||||
|
: toast.error(t("state.download_failed"))
|
||||||
|
}
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
const options = ref<TippyComponent | null>(null)
|
const options = ref<TippyComponent | null>(null)
|
||||||
const edit = ref<typeof HoppSmartItem>()
|
const edit = ref<typeof HoppSmartItem>()
|
||||||
const duplicate = ref<typeof HoppSmartItem>()
|
const duplicate = ref<typeof HoppSmartItem>()
|
||||||
|
const exportAsJsonEl = ref<typeof HoppSmartItem>()
|
||||||
const deleteAction = ref<typeof HoppSmartItem>()
|
const deleteAction = ref<typeof HoppSmartItem>()
|
||||||
|
|
||||||
const removeEnvironment = () => {
|
const removeEnvironment = () => {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
@click="displayModalImportExport(true)"
|
@click="displayModalImportExport(true)"
|
||||||
/>
|
/>
|
||||||
@@ -33,17 +33,32 @@
|
|||||||
@edit-environment="editEnvironment(index)"
|
@edit-environment="editEnvironment(index)"
|
||||||
/>
|
/>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="environments.length === 0"
|
v-if="!environments.length"
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div class="flex flex-col items-center space-y-4">
|
||||||
:label="`${t('add.new')}`"
|
<span class="text-secondaryLight text-center">
|
||||||
filled
|
{{ t("environment.import_or_create") }}
|
||||||
outline
|
</span>
|
||||||
@click="displayModalAdd(true)"
|
<div class="flex gap-4 flex-col items-stretch">
|
||||||
/>
|
<HoppButtonPrimary
|
||||||
|
:icon="IconImport"
|
||||||
|
:label="t('import.title')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="displayModalImportExport(true)"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:icon="IconPlus"
|
||||||
|
:label="`${t('add.new')}`"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<EnvironmentsMyDetails
|
<EnvironmentsMyDetails
|
||||||
:show="showModalDetails"
|
:show="showModalDetails"
|
||||||
@@ -66,8 +81,8 @@ import { environments$ } from "~/newstore/environments"
|
|||||||
import { useColorMode } from "~/composables/theming"
|
import { useColorMode } from "~/composables/theming"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import IconArchive from "~icons/lucide/archive"
|
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
import { Environment } from "@hoppscotch/data"
|
import { Environment } from "@hoppscotch/data"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
role="menu"
|
role="menu"
|
||||||
@keyup.e="edit!.$el.click()"
|
@keyup.e="edit!.$el.click()"
|
||||||
@keyup.d="duplicate!.$el.click()"
|
@keyup.d="duplicate!.$el.click()"
|
||||||
|
@keyup.j="exportAsJsonEl!.$el.click()"
|
||||||
@keyup.delete="deleteAction!.$el.click()"
|
@keyup.delete="deleteAction!.$el.click()"
|
||||||
@keyup.escape="options!.tippy().hide()"
|
@keyup.escape="options!.tippy().hide()"
|
||||||
>
|
>
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
ref="duplicate"
|
ref="duplicate"
|
||||||
:icon="IconCopy"
|
:icon="IconCopy"
|
||||||
@@ -66,6 +68,18 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="exportAsJsonEl"
|
||||||
|
:icon="IconEdit"
|
||||||
|
:label="`${t('export.as_json')}`"
|
||||||
|
:shortcut="['J']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
exportEnvironmentAsJSON()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
ref="deleteAction"
|
ref="deleteAction"
|
||||||
:icon="IconTrash2"
|
:icon="IconTrash2"
|
||||||
@@ -109,6 +123,7 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
|||||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||||
|
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -124,11 +139,17 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const confirmRemove = ref(false)
|
const confirmRemove = ref(false)
|
||||||
|
|
||||||
|
const exportEnvironmentAsJSON = () =>
|
||||||
|
exportAsJSON(props.environment)
|
||||||
|
? toast.success(t("state.download_started"))
|
||||||
|
: toast.error(t("state.download_failed"))
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
const options = ref<TippyComponent | null>(null)
|
const options = ref<TippyComponent | null>(null)
|
||||||
const edit = ref<typeof HoppSmartItem>()
|
const edit = ref<typeof HoppSmartItem>()
|
||||||
const duplicate = ref<typeof HoppSmartItem>()
|
const duplicate = ref<typeof HoppSmartItem>()
|
||||||
const deleteAction = ref<typeof HoppSmartItem>()
|
const deleteAction = ref<typeof HoppSmartItem>()
|
||||||
|
const exportAsJsonEl = ref<typeof HoppSmartItem>()
|
||||||
|
|
||||||
const removeEnvironment = () => {
|
const removeEnvironment = () => {
|
||||||
pipe(
|
pipe(
|
||||||
|
|||||||
@@ -31,40 +31,49 @@
|
|||||||
v-if="team !== undefined && team.myRole === 'VIEWER'"
|
v-if="team !== undefined && team.myRole === 'VIEWER'"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
disabled
|
disabled
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-else
|
v-else
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
@click="displayModalImportExport(true)"
|
@click="displayModalImportExport(true)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="!loading && teamEnvironments.length === 0 && !adapterError"
|
v-if="!loading && !teamEnvironments.length && !adapterError"
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div class="flex flex-col items-center space-y-4">
|
||||||
v-if="team === undefined || team.myRole === 'VIEWER'"
|
<span class="text-secondaryLight text-center">
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
{{ t("environment.import_or_create") }}
|
||||||
disabled
|
</span>
|
||||||
filled
|
<div class="flex gap-4 flex-col items-stretch">
|
||||||
:icon="IconPlus"
|
<HoppButtonPrimary
|
||||||
:title="t('team.no_access')"
|
:icon="IconImport"
|
||||||
:label="t('action.new')"
|
:label="t('import.title')"
|
||||||
/>
|
filled
|
||||||
<HoppButtonSecondary
|
outline
|
||||||
v-else
|
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||||
:label="`${t('add.new')}`"
|
:disabled="isTeamViewer"
|
||||||
filled
|
@click="isTeamViewer ? null : displayModalImportExport(true)"
|
||||||
outline
|
/>
|
||||||
@click="displayModalAdd(true)"
|
<HoppButtonSecondary
|
||||||
/>
|
:label="`${t('add.new')}`"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
:icon="IconPlus"
|
||||||
|
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||||
|
:disabled="isTeamViewer"
|
||||||
|
@click="isTeamViewer ? null : displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div v-else-if="!loading">
|
<div v-else-if="!loading">
|
||||||
<EnvironmentsTeamsEnvironment
|
<EnvironmentsTeamsEnvironment
|
||||||
@@ -108,14 +117,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { useColorMode } from "~/composables/theming"
|
import { useColorMode } from "~/composables/theming"
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconArchive from "~icons/lucide/archive"
|
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
|
|
||||||
@@ -138,6 +147,8 @@ const action = ref<"new" | "edit">("edit")
|
|||||||
const editingEnvironment = ref<TeamEnvironment | null>(null)
|
const editingEnvironment = ref<TeamEnvironment | null>(null)
|
||||||
const editingVariableName = ref("")
|
const editingVariableName = ref("")
|
||||||
|
|
||||||
|
const isTeamViewer = computed(() => props.team?.myRole === "VIEWER")
|
||||||
|
|
||||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||||
action.value = "new"
|
action.value = "new"
|
||||||
showModalDetails.value = shouldDisplay
|
showModalDetails.value = shouldDisplay
|
||||||
|
|||||||
@@ -64,7 +64,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { currentActiveTab } from "~/helpers/graphql/tab"
|
|
||||||
import { computed, ref, watch } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import { connection } from "~/helpers/graphql/connection"
|
import { connection } from "~/helpers/graphql/connection"
|
||||||
import { connect } from "~/helpers/graphql/connection"
|
import { connect } from "~/helpers/graphql/connection"
|
||||||
@@ -72,8 +71,10 @@ import { disconnect } from "~/helpers/graphql/connection"
|
|||||||
import { InterceptorService } from "~/services/interceptor.service"
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
const interceptorService = useService(InterceptorService)
|
const interceptorService = useService(InterceptorService)
|
||||||
|
|
||||||
@@ -82,9 +83,9 @@ const connectionSwitchModal = ref(false)
|
|||||||
const connected = computed(() => connection.state === "CONNECTED")
|
const connected = computed(() => connection.state === "CONNECTED")
|
||||||
|
|
||||||
const url = computed({
|
const url = computed({
|
||||||
get: () => currentActiveTab.value?.document.request.url ?? "",
|
get: () => tabs.currentActiveTab.value?.document.request.url ?? "",
|
||||||
set: (value) => {
|
set: (value) => {
|
||||||
currentActiveTab.value!.document.request.url = value
|
tabs.currentActiveTab.value!.document.request.url = value
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ const onConnectClick = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const gqlConnect = () => {
|
const gqlConnect = () => {
|
||||||
connect(url.value, currentActiveTab.value?.document.request.headers)
|
connect(url.value, tabs.currentActiveTab.value?.document.request.headers)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_REQUEST_RUN",
|
type: "HOPP_REQUEST_RUN",
|
||||||
@@ -114,7 +115,7 @@ const switchConnection = () => {
|
|||||||
const lastTwoUrls = ref<string[]>([])
|
const lastTwoUrls = ref<string[]>([])
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
currentActiveTab,
|
tabs.currentActiveTab,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
lastTwoUrls.value.push(newVal.document.request.url)
|
lastTwoUrls.value.push(newVal.document.request.url)
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ import { computed, ref, watch } from "vue"
|
|||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { currentActiveTab } from "~/helpers/graphql/tab"
|
import { computedWithControl, useVModel } from "@vueuse/core"
|
||||||
import { computedWithControl } from "@vueuse/core"
|
|
||||||
import {
|
import {
|
||||||
GQLResponseEvent,
|
GQLResponseEvent,
|
||||||
runGQLOperation,
|
runGQLOperation,
|
||||||
@@ -68,26 +67,39 @@ import {
|
|||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
import { editGraphqlRequest } from "~/newstore/collections"
|
import { editGraphqlRequest } from "~/newstore/collections"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
|
const VALID_GQL_OPERATIONS = [
|
||||||
|
"query",
|
||||||
|
"headers",
|
||||||
|
"variables",
|
||||||
|
"authorization",
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type GQLOptionTabs = (typeof VALID_GQL_OPERATIONS)[number]
|
||||||
|
|
||||||
export type GQLOptionTabs = "query" | "headers" | "variables" | "authorization"
|
|
||||||
const selectedOptionTab = ref<GQLOptionTabs>("query")
|
|
||||||
const interceptorService = useService(InterceptorService)
|
const interceptorService = useService(InterceptorService)
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
// v-model integration with props and emit
|
// v-model integration with props and emit
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
modelValue: HoppGQLRequest
|
modelValue: HoppGQLRequest
|
||||||
response?: GQLResponseEvent[] | null
|
response?: GQLResponseEvent[] | null
|
||||||
|
optionTab?: GQLOptionTabs
|
||||||
tabId: string
|
tabId: string
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
response: null,
|
response: null,
|
||||||
|
optionTab: "query",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const emit = defineEmits(["update:modelValue", "update:response"])
|
const emit = defineEmits(["update:modelValue", "update:response"])
|
||||||
|
const selectedOptionTab = useVModel(props, "optionTab", emit)
|
||||||
|
|
||||||
const request = ref(props.modelValue)
|
const request = ref(props.modelValue)
|
||||||
|
|
||||||
@@ -100,8 +112,8 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const url = computedWithControl(
|
const url = computedWithControl(
|
||||||
() => currentActiveTab.value,
|
() => tabs.currentActiveTab.value,
|
||||||
() => currentActiveTab.value.document.request.url
|
() => tabs.currentActiveTab.value.document.request.url
|
||||||
)
|
)
|
||||||
|
|
||||||
const activeGQLHeadersCount = computed(
|
const activeGQLHeadersCount = computed(
|
||||||
@@ -136,6 +148,9 @@ const runQuery = async (
|
|||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
completePageProgress()
|
completePageProgress()
|
||||||
toast.success(`${t("state.finished_in", { duration })}`)
|
toast.success(`${t("state.finished_in", { duration })}`)
|
||||||
|
if (definition?.operation === "subscription" && request.value.auth) {
|
||||||
|
toast.success(t("authorization.graphql_headers"))
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
// response.value = [`${e}`]
|
// response.value = [`${e}`]
|
||||||
@@ -182,17 +197,17 @@ const hideRequestModal = () => {
|
|||||||
}
|
}
|
||||||
const saveRequest = () => {
|
const saveRequest = () => {
|
||||||
if (
|
if (
|
||||||
currentActiveTab.value.document.saveContext &&
|
tabs.currentActiveTab.value.document.saveContext &&
|
||||||
currentActiveTab.value.document.saveContext.originLocation ===
|
tabs.currentActiveTab.value.document.saveContext.originLocation ===
|
||||||
"user-collection"
|
"user-collection"
|
||||||
) {
|
) {
|
||||||
editGraphqlRequest(
|
editGraphqlRequest(
|
||||||
currentActiveTab.value.document.saveContext.folderPath,
|
tabs.currentActiveTab.value.document.saveContext.folderPath,
|
||||||
currentActiveTab.value.document.saveContext.requestIndex,
|
tabs.currentActiveTab.value.document.saveContext.requestIndex,
|
||||||
currentActiveTab.value.document.request
|
tabs.currentActiveTab.value.document.request
|
||||||
)
|
)
|
||||||
|
|
||||||
currentActiveTab.value.document.isDirty = false
|
tabs.currentActiveTab.value.document.isDirty = false
|
||||||
} else {
|
} else {
|
||||||
showSaveRequestModal.value = true
|
showSaveRequestModal.value = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
<template #primary>
|
<template #primary>
|
||||||
<GraphqlRequestOptions
|
<GraphqlRequestOptions
|
||||||
v-model="tab.document.request"
|
v-model="tab.document.request"
|
||||||
v-model:response="tab.response"
|
v-model:response="tab.document.response"
|
||||||
|
v-model:option-tab="tab.document.optionTabPreference"
|
||||||
:tab-id="tab.id"
|
:tab-id="tab.id"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #secondary>
|
<template #secondary>
|
||||||
<GraphqlResponse :response="tab.response" />
|
<GraphqlResponse :response="tab.document.response" />
|
||||||
</template>
|
</template>
|
||||||
</AppPaneLayout>
|
</AppPaneLayout>
|
||||||
</template>
|
</template>
|
||||||
@@ -18,14 +19,15 @@ import { useVModel } from "@vueuse/core"
|
|||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { watch } from "vue"
|
import { watch } from "vue"
|
||||||
import { isEqualHoppGQLRequest } from "~/helpers/graphql"
|
import { isEqualHoppGQLRequest } from "~/helpers/graphql"
|
||||||
import { HoppGQLTab } from "~/helpers/graphql/tab"
|
import { HoppGQLDocument } from "~/helpers/graphql/document"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
|
||||||
// TODO: Move Response and Request execution code to over here
|
// TODO: Move Response and Request execution code to over here
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: HoppGQLTab }>()
|
const props = defineProps<{ modelValue: HoppTab<HoppGQLDocument> }>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:modelValue", val: HoppGQLTab): void
|
(e: "update:modelValue", val: HoppTab<HoppGQLDocument>): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tab = useVModel(props, "modelValue", emit)
|
const tab = useVModel(props, "modelValue", emit)
|
||||||
|
|||||||
@@ -30,8 +30,8 @@
|
|||||||
v-model="graphqlFieldsFilterText"
|
v-model="graphqlFieldsFilterText"
|
||||||
type="search"
|
type="search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
class="flex w-full p-4 py-2 bg-transparent h-8"
|
||||||
:placeholder="`${t('action.search')}`"
|
:placeholder="`${t('action.search')}`"
|
||||||
class="flex flex-1 p-4 py-2 bg-transparent"
|
|
||||||
/>
|
/>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
|
|||||||
@@ -92,12 +92,13 @@ import IconXCircle from "~icons/lucide/x-circle"
|
|||||||
import IconXSquare from "~icons/lucide/x-square"
|
import IconXSquare from "~icons/lucide/x-square"
|
||||||
import IconFileEdit from "~icons/lucide/file-edit"
|
import IconFileEdit from "~icons/lucide/file-edit"
|
||||||
import IconCopy from "~icons/lucide/copy"
|
import IconCopy from "~icons/lucide/copy"
|
||||||
import { HoppGQLTab } from "~/helpers/graphql/tab"
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppGQLDocument } from "~/helpers/graphql/document"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
tab: HoppGQLTab
|
tab: HoppTab<HoppGQLDocument>
|
||||||
isRemovable: boolean
|
isRemovable: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -67,9 +67,11 @@ import IconMaximize2 from "~icons/lucide/maximize-2"
|
|||||||
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { makeGQLRequest } from "@hoppscotch/data"
|
import { makeGQLRequest } from "@hoppscotch/data"
|
||||||
import { createNewTab } from "~/helpers/graphql/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
entry: GQLHistoryEntry
|
entry: GQLHistoryEntry
|
||||||
@@ -93,7 +95,7 @@ const query = computed(() =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
const useEntry = () => {
|
const useEntry = () => {
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: makeGQLRequest({
|
request: makeGQLRequest({
|
||||||
name: props.entry.request.name,
|
name: props.entry.request.name,
|
||||||
url: props.entry.request.url,
|
url: props.entry.request.url,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
v-model="filterText"
|
v-model="filterText"
|
||||||
type="search"
|
type="search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
class="flex flex-1 p-4 py-2 bg-transparent"
|
class="flex w-full p-4 py-2 bg-transparent h-8"
|
||||||
:placeholder="`${t('action.search')}`"
|
:placeholder="`${t('action.search')}`"
|
||||||
/>
|
/>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@@ -176,8 +176,9 @@ 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 { defineActionHandler, invokeAction } from "~/helpers/actions"
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
type HistoryEntry = GQLHistoryEntry | RESTHistoryEntry
|
type HistoryEntry = GQLHistoryEntry | RESTHistoryEntry
|
||||||
|
|
||||||
@@ -293,8 +294,9 @@ const clearHistory = () => {
|
|||||||
|
|
||||||
// NOTE: For GQL, the HistoryGraphqlCard component already implements useEntry
|
// NOTE: For GQL, the HistoryGraphqlCard component already implements useEntry
|
||||||
// (That is not a really good behaviour tho ¯\_(ツ)_/¯)
|
// (That is not a really good behaviour tho ¯\_(ツ)_/¯)
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
const useHistory = (entry: RESTHistoryEntry) => {
|
const useHistory = (entry: RESTHistoryEntry) => {
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: entry.request,
|
request: entry.request,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -59,7 +59,9 @@
|
|||||||
:key="`contentTypeItem-${contentTypeIndex}`"
|
:key="`contentTypeItem-${contentTypeIndex}`"
|
||||||
:label="contentTypeItem"
|
:label="contentTypeItem"
|
||||||
:info-icon="
|
:info-icon="
|
||||||
contentTypeItem === body.contentType ? IconDone : null
|
contentTypeItem === body.contentType
|
||||||
|
? IconDone
|
||||||
|
: undefined
|
||||||
"
|
"
|
||||||
:active-info-icon="contentTypeItem === body.contentType"
|
:active-info-icon="contentTypeItem === body.contentType"
|
||||||
@click="
|
@click="
|
||||||
@@ -136,7 +138,7 @@ import IconDone from "~icons/lucide/check"
|
|||||||
import IconExternalLink from "~icons/lucide/external-link"
|
import IconExternalLink from "~icons/lucide/external-link"
|
||||||
import IconInfo from "~icons/lucide/info"
|
import IconInfo from "~icons/lucide/info"
|
||||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||||
import { RequestOptionTabs } from "./RequestOptions.vue"
|
import { RESTOptionTabs } from "./RequestOptions.vue"
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@@ -147,7 +149,7 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "change-tab", value: RequestOptionTabs): void
|
(e: "change-tab", value: RESTOptionTabs): void
|
||||||
(e: "update:headers", value: HoppRESTHeader[]): void
|
(e: "update:headers", value: HoppRESTHeader[]): void
|
||||||
(e: "update:body", value: HoppRESTReqBody): void
|
(e: "update:body", value: HoppRESTReqBody): void
|
||||||
}>()
|
}>()
|
||||||
@@ -164,7 +166,7 @@ const overridenContentType = computed(() =>
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const contentTypeOverride = (tab: RequestOptionTabs) => {
|
const contentTypeOverride = (tab: RESTOptionTabs) => {
|
||||||
emit("change-tab", tab)
|
emit("change-tab", tab)
|
||||||
if (!isContentTypeAlreadyExist()) {
|
if (!isContentTypeAlreadyExist()) {
|
||||||
// TODO: Fix this
|
// TODO: Fix this
|
||||||
|
|||||||
@@ -157,9 +157,10 @@ import {
|
|||||||
import IconCopy from "~icons/lucide/copy"
|
import IconCopy from "~icons/lucide/copy"
|
||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
import IconWrapText from "~icons/lucide/wrap-text"
|
import IconWrapText from "~icons/lucide/wrap-text"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
|
||||||
import cloneDeep from "lodash-es/cloneDeep"
|
import cloneDeep from "lodash-es/cloneDeep"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -173,7 +174,8 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const request = ref(cloneDeep(currentActiveTab.value.document.request))
|
const tabs = useService(RESTTabService)
|
||||||
|
const request = ref(cloneDeep(tabs.currentActiveTab.value.document.request))
|
||||||
const codegenType = ref<CodegenName>("shell-curl")
|
const codegenType = ref<CodegenName>("shell-curl")
|
||||||
const errorState = ref(false)
|
const errorState = ref(false)
|
||||||
|
|
||||||
@@ -242,7 +244,7 @@ watch(
|
|||||||
() => props.show,
|
() => props.show,
|
||||||
(goingToShow) => {
|
(goingToShow) => {
|
||||||
if (goingToShow) {
|
if (goingToShow) {
|
||||||
request.value = cloneDeep(currentActiveTab.value.document.request)
|
request.value = cloneDeep(tabs.currentActiveTab.value.document.request)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_REST_CODEGEN_OPENED",
|
type: "HOPP_REST_CODEGEN_OPENED",
|
||||||
|
|||||||
@@ -185,18 +185,24 @@
|
|||||||
<span>
|
<span>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="header.source === 'auth'"
|
v-if="header.source === 'auth'"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t(masking ? 'state.show' : 'state.hide')"
|
||||||
:icon="masking ? IconEye : IconEyeOff"
|
:icon="masking ? IconEye : IconEyeOff"
|
||||||
@click="toggleMask()"
|
@click="toggleMask()"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-else
|
v-else
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconArrowUpRight"
|
:icon="IconArrowUpRight"
|
||||||
|
:title="t('request.go_to_authorization_tab')"
|
||||||
class="cursor-auto text-primary hover:text-primary"
|
class="cursor-auto text-primary hover:text-primary"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconArrowUpRight"
|
:icon="IconArrowUpRight"
|
||||||
|
:title="t('request.go_to_authorization_tab')"
|
||||||
@click="changeTab(header.source)"
|
@click="changeTab(header.source)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -250,7 +256,7 @@ import * as E from "fp-ts/Either"
|
|||||||
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"
|
||||||
import draggable from "vuedraggable-es"
|
import draggable from "vuedraggable-es"
|
||||||
import { RequestOptionTabs } from "./RequestOptions.vue"
|
import { RESTOptionTabs } from "./RequestOptions.vue"
|
||||||
import { useCodemirror } from "@composables/codemirror"
|
import { useCodemirror } from "@composables/codemirror"
|
||||||
import { commonHeaders } from "~/helpers/headers"
|
import { commonHeaders } from "~/helpers/headers"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
@@ -267,10 +273,13 @@ import { aggregateEnvs$, getAggregateEnvs } from "~/newstore/environments"
|
|||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService, InspectorResult } from "~/services/inspection"
|
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||||
import { currentTabID } from "~/helpers/rest/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const idTicker = ref(0)
|
const idTicker = ref(0)
|
||||||
@@ -286,7 +295,7 @@ const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
|||||||
const props = defineProps<{ modelValue: HoppRESTRequest }>()
|
const props = defineProps<{ modelValue: HoppRESTRequest }>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "change-tab", value: RequestOptionTabs): void
|
(e: "change-tab", value: RESTOptionTabs): void
|
||||||
(e: "update:modelValue", value: HoppRESTRequest): void
|
(e: "update:modelValue", value: HoppRESTRequest): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -509,13 +518,13 @@ const changeTab = (tab: ComputedHeader["source"]) => {
|
|||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const headerKeyResults = inspectionService.getResultViewFor(
|
const headerKeyResults = inspectionService.getResultViewFor(
|
||||||
currentTabID.value,
|
tabs.currentTabID.value,
|
||||||
(result) =>
|
(result) =>
|
||||||
result.locations.type === "header" && result.locations.position === "key"
|
result.locations.type === "header" && result.locations.position === "key"
|
||||||
)
|
)
|
||||||
|
|
||||||
const headerValueResults = inspectionService.getResultViewFor(
|
const headerValueResults = inspectionService.getResultViewFor(
|
||||||
currentTabID.value,
|
tabs.currentTabID.value,
|
||||||
(result) =>
|
(result) =>
|
||||||
result.locations.type === "header" && result.locations.position === "value"
|
result.locations.type === "header" && result.locations.position === "value"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -93,13 +93,16 @@ import IconWrapText from "~icons/lucide/wrap-text"
|
|||||||
import IconClipboard from "~icons/lucide/clipboard"
|
import IconClipboard from "~icons/lucide/clipboard"
|
||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const curl = ref("")
|
const curl = ref("")
|
||||||
|
|
||||||
const curlEditor = ref<any | null>(null)
|
const curlEditor = ref<any | null>(null)
|
||||||
@@ -149,7 +152,7 @@ const handleImport = () => {
|
|||||||
type: "HOPP_REST_IMPORT_CURL",
|
type: "HOPP_REST_IMPORT_CURL",
|
||||||
})
|
})
|
||||||
|
|
||||||
currentActiveTab.value.document.request = req
|
tabs.currentActiveTab.value.document.request = req
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast.error(`${t("error.curl_invalid_format")}`)
|
toast.error(`${t("error.curl_invalid_format")}`)
|
||||||
|
|||||||
@@ -202,12 +202,13 @@ import { objRemoveKey } from "@functional/object"
|
|||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService, InspectorResult } from "~/services/inspection"
|
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||||
import { currentTabID } from "~/helpers/rest/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const idTicker = ref(0)
|
const idTicker = ref(0)
|
||||||
|
|
||||||
@@ -410,13 +411,13 @@ const clearContent = () => {
|
|||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const parameterKeyResults = inspectionService.getResultViewFor(
|
const parameterKeyResults = inspectionService.getResultViewFor(
|
||||||
currentTabID.value,
|
tabs.currentTabID.value,
|
||||||
(result) =>
|
(result) =>
|
||||||
result.locations.type === "parameter" && result.locations.position === "key"
|
result.locations.type === "parameter" && result.locations.position === "key"
|
||||||
)
|
)
|
||||||
|
|
||||||
const parameterValueResults = inspectionService.getResultViewFor(
|
const parameterValueResults = inspectionService.getResultViewFor(
|
||||||
currentTabID.value,
|
tabs.currentTabID.value,
|
||||||
(result) =>
|
(result) =>
|
||||||
result.locations.type === "parameter" &&
|
result.locations.type === "parameter" &&
|
||||||
result.locations.position === "value"
|
result.locations.position === "value"
|
||||||
|
|||||||
@@ -217,6 +217,7 @@
|
|||||||
@hide-modal="showCurlImportModal = false"
|
@hide-modal="showCurlImportModal = false"
|
||||||
/>
|
/>
|
||||||
<HttpCodegenModal
|
<HttpCodegenModal
|
||||||
|
v-if="showCodegenModal"
|
||||||
:show="showCodegenModal"
|
:show="showCodegenModal"
|
||||||
@hide-modal="showCodegenModal = false"
|
@hide-modal="showCodegenModal = false"
|
||||||
/>
|
/>
|
||||||
@@ -257,7 +258,6 @@ 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, 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"
|
||||||
@@ -265,6 +265,9 @@ import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
|||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const interceptorService = useService(InterceptorService)
|
const interceptorService = useService(InterceptorService)
|
||||||
@@ -286,7 +289,7 @@ const toast = useToast()
|
|||||||
|
|
||||||
const { subscribeToStream } = useStreamSubscriber()
|
const { subscribeToStream } = useStreamSubscriber()
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: HoppRESTTab }>()
|
const props = defineProps<{ modelValue: HoppTab<HoppRESTDocument> }>()
|
||||||
const emit = defineEmits(["update:modelValue"])
|
const emit = defineEmits(["update:modelValue"])
|
||||||
|
|
||||||
const tab = useVModel(props, "modelValue", emit)
|
const tab = useVModel(props, "modelValue", emit)
|
||||||
@@ -426,7 +429,7 @@ const updateMethod = (method: string) => {
|
|||||||
|
|
||||||
const onSelectMethod = (e: Event | any) => {
|
const onSelectMethod = (e: Event | any) => {
|
||||||
// type any because of value property not being recognized by TS in the event.target object. It is a valid property though.
|
// type any because of value property not being recognized by TS in the event.target object. It is a valid property though.
|
||||||
updateMethod(e.value)
|
updateMethod(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearContent = () => {
|
const clearContent = () => {
|
||||||
@@ -434,7 +437,7 @@ const clearContent = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateRESTResponse = (response: HoppRESTResponse | null) => {
|
const updateRESTResponse = (response: HoppRESTResponse | null) => {
|
||||||
tab.value.response = response
|
tab.value.document.response = response
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyLinkIcon = refAutoReset<
|
const copyLinkIcon = refAutoReset<
|
||||||
@@ -642,5 +645,6 @@ const COLUMN_LAYOUT = useSetting("COLUMN_LAYOUT")
|
|||||||
|
|
||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const tabResults = inspectionService.getResultViewFor(currentTabID.value)
|
const tabs = useService(RESTTabService)
|
||||||
|
const tabResults = inspectionService.getResultViewFor(tabs.currentTabID.value)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<HoppSmartTabs
|
<HoppSmartTabs
|
||||||
v-model="selectedOptionsTab"
|
v-model="selectedOptionTab"
|
||||||
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
|
||||||
>
|
>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<HttpBody
|
<HttpBody
|
||||||
v-model:headers="request.headers"
|
v-model:headers="request.headers"
|
||||||
v-model:body="request.body"
|
v-model:body="request.body"
|
||||||
@change-tab="changeTab"
|
@change-tab="changeOptionTab"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
:label="`${t('tab.headers')}`"
|
:label="`${t('tab.headers')}`"
|
||||||
:info="`${newActiveHeadersCount$}`"
|
:info="`${newActiveHeadersCount$}`"
|
||||||
>
|
>
|
||||||
<HttpHeaders v-model="request" @change-tab="changeTab" />
|
<HttpHeaders v-model="request" @change-tab="changeOptionTab" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
|
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
|
||||||
<HttpAuthorization v-model="request.auth" />
|
<HttpAuthorization v-model="request.auth" />
|
||||||
@@ -55,31 +55,43 @@
|
|||||||
import { useI18n } from "@composables/i18n"
|
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 } from "vue"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
|
||||||
export type RequestOptionTabs =
|
const VALID_OPTION_TABS = [
|
||||||
| "params"
|
"params",
|
||||||
| "bodyParams"
|
"bodyParams",
|
||||||
| "headers"
|
"headers",
|
||||||
| "authorization"
|
"authorization",
|
||||||
| "preRequestScript"
|
"preRequestScript",
|
||||||
| "tests"
|
"tests",
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type RESTOptionTabs = (typeof VALID_OPTION_TABS)[number]
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
// v-model integration with props and emit
|
// v-model integration with props and emit
|
||||||
const props = defineProps<{ modelValue: HoppRESTRequest }>()
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: HoppRESTRequest
|
||||||
|
optionTab: RESTOptionTabs
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
optionTab: "params",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:modelValue", value: HoppRESTRequest): void
|
(e: "update:modelValue", value: HoppRESTRequest): void
|
||||||
|
(e: "update:optionTab", value: RESTOptionTabs): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const request = useVModel(props, "modelValue", emit)
|
const request = useVModel(props, "modelValue", emit)
|
||||||
|
const selectedOptionTab = useVModel(props, "optionTab", emit)
|
||||||
|
|
||||||
const selectedOptionsTab = ref<RequestOptionTabs>("params")
|
const changeOptionTab = (e: RESTOptionTabs) => {
|
||||||
|
selectedOptionTab.value = e
|
||||||
const changeTab = (e: RequestOptionTabs) => {
|
|
||||||
selectedOptionsTab.value = e
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newActiveParamsCount$ = computed(() => {
|
const newActiveParamsCount$ = computed(() => {
|
||||||
@@ -101,6 +113,6 @@ const newActiveHeadersCount$ = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
defineActionHandler("request.open-tab", ({ tab }) => {
|
defineActionHandler("request.open-tab", ({ tab }) => {
|
||||||
selectedOptionsTab.value = tab as RequestOptionTabs
|
selectedOptionTab.value = tab as RESTOptionTabs
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
<AppPaneLayout layout-id="rest-primary">
|
<AppPaneLayout layout-id="rest-primary">
|
||||||
<template #primary>
|
<template #primary>
|
||||||
<HttpRequest v-model="tab" />
|
<HttpRequest v-model="tab" />
|
||||||
<HttpRequestOptions v-model="tab.document.request" />
|
<HttpRequestOptions
|
||||||
|
v-model="tab.document.request"
|
||||||
|
v-model:option-tab="tab.document.optionTabPreference"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #secondary>
|
<template #secondary>
|
||||||
<HttpResponse v-model:tab="tab" />
|
<HttpResponse v-model:document="tab.document" />
|
||||||
</template>
|
</template>
|
||||||
</AppPaneLayout>
|
</AppPaneLayout>
|
||||||
</template>
|
</template>
|
||||||
@@ -13,16 +16,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { watch } from "vue"
|
import { watch } from "vue"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { isEqualHoppRESTRequest } from "@hoppscotch/data"
|
import { isEqualHoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
// TODO: Move Response and Request execution code to over here
|
// TODO: Move Response and Request execution code to over here
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: HoppRESTTab }>()
|
const props = defineProps<{ modelValue: HoppTab<HoppRESTDocument> }>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:modelValue", val: HoppRESTTab): void
|
(e: "update:modelValue", val: HoppTab<HoppRESTDocument>): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tab = useVModel(props, "modelValue", emit)
|
const tab = useVModel(props, "modelValue", emit)
|
||||||
|
|||||||
@@ -1,36 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col flex-1 relative">
|
<div class="flex flex-col flex-1 relative">
|
||||||
<HttpResponseMeta :response="tab.response" />
|
<HttpResponseMeta :response="doc.response" />
|
||||||
<LensesResponseBodyRenderer
|
<LensesResponseBodyRenderer
|
||||||
v-if="!loading && hasResponse"
|
v-if="!loading && hasResponse"
|
||||||
v-model:selected-tab-preference="selectedTabPreference"
|
v-model:document="doc"
|
||||||
v-model:tab="tab"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from "vue"
|
|
||||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tab: HoppRESTTab
|
document: HoppRESTDocument
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:tab", val: HoppRESTTab): void
|
(e: "update:tab", val: HoppRESTDocument): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tab = useVModel(props, "tab", emit)
|
const doc = useVModel(props, "document", emit)
|
||||||
|
|
||||||
const selectedTabPreference = ref<string | null>(null)
|
|
||||||
|
|
||||||
const hasResponse = computed(
|
const hasResponse = computed(
|
||||||
() =>
|
() =>
|
||||||
tab.value.response?.type === "success" ||
|
doc.value.response?.type === "success" ||
|
||||||
tab.value.response?.type === "fail"
|
doc.value.response?.type === "fail"
|
||||||
)
|
)
|
||||||
|
|
||||||
const loading = computed(() => tab.value.response?.type === "loading")
|
const loading = computed(() => doc.value.response?.type === "loading")
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -93,10 +93,11 @@ import { useColorMode } from "@composables/theming"
|
|||||||
import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
import { currentTabID } from "~/helpers/rest/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
response: HoppRESTResponse | null | undefined
|
response: HoppRESTResponse | null | undefined
|
||||||
@@ -146,7 +147,7 @@ const statusCategory = computed(() => {
|
|||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const tabResults = inspectionService.getResultViewFor(
|
const tabResults = inspectionService.getResultViewFor(
|
||||||
currentTabID.value,
|
tabs.currentTabID.value,
|
||||||
(result) => result.locations.type === "response"
|
(result) => result.locations.type === "response"
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -96,16 +96,17 @@ import { ref } from "vue"
|
|||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
|
||||||
import IconXCircle from "~icons/lucide/x-circle"
|
import IconXCircle from "~icons/lucide/x-circle"
|
||||||
import IconXSquare from "~icons/lucide/x-square"
|
import IconXSquare from "~icons/lucide/x-square"
|
||||||
import IconFileEdit from "~icons/lucide/file-edit"
|
import IconFileEdit from "~icons/lucide/file-edit"
|
||||||
import IconCopy from "~icons/lucide/copy"
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
tab: HoppRESTTab
|
tab: HoppTab<HoppRESTDocument>
|
||||||
isRemovable: boolean
|
isRemovable: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<HoppSmartTabs
|
<HoppSmartTabs
|
||||||
v-if="tab.response"
|
v-if="doc.response"
|
||||||
v-model="selectedLensTab"
|
v-model="selectedLensTab"
|
||||||
styles="sticky overflow-x-auto flex-shrink-0 z-10 bg-primary top-lowerPrimaryStickyFold"
|
styles="sticky overflow-x-auto flex-shrink-0 z-10 bg-primary top-lowerPrimaryStickyFold"
|
||||||
>
|
>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="lensRendererFor(lens.renderer)"
|
:is="lensRendererFor(lens.renderer)"
|
||||||
:response="tab.response"
|
:response="doc.response"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
@@ -28,19 +28,10 @@
|
|||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
id="results"
|
id="results"
|
||||||
:label="t('test.results')"
|
:label="t('test.results')"
|
||||||
:indicator="
|
:indicator="showIndicator"
|
||||||
tab.testResults &&
|
|
||||||
(tab.testResults.expectResults.length ||
|
|
||||||
tab.testResults.tests.length ||
|
|
||||||
tab.testResults.envDiff.selected.additions.length ||
|
|
||||||
tab.testResults.envDiff.selected.updations.length ||
|
|
||||||
tab.testResults.envDiff.global.updations.length)
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
"
|
|
||||||
class="flex flex-col flex-1"
|
class="flex flex-col flex-1"
|
||||||
>
|
>
|
||||||
<HttpTestResult v-model="tab.testResults" />
|
<HttpTestResult v-model="doc.testResults" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
</HoppSmartTabs>
|
</HoppSmartTabs>
|
||||||
</template>
|
</template>
|
||||||
@@ -54,20 +45,30 @@ import {
|
|||||||
} from "~/helpers/lenses/lenses"
|
} from "~/helpers/lenses/lenses"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tab: HoppRESTTab
|
document: HoppRESTDocument
|
||||||
selectedTabPreference: string | null
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:tab", val: HoppRESTTab): void
|
(e: "update:document", document: HoppRESTDocument): void
|
||||||
(e: "update:selectedTabPreference", newTab: string): void
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tab = useVModel(props, "tab", emit)
|
const doc = useVModel(props, "document", emit)
|
||||||
const selectedTabPreference = useVModel(props, "selectedTabPreference", emit)
|
|
||||||
|
const showIndicator = computed(() => {
|
||||||
|
if (!doc.value.testResults) return false
|
||||||
|
|
||||||
|
const { expectResults, tests, envDiff } = doc.value.testResults
|
||||||
|
return Boolean(
|
||||||
|
expectResults.length ||
|
||||||
|
tests.length ||
|
||||||
|
envDiff.selected.additions.length ||
|
||||||
|
envDiff.selected.updations.length ||
|
||||||
|
envDiff.global.updations.length
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const allLensRenderers = getLensRenderers()
|
const allLensRenderers = getLensRenderers()
|
||||||
|
|
||||||
@@ -81,19 +82,19 @@ const selectedLensTab = ref("")
|
|||||||
|
|
||||||
const maybeHeaders = computed(() => {
|
const maybeHeaders = computed(() => {
|
||||||
if (
|
if (
|
||||||
!tab.value.response ||
|
!doc.value.response ||
|
||||||
!(
|
!(
|
||||||
tab.value.response.type === "success" ||
|
doc.value.response.type === "success" ||
|
||||||
tab.value.response.type === "fail"
|
doc.value.response.type === "fail"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
return tab.value.response.headers
|
return doc.value.response.headers
|
||||||
})
|
})
|
||||||
|
|
||||||
const validLenses = computed(() => {
|
const validLenses = computed(() => {
|
||||||
if (!tab.value.response) return []
|
if (!doc.value.response) return []
|
||||||
return getSuitableLenses(tab.value.response)
|
return getSuitableLenses(doc.value.response)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -107,11 +108,13 @@ watch(
|
|||||||
"results",
|
"results",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const { responseTabPreference } = doc.value
|
||||||
|
|
||||||
if (
|
if (
|
||||||
selectedTabPreference.value &&
|
responseTabPreference &&
|
||||||
validRenderers.includes(selectedTabPreference.value)
|
validRenderers.includes(responseTabPreference)
|
||||||
) {
|
) {
|
||||||
selectedLensTab.value = selectedTabPreference.value
|
selectedLensTab.value = responseTabPreference
|
||||||
} else {
|
} else {
|
||||||
selectedLensTab.value = newLenses[0].renderer
|
selectedLensTab.value = newLenses[0].renderer
|
||||||
}
|
}
|
||||||
@@ -120,6 +123,6 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
watch(selectedLensTab, (newLensID) => {
|
watch(selectedLensTab, (newLensID) => {
|
||||||
selectedTabPreference.value = newLensID
|
doc.value.responseTabPreference = newLensID
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -35,12 +35,12 @@
|
|||||||
v-if="
|
v-if="
|
||||||
!teamDetails.loading &&
|
!teamDetails.loading &&
|
||||||
E.isRight(teamDetails.data) &&
|
E.isRight(teamDetails.data) &&
|
||||||
teamDetails.data.right.team.teamMembers
|
teamDetails.data.right.team?.teamMembers
|
||||||
"
|
"
|
||||||
class="border rounded border-divider"
|
class="border rounded border-divider"
|
||||||
>
|
>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="teamDetails.data.right.team.teamMembers === 0"
|
v-if="teamDetails.data.right.team.teamMembers.length === 0"
|
||||||
:src="`/images/states/${colorMode.value}/add_group.svg`"
|
:src="`/images/states/${colorMode.value}/add_group.svg`"
|
||||||
:alt="`${t('empty.members')}`"
|
:alt="`${t('empty.members')}`"
|
||||||
:text="t('empty.members')"
|
:text="t('empty.members')"
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(invitee, index) in pendingInvites.data.right.team
|
v-for="(invitee, index) in pendingInvites.data.right.team
|
||||||
.teamInvitations"
|
?.teamInvitations"
|
||||||
:key="`invitee-${index}`"
|
:key="`invitee-${index}`"
|
||||||
class="flex divide-x divide-dividerLight"
|
class="flex divide-x divide-dividerLight"
|
||||||
>
|
>
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="
|
v-if="
|
||||||
E.isRight(pendingInvites.data) &&
|
E.isRight(pendingInvites.data) &&
|
||||||
pendingInvites.data.right.team.teamInvitations.length === 0
|
pendingInvites.data.right.team?.teamInvitations.length === 0
|
||||||
"
|
"
|
||||||
:text="t('empty.pending_invites')"
|
:text="t('empty.pending_invites')"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
WatchStopHandle,
|
WatchStopHandle,
|
||||||
watchSyncEffect,
|
watchSyncEffect,
|
||||||
watch,
|
watch,
|
||||||
|
nextTick,
|
||||||
} from "vue"
|
} from "vue"
|
||||||
import {
|
import {
|
||||||
client,
|
client,
|
||||||
@@ -101,7 +102,7 @@ export const useGQLQuery = <
|
|||||||
|
|
||||||
const rerunQuery = () => {
|
const rerunQuery = () => {
|
||||||
source.value = !isPaused.value
|
source.value = !isPaused.value
|
||||||
? client.value.executeQuery<DocType, DocVarType>(request.value, {
|
? client.value?.executeQuery<DocType, DocVarType>(request.value, {
|
||||||
requestPolicy: "network-only",
|
requestPolicy: "network-only",
|
||||||
})
|
})
|
||||||
: undefined
|
: undefined
|
||||||
@@ -126,7 +127,7 @@ export const useGQLQuery = <
|
|||||||
|
|
||||||
const invalidateStops = args.updateSubs!.map((sub) => {
|
const invalidateStops = args.updateSubs!.map((sub) => {
|
||||||
return wonkaPipe(
|
return wonkaPipe(
|
||||||
client.value.executeSubscription(sub),
|
client.value!.executeSubscription(sub),
|
||||||
onEnd(() => {
|
onEnd(() => {
|
||||||
if (source.value) execute()
|
if (source.value) execute()
|
||||||
}),
|
}),
|
||||||
@@ -191,10 +192,12 @@ export const useGQLQuery = <
|
|||||||
} else {
|
} else {
|
||||||
args.variables = updatedVars
|
args.variables = updatedVars
|
||||||
}
|
}
|
||||||
|
nextTick(rerunQuery)
|
||||||
|
} else {
|
||||||
|
rerunQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
isPaused.value = false
|
isPaused.value = false
|
||||||
rerunQuery()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pause = () => {
|
const pause = () => {
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ import {
|
|||||||
setGlobalEnvVariables,
|
setGlobalEnvVariables,
|
||||||
updateEnvironment,
|
updateEnvironment,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import { HoppRESTTab } from "./rest/tab"
|
|
||||||
import { Ref } from "vue"
|
import { Ref } from "vue"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "./rest/document"
|
||||||
|
|
||||||
const getTestableBody = (
|
const getTestableBody = (
|
||||||
res: HoppRESTResponse & { type: "success" | "fail" }
|
res: HoppRESTResponse & { type: "success" | "fail" }
|
||||||
@@ -69,7 +70,7 @@ export const executedResponses$ = new Subject<
|
|||||||
>()
|
>()
|
||||||
|
|
||||||
export function runRESTRequest$(
|
export function runRESTRequest$(
|
||||||
tab: Ref<HoppRESTTab>
|
tab: Ref<HoppTab<HoppRESTDocument>>
|
||||||
): [
|
): [
|
||||||
() => void,
|
() => void,
|
||||||
Promise<
|
Promise<
|
||||||
@@ -127,7 +128,7 @@ export function runRESTRequest$(
|
|||||||
)()
|
)()
|
||||||
|
|
||||||
if (E.isRight(runResult)) {
|
if (E.isRight(runResult)) {
|
||||||
tab.value.testResults = translateToSandboxTestResults(
|
tab.value.document.testResults = translateToSandboxTestResults(
|
||||||
runResult.right
|
runResult.right
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -163,7 +164,7 @@ export function runRESTRequest$(
|
|||||||
)()
|
)()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tab.value.testResults = {
|
tab.value.document.testResults = {
|
||||||
description: "",
|
description: "",
|
||||||
expectResults: [],
|
expectResults: [],
|
||||||
tests: [],
|
tests: [],
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Ref, onBeforeUnmount, onMounted, reactive, watch } from "vue"
|
|||||||
import { BehaviorSubject } from "rxjs"
|
import { BehaviorSubject } from "rxjs"
|
||||||
import { HoppRESTDocument } from "./rest/document"
|
import { HoppRESTDocument } from "./rest/document"
|
||||||
import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { RequestOptionTabs } from "~/components/http/RequestOptions.vue"
|
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||||
import { HoppGQLSaveContext } from "./graphql/document"
|
import { HoppGQLSaveContext } from "./graphql/document"
|
||||||
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
@@ -113,7 +113,7 @@ type HoppActionArgsMap = {
|
|||||||
request: HoppGQLRequest
|
request: HoppGQLRequest
|
||||||
}
|
}
|
||||||
"request.open-tab": {
|
"request.open-tab": {
|
||||||
tab: RequestOptionTabs | GQLOptionTabs
|
tab: RESTOptionTabs | GQLOptionTabs
|
||||||
}
|
}
|
||||||
|
|
||||||
"tab.duplicate-tab": {
|
"tab.duplicate-tab": {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { getTabsRefTo } from "../rest/tab"
|
|
||||||
import { getAffectedIndexes } from "./affectedIndex"
|
import { getAffectedIndexes } from "./affectedIndex"
|
||||||
import { GetSingleRequestDocument } from "../backend/graphql"
|
import { GetSingleRequestDocument } from "../backend/graphql"
|
||||||
import { runGQLQuery } from "../backend/GQLClient"
|
import { runGQLQuery } from "../backend/GQLClient"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve save context on reorder
|
* Resolve save context on reorder
|
||||||
@@ -56,7 +57,9 @@ export function resolveSaveContextOnCollectionReorder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = getTabsRefTo((tab) => {
|
const tabService = getService(RESTTabService)
|
||||||
|
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return (
|
return (
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
affectedPaths.has(tab.document.saveContext.folderPath)
|
affectedPaths.has(tab.document.saveContext.folderPath)
|
||||||
@@ -84,7 +87,8 @@ export function updateSaveContextForAffectedRequests(
|
|||||||
oldFolderPath: string,
|
oldFolderPath: string,
|
||||||
newFolderPath: string
|
newFolderPath: string
|
||||||
) {
|
) {
|
||||||
const tabs = getTabsRefTo((tab) => {
|
const tabService = getService(RESTTabService)
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return (
|
return (
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
tab.document.saveContext.folderPath.startsWith(oldFolderPath)
|
tab.document.saveContext.folderPath.startsWith(oldFolderPath)
|
||||||
@@ -105,7 +109,8 @@ export function updateSaveContextForAffectedRequests(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resetSaveContextForAffectedRequests(folderPath: string) {
|
function resetSaveContextForAffectedRequests(folderPath: string) {
|
||||||
const tabs = getTabsRefTo((tab) => {
|
const tabService = getService(RESTTabService)
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return (
|
return (
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
tab.document.saveContext.folderPath.startsWith(folderPath)
|
tab.document.saveContext.folderPath.startsWith(folderPath)
|
||||||
@@ -124,7 +129,8 @@ function resetSaveContextForAffectedRequests(folderPath: string) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export async function resetTeamRequestsContext() {
|
export async function resetTeamRequestsContext() {
|
||||||
const tabs = getTabsRefTo((tab) => {
|
const tabService = getService(RESTTabService)
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return tab.document.saveContext?.originLocation === "team-collection"
|
return tab.document.saveContext?.originLocation === "team-collection"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { getTabsRefTo } from "../rest/tab"
|
|
||||||
import { getAffectedIndexes } from "./affectedIndex"
|
import { getAffectedIndexes } from "./affectedIndex"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve save context on reorder
|
* Resolve save context on reorder
|
||||||
@@ -32,7 +33,8 @@ export function resolveSaveContextOnRequestReorder(payload: {
|
|||||||
// if (newIndex === -1) remove it from the map because it will be deleted
|
// if (newIndex === -1) remove it from the map because it will be deleted
|
||||||
if (newIndex === -1) affectedIndexes.delete(lastIndex)
|
if (newIndex === -1) affectedIndexes.delete(lastIndex)
|
||||||
|
|
||||||
const tabs = getTabsRefTo((tab) => {
|
const tabService = getService(RESTTabService)
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return (
|
return (
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
tab.document.saveContext.folderPath === folderPath &&
|
tab.document.saveContext.folderPath === folderPath &&
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { GQLHeader, HoppGQLAuth, makeGQLRequest } from "@hoppscotch/data"
|
import { GQLHeader, HoppGQLAuth, makeGQLRequest } from "@hoppscotch/data"
|
||||||
import { OperationType } from "@urql/core"
|
import { OperationType } from "@urql/core"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
import {
|
import {
|
||||||
GraphQLEnumType,
|
GraphQLEnumType,
|
||||||
GraphQLInputObjectType,
|
GraphQLInputObjectType,
|
||||||
@@ -11,11 +12,12 @@ import {
|
|||||||
printSchema,
|
printSchema,
|
||||||
} from "graphql"
|
} from "graphql"
|
||||||
import { computed, reactive, ref } from "vue"
|
import { computed, reactive, ref } from "vue"
|
||||||
import { addGraphqlHistoryEntry, makeGQLHistoryEntry } from "~/newstore/history"
|
|
||||||
import { currentTabID } from "./tab"
|
|
||||||
import { getService } from "~/modules/dioc"
|
import { getService } from "~/modules/dioc"
|
||||||
|
|
||||||
|
import { addGraphqlHistoryEntry, makeGQLHistoryEntry } from "~/newstore/history"
|
||||||
|
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
import * as E from "fp-ts/Either"
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const GQL_SCHEMA_POLL_INTERVAL = 7000
|
const GQL_SCHEMA_POLL_INTERVAL = 7000
|
||||||
|
|
||||||
@@ -61,6 +63,9 @@ type Connection = {
|
|||||||
schema: GraphQLSchema | null
|
schema: GraphQLSchema | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabs = getService(GQLTabService)
|
||||||
|
const currentTabID = computed(() => tabs.currentTabID.value)
|
||||||
|
|
||||||
export const connection = reactive<Connection>({
|
export const connection = reactive<Connection>({
|
||||||
state: "DISCONNECTED",
|
state: "DISCONNECTED",
|
||||||
subscriptionState: new Map<string, SubscriptionState>(),
|
subscriptionState: new Map<string, SubscriptionState>(),
|
||||||
@@ -268,7 +273,7 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (operationType === "subscription") {
|
if (operationType === "subscription") {
|
||||||
return runSubscription(options)
|
return runSubscription(options, finalHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
const interceptorService = getService(InterceptorService)
|
const interceptorService = getService(InterceptorService)
|
||||||
@@ -299,7 +304,10 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||||||
return responseText
|
return responseText
|
||||||
}
|
}
|
||||||
|
|
||||||
export const runSubscription = (options: RunQueryOptions) => {
|
export const runSubscription = (
|
||||||
|
options: RunQueryOptions,
|
||||||
|
headers?: Record<string, string>
|
||||||
|
) => {
|
||||||
const { url, query, operationName } = options
|
const { url, query, operationName } = options
|
||||||
const wsUrl = url.replace(/^http/, "ws")
|
const wsUrl = url.replace(/^http/, "ws")
|
||||||
|
|
||||||
@@ -309,10 +317,11 @@ export const runSubscription = (options: RunQueryOptions) => {
|
|||||||
|
|
||||||
connection.socket.onopen = (event) => {
|
connection.socket.onopen = (event) => {
|
||||||
console.log("WebSocket is open now.", event)
|
console.log("WebSocket is open now.", event)
|
||||||
|
|
||||||
connection.socket?.send(
|
connection.socket?.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: GQL.CONNECTION_INIT,
|
type: GQL.CONNECTION_INIT,
|
||||||
payload: {},
|
payload: headers ?? {},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||||
|
import { GQLResponseEvent } from "./connection"
|
||||||
|
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
||||||
|
|
||||||
export type HoppGQLSaveContext =
|
export type HoppGQLSaveContext =
|
||||||
| {
|
| {
|
||||||
@@ -55,4 +57,20 @@ export type HoppGQLDocument = {
|
|||||||
* This contains where the request is originated from basically.
|
* This contains where the request is originated from basically.
|
||||||
*/
|
*/
|
||||||
saveContext?: HoppGQLSaveContext
|
saveContext?: HoppGQLSaveContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The response as it is in the document
|
||||||
|
* (if any)
|
||||||
|
*/
|
||||||
|
response?: GQLResponseEvent[] | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response tab preference for the current tab's document
|
||||||
|
*/
|
||||||
|
responseTabPreference?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options tab preference for the current tab's document
|
||||||
|
*/
|
||||||
|
optionTabPreference?: GQLOptionTabs
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,226 +0,0 @@
|
|||||||
import { refWithControl } from "@vueuse/core"
|
|
||||||
import { isEqual } from "lodash-es"
|
|
||||||
import { v4 as uuidV4 } from "uuid"
|
|
||||||
import { computed, reactive, ref, shallowReadonly, watch } from "vue"
|
|
||||||
import { HoppTestResult } from "../types/HoppTestResult"
|
|
||||||
import { GQLResponseEvent } from "./connection"
|
|
||||||
import { getDefaultGQLRequest } from "./default"
|
|
||||||
import { HoppGQLDocument, HoppGQLSaveContext } from "./document"
|
|
||||||
|
|
||||||
export type HoppGQLTab = {
|
|
||||||
id: string
|
|
||||||
document: HoppGQLDocument
|
|
||||||
response?: GQLResponseEvent[] | null
|
|
||||||
testResults?: HoppTestResult | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PersistableGQLTabState = {
|
|
||||||
lastActiveTabID: string
|
|
||||||
orderedDocs: Array<{
|
|
||||||
tabID: string
|
|
||||||
doc: HoppGQLDocument
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const currentTabID = refWithControl("test", {
|
|
||||||
onBeforeChange(newTabID) {
|
|
||||||
if (!newTabID || !tabMap.has(newTabID)) {
|
|
||||||
console.warn(
|
|
||||||
`Tried to set current tab id to an invalid value. (value: ${newTabID})`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Don't allow change
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const tabMap = reactive(
|
|
||||||
new Map<string, HoppGQLTab>([
|
|
||||||
[
|
|
||||||
"test",
|
|
||||||
{
|
|
||||||
id: "test",
|
|
||||||
document: {
|
|
||||||
request: getDefaultGQLRequest(),
|
|
||||||
isDirty: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
])
|
|
||||||
)
|
|
||||||
const tabOrdering = ref<string[]>(["test"])
|
|
||||||
|
|
||||||
watch(
|
|
||||||
tabOrdering,
|
|
||||||
(newOrdering) => {
|
|
||||||
if (!currentTabID.value || !newOrdering.includes(currentTabID.value)) {
|
|
||||||
currentTabID.value = newOrdering[newOrdering.length - 1] // newOrdering should always be non-empty
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
export const persistableTabState = computed<PersistableGQLTabState>(() => ({
|
|
||||||
lastActiveTabID: currentTabID.value,
|
|
||||||
orderedDocs: tabOrdering.value.map((tabID) => {
|
|
||||||
const tab = tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
|
|
||||||
return {
|
|
||||||
tabID: tab.id,
|
|
||||||
doc: tab.document,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const currentActiveTab = computed(() => tabMap.get(currentTabID.value)!) // Guaranteed to not be undefined
|
|
||||||
|
|
||||||
// TODO: Mark this unknown and do validations
|
|
||||||
export function loadTabsFromPersistedState(data: PersistableGQLTabState) {
|
|
||||||
if (data) {
|
|
||||||
tabMap.clear()
|
|
||||||
tabOrdering.value = []
|
|
||||||
|
|
||||||
for (const doc of data.orderedDocs) {
|
|
||||||
tabMap.set(doc.tabID, {
|
|
||||||
id: doc.tabID,
|
|
||||||
document: doc.doc,
|
|
||||||
})
|
|
||||||
|
|
||||||
tabOrdering.value.push(doc.tabID)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTabID.value = data.lastActiveTabID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the active Tab IDs in order
|
|
||||||
*/
|
|
||||||
export function getActiveTabs() {
|
|
||||||
return shallowReadonly(
|
|
||||||
computed(() => tabOrdering.value.map((x) => tabMap.get(x)!))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTabRef(tabID: string) {
|
|
||||||
return computed({
|
|
||||||
get() {
|
|
||||||
const result = tabMap.get(tabID)
|
|
||||||
|
|
||||||
if (result === undefined) throw new Error(`Invalid tab id: ${tabID}`)
|
|
||||||
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
return tabMap.set(tabID, value)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateNewTabID() {
|
|
||||||
while (true) {
|
|
||||||
const id = uuidV4()
|
|
||||||
|
|
||||||
if (!tabMap.has(id)) return id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTab(tabUpdate: HoppGQLTab) {
|
|
||||||
if (!tabMap.has(tabUpdate.id)) {
|
|
||||||
console.warn(
|
|
||||||
`Cannot update tab as tab with that tab id does not exist (id: ${tabUpdate.id})`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
tabMap.set(tabUpdate.id, tabUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createNewTab(document: HoppGQLDocument, switchToIt = true) {
|
|
||||||
const id = generateNewTabID()
|
|
||||||
|
|
||||||
const tab: HoppGQLTab = { id, document }
|
|
||||||
|
|
||||||
tabMap.set(id, tab)
|
|
||||||
tabOrdering.value.push(id)
|
|
||||||
|
|
||||||
if (switchToIt) {
|
|
||||||
currentTabID.value = id
|
|
||||||
}
|
|
||||||
|
|
||||||
return tab
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTabOrdering(fromIndex: number, toIndex: number) {
|
|
||||||
tabOrdering.value.splice(
|
|
||||||
toIndex,
|
|
||||||
0,
|
|
||||||
tabOrdering.value.splice(fromIndex, 1)[0]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeTab(tabID: string) {
|
|
||||||
if (!tabMap.has(tabID)) {
|
|
||||||
console.warn(`Tried to close a tab which does not exist (tab id: ${tabID})`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tabOrdering.value.length === 1) {
|
|
||||||
console.warn(
|
|
||||||
`Tried to close the only tab open, which is not allowed. (tab id: ${tabID})`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tabOrdering.value.splice(tabOrdering.value.indexOf(tabID), 1)
|
|
||||||
|
|
||||||
tabMap.delete(tabID)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTabRefWithSaveContext(ctx: HoppGQLSaveContext) {
|
|
||||||
for (const tab of tabMap.values()) {
|
|
||||||
// For `team-collection` request id can be considered unique
|
|
||||||
if (ctx && ctx.originLocation === "team-collection") {
|
|
||||||
if (
|
|
||||||
tab.document.saveContext?.originLocation === "team-collection" &&
|
|
||||||
tab.document.saveContext.requestID === ctx.requestID
|
|
||||||
) {
|
|
||||||
return getTabRef(tab.id)
|
|
||||||
}
|
|
||||||
} else if (isEqual(ctx, tab.document.saveContext)) return getTabRef(tab.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTabsRefTo(func: (tab: HoppGQLTab) => boolean) {
|
|
||||||
return Array.from(tabMap.values())
|
|
||||||
.filter(func)
|
|
||||||
.map((tab) => getTabRef(tab.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Environment } from "@hoppscotch/data"
|
||||||
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
|
import { cloneDeep } from "lodash-es"
|
||||||
|
|
||||||
|
const getEnvironmentJson = (
|
||||||
|
environmentObj: TeamEnvironment | Environment,
|
||||||
|
environmentIndex?: number | "Global" | null
|
||||||
|
) => {
|
||||||
|
const newEnvironment =
|
||||||
|
"environment" in environmentObj
|
||||||
|
? cloneDeep(environmentObj.environment)
|
||||||
|
: cloneDeep(environmentObj)
|
||||||
|
|
||||||
|
delete newEnvironment.id
|
||||||
|
|
||||||
|
const environmentId =
|
||||||
|
environmentIndex || environmentIndex === 0
|
||||||
|
? environmentIndex
|
||||||
|
: environmentObj.id
|
||||||
|
|
||||||
|
return environmentId !== null
|
||||||
|
? JSON.stringify(newEnvironment, null, 2)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exportAsJSON = (
|
||||||
|
environmentObj: Environment | TeamEnvironment,
|
||||||
|
environmentIndex?: number | "Global" | null
|
||||||
|
): boolean => {
|
||||||
|
const dataToWrite = getEnvironmentJson(environmentObj, environmentIndex)
|
||||||
|
|
||||||
|
if (!dataToWrite) return false
|
||||||
|
|
||||||
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
|
const a = document.createElement("a")
|
||||||
|
const url = URL.createObjectURL(file)
|
||||||
|
a.href = url
|
||||||
|
|
||||||
|
// Extracts the path from url, removes fragment identifier and query parameters if any, appends the ".json" extension, and assigns it
|
||||||
|
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
}, 0)
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -131,7 +131,8 @@ function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPressedKey(ev: KeyboardEvent): Key | null {
|
function getPressedKey(ev: KeyboardEvent): Key | null {
|
||||||
const val = ev.code.toLowerCase()
|
// Sometimes the property code is not available on the KeyboardEvent object
|
||||||
|
const val = (ev.code ?? "").toLowerCase()
|
||||||
|
|
||||||
// Check arrow keys
|
// Check arrow keys
|
||||||
if (val === "arrowup") return "up"
|
if (val === "arrowup") return "up"
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
||||||
|
import { HoppTestResult } from "../types/HoppTestResult"
|
||||||
|
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||||
|
|
||||||
export type HoppRESTSaveContext =
|
export type HoppRESTSaveContext =
|
||||||
| {
|
| {
|
||||||
@@ -55,4 +58,26 @@ export type HoppRESTDocument = {
|
|||||||
* This contains where the request is originated from basically.
|
* This contains where the request is originated from basically.
|
||||||
*/
|
*/
|
||||||
saveContext?: HoppRESTSaveContext
|
saveContext?: HoppRESTSaveContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The response as it is in the document
|
||||||
|
* (if any)
|
||||||
|
*/
|
||||||
|
response?: HoppRESTResponse | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The test results as it is in the document
|
||||||
|
* (if any)
|
||||||
|
*/
|
||||||
|
testResults?: HoppTestResult | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response tab preference for the current tab's document
|
||||||
|
*/
|
||||||
|
responseTabPreference?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options tab preference for the current tab's document
|
||||||
|
*/
|
||||||
|
optionTabPreference?: RESTOptionTabs
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,234 +0,0 @@
|
|||||||
import { v4 as uuidV4 } from "uuid"
|
|
||||||
import { isEqual } from "lodash-es"
|
|
||||||
import { reactive, watch, computed, ref, shallowReadonly } from "vue"
|
|
||||||
import { HoppRESTDocument, HoppRESTSaveContext } from "./document"
|
|
||||||
import { refWithControl } from "@vueuse/core"
|
|
||||||
import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
|
||||||
import { getDefaultRESTRequest } from "./default"
|
|
||||||
import { HoppTestResult } from "../types/HoppTestResult"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { nextTick } from "vue"
|
|
||||||
|
|
||||||
export type HoppRESTTab = {
|
|
||||||
id: string
|
|
||||||
document: HoppRESTDocument
|
|
||||||
response?: HoppRESTResponse | null
|
|
||||||
testResults?: HoppTestResult | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PersistableRESTTabState = {
|
|
||||||
lastActiveTabID: string
|
|
||||||
orderedDocs: Array<{
|
|
||||||
tabID: string
|
|
||||||
doc: HoppRESTDocument
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const currentTabID = refWithControl("test", {
|
|
||||||
onBeforeChange(newTabID) {
|
|
||||||
if (!newTabID || !tabMap.has(newTabID)) {
|
|
||||||
console.warn(
|
|
||||||
`Tried to set current tab id to an invalid value. (value: ${newTabID})`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Don't allow change
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const tabMap = reactive(
|
|
||||||
new Map<string, HoppRESTTab>([
|
|
||||||
[
|
|
||||||
"test",
|
|
||||||
{
|
|
||||||
id: "test",
|
|
||||||
document: {
|
|
||||||
request: getDefaultRESTRequest(),
|
|
||||||
isDirty: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
])
|
|
||||||
)
|
|
||||||
const tabOrdering = ref<string[]>(["test"])
|
|
||||||
|
|
||||||
watch(
|
|
||||||
tabOrdering,
|
|
||||||
(newOrdering) => {
|
|
||||||
if (!currentTabID.value || !newOrdering.includes(currentTabID.value)) {
|
|
||||||
currentTabID.value = newOrdering[newOrdering.length - 1] // newOrdering should always be non-empty
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
export const persistableTabState = computed<PersistableRESTTabState>(() => ({
|
|
||||||
lastActiveTabID: currentTabID.value,
|
|
||||||
orderedDocs: tabOrdering.value.map((tabID) => {
|
|
||||||
const tab = tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
|
|
||||||
return {
|
|
||||||
tabID: tab.id,
|
|
||||||
doc: tab.document,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const currentActiveTab = computed(() => tabMap.get(currentTabID.value)!) // Guaranteed to not be undefined
|
|
||||||
|
|
||||||
// TODO: Mark this unknown and do validations
|
|
||||||
export function loadTabsFromPersistedState(data: PersistableRESTTabState) {
|
|
||||||
if (data) {
|
|
||||||
tabMap.clear()
|
|
||||||
tabOrdering.value = []
|
|
||||||
|
|
||||||
for (const doc of data.orderedDocs) {
|
|
||||||
tabMap.set(doc.tabID, {
|
|
||||||
id: doc.tabID,
|
|
||||||
document: doc.doc,
|
|
||||||
})
|
|
||||||
|
|
||||||
tabOrdering.value.push(doc.tabID)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTabID.value = data.lastActiveTabID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the active Tab IDs in order
|
|
||||||
*/
|
|
||||||
export function getActiveTabs() {
|
|
||||||
return shallowReadonly(
|
|
||||||
computed(() => tabOrdering.value.map((x) => tabMap.get(x)!))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTabRef(tabID: string) {
|
|
||||||
return computed({
|
|
||||||
get() {
|
|
||||||
const result = tabMap.get(tabID)
|
|
||||||
|
|
||||||
if (result === undefined) throw new Error(`Invalid tab id: ${tabID}`)
|
|
||||||
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
return tabMap.set(tabID, value)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateNewTabID() {
|
|
||||||
while (true) {
|
|
||||||
const id = uuidV4()
|
|
||||||
|
|
||||||
if (!tabMap.has(id)) return id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTab(tabUpdate: HoppRESTTab) {
|
|
||||||
if (!tabMap.has(tabUpdate.id)) {
|
|
||||||
console.warn(
|
|
||||||
`Cannot update tab as tab with that tab id does not exist (id: ${tabUpdate.id})`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
tabMap.set(tabUpdate.id, tabUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createNewTab(document: HoppRESTDocument, switchToIt = true) {
|
|
||||||
const id = generateNewTabID()
|
|
||||||
|
|
||||||
const tab: HoppRESTTab = { id, document }
|
|
||||||
|
|
||||||
tabMap.set(id, tab)
|
|
||||||
tabOrdering.value.push(id)
|
|
||||||
|
|
||||||
if (switchToIt) {
|
|
||||||
currentTabID.value = id
|
|
||||||
}
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_REST_NEW_TAB_OPENED",
|
|
||||||
})
|
|
||||||
|
|
||||||
return tab
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTabOrdering(fromIndex: number, toIndex: number) {
|
|
||||||
tabOrdering.value.splice(
|
|
||||||
toIndex,
|
|
||||||
0,
|
|
||||||
tabOrdering.value.splice(fromIndex, 1)[0]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeTab(tabID: string) {
|
|
||||||
if (!tabMap.has(tabID)) {
|
|
||||||
console.warn(`Tried to close a tab which does not exist (tab id: ${tabID})`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tabOrdering.value.length === 1) {
|
|
||||||
console.warn(
|
|
||||||
`Tried to close the only tab open, which is not allowed. (tab id: ${tabID})`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tabOrdering.value.splice(tabOrdering.value.indexOf(tabID), 1)
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
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) {
|
|
||||||
for (const tab of tabMap.values()) {
|
|
||||||
// For `team-collection` request id can be considered unique
|
|
||||||
if (ctx && ctx.originLocation === "team-collection") {
|
|
||||||
if (
|
|
||||||
tab.document.saveContext?.originLocation === "team-collection" &&
|
|
||||||
tab.document.saveContext.requestID === ctx.requestID
|
|
||||||
) {
|
|
||||||
return getTabRef(tab.id)
|
|
||||||
}
|
|
||||||
} else if (isEqual(ctx, tab.document.saveContext)) return getTabRef(tab.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTabsRefTo(func: (tab: HoppRESTTab) => boolean) {
|
|
||||||
return Array.from(tabMap.values())
|
|
||||||
.filter(func)
|
|
||||||
.map((tab) => getTabRef(tab.id))
|
|
||||||
}
|
|
||||||
@@ -198,11 +198,11 @@ export const resolvesEnvsInBody = (
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
return {
|
|
||||||
contentType: body.contentType,
|
return {
|
||||||
body: parseTemplateString(body.body, env.variables),
|
contentType: body.contentType,
|
||||||
}
|
body: parseTemplateString(body.body ?? "", env.variables),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,9 +210,7 @@ function getFinalBodyFromRequest(
|
|||||||
request: HoppRESTRequest,
|
request: HoppRESTRequest,
|
||||||
envVariables: Environment["variables"]
|
envVariables: Environment["variables"]
|
||||||
): FormData | string | null {
|
): FormData | string | null {
|
||||||
if (request.body.contentType === null) {
|
if (request.body.contentType === null) return null
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.body.contentType === "application/x-www-form-urlencoded") {
|
if (request.body.contentType === "application/x-www-form-urlencoded") {
|
||||||
const parsedBodyRecord = pipe(
|
const parsedBodyRecord = pipe(
|
||||||
@@ -280,7 +278,10 @@ function getFinalBodyFromRequest(
|
|||||||
),
|
),
|
||||||
toFormData
|
toFormData
|
||||||
)
|
)
|
||||||
} else return parseBodyEnvVariables(request.body.body, envVariables)
|
}
|
||||||
|
|
||||||
|
// body can be null if the content-type is not set
|
||||||
|
return parseBodyEnvVariables(request.body.body ?? "", envVariables)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -58,7 +58,13 @@ export const FALLBACK_LANG = pipe(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// A reference to the i18n instance
|
// A reference to the i18n instance
|
||||||
let i18nInstance: I18n<any, any, any> | null = null
|
let i18nInstance: I18n<
|
||||||
|
Record<string, unknown>,
|
||||||
|
Record<string, unknown>,
|
||||||
|
Record<string, unknown>,
|
||||||
|
string,
|
||||||
|
true
|
||||||
|
> | null = null
|
||||||
|
|
||||||
const resolveCurrentLocale = () =>
|
const resolveCurrentLocale = () =>
|
||||||
pipe(
|
pipe(
|
||||||
@@ -119,7 +125,6 @@ export const changeAppLanguage = async (locale: string) => {
|
|||||||
* Returns the i18n instance
|
* Returns the i18n instance
|
||||||
*/
|
*/
|
||||||
export function getI18n() {
|
export function getI18n() {
|
||||||
// @ts-expect-error Something weird with the i18n errors
|
|
||||||
return i18nInstance!.global.t
|
return i18nInstance!.global.t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
import { HoppModule } from "."
|
|
||||||
import * as Sentry from "@sentry/vue"
|
|
||||||
import { BrowserTracing } from "@sentry/tracing"
|
|
||||||
import { Route } from "@sentry/vue/types/router"
|
|
||||||
import { RouteLocationNormalized, Router } from "vue-router"
|
|
||||||
import { settingsStore } from "~/newstore/settings"
|
|
||||||
import { App } from "vue"
|
|
||||||
import { APP_IS_IN_DEV_MODE } from "~/helpers/dev"
|
|
||||||
import { gqlClientError$ } from "~/helpers/backend/GQLClient"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The tag names we allow giving to Sentry
|
|
||||||
*/
|
|
||||||
type SentryTag = "BACKEND_OPERATIONS"
|
|
||||||
|
|
||||||
interface SentryVueRouter {
|
|
||||||
onError: (fn: (err: Error) => void) => void
|
|
||||||
beforeEach: (fn: (to: Route, from: Route, next: () => void) => void) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizedRouteToSentryRoute(route: RouteLocationNormalized): Route {
|
|
||||||
return {
|
|
||||||
matched: route.matched,
|
|
||||||
// route.params' type translates just to a fancy version of this, hence assertion
|
|
||||||
params: route.params as Route["params"],
|
|
||||||
path: route.path,
|
|
||||||
// route.query's type translates just to a fancy version of this, hence assertion
|
|
||||||
query: route.query as Route["query"],
|
|
||||||
name: route.name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInstrumentationVueRouter(router: Router): SentryVueRouter {
|
|
||||||
return <SentryVueRouter>{
|
|
||||||
onError: router.onError,
|
|
||||||
beforeEach(func) {
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
func(
|
|
||||||
normalizedRouteToSentryRoute(to),
|
|
||||||
normalizedRouteToSentryRoute(from),
|
|
||||||
next
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let sentryActive = false
|
|
||||||
|
|
||||||
function initSentry(dsn: string, router: Router, app: App) {
|
|
||||||
Sentry.init({
|
|
||||||
app,
|
|
||||||
dsn,
|
|
||||||
release: import.meta.env.VITE_SENTRY_RELEASE_TAG ?? undefined,
|
|
||||||
environment: APP_IS_IN_DEV_MODE
|
|
||||||
? "dev"
|
|
||||||
: import.meta.env.VITE_SENTRY_ENVIRONMENT,
|
|
||||||
integrations: [
|
|
||||||
new BrowserTracing({
|
|
||||||
routingInstrumentation: Sentry.vueRouterInstrumentation(
|
|
||||||
getInstrumentationVueRouter(router)
|
|
||||||
),
|
|
||||||
// TODO: We may want to limit this later on
|
|
||||||
tracingOrigins: [new URL(import.meta.env.VITE_BACKEND_GQL_URL).origin],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
tracesSampleRate: 0.8,
|
|
||||||
})
|
|
||||||
sentryActive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function deinitSentry() {
|
|
||||||
Sentry.close()
|
|
||||||
sentryActive = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reports a set of related errors to Sentry
|
|
||||||
* @param errs The errors to report
|
|
||||||
* @param tag The tag for the errord
|
|
||||||
* @param extraTags Additional tag data to add
|
|
||||||
* @param extras Extra information to attach
|
|
||||||
*/
|
|
||||||
function reportErrors(
|
|
||||||
errs: Error[],
|
|
||||||
tag: SentryTag,
|
|
||||||
extraTags: Record<string, string | number | boolean> | null = null,
|
|
||||||
extras: any = undefined
|
|
||||||
) {
|
|
||||||
if (sentryActive) {
|
|
||||||
Sentry.withScope((scope) => {
|
|
||||||
scope.setTag("tag", tag)
|
|
||||||
if (extraTags) {
|
|
||||||
Object.entries(extraTags).forEach(([key, value]) => {
|
|
||||||
scope.setTag(key, value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (extras !== null && extras === undefined) scope.setExtras(extras)
|
|
||||||
|
|
||||||
scope.addAttachment({
|
|
||||||
filename: "extras-dump.json",
|
|
||||||
data: JSON.stringify(extras),
|
|
||||||
contentType: "application/json",
|
|
||||||
})
|
|
||||||
|
|
||||||
errs.forEach((err) => Sentry.captureException(err))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reports a specific error to Sentry
|
|
||||||
* @param err The error to report
|
|
||||||
* @param tag The tag for the error
|
|
||||||
* @param extraTags Additional tag data to add
|
|
||||||
* @param extras Extra information to attach
|
|
||||||
*/
|
|
||||||
function reportError(
|
|
||||||
err: Error,
|
|
||||||
tag: SentryTag,
|
|
||||||
extraTags: Record<string, string | number | boolean> | null = null,
|
|
||||||
extras: any = undefined
|
|
||||||
) {
|
|
||||||
reportErrors([err], tag, extraTags, extras)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribes to events occuring in various subsystems in the app
|
|
||||||
* for personalized error reporting
|
|
||||||
*/
|
|
||||||
function subscribeToAppEventsForReporting() {
|
|
||||||
gqlClientError$.subscribe((ev) => {
|
|
||||||
switch (ev.type) {
|
|
||||||
case "SUBSCRIPTION_CONN_CALLBACK_ERR_REPORT":
|
|
||||||
reportErrors(ev.errors, "BACKEND_OPERATIONS", { from: ev.type })
|
|
||||||
break
|
|
||||||
|
|
||||||
case "CLIENT_REPORTED_ERROR":
|
|
||||||
reportError(
|
|
||||||
ev.error,
|
|
||||||
"BACKEND_OPERATIONS",
|
|
||||||
{ from: ev.type },
|
|
||||||
{ op: ev.op }
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
case "GQL_CLIENT_REPORTED_ERROR":
|
|
||||||
reportError(
|
|
||||||
new Error("Backend Query Failed"),
|
|
||||||
"BACKEND_OPERATIONS",
|
|
||||||
{ opType: ev.opType },
|
|
||||||
{
|
|
||||||
opResult: ev.opResult,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribe to app system events for adding
|
|
||||||
* additional data tags for the error reporting
|
|
||||||
*/
|
|
||||||
function subscribeForAppDataTags() {
|
|
||||||
const currentUser$ = platform.auth.getCurrentUserStream()
|
|
||||||
|
|
||||||
currentUser$.subscribe((user) => {
|
|
||||||
if (sentryActive) {
|
|
||||||
Sentry.setTag("user_logged_in", !!user)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default <HoppModule>{
|
|
||||||
onRouterInit(app, router) {
|
|
||||||
if (!import.meta.env.VITE_SENTRY_DSN) {
|
|
||||||
console.log(
|
|
||||||
"Sentry tracing is not enabled because 'VITE_SENTRY_DSN' env is not defined"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settingsStore.value.TELEMETRY_ENABLED) {
|
|
||||||
initSentry(import.meta.env.VITE_SENTRY_DSN, router, app)
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsStore.subject$.subscribe(({ TELEMETRY_ENABLED }) => {
|
|
||||||
if (!TELEMETRY_ENABLED && sentryActive) {
|
|
||||||
deinitSentry()
|
|
||||||
} else if (TELEMETRY_ENABLED && !sentryActive) {
|
|
||||||
initSentry(import.meta.env.VITE_SENTRY_DSN!, router, app)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
subscribeToAppEventsForReporting()
|
|
||||||
subscribeForAppDataTags()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,9 @@ import {
|
|||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { getTabRefWithSaveContext } from "~/helpers/rest/tab"
|
|
||||||
import { resolveSaveContextOnRequestReorder } from "~/helpers/collection/request"
|
import { resolveSaveContextOnRequestReorder } from "~/helpers/collection/request"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const defaultRESTCollectionState = {
|
const defaultRESTCollectionState = {
|
||||||
state: [
|
state: [
|
||||||
@@ -454,7 +455,10 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
|
|
||||||
// Deal with situations where a tab with the given thing is deleted
|
// Deal with situations where a tab with the given thing is deleted
|
||||||
// We are just going to dissociate the save context of the tab and mark it dirty
|
// We are just going to dissociate the save context of the tab and mark it dirty
|
||||||
const tab = getTabRefWithSaveContext({
|
|
||||||
|
const tabService = getService(RESTTabService)
|
||||||
|
|
||||||
|
const tab = tabService.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: path,
|
folderPath: path,
|
||||||
requestIndex: requestIndex,
|
requestIndex: requestIndex,
|
||||||
@@ -512,7 +516,8 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
destLocation.requests.push(req)
|
destLocation.requests.push(req)
|
||||||
targetLocation.requests.splice(requestIndex, 1)
|
targetLocation.requests.splice(requestIndex, 1)
|
||||||
|
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const tabService = getService(RESTTabService)
|
||||||
|
const possibleTab = tabService.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: path,
|
folderPath: path,
|
||||||
requestIndex,
|
requestIndex,
|
||||||
|
|||||||
@@ -44,14 +44,9 @@ import { SSERequest$, setSSERequest } from "./SSESession"
|
|||||||
import { MQTTRequest$, setMQTTRequest } from "./MQTTSession"
|
import { MQTTRequest$, setMQTTRequest } from "./MQTTSession"
|
||||||
import { bulkApplyLocalState, localStateStore } from "./localstate"
|
import { bulkApplyLocalState, localStateStore } from "./localstate"
|
||||||
import { StorageLike, watchDebounced } from "@vueuse/core"
|
import { StorageLike, watchDebounced } from "@vueuse/core"
|
||||||
import {
|
import { getService } from "~/modules/dioc"
|
||||||
loadTabsFromPersistedState,
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
persistableTabState,
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
} from "~/helpers/rest/tab"
|
|
||||||
import {
|
|
||||||
loadTabsFromPersistedState as loadGQLTabsFromPersistedState,
|
|
||||||
persistableTabState as persistableGQLTabState,
|
|
||||||
} from "~/helpers/graphql/tab"
|
|
||||||
|
|
||||||
function checkAndMigrateOldSettings() {
|
function checkAndMigrateOldSettings() {
|
||||||
if (window.localStorage.getItem("selectedEnvIndex")) {
|
if (window.localStorage.getItem("selectedEnvIndex")) {
|
||||||
@@ -320,11 +315,13 @@ function setupGlobalEnvsPersistence() {
|
|||||||
|
|
||||||
// TODO: Graceful error handling ?
|
// TODO: Graceful error handling ?
|
||||||
export function setupRESTTabsPersistence() {
|
export function setupRESTTabsPersistence() {
|
||||||
|
const tabService = getService(RESTTabService)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const state = window.localStorage.getItem("restTabState")
|
const state = window.localStorage.getItem("restTabState")
|
||||||
if (state) {
|
if (state) {
|
||||||
const data = JSON.parse(state)
|
const data = JSON.parse(state)
|
||||||
loadTabsFromPersistedState(data)
|
tabService.loadTabsFromPersistedState(data)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -334,7 +331,7 @@ export function setupRESTTabsPersistence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watchDebounced(
|
watchDebounced(
|
||||||
persistableTabState,
|
tabService.persistableTabState,
|
||||||
(state) => {
|
(state) => {
|
||||||
window.localStorage.setItem("restTabState", JSON.stringify(state))
|
window.localStorage.setItem("restTabState", JSON.stringify(state))
|
||||||
},
|
},
|
||||||
@@ -343,11 +340,13 @@ export function setupRESTTabsPersistence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupGQLTabsPersistence() {
|
function setupGQLTabsPersistence() {
|
||||||
|
const tabService = getService(GQLTabService)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const state = window.localStorage.getItem("gqlTabState")
|
const state = window.localStorage.getItem("gqlTabState")
|
||||||
if (state) {
|
if (state) {
|
||||||
const data = JSON.parse(state)
|
const data = JSON.parse(state)
|
||||||
loadGQLTabsFromPersistedState(data)
|
tabService.loadTabsFromPersistedState(data)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -357,7 +356,7 @@ function setupGQLTabsPersistence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watchDebounced(
|
watchDebounced(
|
||||||
persistableGQLTabState,
|
tabService.persistableTabState,
|
||||||
(state) => {
|
(state) => {
|
||||||
window.localStorage.setItem("gqlTabState", JSON.stringify(state))
|
window.localStorage.setItem("gqlTabState", JSON.stringify(state))
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,23 +7,24 @@
|
|||||||
<HoppSmartWindows
|
<HoppSmartWindows
|
||||||
v-if="currentTabID"
|
v-if="currentTabID"
|
||||||
:id="'gql_windows'"
|
:id="'gql_windows'"
|
||||||
v-model="currentTabID"
|
:model-value="currentTabID"
|
||||||
|
@update:model-value="(tabID) => tabs.setActiveTab(tabID)"
|
||||||
@remove-tab="removeTab"
|
@remove-tab="removeTab"
|
||||||
@add-tab="addNewTab"
|
@add-tab="addNewTab"
|
||||||
@sort="sortTabs"
|
@sort="sortTabs"
|
||||||
>
|
>
|
||||||
<HoppSmartWindow
|
<HoppSmartWindow
|
||||||
v-for="tab in tabs"
|
v-for="tab in activeTabs"
|
||||||
:id="tab.id"
|
:id="tab.id"
|
||||||
:key="'removable_tab_' + tab.id"
|
:key="'removable_tab_' + tab.id"
|
||||||
:label="tab.document.request.name"
|
:label="tab.document.request.name"
|
||||||
:is-removable="tabs.length > 1"
|
:is-removable="activeTabs.length > 1"
|
||||||
:close-visibility="'hover'"
|
:close-visibility="'hover'"
|
||||||
>
|
>
|
||||||
<template #tabhead>
|
<template #tabhead>
|
||||||
<GraphqlTabHead
|
<GraphqlTabHead
|
||||||
:tab="tab"
|
:tab="tab"
|
||||||
:is-removable="tabs.length > 1"
|
:is-removable="activeTabs.length > 1"
|
||||||
@open-rename-modal="openReqRenameModal(tab)"
|
@open-rename-modal="openReqRenameModal(tab)"
|
||||||
@close-tab="removeTab(tab.id)"
|
@close-tab="removeTab(tab.id)"
|
||||||
@close-other-tabs="closeOtherTabsAction(tab.id)"
|
@close-other-tabs="closeOtherTabsAction(tab.id)"
|
||||||
@@ -89,21 +90,15 @@ import { computed, onBeforeUnmount, ref } from "vue"
|
|||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { connection, disconnect } from "~/helpers/graphql/connection"
|
import { connection, disconnect } from "~/helpers/graphql/connection"
|
||||||
import { getDefaultGQLRequest } from "~/helpers/graphql/default"
|
import { getDefaultGQLRequest } from "~/helpers/graphql/default"
|
||||||
import {
|
import { HoppGQLDocument } from "~/helpers/graphql/document"
|
||||||
HoppGQLTab,
|
|
||||||
closeOtherTabs,
|
|
||||||
closeTab,
|
|
||||||
createNewTab,
|
|
||||||
currentTabID,
|
|
||||||
getActiveTabs,
|
|
||||||
getDirtyTabsCount,
|
|
||||||
getTabRef,
|
|
||||||
updateTab,
|
|
||||||
updateTabOrdering,
|
|
||||||
} from "~/helpers/graphql/tab"
|
|
||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
|
const currentTabID = computed(() => tabs.currentTabID.value)
|
||||||
|
|
||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
@@ -113,27 +108,27 @@ usePageHead({
|
|||||||
title: computed(() => t("navigation.graphql")),
|
title: computed(() => t("navigation.graphql")),
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabs = getActiveTabs()
|
const activeTabs = tabs.getActiveTabs()
|
||||||
|
|
||||||
const addNewTab = () => {
|
const addNewTab = () => {
|
||||||
const tab = createNewTab({
|
const tab = tabs.createNewTab({
|
||||||
request: getDefaultGQLRequest(),
|
request: getDefaultGQLRequest(),
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
currentTabID.value = tab.id
|
tabs.setActiveTab(tab.id)
|
||||||
}
|
}
|
||||||
const sortTabs = (e: { oldIndex: number; newIndex: number }) => {
|
const sortTabs = (e: { oldIndex: number; newIndex: number }) => {
|
||||||
updateTabOrdering(e.oldIndex, e.newIndex)
|
tabs.updateTabOrdering(e.oldIndex, e.newIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeTab = (tabID: string) => {
|
const removeTab = (tabID: string) => {
|
||||||
const tabState = getTabRef(tabID).value
|
const tabState = tabs.getTabRef(tabID).value
|
||||||
|
|
||||||
if (tabState.document.isDirty) {
|
if (tabState.document.isDirty) {
|
||||||
confirmingCloseForTabID.value = tabID
|
confirmingCloseForTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
closeTab(tabState.id)
|
tabs.closeTab(tabState.id)
|
||||||
inspectionService.deleteTabInspectorResult(tabState.id)
|
inspectionService.deleteTabInspectorResult(tabState.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +145,7 @@ const onCloseConfirm = () => {
|
|||||||
*/
|
*/
|
||||||
const onResolveConfirm = () => {
|
const onResolveConfirm = () => {
|
||||||
if (confirmingCloseForTabID.value) {
|
if (confirmingCloseForTabID.value) {
|
||||||
closeTab(confirmingCloseForTabID.value)
|
tabs.closeTab(confirmingCloseForTabID.value)
|
||||||
confirmingCloseForTabID.value = null
|
confirmingCloseForTabID.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,24 +155,24 @@ const unsavedTabsCount = ref(0)
|
|||||||
const exceptedTabID = ref<string | null>(null)
|
const exceptedTabID = ref<string | null>(null)
|
||||||
|
|
||||||
const closeOtherTabsAction = (tabID: string) => {
|
const closeOtherTabsAction = (tabID: string) => {
|
||||||
const dirtyTabCount = getDirtyTabsCount()
|
const dirtyTabCount = tabs.getDirtyTabsCount()
|
||||||
// If there are dirty tabs, show the confirm modal
|
// If there are dirty tabs, show the confirm modal
|
||||||
if (dirtyTabCount > 0) {
|
if (dirtyTabCount > 0) {
|
||||||
confirmingCloseAllTabs.value = true
|
confirmingCloseAllTabs.value = true
|
||||||
unsavedTabsCount.value = dirtyTabCount
|
unsavedTabsCount.value = dirtyTabCount
|
||||||
exceptedTabID.value = tabID
|
exceptedTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
closeOtherTabs(tabID)
|
tabs.closeOtherTabs(tabID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onResolveConfirmCloseAllTabs = () => {
|
const onResolveConfirmCloseAllTabs = () => {
|
||||||
if (exceptedTabID.value) closeOtherTabs(exceptedTabID.value)
|
if (exceptedTabID.value) tabs.closeOtherTabs(exceptedTabID.value)
|
||||||
confirmingCloseAllTabs.value = false
|
confirmingCloseAllTabs.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const onTabUpdate = (tab: HoppGQLTab) => {
|
const onTabUpdate = (tab: HoppTab<HoppGQLDocument>) => {
|
||||||
updateTab(tab)
|
tabs.updateTab(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@@ -189,33 +184,33 @@ onBeforeUnmount(() => {
|
|||||||
const editReqModalReqName = ref("")
|
const editReqModalReqName = ref("")
|
||||||
const showRenamingReqNameModalForTabID = ref<string>()
|
const showRenamingReqNameModalForTabID = ref<string>()
|
||||||
|
|
||||||
const openReqRenameModal = (tab: HoppGQLTab) => {
|
const openReqRenameModal = (tab: HoppTab<HoppGQLDocument>) => {
|
||||||
editReqModalReqName.value = tab.document.request.name
|
editReqModalReqName.value = tab.document.request.name
|
||||||
showRenamingReqNameModalForTabID.value = tab.id
|
showRenamingReqNameModalForTabID.value = tab.id
|
||||||
}
|
}
|
||||||
|
|
||||||
const renameReqName = () => {
|
const renameReqName = () => {
|
||||||
const tab = getTabRef(showRenamingReqNameModalForTabID.value!)
|
const tab = tabs.getTabRef(showRenamingReqNameModalForTabID.value!)
|
||||||
if (tab.value) {
|
if (tab.value) {
|
||||||
tab.value.document.request.name = editReqModalReqName.value
|
tab.value.document.request.name = editReqModalReqName.value
|
||||||
updateTab(tab.value)
|
tabs.updateTab(tab.value)
|
||||||
}
|
}
|
||||||
showRenamingReqNameModalForTabID.value = undefined
|
showRenamingReqNameModalForTabID.value = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateTab = (tabID: string) => {
|
const duplicateTab = (tabID: string) => {
|
||||||
const tab = getTabRef(tabID)
|
const tab = tabs.getTabRef(tabID)
|
||||||
if (tab.value) {
|
if (tab.value) {
|
||||||
const newTab = createNewTab({
|
const newTab = tabs.createNewTab({
|
||||||
request: tab.value.document.request,
|
request: tab.value.document.request,
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
})
|
})
|
||||||
currentTabID.value = newTab.id
|
tabs.setActiveTab(newTab.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defineActionHandler("gql.request.open", ({ request, saveContext }) => {
|
defineActionHandler("gql.request.open", ({ request, saveContext }) => {
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
saveContext,
|
saveContext,
|
||||||
request: request,
|
request: request,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
@@ -223,7 +218,7 @@ defineActionHandler("gql.request.open", ({ request, saveContext }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
defineActionHandler("request.rename", () => {
|
defineActionHandler("request.rename", () => {
|
||||||
openReqRenameModal(getTabRef(currentTabID.value).value!)
|
openReqRenameModal(tabs.getTabRef(currentTabID.value).value!)
|
||||||
})
|
})
|
||||||
|
|
||||||
defineActionHandler("tab.duplicate-tab", ({ tabID }) => {
|
defineActionHandler("tab.duplicate-tab", ({ tabID }) => {
|
||||||
@@ -233,7 +228,7 @@ defineActionHandler("tab.close-current", () => {
|
|||||||
removeTab(currentTabID.value)
|
removeTab(currentTabID.value)
|
||||||
})
|
})
|
||||||
defineActionHandler("tab.close-other", () => {
|
defineActionHandler("tab.close-other", () => {
|
||||||
closeOtherTabs(currentTabID.value)
|
tabs.closeOtherTabs(currentTabID.value)
|
||||||
})
|
})
|
||||||
defineActionHandler("tab.open-new", addNewTab)
|
defineActionHandler("tab.open-new", addNewTab)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -11,17 +11,17 @@
|
|||||||
@sort="sortTabs"
|
@sort="sortTabs"
|
||||||
>
|
>
|
||||||
<HoppSmartWindow
|
<HoppSmartWindow
|
||||||
v-for="tab in tabs"
|
v-for="tab in activeTabs"
|
||||||
:id="tab.id"
|
:id="tab.id"
|
||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
:label="tab.document.request.name"
|
:label="tab.document.request.name"
|
||||||
:is-removable="tabs.length > 1"
|
:is-removable="activeTabs.length > 1"
|
||||||
:close-visibility="'hover'"
|
:close-visibility="'hover'"
|
||||||
>
|
>
|
||||||
<template #tabhead>
|
<template #tabhead>
|
||||||
<HttpTabHead
|
<HttpTabHead
|
||||||
:tab="tab"
|
:tab="tab"
|
||||||
:is-removable="tabs.length > 1"
|
:is-removable="activeTabs.length > 1"
|
||||||
@open-rename-modal="openReqRenameModal(tab.id)"
|
@open-rename-modal="openReqRenameModal(tab.id)"
|
||||||
@close-tab="removeTab(tab.id)"
|
@close-tab="removeTab(tab.id)"
|
||||||
@close-other-tabs="closeOtherTabsAction(tab.id)"
|
@close-other-tabs="closeOtherTabsAction(tab.id)"
|
||||||
@@ -99,21 +99,6 @@ import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
|||||||
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
||||||
import { useRoute } from "vue-router"
|
import { useRoute } from "vue-router"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import {
|
|
||||||
closeTab,
|
|
||||||
closeOtherTabs,
|
|
||||||
createNewTab,
|
|
||||||
currentActiveTab,
|
|
||||||
currentTabID,
|
|
||||||
getActiveTabs,
|
|
||||||
getTabRef,
|
|
||||||
HoppRESTTab,
|
|
||||||
loadTabsFromPersistedState,
|
|
||||||
persistableTabState,
|
|
||||||
updateTab,
|
|
||||||
updateTabOrdering,
|
|
||||||
getDirtyTabsCount,
|
|
||||||
} from "~/helpers/rest/tab"
|
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
import { onLoggedIn } from "~/composables/auth"
|
import { onLoggedIn } from "~/composables/auth"
|
||||||
@@ -128,7 +113,6 @@ import {
|
|||||||
Subscription,
|
Subscription,
|
||||||
} from "rxjs"
|
} from "rxjs"
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { PersistableRESTTabState } from "~/helpers/rest/tab"
|
|
||||||
import { watchDebounced } from "@vueuse/core"
|
import { watchDebounced } from "@vueuse/core"
|
||||||
import { oauthRedirect } from "~/helpers/oauth"
|
import { oauthRedirect } from "~/helpers/oauth"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
@@ -142,6 +126,9 @@ import { HeaderInspectorService } from "~/services/inspection/inspectors/header.
|
|||||||
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
||||||
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { HoppTab, PersistableTabState } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
const savingRequest = ref(false)
|
const savingRequest = ref(false)
|
||||||
const confirmingCloseForTabID = ref<string | null>(null)
|
const confirmingCloseForTabID = ref<string | null>(null)
|
||||||
@@ -155,6 +142,10 @@ const renameTabID = ref<string | null>(null)
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
|
const currentTabID = tabs.currentTabID
|
||||||
|
|
||||||
type PopupDetails = {
|
type PopupDetails = {
|
||||||
show: boolean
|
show: boolean
|
||||||
position: {
|
position: {
|
||||||
@@ -173,13 +164,13 @@ const contextMenu = ref<PopupDetails>({
|
|||||||
text: null,
|
text: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabs = getActiveTabs()
|
const activeTabs = tabs.getActiveTabs()
|
||||||
|
|
||||||
const confirmSync = useReadonlyStream(currentSyncingStatus$, {
|
const confirmSync = useReadonlyStream(currentSyncingStatus$, {
|
||||||
isInitialSync: false,
|
isInitialSync: false,
|
||||||
shouldSync: true,
|
shouldSync: true,
|
||||||
})
|
})
|
||||||
const tabStateForSync = ref<PersistableRESTTabState | null>(null)
|
const tabStateForSync = ref<PersistableTabState<HoppRESTDocument> | null>(null)
|
||||||
|
|
||||||
function bindRequestToURLParams() {
|
function bindRequestToURLParams() {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -190,91 +181,92 @@ function bindRequestToURLParams() {
|
|||||||
// We skip URL params parsing
|
// We skip URL params parsing
|
||||||
if (Object.keys(query).length === 0 || query.code || query.error) return
|
if (Object.keys(query).length === 0 || query.code || query.error) return
|
||||||
|
|
||||||
const request = currentActiveTab.value.document.request
|
const request = tabs.currentActiveTab.value.document.request
|
||||||
|
|
||||||
currentActiveTab.value.document.request = safelyExtractRESTRequest(
|
tabs.currentActiveTab.value.document.request = safelyExtractRESTRequest(
|
||||||
translateExtURLParams(query, request),
|
translateExtURLParams(query, request),
|
||||||
getDefaultRESTRequest()
|
getDefaultRESTRequest()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onTabUpdate = (tab: HoppRESTTab) => {
|
const onTabUpdate = (tab: HoppTab<HoppRESTDocument>) => {
|
||||||
updateTab(tab)
|
tabs.updateTab(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
const addNewTab = () => {
|
const addNewTab = () => {
|
||||||
const tab = createNewTab({
|
const tab = tabs.createNewTab({
|
||||||
request: getDefaultRESTRequest(),
|
request: getDefaultRESTRequest(),
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
currentTabID.value = tab.id
|
tabs.setActiveTab(tab.id)
|
||||||
}
|
}
|
||||||
const sortTabs = (e: { oldIndex: number; newIndex: number }) => {
|
const sortTabs = (e: { oldIndex: number; newIndex: number }) => {
|
||||||
updateTabOrdering(e.oldIndex, e.newIndex)
|
tabs.updateTabOrdering(e.oldIndex, e.newIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const removeTab = (tabID: string) => {
|
const removeTab = (tabID: string) => {
|
||||||
const tabState = getTabRef(tabID).value
|
const tabState = tabs.getTabRef(tabID).value
|
||||||
|
|
||||||
if (tabState.document.isDirty) {
|
if (tabState.document.isDirty) {
|
||||||
confirmingCloseForTabID.value = tabID
|
confirmingCloseForTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
closeTab(tabState.id)
|
tabs.closeTab(tabState.id)
|
||||||
inspectionService.deleteTabInspectorResult(tabState.id)
|
inspectionService.deleteTabInspectorResult(tabState.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeOtherTabsAction = (tabID: string) => {
|
const closeOtherTabsAction = (tabID: string) => {
|
||||||
const isTabDirty = getTabRef(tabID).value?.document.isDirty
|
const isTabDirty = tabs.getTabRef(tabID).value?.document.isDirty
|
||||||
const dirtyTabCount = getDirtyTabsCount()
|
const dirtyTabCount = tabs.getDirtyTabsCount()
|
||||||
// If current tab is dirty, so we need to subtract 1 from the dirty tab count
|
// If current tab is dirty, so we need to subtract 1 from the dirty tab count
|
||||||
const balanceDirtyTabCount = isTabDirty ? dirtyTabCount - 1 : dirtyTabCount
|
const balanceDirtyTabCount = isTabDirty ? dirtyTabCount - 1 : dirtyTabCount
|
||||||
|
|
||||||
// If there are dirty tabs, show the confirm modal
|
// If there are dirty tabs, show the confirm modal
|
||||||
if (balanceDirtyTabCount > 0) {
|
if (balanceDirtyTabCount > 0) {
|
||||||
confirmingCloseAllTabs.value = true
|
confirmingCloseAllTabs.value = true
|
||||||
unsavedTabsCount.value = balanceDirtyTabCount
|
unsavedTabsCount.value = balanceDirtyTabCount
|
||||||
exceptedTabID.value = tabID
|
exceptedTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
closeOtherTabs(tabID)
|
tabs.closeOtherTabs(tabID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateTab = (tabID: string) => {
|
const duplicateTab = (tabID: string) => {
|
||||||
const tab = getTabRef(tabID)
|
const tab = tabs.getTabRef(tabID)
|
||||||
if (tab.value) {
|
if (tab.value) {
|
||||||
const newTab = createNewTab({
|
const newTab = tabs.createNewTab({
|
||||||
request: cloneDeep(tab.value.document.request),
|
request: cloneDeep(tab.value.document.request),
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
})
|
})
|
||||||
currentTabID.value = newTab.id
|
tabs.setActiveTab(newTab.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onResolveConfirmCloseAllTabs = () => {
|
const onResolveConfirmCloseAllTabs = () => {
|
||||||
if (exceptedTabID.value) closeOtherTabs(exceptedTabID.value)
|
if (exceptedTabID.value) tabs.closeOtherTabs(exceptedTabID.value)
|
||||||
confirmingCloseAllTabs.value = false
|
confirmingCloseAllTabs.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const openReqRenameModal = (tabID?: string) => {
|
const openReqRenameModal = (tabID?: string) => {
|
||||||
if (tabID) {
|
if (tabID) {
|
||||||
const tab = getTabRef(tabID)
|
const tab = tabs.getTabRef(tabID)
|
||||||
reqName.value = tab.value.document.request.name
|
reqName.value = tab.value.document.request.name
|
||||||
renameTabID.value = tabID
|
renameTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
reqName.value = currentActiveTab.value.document.request.name
|
reqName.value = tabs.currentActiveTab.value.document.request.name
|
||||||
}
|
}
|
||||||
showRenamingReqNameModal.value = true
|
showRenamingReqNameModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const renameReqName = () => {
|
const renameReqName = () => {
|
||||||
const tab = getTabRef(renameTabID.value ?? currentTabID.value)
|
const tab = tabs.getTabRef(renameTabID.value ?? currentTabID.value)
|
||||||
if (tab.value) {
|
if (tab.value) {
|
||||||
tab.value.document.request.name = reqName.value
|
tab.value.document.request.name = reqName.value
|
||||||
updateTab(tab.value)
|
tabs.updateTab(tab.value)
|
||||||
}
|
}
|
||||||
showRenamingReqNameModal.value = false
|
showRenamingReqNameModal.value = false
|
||||||
}
|
}
|
||||||
@@ -284,7 +276,7 @@ const renameReqName = () => {
|
|||||||
*/
|
*/
|
||||||
const onCloseConfirmSaveTab = () => {
|
const onCloseConfirmSaveTab = () => {
|
||||||
if (!savingRequest.value && confirmingCloseForTabID.value) {
|
if (!savingRequest.value && confirmingCloseForTabID.value) {
|
||||||
closeTab(confirmingCloseForTabID.value)
|
tabs.closeTab(confirmingCloseForTabID.value)
|
||||||
inspectionService.deleteTabInspectorResult(confirmingCloseForTabID.value)
|
inspectionService.deleteTabInspectorResult(confirmingCloseForTabID.value)
|
||||||
confirmingCloseForTabID.value = null
|
confirmingCloseForTabID.value = null
|
||||||
}
|
}
|
||||||
@@ -294,11 +286,11 @@ const onCloseConfirmSaveTab = () => {
|
|||||||
* Called when the user confirms they want to save the tab
|
* Called when the user confirms they want to save the tab
|
||||||
*/
|
*/
|
||||||
const onResolveConfirmSaveTab = () => {
|
const onResolveConfirmSaveTab = () => {
|
||||||
if (currentActiveTab.value.document.saveContext) {
|
if (tabs.currentActiveTab.value.document.saveContext) {
|
||||||
invokeAction("request.save")
|
invokeAction("request.save")
|
||||||
|
|
||||||
if (confirmingCloseForTabID.value) {
|
if (confirmingCloseForTabID.value) {
|
||||||
closeTab(confirmingCloseForTabID.value)
|
tabs.closeTab(confirmingCloseForTabID.value)
|
||||||
confirmingCloseForTabID.value = null
|
confirmingCloseForTabID.value = null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -312,13 +304,14 @@ const onResolveConfirmSaveTab = () => {
|
|||||||
const onSaveModalClose = () => {
|
const onSaveModalClose = () => {
|
||||||
savingRequest.value = false
|
savingRequest.value = false
|
||||||
if (confirmingCloseForTabID.value) {
|
if (confirmingCloseForTabID.value) {
|
||||||
closeTab(confirmingCloseForTabID.value)
|
tabs.closeTab(confirmingCloseForTabID.value)
|
||||||
confirmingCloseForTabID.value = null
|
confirmingCloseForTabID.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncTabState = () => {
|
const syncTabState = () => {
|
||||||
if (tabStateForSync.value) loadTabsFromPersistedState(tabStateForSync.value)
|
if (tabStateForSync.value)
|
||||||
|
tabs.loadTabsFromPersistedState(tabStateForSync.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -329,10 +322,11 @@ const syncTabState = () => {
|
|||||||
*/
|
*/
|
||||||
function startTabStateSync(): Subscription {
|
function startTabStateSync(): Subscription {
|
||||||
const currentUser$ = platform.auth.getCurrentUserStream()
|
const currentUser$ = platform.auth.getCurrentUserStream()
|
||||||
const tabState$ = new BehaviorSubject<PersistableRESTTabState | null>(null)
|
const tabState$ =
|
||||||
|
new BehaviorSubject<PersistableTabState<HoppRESTDocument> | null>(null)
|
||||||
|
|
||||||
watchDebounced(
|
watchDebounced(
|
||||||
persistableTabState,
|
tabs.persistableTabState,
|
||||||
(state) => {
|
(state) => {
|
||||||
tabState$.next(state)
|
tabState$.next(state)
|
||||||
},
|
},
|
||||||
@@ -429,9 +423,10 @@ function oAuthURL() {
|
|||||||
tokenInfo.hasOwnProperty("access_token")
|
tokenInfo.hasOwnProperty("access_token")
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
currentActiveTab.value.document.request.auth.authType === "oauth-2"
|
tabs.currentActiveTab.value.document.request.auth.authType ===
|
||||||
|
"oauth-2"
|
||||||
) {
|
) {
|
||||||
currentActiveTab.value.document.request.auth.token =
|
tabs.currentActiveTab.value.document.request.auth.token =
|
||||||
tokenInfo.access_token
|
tokenInfo.access_token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,7 +457,7 @@ bindRequestToURLParams()
|
|||||||
oAuthURL()
|
oAuthURL()
|
||||||
|
|
||||||
defineActionHandler("rest.request.open", ({ doc }) => {
|
defineActionHandler("rest.request.open", ({ doc }) => {
|
||||||
createNewTab(doc)
|
tabs.createNewTab(doc)
|
||||||
})
|
})
|
||||||
|
|
||||||
defineActionHandler("request.rename", openReqRenameModal)
|
defineActionHandler("request.rename", openReqRenameModal)
|
||||||
@@ -473,7 +468,7 @@ defineActionHandler("tab.close-current", () => {
|
|||||||
removeTab(currentTabID.value)
|
removeTab(currentTabID.value)
|
||||||
})
|
})
|
||||||
defineActionHandler("tab.close-other", () => {
|
defineActionHandler("tab.close-other", () => {
|
||||||
closeOtherTabs(currentTabID.value)
|
tabs.closeOtherTabs(currentTabID.value)
|
||||||
})
|
})
|
||||||
defineActionHandler("tab.open-new", addNewTab)
|
defineActionHandler("tab.open-new", addNewTab)
|
||||||
|
|
||||||
|
|||||||
@@ -82,15 +82,18 @@ import {
|
|||||||
|
|
||||||
import IconHome from "~icons/lucide/home"
|
import IconHome from "~icons/lucide/home"
|
||||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||||
import { createNewTab } from "~/helpers/rest/tab"
|
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const invalidLink = ref(false)
|
const invalidLink = ref(false)
|
||||||
const shortcodeID = ref("")
|
const shortcodeID = ref("")
|
||||||
|
|
||||||
@@ -127,7 +130,7 @@ const addRequestToTab = () => {
|
|||||||
|
|
||||||
const request: unknown = JSON.parse(data.right.shortcode?.request as string)
|
const request: unknown = JSON.parse(data.right.shortcode?.request as string)
|
||||||
|
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: safelyExtractRESTRequest(request, getDefaultRESTRequest()),
|
request: safelyExtractRESTRequest(request, getDefaultRESTRequest()),
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
<div class="py-4 space-y-4">
|
<div class="py-4 space-y-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<HoppSmartToggle
|
<HoppSmartToggle
|
||||||
|
v-if="hasPlatformTelemetry"
|
||||||
:on="TELEMETRY_ENABLED"
|
:on="TELEMETRY_ENABLED"
|
||||||
@change="showConfirmModal"
|
@change="showConfirmModal"
|
||||||
>
|
>
|
||||||
@@ -134,6 +135,7 @@ import { InterceptorService } from "~/services/interceptor.service"
|
|||||||
import { pipe } from "fp-ts/function"
|
import { 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"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
@@ -163,6 +165,8 @@ const TELEMETRY_ENABLED = useSetting("TELEMETRY_ENABLED")
|
|||||||
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
||||||
const SIDEBAR_ON_LEFT = useSetting("SIDEBAR_ON_LEFT")
|
const SIDEBAR_ON_LEFT = useSetting("SIDEBAR_ON_LEFT")
|
||||||
|
|
||||||
|
const hasPlatformTelemetry = Boolean(platform.platformFeatureFlags.hasTelemetry)
|
||||||
|
|
||||||
const confirmRemove = ref(false)
|
const confirmRemove = ref(false)
|
||||||
|
|
||||||
const proxySettings = computed(() => ({
|
const proxySettings = computed(() => ({
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export type PlatformDef = {
|
|||||||
additionalInspectors?: InspectorsPlatformDef
|
additionalInspectors?: InspectorsPlatformDef
|
||||||
platformFeatureFlags: {
|
platformFeatureFlags: {
|
||||||
exportAsGIST: boolean
|
exportAsGIST: boolean
|
||||||
|
hasTelemetry: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { PersistableRESTTabState } from "~/helpers/rest/tab"
|
import { PersistableTabState } from "~/services/tab"
|
||||||
import { HoppUser } from "./auth"
|
import { HoppUser } from "./auth"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
export type TabStatePlatformDef = {
|
export type TabStatePlatformDef = {
|
||||||
loadTabStateFromSync: () => Promise<PersistableRESTTabState | null>
|
loadTabStateFromSync: () => Promise<PersistableTabState<HoppRESTDocument> | null>
|
||||||
writeCurrentTabState: (
|
writeCurrentTabState: (
|
||||||
user: HoppUser,
|
user: HoppUser,
|
||||||
persistableTabState: PersistableRESTTabState
|
persistableTabState: PersistableTabState<HoppRESTDocument>
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,6 @@ vi.mock("~/modules/i18n", () => ({
|
|||||||
getI18n: () => (x: string) => x,
|
getI18n: () => (x: string) => x,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const tabMock = vi.hoisted(() => ({
|
|
||||||
currentActiveTab: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock("~/helpers/rest/tab", () => ({
|
|
||||||
__esModule: true,
|
|
||||||
currentActiveTab: tabMock.currentActiveTab,
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe("ParameterMenuService", () => {
|
describe("ParameterMenuService", () => {
|
||||||
it("registers with the contextmenu service upon initialization", () => {
|
it("registers with the contextmenu service upon initialization", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { TestContainer } from "dioc/testing"
|
import { TestContainer } from "dioc/testing"
|
||||||
import { describe, expect, it, vi } from "vitest"
|
import { describe, expect, it, vi } from "vitest"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { ContextMenuService } from "../.."
|
import { ContextMenuService } from "../.."
|
||||||
import { URLMenuService } from "../url.menu"
|
import { URLMenuService } from "../url.menu"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
@@ -9,15 +10,6 @@ vi.mock("~/modules/i18n", () => ({
|
|||||||
getI18n: () => (x: string) => x,
|
getI18n: () => (x: string) => x,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const tabMock = vi.hoisted(() => ({
|
|
||||||
createNewTab: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock("~/helpers/rest/tab", () => ({
|
|
||||||
__esModule: true,
|
|
||||||
createNewTab: tabMock.createNewTab,
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe("URLMenuService", () => {
|
describe("URLMenuService", () => {
|
||||||
it("registers with the contextmenu service upon initialization", () => {
|
it("registers with the contextmenu service upon initialization", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
@@ -64,6 +56,10 @@ describe("URLMenuService", () => {
|
|||||||
|
|
||||||
it("should call the openNewTab function when action is called and a new hoppscotch tab is opened", () => {
|
it("should call the openNewTab function when action is called and a new hoppscotch tab is opened", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
|
const createNewTabFn = vi.fn()
|
||||||
|
container.bindMock(RESTTabService, {
|
||||||
|
createNewTab: createNewTabFn,
|
||||||
|
})
|
||||||
const url = container.bind(URLMenuService)
|
const url = container.bind(URLMenuService)
|
||||||
|
|
||||||
const test = "https://hoppscotch.io"
|
const test = "https://hoppscotch.io"
|
||||||
@@ -76,8 +72,8 @@ describe("URLMenuService", () => {
|
|||||||
endpoint: test,
|
endpoint: test,
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(tabMock.createNewTab).toHaveBeenCalledOnce()
|
expect(createNewTabFn).toHaveBeenCalledOnce()
|
||||||
expect(tabMock.createNewTab).toHaveBeenCalledWith({
|
expect(createNewTabFn).toHaveBeenCalledWith({
|
||||||
request: request,
|
request: request,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import {
|
|||||||
} from "../"
|
} from "../"
|
||||||
import { markRaw, ref } from "vue"
|
import { markRaw, ref } from "vue"
|
||||||
import IconArrowDownRight from "~icons/lucide/arrow-down-right"
|
import IconArrowDownRight from "~icons/lucide/arrow-down-right"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
|
||||||
import { getI18n } from "~/modules/i18n"
|
import { getI18n } from "~/modules/i18n"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
|
||||||
//regex containing both url and parameter
|
//regex containing both url and parameter
|
||||||
const urlAndParameterRegex = new RegExp("[^&?]*?=[^&?]*")
|
const urlAndParameterRegex = new RegExp("[^&?]*?=[^&?]*")
|
||||||
@@ -88,20 +89,23 @@ export class ParameterMenuService extends Service implements ContextMenu {
|
|||||||
queryParams.push({ key, value, active: true })
|
queryParams.push({ key, value, active: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabService = getService(RESTTabService)
|
||||||
|
|
||||||
// add the parameters to the current request parameters
|
// add the parameters to the current request parameters
|
||||||
currentActiveTab.value.document.request.params = [
|
tabService.currentActiveTab.value.document.request.params = [
|
||||||
...currentActiveTab.value.document.request.params,
|
...tabService.currentActiveTab.value.document.request.params,
|
||||||
...queryParams,
|
...queryParams,
|
||||||
]
|
]
|
||||||
|
|
||||||
if (newURL) {
|
if (newURL) {
|
||||||
currentActiveTab.value.document.request.endpoint = newURL
|
tabService.currentActiveTab.value.document.request.endpoint = newURL
|
||||||
} else {
|
} else {
|
||||||
// remove the parameter from the URL
|
// remove the parameter from the URL
|
||||||
const textRegex = new RegExp(`\\b${text.replace(/\?/g, "")}\\b`, "gi")
|
const textRegex = new RegExp(`\\b${text.replace(/\?/g, "")}\\b`, "gi")
|
||||||
const sanitizedWord = currentActiveTab.value.document.request.endpoint
|
const sanitizedWord =
|
||||||
|
tabService.currentActiveTab.value.document.request.endpoint
|
||||||
const newURL = sanitizedWord.replace(textRegex, "")
|
const newURL = sanitizedWord.replace(textRegex, "")
|
||||||
currentActiveTab.value.document.request.endpoint = newURL
|
tabService.currentActiveTab.value.document.request.endpoint = newURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Service } from "dioc"
|
import { Service } from "dioc"
|
||||||
|
import { markRaw, ref } from "vue"
|
||||||
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
|
import { getI18n } from "~/modules/i18n"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import IconCopyPlus from "~icons/lucide/copy-plus"
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuResult,
|
ContextMenuResult,
|
||||||
ContextMenuService,
|
ContextMenuService,
|
||||||
ContextMenuState,
|
ContextMenuState,
|
||||||
} from ".."
|
} from ".."
|
||||||
import { markRaw, ref } from "vue"
|
|
||||||
import IconCopyPlus from "~icons/lucide/copy-plus"
|
|
||||||
import { createNewTab } from "~/helpers/rest/tab"
|
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
|
||||||
import { getI18n } from "~/modules/i18n"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to check if a string is a valid URL
|
* Used to check if a string is a valid URL
|
||||||
@@ -37,6 +37,7 @@ export class URLMenuService extends Service implements ContextMenu {
|
|||||||
public readonly menuID = "url"
|
public readonly menuID = "url"
|
||||||
|
|
||||||
private readonly contextMenu = this.bind(ContextMenuService)
|
private readonly contextMenu = this.bind(ContextMenuService)
|
||||||
|
private readonly restTab = this.bind(RESTTabService)
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
@@ -55,7 +56,7 @@ export class URLMenuService extends Service implements ContextMenu {
|
|||||||
endpoint: url,
|
endpoint: url,
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewTab({
|
this.restTab.createNewTab({
|
||||||
request: request,
|
request: request,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { refDebounced } from "@vueuse/core"
|
|||||||
import { Service } from "dioc"
|
import { Service } from "dioc"
|
||||||
import { computed, markRaw, reactive } from "vue"
|
import { computed, markRaw, reactive } from "vue"
|
||||||
import { Component, Ref, ref, watch } from "vue"
|
import { Component, Ref, ref, watch } from "vue"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
|
||||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
|
import { RESTTabService } from "../tab/rest"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines how to render the text in an Inspector Result
|
* Defines how to render the text in an Inspector Result
|
||||||
@@ -105,6 +105,8 @@ export class InspectionService extends Service {
|
|||||||
|
|
||||||
private tabs: Ref<Map<string, InspectorResult[]>> = ref(new Map())
|
private tabs: Ref<Map<string, InspectorResult[]>> = ref(new Map())
|
||||||
|
|
||||||
|
private readonly restTab = this.bind(RESTTabService)
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
@@ -122,10 +124,14 @@ export class InspectionService extends Service {
|
|||||||
|
|
||||||
private initializeListeners() {
|
private initializeListeners() {
|
||||||
watch(
|
watch(
|
||||||
() => [this.inspectors.entries(), currentActiveTab.value.id],
|
() => [this.inspectors.entries(), this.restTab.currentActiveTab.value.id],
|
||||||
() => {
|
() => {
|
||||||
const reqRef = computed(() => currentActiveTab.value.document.request)
|
const reqRef = computed(
|
||||||
const resRef = computed(() => currentActiveTab.value.response)
|
() => this.restTab.currentActiveTab.value.document.request
|
||||||
|
)
|
||||||
|
const resRef = computed(
|
||||||
|
() => this.restTab.currentActiveTab.value.document.response
|
||||||
|
)
|
||||||
|
|
||||||
const debouncedReq = refDebounced(reqRef, 1000, { maxWait: 2000 })
|
const debouncedReq = refDebounced(reqRef, 1000, { maxWait: 2000 })
|
||||||
const debouncedRes = refDebounced(resRef, 1000, { maxWait: 2000 })
|
const debouncedRes = refDebounced(resRef, 1000, { maxWait: 2000 })
|
||||||
@@ -142,7 +148,7 @@ export class InspectionService extends Service {
|
|||||||
() => [...inspectorRefs.flatMap((x) => x!.value)],
|
() => [...inspectorRefs.flatMap((x) => x!.value)],
|
||||||
() => {
|
() => {
|
||||||
this.tabs.value.set(
|
this.tabs.value.set(
|
||||||
currentActiveTab.value.id,
|
this.restTab.currentActiveTab.value.id,
|
||||||
activeInspections.value
|
activeInspections.value
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,20 +7,12 @@ import { GQLHistoryEntry, RESTHistoryEntry } from "~/newstore/history"
|
|||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { HoppAction, HoppActionWithArgs } from "~/helpers/actions"
|
import { HoppAction, HoppActionWithArgs } from "~/helpers/actions"
|
||||||
import { getDefaultGQLRequest } from "~/helpers/graphql/default"
|
import { getDefaultGQLRequest } from "~/helpers/graphql/default"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
async function flushPromises() {
|
async function flushPromises() {
|
||||||
return await new Promise((r) => setTimeout(r))
|
return await new Promise((r) => setTimeout(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabMock = vi.hoisted(() => ({
|
|
||||||
createNewTab: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock("~/helpers/rest/tab", () => ({
|
|
||||||
__esModule: true,
|
|
||||||
createNewTab: tabMock.createNewTab,
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock("~/modules/i18n", () => ({
|
vi.mock("~/modules/i18n", () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
getI18n: () => (x: string) => x,
|
getI18n: () => (x: string) => x,
|
||||||
@@ -72,8 +64,16 @@ describe("HistorySpotlightSearcherService", () => {
|
|||||||
y = historyMock.restEntries.pop()
|
y = historyMock.restEntries.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const container = new TestContainer()
|
||||||
|
|
||||||
|
const createNewTabFn = vi.fn()
|
||||||
|
|
||||||
|
container.bindMock(RESTTabService, {
|
||||||
|
createNewTab: createNewTabFn,
|
||||||
|
})
|
||||||
|
|
||||||
actionsMock.invokeAction.mockReset()
|
actionsMock.invokeAction.mockReset()
|
||||||
tabMock.createNewTab.mockReset()
|
createNewTabFn.mockReset()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("registers with the spotlight service upon initialization", async () => {
|
it("registers with the spotlight service upon initialization", async () => {
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ import {
|
|||||||
import IconFolder from "~icons/lucide/folder"
|
import IconFolder from "~icons/lucide/folder"
|
||||||
import RESTRequestSpotlightEntry from "~/components/app/spotlight/entry/RESTRequest.vue"
|
import RESTRequestSpotlightEntry from "~/components/app/spotlight/entry/RESTRequest.vue"
|
||||||
import GQLRequestSpotlightEntry from "~/components/app/spotlight/entry/GQLRequest.vue"
|
import GQLRequestSpotlightEntry from "~/components/app/spotlight/entry/GQLRequest.vue"
|
||||||
import { createNewTab } from "~/helpers/rest/tab"
|
|
||||||
import { createNewTab as createNewGQLTab } from "~/helpers/graphql/tab"
|
|
||||||
import { getTabRefWithSaveContext } from "~/helpers/rest/tab"
|
|
||||||
import { currentTabID } from "~/helpers/rest/tab"
|
|
||||||
import {
|
import {
|
||||||
HoppCollection,
|
HoppCollection,
|
||||||
HoppGQLRequest,
|
HoppGQLRequest,
|
||||||
@@ -27,6 +23,8 @@ import {
|
|||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
import { invokeAction } from "~/helpers/actions"
|
import { invokeAction } from "~/helpers/actions"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A spotlight searcher that searches through the user's collections
|
* A spotlight searcher that searches through the user's collections
|
||||||
@@ -44,6 +42,9 @@ export class CollectionsSpotlightSearcherService
|
|||||||
public searcherID = "collections"
|
public searcherID = "collections"
|
||||||
public searcherSectionTitle = this.t("collection.my_collections")
|
public searcherSectionTitle = this.t("collection.my_collections")
|
||||||
|
|
||||||
|
private readonly restTab = this.bind(RESTTabService)
|
||||||
|
private readonly gqlTab = this.bind(GQLTabService)
|
||||||
|
|
||||||
private readonly spotlight = this.bind(SpotlightService)
|
private readonly spotlight = this.bind(SpotlightService)
|
||||||
private readonly workspaceService = this.bind(WorkspaceService)
|
private readonly workspaceService = this.bind(WorkspaceService)
|
||||||
|
|
||||||
@@ -290,21 +291,21 @@ export class CollectionsSpotlightSearcherService
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = this.restTab.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: folderPath.join("/"),
|
folderPath: folderPath.join("/"),
|
||||||
requestIndex: reqIndex,
|
requestIndex: reqIndex,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (possibleTab) {
|
if (possibleTab) {
|
||||||
currentTabID.value = possibleTab.value.id
|
this.restTab.setActiveTab(possibleTab.value.id)
|
||||||
} else {
|
} else {
|
||||||
const req = this.getRESTFolderFromFolderPath(folderPath.join("/"))
|
const req = this.getRESTFolderFromFolderPath(folderPath.join("/"))
|
||||||
?.requests[reqIndex]
|
?.requests[reqIndex]
|
||||||
|
|
||||||
if (!req) return
|
if (!req) return
|
||||||
|
|
||||||
createNewTab(
|
this.restTab.createNewTab(
|
||||||
{
|
{
|
||||||
request: req,
|
request: req,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
@@ -326,7 +327,7 @@ export class CollectionsSpotlightSearcherService
|
|||||||
|
|
||||||
if (!req) return
|
if (!req) return
|
||||||
|
|
||||||
createNewGQLTab({
|
this.gqlTab.createNewTab({
|
||||||
saveContext: {
|
saveContext: {
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: folderPath.join("/"),
|
folderPath: folderPath.join("/"),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user