Compare commits
54 Commits
release/20
...
refactor/i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00588bcc0a | ||
|
|
e24d0ce605 | ||
|
|
de725337d6 | ||
|
|
9d1d369f37 | ||
|
|
2bd925d441 | ||
|
|
bb8dc6f7eb | ||
|
|
be3e5ba7e7 | ||
|
|
663134839f | ||
|
|
736f83a70c | ||
|
|
05d2175f43 | ||
|
|
97bd808431 | ||
|
|
a13c2fd4c1 | ||
|
|
16044b5840 | ||
|
|
4ebf850cb6 | ||
|
|
76af7d5e10 | ||
|
|
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
@@ -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
@@ -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
@@ -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.0",
|
||||||
"@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.3-1",
|
||||||
"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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"action": {
|
"action": {
|
||||||
|
"add": "Add",
|
||||||
"autoscroll": "Autoscroll",
|
"autoscroll": "Autoscroll",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"choose_file": "Choose a file",
|
"choose_file": "Choose a file",
|
||||||
@@ -54,9 +55,28 @@
|
|||||||
"new": "Add new",
|
"new": "Add new",
|
||||||
"star": "Add star"
|
"star": "Add star"
|
||||||
},
|
},
|
||||||
|
"cookies": {
|
||||||
|
"modal": {
|
||||||
|
"new_domain_name": "New domain name",
|
||||||
|
"set": "Set a cookie",
|
||||||
|
"cookie_string": "Cookie string",
|
||||||
|
"enter_cookie_string": "Enter cookie string",
|
||||||
|
"cookie_name": "Name",
|
||||||
|
"cookie_value": "Value",
|
||||||
|
"cookie_path": "Path",
|
||||||
|
"cookie_expires": "Expires",
|
||||||
|
"managed_tab": "Managed",
|
||||||
|
"raw_tab": "Raw",
|
||||||
|
"interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.",
|
||||||
|
"empty_domains": "Domain list is empty",
|
||||||
|
"empty_domain": "Domain is empty",
|
||||||
|
"no_cookies_in_domain": "No cookies set for this domain"
|
||||||
|
}
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"chat_with_us": "Chat with us",
|
"chat_with_us": "Chat with us",
|
||||||
"contact_us": "Contact us",
|
"contact_us": "Contact us",
|
||||||
|
"cookies": "Cookies",
|
||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
"copy_user_id": "Copy User Auth Token",
|
"copy_user_id": "Copy User Auth Token",
|
||||||
"developer_option": "Developer options",
|
"developer_option": "Developer options",
|
||||||
@@ -112,6 +132,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 +145,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 +231,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",
|
||||||
@@ -234,6 +257,7 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"browser_support_sse": "This browser doesn't seems to have Server Sent Events support.",
|
"browser_support_sse": "This browser doesn't seems to have Server Sent Events support.",
|
||||||
"check_console_details": "Check console log for details.",
|
"check_console_details": "Check console log for details.",
|
||||||
|
"check_how_to_add_origin": "Check how you can add an origin",
|
||||||
"curl_invalid_format": "cURL is not formatted properly",
|
"curl_invalid_format": "cURL is not formatted properly",
|
||||||
"danger_zone": "Danger zone",
|
"danger_zone": "Danger zone",
|
||||||
"delete_account": "Your account is currently an owner in these teams:",
|
"delete_account": "Your account is currently an owner in these teams:",
|
||||||
@@ -249,9 +273,12 @@
|
|||||||
"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",
|
||||||
|
"please_install_extension": "Please install the extension and add origin to the extension.",
|
||||||
"proxy_error": "Proxy error",
|
"proxy_error": "Proxy error",
|
||||||
"script_fail": "Could not execute pre-request script",
|
"script_fail": "Could not execute pre-request script",
|
||||||
"something_went_wrong": "Something went wrong",
|
"something_went_wrong": "Something went wrong",
|
||||||
@@ -456,6 +483,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 +771,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 +786,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 +868,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.3-1",
|
||||||
"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.0",
|
||||||
"@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",
|
||||||
@@ -54,6 +52,7 @@
|
|||||||
"acorn-walk": "^8.2.0",
|
"acorn-walk": "^8.2.0",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
|
"cookie-es": "^1.0.0",
|
||||||
"dioc": "workspace:^",
|
"dioc": "workspace:^",
|
||||||
"esprima": "^4.0.1",
|
"esprima": "^4.0.1",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
@@ -78,6 +77,8 @@
|
|||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"qs": "^6.11.2",
|
"qs": "^6.11.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
|
"set-cookie-parser": "^2.6.0",
|
||||||
|
"set-cookie-parser-es": "^1.0.5",
|
||||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||||
"socket.io-client-v3": "npm:socket.io-client@^3.1.3",
|
"socket.io-client-v3": "npm:socket.io-client@^3.1.3",
|
||||||
"socket.io-client-v4": "npm:socket.io-client@^4.4.1",
|
"socket.io-client-v4": "npm:socket.io-client@^4.4.1",
|
||||||
@@ -100,7 +101,8 @@
|
|||||||
"wonka": "^6.3.4",
|
"wonka": "^6.3.4",
|
||||||
"workbox-window": "^7.0.0",
|
"workbox-window": "^7.0.0",
|
||||||
"xml-formatter": "^3.5.0",
|
"xml-formatter": "^3.5.0",
|
||||||
"yargs-parser": "^21.1.1"
|
"yargs-parser": "^21.1.1",
|
||||||
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||||
@@ -138,6 +140,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",
|
||||||
|
|||||||
1
packages/hoppscotch-common/public/badge-dark.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="156" height="32" fill="none"><rect width="156" height="32" fill="#000" rx="4"/><text xmlns="http://www.w3.org/2000/svg" x="50%" y="50%" fill="#fff" dominant-baseline="central" font-family="Helvetica,sans-serif" font-size="12" font-weight="bold" text-anchor="middle" text-rendering="geometricPrecision">▶ Run in Hoppscotch</text></svg>
|
||||||
|
After Width: | Height: | Size: 386 B |
1
packages/hoppscotch-common/public/badge-light.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="156" height="32" fill="none"><rect width="156" height="32" fill="#fff" rx="4"/><text xmlns="http://www.w3.org/2000/svg" x="50%" y="50%" fill="#000" dominant-baseline="central" font-family="Helvetica,sans-serif" font-size="12" font-weight="bold" text-anchor="middle" text-rendering="geometricPrecision">▶ Run in Hoppscotch</text></svg>
|
||||||
|
After Width: | Height: | Size: 386 B |
|
Before Width: | Height: | Size: 926 KiB After Width: | Height: | Size: 354 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 462 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 400 B After Width: | Height: | Size: 624 B |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 871 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 510 KiB After Width: | Height: | Size: 360 KiB |
|
Before Width: | Height: | Size: 535 KiB After Width: | Height: | Size: 385 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 178 KiB |
@@ -1 +1,50 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" fill="none"><path fill="#10B981" d="M0 0h512v512H0z"/><circle cx="197.76" cy="157.84" r="10" fill="#fff" fill-opacity=".75"/><circle cx="259.76" cy="161.84" r="12" fill="#fff" fill-opacity=".75"/><circle cx="319.76" cy="177.84" r="10" fill="#fff" fill-opacity=".75"/><path d="M344.963 235.676c2.075-12.698-38.872-29.804-90.967-38.094-52.09-8.296-96.404-4.665-98.48 8.033-.257 1.035 0 1.812.263 2.853-1.298-.521-76.714 211.212-76.714 211.212H364.14s-17.621-181.414-20.211-181.414c.515-.772 1.035-1.549 1.035-2.59Z" fill="url(#a)"/><path d="M314.902 227.386c-1.298 8.033-30.839 9.845-66.343 4.402-35.247-5.7-62.982-16.843-61.684-24.618.521-2.59 3.888-4.665 9.331-5.7-18.141.777-30.062 4.145-31.096 9.845-1.555 10.628 34.726 25.139 81.373 32.657 46.647 7.512 85.782 4.665 87.594-5.7 1.041-6.226-9.33-12.961-26.431-19.439 4.923 2.847 7.513 5.957 7.256 8.553Z" fill="#A7F3D0" fill-opacity=".5"/><path d="M333.557 157.413c-3.104-32.137-27.729-59.351-60.9-64.53-33.172-5.186-64.531 12.954-77.749 42.238 21.251 1.298 44.057 3.631 67.904 7.518 25.396 3.888 49.237 9.074 70.745 14.774Z" fill="url(#b)"/><path d="M74.142 158.002c-2.59 15.808 30.319 35.247 81.894 51.055-.257-1.04-.257-1.818-.257-2.853 2.07-12.698 46.127-16.328 98.48-8.032 52.347 8.29 93.037 25.396 90.961 38.094-.257 1.04-.514 1.818-1.035 2.589 53.645.778 90.968-7.512 93.557-23.32 3.625-24.104-74.638-56.498-174.93-72.306-100.555-15.808-185.045-9.331-188.67 14.773Zm115.586-1.298c.778-4.145 4.665-7.255 8.81-6.477 4.145.777 7.256 4.665 6.478 8.81-.52 4.145-4.665 6.998-8.81 6.478-4.145-.778-7.255-4.666-6.478-8.811Zm59.866 4.145c.777-5.7 6.22-9.587 11.92-8.547 5.7.778 9.588 6.215 8.553 11.921-1.041 5.442-6.478 9.33-11.92 8.553-5.706-.778-9.594-6.221-8.553-11.927Zm62.975 15.294c.778-4.145 4.665-7.255 8.81-6.478 4.145.778 7.255 4.666 6.478 8.811-.515 4.145-4.665 7.255-8.81 6.477-4.145-.777-7.256-4.665-6.478-8.81Z" fill="url(#c)"/><defs><radialGradient id="b" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 32.7063 -69.3245 0 264.232 124.706)"><stop stop-color="#047857"/><stop offset="1" stop-color="#064E3B"/></radialGradient><radialGradient id="c" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(255.837 186.754) scale(1389.61)"><stop stop-color="#047857"/><stop offset=".115" stop-color="#064E3B"/></radialGradient><linearGradient id="a" x1="224.998" y1="157.606" x2="224.998" y2="403.696" gradientUnits="userSpaceOnUse"><stop stop-color="#86EFAC" stop-opacity=".75"/><stop offset=".635" stop-color="#fff" stop-opacity=".2"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient></defs></svg>
|
<svg width="824" height="824" viewBox="0 0 824 824" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="824" height="824" rx="184" fill="#08110F"/>
|
||||||
|
<rect width="824" height="824" rx="184" fill="url(#paint0_radial_0_21)" fill-opacity="0.5"/>
|
||||||
|
<path d="M435.425 463.217C429.441 476.657 411.033 481.515 394.309 474.07C377.585 466.624 368.879 449.693 374.863 436.253C380.846 422.813 399.254 417.954 415.978 425.4C432.702 432.846 441.409 449.777 435.425 463.217Z" fill="url(#paint1_linear_0_21)"/>
|
||||||
|
<path d="M435.425 463.217C429.441 476.657 411.033 481.515 394.309 474.07C377.585 466.624 368.879 449.693 374.863 436.253C380.846 422.813 399.254 417.954 415.978 425.4C432.702 432.846 441.409 449.777 435.425 463.217Z" fill="url(#paint2_radial_0_21)" style="mix-blend-mode:soft-light"/>
|
||||||
|
<path d="M535.563 521.172C553.071 526.191 570.536 518.856 574.571 504.789C578.606 490.722 567.684 475.251 550.175 470.232C532.666 465.213 515.201 472.548 511.166 486.615C507.131 500.682 518.054 516.153 535.563 521.172Z" fill="url(#paint3_linear_0_21)"/>
|
||||||
|
<path d="M535.563 521.172C553.071 526.191 570.536 518.856 574.571 504.789C578.606 490.722 567.684 475.251 550.175 470.232C532.666 465.213 515.201 472.548 511.166 486.615C507.131 500.682 518.054 516.153 535.563 521.172Z" fill="url(#paint4_radial_0_21)" style="mix-blend-mode:soft-light"/>
|
||||||
|
<path d="M292.782 355.633C308.227 365.286 314.462 383.173 306.709 395.584C298.955 407.995 280.149 410.231 264.704 400.578C249.258 390.924 243.023 373.037 250.777 360.626C258.53 348.215 277.337 345.98 292.782 355.633Z" fill="url(#paint5_linear_0_21)"/>
|
||||||
|
<path d="M292.782 355.633C308.227 365.286 314.462 383.173 306.709 395.584C298.955 407.995 280.149 410.231 264.704 400.578C249.258 390.924 243.023 373.037 250.777 360.626C258.53 348.215 277.337 345.98 292.782 355.633Z" fill="url(#paint6_radial_0_21)" style="mix-blend-mode:soft-light"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M502.355 231.325C581.373 266.506 632.095 343.263 634.119 429.03C680.633 465.639 726.858 516.883 705.36 565.168C681.25 619.319 595.382 617.091 497.781 589.689C450.767 615.718 392.444 620.168 339.689 596.68C286.934 573.192 251.229 526.908 239.1 474.517C153.428 420.321 94.3151 357.999 118.425 303.847C139.923 255.562 208.935 255.626 267.265 265.697C332.356 209.81 423.338 196.144 502.355 231.325ZM159.38 322.082C147.667 348.389 210.578 423.052 382.845 499.751C555.111 576.449 652.693 573.241 664.405 546.934C674.099 525.16 634.213 483.308 588.537 450.878C553.009 425.484 504.344 397.494 440.864 369.231C423.586 361.538 416.839 341.008 424.104 324.691C431.369 308.374 447.329 297.463 480.93 295.91C496.747 295.862 498.823 291.476 499.546 287.716C500.442 281.915 492.401 276.002 484.108 272.31C418.17 242.953 337.453 255.265 281.503 314.178C226.84 301.933 169.074 300.309 159.38 322.082Z" fill="url(#paint7_linear_0_21)"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M502.355 231.325C581.373 266.506 632.095 343.263 634.119 429.03C680.633 465.639 726.858 516.883 705.36 565.168C681.25 619.319 595.382 617.091 497.781 589.689C450.767 615.718 392.444 620.168 339.689 596.68C286.934 573.192 251.229 526.908 239.1 474.517C153.428 420.321 94.3151 357.999 118.425 303.847C139.923 255.562 208.935 255.626 267.265 265.697C332.356 209.81 423.338 196.144 502.355 231.325ZM159.38 322.082C147.667 348.389 210.578 423.052 382.845 499.751C555.111 576.449 652.693 573.241 664.405 546.934C674.099 525.16 634.213 483.308 588.537 450.878C553.009 425.484 504.344 397.494 440.864 369.231C423.586 361.538 416.839 341.008 424.104 324.691C431.369 308.374 447.329 297.463 480.93 295.91C496.747 295.862 498.823 291.476 499.546 287.716C500.442 281.915 492.401 276.002 484.108 272.31C418.17 242.953 337.453 255.265 281.503 314.178C226.84 301.933 169.074 300.309 159.38 322.082Z" fill="url(#paint8_radial_0_21)" style="mix-blend-mode:soft-light"/>
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="paint0_radial_0_21" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(814.524 12.36) rotate(125.613) scale(1089.59 1210.34)">
|
||||||
|
<stop stop-color="#00D196" stop-opacity="0.5"/>
|
||||||
|
<stop offset="0.996771" stop-color="#00D196" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="paint1_linear_0_21" x1="411.893" y1="212" x2="411.893" y2="612" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#00D196"/>
|
||||||
|
<stop offset="1" stop-color="#00B381"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="paint2_radial_0_21" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(644.721 344.481) rotate(159.984) scale(631.37 385.135)">
|
||||||
|
<stop stop-color="white"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="paint3_linear_0_21" x1="411.893" y1="212" x2="411.893" y2="612" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#00D196"/>
|
||||||
|
<stop offset="1" stop-color="#00B381"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="paint4_radial_0_21" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(644.721 344.481) rotate(159.984) scale(631.37 385.135)">
|
||||||
|
<stop stop-color="white"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="paint5_linear_0_21" x1="411.893" y1="212" x2="411.893" y2="612" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#00D196"/>
|
||||||
|
<stop offset="1" stop-color="#00B381"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="paint6_radial_0_21" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(644.721 344.481) rotate(159.984) scale(631.37 385.135)">
|
||||||
|
<stop stop-color="white"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="paint7_linear_0_21" x1="411.893" y1="212" x2="411.893" y2="612" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#00D196"/>
|
||||||
|
<stop offset="1" stop-color="#00B381"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="paint8_radial_0_21" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(644.721 344.481) rotate(159.984) scale(631.37 385.135)">
|
||||||
|
<stop stop-color="white"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 5.9 KiB |
@@ -58,6 +58,8 @@ declare module 'vue' {
|
|||||||
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
||||||
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
||||||
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
||||||
|
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
|
||||||
|
CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default']
|
||||||
Environments: typeof import('./components/environments/index.vue')['default']
|
Environments: typeof import('./components/environments/index.vue')['default']
|
||||||
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
||||||
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
||||||
@@ -90,13 +92,11 @@ declare module 'vue' {
|
|||||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
||||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
||||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
||||||
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
|
|
||||||
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
||||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
||||||
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
||||||
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
||||||
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
||||||
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
|
|
||||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||||
@@ -143,7 +143,6 @@ declare module 'vue' {
|
|||||||
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
||||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||||
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
||||||
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
|
|
||||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||||
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
||||||
@@ -153,10 +152,9 @@ declare module 'vue' {
|
|||||||
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
||||||
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
||||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||||
IconLucideRss: typeof import('~icons/lucide/rss')['default']
|
|
||||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||||
IconLucideVerified: typeof import('~icons/lucide/verified')['default']
|
InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default']
|
||||||
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
||||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||||
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
||||||
@@ -189,7 +187,6 @@ declare module 'vue' {
|
|||||||
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
|
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
|
||||||
SmartExpand: typeof import('./../../hoppscotch-ui/src/components/smart/Expand.vue')['default']
|
SmartExpand: typeof import('./../../hoppscotch-ui/src/components/smart/Expand.vue')['default']
|
||||||
SmartFileChip: typeof import('./../../hoppscotch-ui/src/components/smart/FileChip.vue')['default']
|
SmartFileChip: typeof import('./../../hoppscotch-ui/src/components/smart/FileChip.vue')['default']
|
||||||
SmartFontSizePicker: typeof import('./components/smart/FontSizePicker.vue')['default']
|
|
||||||
SmartInput: typeof import('./../../hoppscotch-ui/src/components/smart/Input.vue')['default']
|
SmartInput: typeof import('./../../hoppscotch-ui/src/components/smart/Input.vue')['default']
|
||||||
SmartIntersection: typeof import('./../../hoppscotch-ui/src/components/smart/Intersection.vue')['default']
|
SmartIntersection: typeof import('./../../hoppscotch-ui/src/components/smart/Intersection.vue')['default']
|
||||||
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
|
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -20,6 +20,12 @@
|
|||||||
<AppInterceptor />
|
<AppInterceptor />
|
||||||
</template>
|
</template>
|
||||||
</tippy>
|
</tippy>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-if="platform.platformFeatureFlags.cookiesEnabled ?? false"
|
||||||
|
:label="t('app.cookies')"
|
||||||
|
:icon="IconCookie"
|
||||||
|
@click="showCookiesModal = true"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<tippy
|
<tippy
|
||||||
@@ -195,12 +201,17 @@
|
|||||||
:show="showDeveloperOptions"
|
:show="showDeveloperOptions"
|
||||||
@hide-modal="showDeveloperOptions = false"
|
@hide-modal="showDeveloperOptions = false"
|
||||||
/>
|
/>
|
||||||
|
<CookiesAllModal
|
||||||
|
:show="showCookiesModal"
|
||||||
|
@hide-modal="showCookiesModal = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { version } from "~/../package.json"
|
import { version } from "~/../package.json"
|
||||||
|
import IconCookie from "~icons/lucide/cookie"
|
||||||
import IconSidebar from "~icons/lucide/sidebar"
|
import IconSidebar from "~icons/lucide/sidebar"
|
||||||
import IconZap from "~icons/lucide/zap"
|
import IconZap from "~icons/lucide/zap"
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
@@ -223,7 +234,9 @@ import { invokeAction } from "@helpers/actions"
|
|||||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const showDeveloperOptions = ref(false)
|
const showDeveloperOptions = ref(false)
|
||||||
|
const showCookiesModal = ref(false)
|
||||||
|
|
||||||
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
||||||
const SIDEBAR = useSetting("SIDEBAR")
|
const SIDEBAR = useSetting("SIDEBAR")
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<header
|
<header
|
||||||
|
ref="headerRef"
|
||||||
class="flex items-center justify-between flex-1 flex-shrink-0 px-2 py-2 space-x-2 overflow-x-auto overflow-y-hidden"
|
class="flex items-center justify-between flex-1 flex-shrink-0 px-2 py-2 space-x-2 overflow-x-auto overflow-y-hidden"
|
||||||
|
@mousedown.prevent="platform.ui?.appHeader?.onHeaderAreaClick?.()"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="inline-flex items-center justify-start flex-1 space-x-2"
|
class="inline-flex items-center justify-start flex-1 space-x-2"
|
||||||
@@ -231,29 +233,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 +290,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 +392,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 +438,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
|
||||||
|
|||||||
@@ -258,27 +258,42 @@ const importFromJSON = () => {
|
|||||||
inputChooseFileToImportFrom.value.value = ""
|
inputChooseFileToImportFrom.value.value = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportJSON = () => {
|
const exportJSON = async () => {
|
||||||
const dataToWrite = collectionJson.value
|
const dataToWrite = collectionJson.value
|
||||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
|
||||||
const a = document.createElement("a")
|
|
||||||
const url = URL.createObjectURL(file)
|
|
||||||
a.href = url
|
|
||||||
|
|
||||||
platform?.analytics?.logEvent({
|
const parsedCollections = JSON.parse(dataToWrite)
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
|
||||||
exporter: "json",
|
if (!parsedCollections.length) {
|
||||||
platform: "gql",
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
|
const url = URL.createObjectURL(file)
|
||||||
|
|
||||||
|
const filename = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||||
|
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
|
||||||
|
const result = await platform.io.saveFileWithDialog({
|
||||||
|
data: dataToWrite,
|
||||||
|
contentType: "application/json",
|
||||||
|
suggestedFilename: filename,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "Hoppscotch Collection JSON file",
|
||||||
|
extensions: ["json"],
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: get uri from meta
|
if (result.type === "unknown" || result.type === "saved") {
|
||||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
platform?.analytics?.logEvent({
|
||||||
document.body.appendChild(a)
|
type: "HOPP_EXPORT_COLLECTION",
|
||||||
a.click()
|
exporter: "json",
|
||||||
toast.success(t("state.download_started").toString())
|
platform: "gql",
|
||||||
setTimeout(() => {
|
})
|
||||||
document.body.removeChild(a)
|
|
||||||
URL.revokeObjectURL(url)
|
toast.success(t("state.download_started").toString())
|
||||||
}, 1000)
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
@@ -1867,28 +1866,25 @@ const getJSONCollection = async () => {
|
|||||||
* @param collectionJSON - JSON string of the collection
|
* @param collectionJSON - JSON string of the collection
|
||||||
* @param name - Name of the collection set as the file name
|
* @param name - Name of the collection set as the file name
|
||||||
*/
|
*/
|
||||||
const initializeDownloadCollection = (
|
const initializeDownloadCollection = async (
|
||||||
collectionJSON: string,
|
collectionJSON: string,
|
||||||
name: string | null
|
name: string | null
|
||||||
) => {
|
) => {
|
||||||
const file = new Blob([collectionJSON], { type: "application/json" })
|
const result = await platform.io.saveFileWithDialog({
|
||||||
const a = document.createElement("a")
|
data: collectionJSON,
|
||||||
const url = URL.createObjectURL(file)
|
contentType: "application/json",
|
||||||
a.href = url
|
suggestedFilename: `${name ?? "collection"}.json`,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "Hoppscotch Collection JSON file",
|
||||||
|
extensions: ["json"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
if (name) {
|
if (result.type === "unknown" || result.type === "saved") {
|
||||||
a.download = `${name}.json`
|
toast.success(t("state.download_started").toString())
|
||||||
} else {
|
|
||||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.appendChild(a)
|
|
||||||
a.click()
|
|
||||||
toast.success(t("state.download_started").toString())
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(a)
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
}, 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1917,11 +1913,14 @@ const exportData = async (
|
|||||||
exportLoading.value = false
|
exportLoading.value = false
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
(coll) => {
|
async (coll) => {
|
||||||
const hoppColl = teamCollToHoppRESTColl(coll)
|
const hoppColl = teamCollToHoppRESTColl(coll)
|
||||||
const collectionJSONString = JSON.stringify(hoppColl)
|
const collectionJSONString = JSON.stringify(hoppColl)
|
||||||
|
|
||||||
initializeDownloadCollection(collectionJSONString, hoppColl.name)
|
await initializeDownloadCollection(
|
||||||
|
collectionJSONString,
|
||||||
|
hoppColl.name
|
||||||
|
)
|
||||||
exportLoading.value = false
|
exportLoading.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
269
packages/hoppscotch-common/src/components/cookies/AllModal.vue
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
<template>
|
||||||
|
<HoppSmartModal
|
||||||
|
v-if="show"
|
||||||
|
dialog
|
||||||
|
:title="t('app.cookies')"
|
||||||
|
aria-modal="true"
|
||||||
|
@close="hideModal"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<HoppSmartPlaceholder
|
||||||
|
v-if="!currentInterceptorSupportsCookies"
|
||||||
|
:text="t('cookies.modal.interceptor_no_support')"
|
||||||
|
>
|
||||||
|
<AppInterceptor class="p-2 border rounded border-dividerLight" />
|
||||||
|
</HoppSmartPlaceholder>
|
||||||
|
<div v-else class="flex flex-col">
|
||||||
|
<div
|
||||||
|
class="flex bg-primary space-x-2 border-b sticky border-dividerLight -mx-4 px-4 py-4 -mt-4"
|
||||||
|
style="top: calc(-1 * var(--line-height-body))"
|
||||||
|
>
|
||||||
|
<HoppSmartInput
|
||||||
|
v-model="newDomainText"
|
||||||
|
class="flex-1"
|
||||||
|
:placeholder="t('cookies.modal.new_domain_name')"
|
||||||
|
@keyup.enter="addNewDomain"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
:label="t('action.add')"
|
||||||
|
@click="addNewDomain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col space-y-4">
|
||||||
|
<HoppSmartPlaceholder
|
||||||
|
v-if="workingCookieJar.size === 0"
|
||||||
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
|
:alt="`${t('cookies.modal.empty_domains')}`"
|
||||||
|
:text="t('cookies.modal.empty_domains')"
|
||||||
|
class="mt-6"
|
||||||
|
>
|
||||||
|
</HoppSmartPlaceholder>
|
||||||
|
<div
|
||||||
|
v-for="[domain, entries] in workingCookieJar.entries()"
|
||||||
|
v-else
|
||||||
|
:key="domain"
|
||||||
|
class="flex flex-col"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between flex-1">
|
||||||
|
<label for="cookiesList" class="p-4">
|
||||||
|
{{ domain }}
|
||||||
|
</label>
|
||||||
|
<div class="flex">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('action.delete')"
|
||||||
|
:icon="IconTrash2"
|
||||||
|
@click="deleteDomain(domain)"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('add.new')"
|
||||||
|
:icon="IconPlus"
|
||||||
|
@click="addCookieToDomain(domain)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="border rounded border-divider">
|
||||||
|
<div class="divide-y divide-dividerLight">
|
||||||
|
<div
|
||||||
|
v-if="entries.length === 0"
|
||||||
|
class="flex flex-col gap-2 p-4 items-center"
|
||||||
|
>
|
||||||
|
{{ t("cookies.modal.no_cookies_in_domain") }}
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-for="(entry, entryIndex) in entries"
|
||||||
|
:key="`${entry}-${entryIndex}`"
|
||||||
|
class="flex divide-x divide-dividerLight"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||||
|
:value="entry"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('action.edit')"
|
||||||
|
:icon="IconEdit"
|
||||||
|
@click="editCookie(domain, entryIndex, entry)"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('action.remove')"
|
||||||
|
:icon="IconTrash"
|
||||||
|
color="red"
|
||||||
|
@click="deleteCookie(domain, entryIndex)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="currentInterceptorSupportsCookies" #footer>
|
||||||
|
<span class="flex space-x-2">
|
||||||
|
<HoppButtonPrimary
|
||||||
|
v-focus
|
||||||
|
:label="t('action.save')"
|
||||||
|
outline
|
||||||
|
@click="saveCookieChanges"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="t('action.cancel')"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
@click="cancelCookieChanges"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="t('action.clear_all')"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
@click="clearAllDomains"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</HoppSmartModal>
|
||||||
|
<CookiesEditCookie
|
||||||
|
:show="!!showEditModalFor"
|
||||||
|
:entry="showEditModalFor"
|
||||||
|
@save-cookie="saveCookie"
|
||||||
|
@hide-modal="showEditModalFor = null"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { CookieJarService } from "~/services/cookie-jar.service"
|
||||||
|
import IconTrash from "~icons/lucide/trash"
|
||||||
|
import IconEdit from "~icons/lucide/edit"
|
||||||
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
|
import IconPlus from "~icons/lucide/plus"
|
||||||
|
import { cloneDeep } from "lodash-es"
|
||||||
|
import { ref, watch, computed } from "vue"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { EditCookieConfig } from "./EditCookie.vue"
|
||||||
|
import { useColorMode } from "@composables/theming"
|
||||||
|
import { useToast } from "@composables/toast"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
show: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "hide-modal"): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
const colorMode = useColorMode()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const newDomainText = ref("")
|
||||||
|
|
||||||
|
const interceptorService = useService(InterceptorService)
|
||||||
|
const cookieJarService = useService(CookieJarService)
|
||||||
|
|
||||||
|
const workingCookieJar = ref(cloneDeep(cookieJarService.cookieJar.value))
|
||||||
|
|
||||||
|
const currentInterceptorSupportsCookies = computed(() => {
|
||||||
|
const currentInterceptor = interceptorService.currentInterceptor.value
|
||||||
|
|
||||||
|
if (!currentInterceptor) return true
|
||||||
|
|
||||||
|
return currentInterceptor.supportsCookies ?? false
|
||||||
|
})
|
||||||
|
|
||||||
|
function addNewDomain() {
|
||||||
|
if (newDomainText.value === "" || /^\s+$/.test(newDomainText.value)) {
|
||||||
|
toast.error(`${t("cookies.modal.empty_domain")}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
workingCookieJar.value.set(newDomainText.value, [])
|
||||||
|
newDomainText.value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteDomain(domain: string) {
|
||||||
|
workingCookieJar.value.delete(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCookieToDomain(domain: string) {
|
||||||
|
showEditModalFor.value = { type: "create", domain }
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAllDomains() {
|
||||||
|
workingCookieJar.value = new Map()
|
||||||
|
toast.success(`${t("state.cleared")}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.show,
|
||||||
|
(show) => {
|
||||||
|
if (show) {
|
||||||
|
workingCookieJar.value = cloneDeep(cookieJarService.cookieJar.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const showEditModalFor = ref<EditCookieConfig | null>(null)
|
||||||
|
|
||||||
|
function saveCookieChanges() {
|
||||||
|
cookieJarService.cookieJar.value = workingCookieJar.value
|
||||||
|
hideModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelCookieChanges() {
|
||||||
|
hideModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
function editCookie(domain: string, entryIndex: number, cookieEntry: string) {
|
||||||
|
showEditModalFor.value = {
|
||||||
|
type: "edit",
|
||||||
|
domain,
|
||||||
|
entryIndex,
|
||||||
|
currentCookieEntry: cookieEntry,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCookie(domain: string, entryIndex: number) {
|
||||||
|
const entry = workingCookieJar.value.get(domain)
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
entry.splice(entryIndex, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCookie(cookie: string) {
|
||||||
|
if (showEditModalFor.value?.type === "create") {
|
||||||
|
const { domain } = showEditModalFor.value
|
||||||
|
|
||||||
|
const entry = workingCookieJar.value.get(domain)!
|
||||||
|
entry.push(cookie)
|
||||||
|
|
||||||
|
showEditModalFor.value = null
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showEditModalFor.value?.type !== "edit") return
|
||||||
|
|
||||||
|
const { domain, entryIndex } = showEditModalFor.value!
|
||||||
|
|
||||||
|
const entry = workingCookieJar.value.get(domain)
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
entry[entryIndex] = cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
showEditModalFor.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideModal = () => {
|
||||||
|
emit("hide-modal")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
195
packages/hoppscotch-common/src/components/cookies/EditCookie.vue
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
<template>
|
||||||
|
<HoppSmartModal
|
||||||
|
v-if="show"
|
||||||
|
dialog
|
||||||
|
:title="t('cookies.modal.set')"
|
||||||
|
@close="hideModal"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<div class="border rounded border-dividerLight">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex items-center justify-between pl-4">
|
||||||
|
<label class="font-semibold truncate text-secondaryLight">
|
||||||
|
{{ t("cookies.modal.cookie_string") }}
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('action.clear_all')"
|
||||||
|
:icon="IconTrash2"
|
||||||
|
@click="clearContent()"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('state.linewrap')"
|
||||||
|
:class="{ '!text-accent': linewrapEnabled }"
|
||||||
|
:icon="IconWrapText"
|
||||||
|
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
|
:title="t('action.download_file')"
|
||||||
|
:icon="downloadIcon"
|
||||||
|
@click="downloadResponse"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
|
:title="t('action.copy')"
|
||||||
|
:icon="copyIcon"
|
||||||
|
@click="copyResponse"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-46">
|
||||||
|
<div
|
||||||
|
ref="cookieEditor"
|
||||||
|
class="h-full border-t rounded-b border-dividerLight"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<HoppButtonPrimary
|
||||||
|
v-focus
|
||||||
|
:label="t('action.save')"
|
||||||
|
outline
|
||||||
|
@click="saveCookieChange"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="t('action.cancel')"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
@click="cancelCookieChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span class="flex">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:icon="pasteIcon"
|
||||||
|
:label="`${t('action.paste')}`"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="handlePaste"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</HoppSmartModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export type EditCookieConfig =
|
||||||
|
| { type: "create"; domain: string }
|
||||||
|
| {
|
||||||
|
type: "edit"
|
||||||
|
domain: string
|
||||||
|
entryIndex: number
|
||||||
|
currentCookieEntry: string
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { useCodemirror } from "~/composables/codemirror"
|
||||||
|
import { watch, ref, reactive } from "vue"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
|
import IconWrapText from "~icons/lucide/wrap-text"
|
||||||
|
import IconClipboard from "~icons/lucide/clipboard"
|
||||||
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
import {
|
||||||
|
useCopyResponse,
|
||||||
|
useDownloadResponse,
|
||||||
|
} from "~/composables/lens-actions"
|
||||||
|
|
||||||
|
// TODO: Build Managed Mode!
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
show: boolean
|
||||||
|
|
||||||
|
entry: EditCookieConfig | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "save-cookie", cookie: string): void
|
||||||
|
(e: "hide-modal"): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const cookieEditor = ref<HTMLElement>()
|
||||||
|
const rawCookieString = ref("")
|
||||||
|
const linewrapEnabled = ref(true)
|
||||||
|
|
||||||
|
useCodemirror(
|
||||||
|
cookieEditor,
|
||||||
|
rawCookieString,
|
||||||
|
reactive({
|
||||||
|
extendedEditorConfig: {
|
||||||
|
mode: "text/plain",
|
||||||
|
placeholder: `${t("cookies.modal.enter_cookie_string")}`,
|
||||||
|
lineWrapping: linewrapEnabled,
|
||||||
|
},
|
||||||
|
linter: null,
|
||||||
|
completer: null,
|
||||||
|
environmentHighlights: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const pasteIcon = refAutoReset<typeof IconClipboard | typeof IconCheck>(
|
||||||
|
IconClipboard,
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.entry,
|
||||||
|
() => {
|
||||||
|
if (!props.entry) return
|
||||||
|
|
||||||
|
if (props.entry.type === "create") {
|
||||||
|
rawCookieString.value = ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rawCookieString.value = props.entry.currentCookieEntry
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function hideModal() {
|
||||||
|
emit("hide-modal")
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelCookieChange() {
|
||||||
|
hideModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePaste() {
|
||||||
|
try {
|
||||||
|
const text = await navigator.clipboard.readText()
|
||||||
|
if (text) {
|
||||||
|
rawCookieString.value = text
|
||||||
|
pasteIcon.value = IconCheck
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to copy: ", e)
|
||||||
|
toast.error(t("profile.no_permission").toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCookieChange() {
|
||||||
|
emit("save-cookie", rawCookieString.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { copyIcon, copyResponse } = useCopyResponse(rawCookieString)
|
||||||
|
const { downloadIcon, downloadResponse } = useDownloadResponse(
|
||||||
|
"",
|
||||||
|
rawCookieString
|
||||||
|
)
|
||||||
|
|
||||||
|
function clearContent() {
|
||||||
|
rawCookieString.value = ""
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -375,22 +375,37 @@ const importFromPostman = ({
|
|||||||
importFromHoppscotch(environments)
|
importFromHoppscotch(environments)
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportJSON = () => {
|
const exportJSON = async () => {
|
||||||
const dataToWrite = environmentJson.value
|
const dataToWrite = environmentJson.value
|
||||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
|
||||||
const a = document.createElement("a")
|
|
||||||
const url = URL.createObjectURL(file)
|
|
||||||
a.href = url
|
|
||||||
|
|
||||||
// TODO: get uri from meta
|
const parsedCollections = JSON.parse(dataToWrite)
|
||||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
|
||||||
document.body.appendChild(a)
|
if (!parsedCollections.length) {
|
||||||
a.click()
|
return toast.error(t("error.no_environments_to_export"))
|
||||||
toast.success(t("state.download_started").toString())
|
}
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(a)
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
URL.revokeObjectURL(url)
|
const url = URL.createObjectURL(file)
|
||||||
}, 1000)
|
|
||||||
|
const filename = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||||
|
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
|
||||||
|
const result = await platform.io.saveFileWithDialog({
|
||||||
|
data: dataToWrite,
|
||||||
|
contentType: "application/json",
|
||||||
|
suggestedFilename: filename,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "JSON file",
|
||||||
|
extensions: ["json"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.type === "unknown" || result.type === "saved") {
|
||||||
|
toast.success(t("state.download_started").toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getErrorMessage = (err: GQLError<string>) => {
|
const getErrorMessage = (err: GQLError<string>) => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,50 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="field-title" :class="{ 'field-highlighted': isHighlighted }">
|
<div class="flex justify-between gap-2">
|
||||||
{{ fieldName }}
|
<div
|
||||||
<span v-if="fieldArgs.length > 0">
|
class="field-title flex-1"
|
||||||
(
|
:class="{ 'field-highlighted': isHighlighted }"
|
||||||
<span v-for="(field, index) in fieldArgs" :key="`field-${index}`">
|
>
|
||||||
{{ field.name }}:
|
{{ fieldName }}
|
||||||
<GraphqlTypeLink
|
<span v-if="fieldArgs.length > 0">
|
||||||
:gql-type="field.type"
|
(
|
||||||
:jump-type-callback="jumpTypeCallback"
|
<span v-for="(field, index) in fieldArgs" :key="`field-${index}`">
|
||||||
/>
|
{{ field.name }}:
|
||||||
<span v-if="index !== fieldArgs.length - 1">, </span>
|
<GraphqlTypeLink
|
||||||
|
:gql-type="field.type"
|
||||||
|
@jump-to-type="jumpToType"
|
||||||
|
/>
|
||||||
|
<span v-if="index !== fieldArgs.length - 1">, </span>
|
||||||
|
</span>
|
||||||
|
) </span
|
||||||
|
>:
|
||||||
|
<GraphqlTypeLink :gql-type="gqlField.type" @jump-to-type="jumpToType" />
|
||||||
|
</div>
|
||||||
|
<div v-if="gqlField.deprecationReason">
|
||||||
|
<span
|
||||||
|
v-tippy="{ theme: 'tomato' }"
|
||||||
|
class="!text-red-500 hover:!text-red-600 text-xs flex items-center gap-2 cursor-pointer"
|
||||||
|
:title="gqlField.deprecationReason"
|
||||||
|
>
|
||||||
|
<IconAlertTriangle /> {{ t("state.deprecated") }}
|
||||||
</span>
|
</span>
|
||||||
) </span
|
</div>
|
||||||
>:
|
|
||||||
<GraphqlTypeLink
|
|
||||||
:gql-type="gqlField.type"
|
|
||||||
:jump-type-callback="jumpTypeCallback"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="gqlField.description"
|
v-if="gqlField.description"
|
||||||
class="py-2 text-secondaryLight field-desc"
|
class="field-desc py-2 text-secondaryLight"
|
||||||
>
|
>
|
||||||
{{ gqlField.description }}
|
{{ gqlField.description }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="gqlField.isDeprecated"
|
|
||||||
class="inline-block px-2 py-1 my-1 text-black bg-yellow-200 rounded field-deprecated"
|
|
||||||
>
|
|
||||||
{{ t("state.deprecated") }}
|
|
||||||
</div>
|
|
||||||
<div v-if="fieldArgs.length > 0">
|
<div v-if="fieldArgs.length > 0">
|
||||||
<h5 class="my-2">Arguments:</h5>
|
<h5 class="my-2">Arguments:</h5>
|
||||||
<div class="pl-4 border-l-2 border-divider">
|
<div class="border-l-2 border-divider pl-4">
|
||||||
<div v-for="(field, index) in fieldArgs" :key="`field-${index}`">
|
<div v-for="(field, index) in fieldArgs" :key="`field-${index}`">
|
||||||
<span>
|
<span>
|
||||||
{{ field.name }}:
|
{{ field.name }}:
|
||||||
<GraphqlTypeLink
|
<GraphqlTypeLink
|
||||||
:gql-type="field.type"
|
:gql-type="field.type"
|
||||||
:jump-type-callback="jumpTypeCallback"
|
@jump-to-type="jumpToType"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
class="py-2 text-secondaryLight field-desc"
|
class="field-desc py-2 text-secondaryLight"
|
||||||
>
|
>
|
||||||
{{ field.description }}
|
{{ field.description }}
|
||||||
</div>
|
</div>
|
||||||
@@ -54,37 +59,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
// TypeScript + Script Setup this :)
|
|
||||||
import { defineComponent } from "vue"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { GraphQLType } from "graphql"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
||||||
|
|
||||||
export default defineComponent({
|
const t = useI18n()
|
||||||
props: {
|
|
||||||
gqlField: { type: Object, default: () => ({}) },
|
|
||||||
jumpTypeCallback: { type: Function, default: () => ({}) },
|
|
||||||
isHighlighted: { type: Boolean, default: false },
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
t: useI18n(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
fieldName() {
|
|
||||||
return this.gqlField.name
|
|
||||||
},
|
|
||||||
|
|
||||||
fieldArgs() {
|
const props = withDefaults(
|
||||||
return this.gqlField.args || []
|
defineProps<{
|
||||||
},
|
gqlField: any
|
||||||
},
|
isHighlighted: boolean
|
||||||
})
|
}>(),
|
||||||
|
{
|
||||||
|
gqlField: {},
|
||||||
|
isHighlighted: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "jump-to-type", type: GraphQLType): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const fieldName = computed(() => props.gqlField.name)
|
||||||
|
|
||||||
|
const fieldArgs = computed(() => props.gqlField.args || [])
|
||||||
|
|
||||||
|
const jumpToType = (type: GraphQLType) => {
|
||||||
|
emit("jump-to-type", type)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.field-highlighted {
|
.field-highlighted {
|
||||||
@apply border-accent border-b-2;
|
@apply border-b-2 border-accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-title {
|
.field-title {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import { useToast } from "@composables/toast"
|
|||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
import { GQLResponseEvent } from "~/helpers/graphql/connection"
|
import { GQLResponseEvent } from "~/helpers/graphql/connection"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -111,21 +112,31 @@ const copyResponse = (str: string) => {
|
|||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadResponse = (str: string) => {
|
const downloadResponse = async (str: string) => {
|
||||||
const dataToWrite = str
|
const dataToWrite = str
|
||||||
const file = new Blob([dataToWrite!], { type: "application/json" })
|
const file = new Blob([dataToWrite!], { type: "application/json" })
|
||||||
const a = document.createElement("a")
|
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
a.href = url
|
|
||||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}`
|
const filename = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||||
document.body.appendChild(a)
|
|
||||||
a.click()
|
URL.revokeObjectURL(url)
|
||||||
downloadResponseIcon.value = IconCheck
|
|
||||||
toast.success(`${t("state.download_started")}`)
|
const result = await platform.io.saveFileWithDialog({
|
||||||
setTimeout(() => {
|
data: dataToWrite,
|
||||||
document.body.removeChild(a)
|
contentType: "application/json",
|
||||||
URL.revokeObjectURL(url)
|
suggestedFilename: filename,
|
||||||
}, 1000)
|
filters: [
|
||||||
|
{
|
||||||
|
name: "JSON file",
|
||||||
|
extensions: ["json"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.type === "unknown" || result.type === "saved") {
|
||||||
|
downloadResponseIcon.value = IconCheck
|
||||||
|
toast.success(`${t("state.download_started")}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defineActionHandler(
|
defineActionHandler(
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
v-for="(field, index) in filteredQueryFields"
|
v-for="(field, index) in filteredQueryFields"
|
||||||
:key="`field-${index}`"
|
:key="`field-${index}`"
|
||||||
:gql-field="field"
|
:gql-field="field"
|
||||||
:jump-type-callback="handleJumpToType"
|
|
||||||
class="p-4"
|
class="p-4"
|
||||||
|
@jump-to-type="handleJumpToType"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
@@ -72,8 +72,8 @@
|
|||||||
v-for="(field, index) in filteredMutationFields"
|
v-for="(field, index) in filteredMutationFields"
|
||||||
:key="`field-${index}`"
|
:key="`field-${index}`"
|
||||||
:gql-field="field"
|
:gql-field="field"
|
||||||
:jump-type-callback="handleJumpToType"
|
|
||||||
class="p-4"
|
class="p-4"
|
||||||
|
@jump-to-type="handleJumpToType"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
@@ -86,8 +86,8 @@
|
|||||||
v-for="(field, index) in filteredSubscriptionFields"
|
v-for="(field, index) in filteredSubscriptionFields"
|
||||||
:key="`field-${index}`"
|
:key="`field-${index}`"
|
||||||
:gql-field="field"
|
:gql-field="field"
|
||||||
:jump-type-callback="handleJumpToType"
|
|
||||||
class="p-4"
|
class="p-4"
|
||||||
|
@jump-to-type="handleJumpToType"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
:gql-types="graphqlTypes"
|
:gql-types="graphqlTypes"
|
||||||
:is-highlighted="isGqlTypeHighlighted(type)"
|
:is-highlighted="isGqlTypeHighlighted(type)"
|
||||||
:highlighted-fields="getGqlTypeHighlightedFields(type)"
|
:highlighted-fields="getGqlTypeHighlightedFields(type)"
|
||||||
:jump-type-callback="handleJumpToType"
|
@jump-to-type="handleJumpToType"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
</HoppSmartTabs>
|
</HoppSmartTabs>
|
||||||
@@ -202,6 +202,7 @@ import {
|
|||||||
schemaString,
|
schemaString,
|
||||||
subscriptionFields,
|
subscriptionFields,
|
||||||
} from "~/helpers/graphql/connection"
|
} from "~/helpers/graphql/connection"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
type NavigationTabs = "history" | "collection" | "docs" | "schema"
|
type NavigationTabs = "history" | "collection" | "docs" | "schema"
|
||||||
type GqlTabs = "queries" | "mutations" | "subscriptions" | "types"
|
type GqlTabs = "queries" | "mutations" | "subscriptions" | "types"
|
||||||
@@ -372,21 +373,33 @@ useCodemirror(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const downloadSchema = () => {
|
const downloadSchema = async () => {
|
||||||
const dataToWrite = JSON.stringify(schemaString.value, null, 2)
|
const dataToWrite = schemaString.value
|
||||||
const file = new Blob([dataToWrite], { type: "application/graphql" })
|
const file = new Blob([dataToWrite], { type: "application/graphql" })
|
||||||
const a = document.createElement("a")
|
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
a.href = url
|
|
||||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.graphql`
|
const filename = `${
|
||||||
document.body.appendChild(a)
|
url.split("/").pop()!.split("#")[0].split("?")[0]
|
||||||
a.click()
|
}.graphql`
|
||||||
downloadSchemaIcon.value = IconCheck
|
|
||||||
toast.success(`${t("state.download_started")}`)
|
URL.revokeObjectURL(url)
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(a)
|
const result = await platform.io.saveFileWithDialog({
|
||||||
URL.revokeObjectURL(url)
|
data: dataToWrite,
|
||||||
}, 1000)
|
contentType: "application/graphql",
|
||||||
|
suggestedFilename: filename,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "GraphQL Schema File",
|
||||||
|
extensions: ["graphql"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.type === "unknown" || result.type === "saved") {
|
||||||
|
downloadSchemaIcon.value = IconCheck
|
||||||
|
toast.success(`${t("state.download_started")}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const copySchema = () => {
|
const copySchema = () => {
|
||||||
|
|||||||
@@ -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
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -7,38 +7,31 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from "vue"
|
import { GraphQLScalarType, GraphQLType } from "graphql"
|
||||||
import { GraphQLScalarType } from "graphql"
|
import { computed } from "vue"
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps<{
|
||||||
props: {
|
gqlType: GraphQLType
|
||||||
// eslint-disable-next-line vue/require-default-prop
|
}>()
|
||||||
gqlType: null,
|
|
||||||
// (typeName: string) => void
|
|
||||||
// eslint-disable-next-line vue/require-default-prop
|
|
||||||
jumpTypeCallback: Function,
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
const emit = defineEmits<{
|
||||||
typeString() {
|
(e: "jump-to-type", type: GraphQLType): void
|
||||||
return `${this.gqlType}`
|
}>()
|
||||||
},
|
|
||||||
isScalar() {
|
|
||||||
return this.resolveRootType(this.gqlType) instanceof GraphQLScalarType
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
const typeString = computed(() => `${props.gqlType}`)
|
||||||
jumpToType() {
|
const isScalar = computed(() => {
|
||||||
if (this.isScalar) return
|
return resolveRootType(props.gqlType) instanceof GraphQLScalarType
|
||||||
this.jumpTypeCallback(this.gqlType)
|
|
||||||
},
|
|
||||||
resolveRootType(type) {
|
|
||||||
let t = type
|
|
||||||
while (t.ofType != null) t = t.ofType
|
|
||||||
return t
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function resolveRootType(type: GraphQLType) {
|
||||||
|
let t = type as any
|
||||||
|
while (t.ofType != null) t = t.ofType
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
function jumpToType() {
|
||||||
|
if (isScalar.value) return
|
||||||
|
emit("jump-to-type", props.gqlType)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -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")}`)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { tokenRequest } from "~/helpers/oauth"
|
import { tokenRequest } from "~/helpers/oauth"
|
||||||
import { getCombinedEnvVariables } from "~/helpers/preRequest"
|
import { getCombinedEnvVariables } from "~/helpers/preRequest"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -98,7 +99,11 @@ const handleAccessTokenRequest = async () => {
|
|||||||
clientSecret: parseTemplateString(clientSecret.value, envVars),
|
clientSecret: parseTemplateString(clientSecret.value, envVars),
|
||||||
scope: parseTemplateString(scope.value, envVars),
|
scope: parseTemplateString(scope.value, envVars),
|
||||||
}
|
}
|
||||||
await tokenRequest(tokenReqParams)
|
const res = await tokenRequest(tokenReqParams)
|
||||||
|
|
||||||
|
if (res && E.isLeft(res)) {
|
||||||
|
toast.error(res.left)
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(`${e}`)
|
toast.error(`${e}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -347,7 +350,6 @@ const newSendRequest = async () => {
|
|||||||
const streamResult = await streamPromise
|
const streamResult = await streamPromise
|
||||||
|
|
||||||
requestCancelFunc.value = cancel
|
requestCancelFunc.value = cancel
|
||||||
|
|
||||||
if (E.isRight(streamResult)) {
|
if (E.isRight(streamResult)) {
|
||||||
subscribeToStream(
|
subscribeToStream(
|
||||||
streamResult.right,
|
streamResult.right,
|
||||||
@@ -362,6 +364,20 @@ const newSendRequest = async () => {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
// TODO: Change this any to a proper type
|
||||||
|
const result = (streamResult.right as any).value
|
||||||
|
if (
|
||||||
|
result.type === "network_fail" &&
|
||||||
|
result.error?.error === "NO_PW_EXT_HOOK"
|
||||||
|
) {
|
||||||
|
const errorResponse: HoppRESTResponse = {
|
||||||
|
type: "extension_error",
|
||||||
|
error: result.error.humanMessage.heading,
|
||||||
|
component: result.error.component,
|
||||||
|
req: result.req,
|
||||||
|
}
|
||||||
|
updateRESTResponse(errorResponse)
|
||||||
|
}
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -426,7 +442,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 +450,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 +658,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>
|
||||||
|
|||||||
@@ -11,6 +11,12 @@
|
|||||||
<HoppSmartSpinner class="my-4" />
|
<HoppSmartSpinner class="my-4" />
|
||||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<component
|
||||||
|
:is="response.component"
|
||||||
|
v-if="response.type === 'extension_error'"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="response.type === 'network_fail'"
|
v-if="response.type === 'network_fail'"
|
||||||
:src="`/images/states/${colorMode.value}/youre_lost.svg`"
|
:src="`/images/states/${colorMode.value}/youre_lost.svg`"
|
||||||
@@ -93,10 +99,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 +153,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
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<HoppSmartPlaceholder
|
||||||
|
:src="`/images/states/${colorMode.value}/youre_lost.svg`"
|
||||||
|
:alt="`${t('error.network_fail')}`"
|
||||||
|
:heading="t('error.network_fail')"
|
||||||
|
large
|
||||||
|
>
|
||||||
|
<div class="my-1 text-secondaryLight flex flex-col items-center">
|
||||||
|
<span>
|
||||||
|
{{ t("error.please_install_extension") }}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{ t("error.check_how_to_add_origin") }}
|
||||||
|
<HoppSmartLink
|
||||||
|
blank
|
||||||
|
to="https://docs.hoppscotch.io/documentation/features/interceptor#browser-extension"
|
||||||
|
class="text-accent hover:text-accentDark"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</HoppSmartLink>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col py-4 space-y-2">
|
||||||
|
<span>
|
||||||
|
<HoppSmartItem
|
||||||
|
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
||||||
|
blank
|
||||||
|
:icon="IconChrome"
|
||||||
|
label="Chrome"
|
||||||
|
:info-icon="hasChromeExtInstalled ? IconCheckCircle : null"
|
||||||
|
:active-info-icon="hasChromeExtInstalled"
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<HoppSmartItem
|
||||||
|
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
||||||
|
blank
|
||||||
|
:icon="IconFirefox"
|
||||||
|
label="Firefox"
|
||||||
|
:info-icon="hasFirefoxExtInstalled ? IconCheckCircle : null"
|
||||||
|
:active-info-icon="hasFirefoxExtInstalled"
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="py-4 space-y-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<HoppSmartToggle
|
||||||
|
:on="extensionEnabled"
|
||||||
|
@change="extensionEnabled = !extensionEnabled"
|
||||||
|
>
|
||||||
|
{{ t("settings.extensions_use_toggle") }}
|
||||||
|
</HoppSmartToggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HoppSmartPlaceholder>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import IconChrome from "~icons/brands/chrome"
|
||||||
|
import IconFirefox from "~icons/brands/firefox"
|
||||||
|
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
import { useColorMode } from "~/composables/theming"
|
||||||
|
|
||||||
|
const colorMode = useColorMode()
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const interceptorService = useService(InterceptorService)
|
||||||
|
const extensionService = useService(ExtensionInterceptorService)
|
||||||
|
|
||||||
|
const hasChromeExtInstalled = extensionService.chromeExtensionInstalled
|
||||||
|
const hasFirefoxExtInstalled = extensionService.firefoxExtensionInstalled
|
||||||
|
|
||||||
|
const extensionEnabled = computed({
|
||||||
|
get() {
|
||||||
|
return (
|
||||||
|
interceptorService.currentInterceptorID.value ===
|
||||||
|
extensionService.interceptorID
|
||||||
|
)
|
||||||
|
},
|
||||||
|
set(active) {
|
||||||
|
if (active) {
|
||||||
|
interceptorService.currentInterceptorID.value =
|
||||||
|
extensionService.interceptorID
|
||||||
|
} else {
|
||||||
|
interceptorService.currentInterceptorID.value =
|
||||||
|
platform.interceptors.default
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -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 = () => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useI18n } from "./i18n"
|
|||||||
import { refAutoReset } from "@vueuse/core"
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { copyToClipboard } from "@helpers/utils/clipboard"
|
import { copyToClipboard } from "@helpers/utils/clipboard"
|
||||||
import { HoppRESTResponse } from "@helpers/types/HoppRESTResponse"
|
import { HoppRESTResponse } from "@helpers/types/HoppRESTResponse"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
export function useCopyResponse(responseBodyText: Ref<any>) {
|
export function useCopyResponse(responseBodyText: Ref<any>) {
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -40,15 +41,14 @@ export function useDownloadResponse(
|
|||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const downloadResponse = () => {
|
const downloadResponse = async () => {
|
||||||
const dataToWrite = responseBody.value
|
const dataToWrite = responseBody.value
|
||||||
const file = new Blob([dataToWrite], { type: contentType })
|
|
||||||
const a = document.createElement("a")
|
|
||||||
const url = URL.createObjectURL(file)
|
|
||||||
a.href = url
|
|
||||||
|
|
||||||
// TODO: get uri from meta
|
// Guess extension and filename
|
||||||
a.download = pipe(
|
const file = new Blob([dataToWrite], { type: contentType })
|
||||||
|
const url = URL.createObjectURL(file)
|
||||||
|
|
||||||
|
const filename = pipe(
|
||||||
url,
|
url,
|
||||||
S.split("/"),
|
S.split("/"),
|
||||||
RNEA.last,
|
RNEA.last,
|
||||||
@@ -58,15 +58,24 @@ export function useDownloadResponse(
|
|||||||
RNEA.head
|
RNEA.head
|
||||||
)
|
)
|
||||||
|
|
||||||
document.body.appendChild(a)
|
URL.revokeObjectURL(url)
|
||||||
a.click()
|
|
||||||
downloadIcon.value = IconCheck
|
console.log(filename)
|
||||||
toast.success(`${t("state.download_started")}`)
|
|
||||||
setTimeout(() => {
|
// TODO: Look at the mime type and determine extension ?
|
||||||
document.body.removeChild(a)
|
const result = await platform.io.saveFileWithDialog({
|
||||||
URL.revokeObjectURL(url)
|
data: dataToWrite,
|
||||||
}, 1000)
|
contentType: contentType,
|
||||||
|
suggestedFilename: filename,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Assume success if unknown as we cannot determine
|
||||||
|
if (result.type === "unknown" || result.type === "saved") {
|
||||||
|
downloadIcon.value = IconCheck
|
||||||
|
toast.success(`${t("state.download_started")}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
downloadIcon,
|
downloadIcon,
|
||||||
downloadResponse,
|
downloadResponse,
|
||||||
|
|||||||
@@ -152,12 +152,14 @@ export function useStreamSubscriber(): {
|
|||||||
error?: (e: any) => void,
|
error?: (e: any) => void,
|
||||||
complete?: () => void
|
complete?: () => void
|
||||||
) => {
|
) => {
|
||||||
const sub = stream.subscribe({
|
let sub: Subscription | null = null
|
||||||
|
|
||||||
|
sub = stream.subscribe({
|
||||||
next,
|
next,
|
||||||
error,
|
error,
|
||||||
complete: () => {
|
complete: () => {
|
||||||
if (complete) complete()
|
if (complete) complete()
|
||||||
subs.splice(subs.indexOf(sub), 1)
|
if (sub) subs.splice(subs.indexOf(sub), 1)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||