Compare commits
8 Commits
refactor/i
...
pr/danithe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9581f1d747 | ||
|
|
882aafdb43 | ||
|
|
f33fa6ac1a | ||
|
|
b9a1cc21f1 | ||
|
|
f530fc2853 | ||
|
|
e0eb8af6f5 | ||
|
|
088f1d6b47 | ||
|
|
7d61e69b3d |
@@ -5,5 +5,5 @@
|
||||
"features": {
|
||||
"ghcr.io/NicoVIII/devcontainer-features/pnpm:1": {}
|
||||
},
|
||||
"postCreateCommand": "cp .env.example .env && pnpm i"
|
||||
"postCreateCommand": "mv .env.example .env && pnpm i"
|
||||
}
|
||||
|
||||
3
.github/workflows/release-push-docker.yml
vendored
@@ -18,9 +18,6 @@ jobs:
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
|
||||
2
.github/workflows/ui.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
# Deploy the ui site with netlify-cli
|
||||
- name: Deploy to Netlify (ui)
|
||||
run: npx netlify-cli@15.11.0 deploy --dir=packages/hoppscotch-ui/.histoire/dist --prod
|
||||
run: npx netlify-cli deploy --dir=packages/hoppscotch-ui/.histoire/dist --prod
|
||||
env:
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_UI_SITE_ID }}
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
|
||||
10
package.json
@@ -22,14 +22,16 @@
|
||||
"workspaces": [
|
||||
"./packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^16.2.3",
|
||||
"@commitlint/config-conventional": "^16.2.1",
|
||||
"@types/node": "17.0.27",
|
||||
"@types/node": "^17.0.24",
|
||||
"cross-env": "^7.0.3",
|
||||
"http-server": "^14.1.1",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "12.4.0"
|
||||
"http-server": "^14.1.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"packageExtensions": {
|
||||
|
||||
@@ -17,16 +17,16 @@
|
||||
"types": "dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@codemirror/language": "6.9.0",
|
||||
"@lezer/highlight": "1.1.4",
|
||||
"@lezer/lr": "^1.3.13"
|
||||
"@codemirror/language": "^6.9.0",
|
||||
"@lezer/highlight": "^1.1.6",
|
||||
"@lezer/lr": "^1.3.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lezer/generator": "^1.5.1",
|
||||
"@lezer/generator": "^1.5.0",
|
||||
"mocha": "^9.2.2",
|
||||
"rollup": "^3.29.3",
|
||||
"rollup-plugin-dts": "^6.0.2",
|
||||
"rollup-plugin-ts": "^3.4.5",
|
||||
"typescript": "^5.2.2"
|
||||
"rollup": "^2.70.2",
|
||||
"rollup-plugin-dts": "^4.2.1",
|
||||
"rollup-plugin-ts": "^2.0.7",
|
||||
"typescript": "^4.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hoppscotch-backend",
|
||||
"version": "2023.8.3-1",
|
||||
"version": "2023.8.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
@@ -24,17 +24,18 @@
|
||||
"do-test": "pnpm run test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/server": "^4.9.4",
|
||||
"@nestjs-modules/mailer": "^1.9.1",
|
||||
"@nestjs/apollo": "^12.0.9",
|
||||
"@nestjs/common": "^10.2.6",
|
||||
"@nestjs/core": "^10.2.6",
|
||||
"@nestjs/graphql": "^12.0.9",
|
||||
"@nestjs/jwt": "^10.1.1",
|
||||
"@nestjs/passport": "^10.0.2",
|
||||
"@nestjs/platform-express": "^10.2.6",
|
||||
"@nestjs/throttler": "^5.0.0",
|
||||
"@nestjs-modules/mailer": "^1.8.1",
|
||||
"@nestjs/apollo": "^10.1.6",
|
||||
"@nestjs/common": "^9.2.1",
|
||||
"@nestjs/core": "^9.2.1",
|
||||
"@nestjs/graphql": "^10.1.6",
|
||||
"@nestjs/jwt": "^10.0.1",
|
||||
"@nestjs/passport": "^9.0.0",
|
||||
"@nestjs/platform-express": "^9.2.1",
|
||||
"@nestjs/throttler": "^4.0.0",
|
||||
"@prisma/client": "^4.16.2",
|
||||
"apollo-server-express": "^3.11.1",
|
||||
"apollo-server-plugin-base": "^3.7.1",
|
||||
"argon2": "^0.30.3",
|
||||
"bcrypt": "^5.1.0",
|
||||
"cookie": "^0.5.0",
|
||||
@@ -42,9 +43,9 @@
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.3",
|
||||
"fp-ts": "^2.13.1",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-query-complexity": "^0.12.0",
|
||||
"graphql-redis-subscriptions": "^2.6.0",
|
||||
"graphql-redis-subscriptions": "^2.5.0",
|
||||
"graphql-subscriptions": "^2.0.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"io-ts": "^2.2.16",
|
||||
@@ -62,9 +63,9 @@
|
||||
"rxjs": "^7.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.1.18",
|
||||
"@nestjs/schematics": "^10.0.2",
|
||||
"@nestjs/testing": "^10.2.6",
|
||||
"@nestjs/cli": "^9.1.5",
|
||||
"@nestjs/schematics": "^9.0.3",
|
||||
"@nestjs/testing": "^9.2.1",
|
||||
"@relmify/jest-fp-ts": "^2.0.2",
|
||||
"@types/argon2": "^0.15.0",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
|
||||
@@ -74,7 +74,7 @@ export class AdminService {
|
||||
|
||||
try {
|
||||
await this.mailerService.sendUserInvitationEmail(inviteeEmail, {
|
||||
template: 'user-invitation',
|
||||
template: 'code-your-own',
|
||||
variables: {
|
||||
inviteeEmail: inviteeEmail,
|
||||
magicLink: `${process.env.VITE_BASE_URL}`,
|
||||
|
||||
@@ -27,7 +27,12 @@ import { AppController } from './app.controller';
|
||||
buildSchemaOptions: {
|
||||
numberScalarMode: 'integer',
|
||||
},
|
||||
cors: {
|
||||
origin: process.env.WHITELISTED_ORIGINS.split(','),
|
||||
credentials: true,
|
||||
},
|
||||
playground: process.env.PRODUCTION !== 'true',
|
||||
debug: process.env.PRODUCTION !== 'true',
|
||||
autoSchemaFile: true,
|
||||
installSubscriptionHandlers: true,
|
||||
subscriptions: {
|
||||
@@ -57,12 +62,10 @@ import { AppController } from './app.controller';
|
||||
}),
|
||||
driver: ApolloDriver,
|
||||
}),
|
||||
ThrottlerModule.forRoot([
|
||||
{
|
||||
ttl: +process.env.RATE_LIMIT_TTL,
|
||||
limit: +process.env.RATE_LIMIT_MAX,
|
||||
},
|
||||
]),
|
||||
ThrottlerModule.forRoot({
|
||||
ttl: +process.env.RATE_LIMIT_TTL,
|
||||
limit: +process.env.RATE_LIMIT_MAX,
|
||||
}),
|
||||
UserModule,
|
||||
AuthModule,
|
||||
AdminModule,
|
||||
|
||||
@@ -229,7 +229,7 @@ export class AuthService {
|
||||
}
|
||||
|
||||
await this.mailerService.sendEmail(email, {
|
||||
template: 'user-invitation',
|
||||
template: 'code-your-own',
|
||||
variables: {
|
||||
inviteeEmail: email,
|
||||
magicLink: `${url}/enter?token=${generatedTokens.token}`,
|
||||
|
||||
@@ -93,7 +93,9 @@ export async function emitGQLSchemaFile() {
|
||||
numberScalarMode: 'integer',
|
||||
});
|
||||
|
||||
const schemaString = printSchema(schema);
|
||||
const schemaString = printSchema(schema, {
|
||||
commentDescriptions: true,
|
||||
});
|
||||
|
||||
logger.log(`Writing schema to GQL_SCHEMA_EMIT_LOCATION (${destination})`);
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
|
||||
protected async getTracker(req: Record<string, any>): Promise<string> {
|
||||
protected getTracker(req: Record<string, any>): string {
|
||||
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 = {
|
||||
template: 'user-invitation';
|
||||
template: 'code-your-own';
|
||||
variables: {
|
||||
inviteeEmail: string;
|
||||
magicLink: string;
|
||||
@@ -16,7 +16,7 @@ export type UserMagicLinkMailDescription = {
|
||||
};
|
||||
|
||||
export type AdminUserInvitationMailDescription = {
|
||||
template: 'user-invitation';
|
||||
template: 'code-your-own';
|
||||
variables: {
|
||||
inviteeEmail: string;
|
||||
magicLink: string;
|
||||
|
||||
@@ -27,7 +27,7 @@ export class MailerService {
|
||||
case 'team-invitation':
|
||||
return `${mailDesc.variables.invitee} invited you to join ${mailDesc.variables.invite_team_name} in Hoppscotch`;
|
||||
|
||||
case 'user-invitation':
|
||||
case 'code-your-own':
|
||||
return 'Sign in to Hoppscotch';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
<style type="text/css" rel="stylesheet" media="all">
|
||||
/* Base ------------------------------ */
|
||||
|
||||
|
||||
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
|
||||
body {
|
||||
width: 100% !important;
|
||||
@@ -22,19 +22,19 @@
|
||||
margin: 0;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
|
||||
a {
|
||||
color: #3869D4;
|
||||
}
|
||||
|
||||
|
||||
a img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
td {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
|
||||
.preheader {
|
||||
display: none !important;
|
||||
visibility: hidden;
|
||||
@@ -47,13 +47,13 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Type ------------------------------ */
|
||||
|
||||
|
||||
body,
|
||||
td,
|
||||
th {
|
||||
font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
color: #333333;
|
||||
@@ -61,7 +61,7 @@
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
color: #333333;
|
||||
@@ -69,7 +69,7 @@
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
color: #333333;
|
||||
@@ -77,12 +77,12 @@
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
td,
|
||||
th {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
@@ -91,25 +91,25 @@
|
||||
font-size: 16px;
|
||||
line-height: 1.625;
|
||||
}
|
||||
|
||||
|
||||
p.sub {
|
||||
font-size: 13px;
|
||||
}
|
||||
/* Utilities ------------------------------ */
|
||||
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
/* Buttons ------------------------------ */
|
||||
|
||||
|
||||
.button {
|
||||
background-color: #3869D4;
|
||||
border-top: 10px solid #3869D4;
|
||||
@@ -124,7 +124,7 @@
|
||||
-webkit-text-size-adjust: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.button--green {
|
||||
background-color: #22BC66;
|
||||
border-top: 10px solid #22BC66;
|
||||
@@ -132,7 +132,7 @@
|
||||
border-bottom: 10px solid #22BC66;
|
||||
border-left: 18px solid #22BC66;
|
||||
}
|
||||
|
||||
|
||||
.button--red {
|
||||
background-color: #FF6136;
|
||||
border-top: 10px solid #FF6136;
|
||||
@@ -140,7 +140,7 @@
|
||||
border-bottom: 10px solid #FF6136;
|
||||
border-left: 18px solid #FF6136;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
.button {
|
||||
width: 100% !important;
|
||||
@@ -148,21 +148,21 @@
|
||||
}
|
||||
}
|
||||
/* Attribute list ------------------------------ */
|
||||
|
||||
|
||||
.attributes {
|
||||
margin: 0 0 21px;
|
||||
}
|
||||
|
||||
|
||||
.attributes_content {
|
||||
background-color: #F4F4F7;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
|
||||
.attributes_item {
|
||||
padding: 0;
|
||||
}
|
||||
/* Related Items ------------------------------ */
|
||||
|
||||
|
||||
.related {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
@@ -171,31 +171,31 @@
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
|
||||
|
||||
.related_item {
|
||||
padding: 10px 0;
|
||||
color: #CBCCCF;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
|
||||
.related_item-title {
|
||||
display: block;
|
||||
margin: .5em 0 0;
|
||||
}
|
||||
|
||||
|
||||
.related_item-thumb {
|
||||
display: block;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
.related_heading {
|
||||
border-top: 1px solid #CBCCCF;
|
||||
text-align: center;
|
||||
padding: 25px 0 10px;
|
||||
}
|
||||
/* Discount Code ------------------------------ */
|
||||
|
||||
|
||||
.discount {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
@@ -206,33 +206,33 @@
|
||||
background-color: #F4F4F7;
|
||||
border: 2px dashed #CBCCCF;
|
||||
}
|
||||
|
||||
|
||||
.discount_heading {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.discount_body {
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
/* Social Icons ------------------------------ */
|
||||
|
||||
|
||||
.social {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
|
||||
.social td {
|
||||
padding: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
|
||||
.social_icon {
|
||||
height: 20px;
|
||||
margin: 0 8px 10px 8px;
|
||||
padding: 0;
|
||||
}
|
||||
/* Data table ------------------------------ */
|
||||
|
||||
|
||||
.purchase {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
@@ -241,7 +241,7 @@
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
|
||||
|
||||
.purchase_content {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
@@ -250,50 +250,50 @@
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
|
||||
|
||||
.purchase_item {
|
||||
padding: 10px 0;
|
||||
color: #51545E;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
|
||||
.purchase_heading {
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #EAEAEC;
|
||||
}
|
||||
|
||||
|
||||
.purchase_heading p {
|
||||
margin: 0;
|
||||
color: #85878E;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
.purchase_footer {
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #EAEAEC;
|
||||
}
|
||||
|
||||
|
||||
.purchase_total {
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
|
||||
.purchase_total--label {
|
||||
padding: 0 15px 0 0;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
background-color: #F2F4F6;
|
||||
color: #51545E;
|
||||
}
|
||||
|
||||
|
||||
p {
|
||||
color: #51545E;
|
||||
}
|
||||
|
||||
|
||||
.email-wrapper {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
@@ -303,7 +303,7 @@
|
||||
-premailer-cellspacing: 0;
|
||||
background-color: #F2F4F6;
|
||||
}
|
||||
|
||||
|
||||
.email-content {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
@@ -313,16 +313,16 @@
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
/* Masthead ----------------------- */
|
||||
|
||||
|
||||
.email-masthead {
|
||||
padding: 25px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.email-masthead_logo {
|
||||
width: 94px;
|
||||
}
|
||||
|
||||
|
||||
.email-masthead_name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
@@ -331,7 +331,7 @@
|
||||
text-shadow: 0 1px 0 white;
|
||||
}
|
||||
/* Body ------------------------------ */
|
||||
|
||||
|
||||
.email-body {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
@@ -340,7 +340,7 @@
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
|
||||
|
||||
.email-body_inner {
|
||||
width: 570px;
|
||||
margin: 0 auto;
|
||||
@@ -350,7 +350,7 @@
|
||||
-premailer-cellspacing: 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
|
||||
.email-footer {
|
||||
width: 570px;
|
||||
margin: 0 auto;
|
||||
@@ -360,11 +360,11 @@
|
||||
-premailer-cellspacing: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.email-footer p {
|
||||
color: #A8AAAF;
|
||||
}
|
||||
|
||||
|
||||
.body-action {
|
||||
width: 100%;
|
||||
margin: 30px auto;
|
||||
@@ -374,25 +374,25 @@
|
||||
-premailer-cellspacing: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.body-sub {
|
||||
margin-top: 25px;
|
||||
padding-top: 25px;
|
||||
border-top: 1px solid #EAEAEC;
|
||||
}
|
||||
|
||||
|
||||
.content-cell {
|
||||
padding: 45px;
|
||||
}
|
||||
/*Media Queries ------------------------------ */
|
||||
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.email-body_inner,
|
||||
.email-footer {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body,
|
||||
.email-body,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { GraphQLSchemaHost } from '@nestjs/graphql';
|
||||
import {
|
||||
ApolloServerPlugin,
|
||||
BaseContext,
|
||||
GraphQLRequestListener,
|
||||
} from '@apollo/server';
|
||||
} from 'apollo-server-plugin-base';
|
||||
import { Plugin } from '@nestjs/apollo';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import {
|
||||
@@ -18,7 +17,7 @@ const COMPLEXITY_LIMIT = 50;
|
||||
export class GQLComplexityPlugin implements ApolloServerPlugin {
|
||||
constructor(private gqlSchemaHost: GraphQLSchemaHost) {}
|
||||
|
||||
async requestDidStart(): Promise<GraphQLRequestListener<BaseContext>> {
|
||||
async requestDidStart(): Promise<GraphQLRequestListener> {
|
||||
const { schema } = this.gqlSchemaHost;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Team, TeamCollection as DBTeamCollection } from '@prisma/client';
|
||||
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||
import { mock, mockDeep, mockReset } from 'jest-mock-extended';
|
||||
import {
|
||||
TEAM_COLL_DEST_SAME,
|
||||
TEAM_COLL_INVALID_JSON,
|
||||
@@ -17,6 +17,9 @@ import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||
import { AuthUser } from 'src/types/AuthUser';
|
||||
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 mockPubSub = mockDeep<PubSubService>();
|
||||
|
||||
@@ -301,7 +301,7 @@ describe('TeamEnvironmentsService', () => {
|
||||
|
||||
describe('createDuplicateEnvironment', () => {
|
||||
test('should successfully duplicate an existing team environment', async () => {
|
||||
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce(
|
||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
||||
teamEnvironment,
|
||||
);
|
||||
|
||||
@@ -322,9 +322,7 @@ describe('TeamEnvironmentsService', () => {
|
||||
});
|
||||
|
||||
test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
||||
mockPrisma.teamEnvironment.findFirstOrThrow.mockRejectedValue(
|
||||
'NotFoundError',
|
||||
);
|
||||
mockPrisma.teamEnvironment.findFirst.mockRejectedValue('NotFoundError');
|
||||
|
||||
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
||||
teamEnvironment.id,
|
||||
@@ -334,7 +332,7 @@ describe('TeamEnvironmentsService', () => {
|
||||
});
|
||||
|
||||
test('should send pubsub message to "team_environment/<teamID>/created" if team environment is updated successfully', async () => {
|
||||
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce(
|
||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
||||
teamEnvironment,
|
||||
);
|
||||
|
||||
|
||||
@@ -183,10 +183,11 @@ export class TeamEnvironmentsService {
|
||||
*/
|
||||
async createDuplicateEnvironment(id: string) {
|
||||
try {
|
||||
const environment = await this.prisma.teamEnvironment.findFirstOrThrow({
|
||||
const environment = await this.prisma.teamEnvironment.findFirst({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
rejectOnNotFound: true,
|
||||
});
|
||||
|
||||
const result = await this.prisma.teamEnvironment.create({
|
||||
|
||||
@@ -142,15 +142,13 @@ describe('UserHistoryService', () => {
|
||||
});
|
||||
describe('createUserHistory', () => {
|
||||
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({
|
||||
userUid: 'abc',
|
||||
id: '1',
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.REST,
|
||||
executedOn,
|
||||
executedOn: new Date(),
|
||||
isStarred: false,
|
||||
});
|
||||
|
||||
@@ -160,7 +158,7 @@ describe('UserHistoryService', () => {
|
||||
request: JSON.stringify([{}]),
|
||||
responseMetadata: JSON.stringify([{}]),
|
||||
reqType: ReqType.REST,
|
||||
executedOn,
|
||||
executedOn: new Date(),
|
||||
isStarred: false,
|
||||
};
|
||||
|
||||
@@ -174,15 +172,13 @@ describe('UserHistoryService', () => {
|
||||
).toEqualRight(userHistory);
|
||||
});
|
||||
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({
|
||||
userUid: 'abc',
|
||||
id: '1',
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.GQL,
|
||||
executedOn,
|
||||
executedOn: new Date(),
|
||||
isStarred: false,
|
||||
});
|
||||
|
||||
@@ -192,7 +188,7 @@ describe('UserHistoryService', () => {
|
||||
request: JSON.stringify([{}]),
|
||||
responseMetadata: JSON.stringify([{}]),
|
||||
reqType: ReqType.GQL,
|
||||
executedOn,
|
||||
executedOn: new Date(),
|
||||
isStarred: false,
|
||||
};
|
||||
|
||||
@@ -216,15 +212,13 @@ describe('UserHistoryService', () => {
|
||||
).toEqualLeft(USER_HISTORY_INVALID_REQ_TYPE);
|
||||
});
|
||||
test('Should create a GQL request to users history and publish a created subscription', async () => {
|
||||
const executedOn = new Date();
|
||||
|
||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||
userUid: 'abc',
|
||||
id: '1',
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.GQL,
|
||||
executedOn,
|
||||
executedOn: new Date(),
|
||||
isStarred: false,
|
||||
});
|
||||
|
||||
@@ -234,7 +228,7 @@ describe('UserHistoryService', () => {
|
||||
request: JSON.stringify([{}]),
|
||||
responseMetadata: JSON.stringify([{}]),
|
||||
reqType: ReqType.GQL,
|
||||
executedOn,
|
||||
executedOn: new Date(),
|
||||
isStarred: false,
|
||||
};
|
||||
|
||||
@@ -251,15 +245,13 @@ describe('UserHistoryService', () => {
|
||||
);
|
||||
});
|
||||
test('Should create a REST request to users history and publish a created subscription', async () => {
|
||||
const executedOn = new Date();
|
||||
|
||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||
userUid: 'abc',
|
||||
id: '1',
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.REST,
|
||||
executedOn,
|
||||
executedOn: new Date(),
|
||||
isStarred: false,
|
||||
});
|
||||
|
||||
@@ -269,7 +261,7 @@ describe('UserHistoryService', () => {
|
||||
request: JSON.stringify([{}]),
|
||||
responseMetadata: JSON.stringify([{}]),
|
||||
reqType: ReqType.REST,
|
||||
executedOn,
|
||||
executedOn: new Date(),
|
||||
isStarred: false,
|
||||
};
|
||||
|
||||
@@ -331,15 +323,13 @@ describe('UserHistoryService', () => {
|
||||
).toEqualLeft(USER_HISTORY_NOT_FOUND);
|
||||
});
|
||||
test('Should star/unstar a request in the history and publish a updated subscription', async () => {
|
||||
const executedOn = new Date();
|
||||
|
||||
mockPrisma.userHistory.findFirst.mockResolvedValueOnce({
|
||||
userUid: 'abc',
|
||||
id: '1',
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.REST,
|
||||
executedOn,
|
||||
executedOn: new Date(),
|
||||
isStarred: false,
|
||||
});
|
||||
|
||||
@@ -349,7 +339,7 @@ describe('UserHistoryService', () => {
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.REST,
|
||||
executedOn,
|
||||
executedOn: new Date(),
|
||||
isStarred: true,
|
||||
});
|
||||
|
||||
@@ -359,7 +349,7 @@ describe('UserHistoryService', () => {
|
||||
request: JSON.stringify([{}]),
|
||||
responseMetadata: JSON.stringify([{}]),
|
||||
reqType: ReqType.REST,
|
||||
executedOn,
|
||||
executedOn: new Date(),
|
||||
isStarred: true,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hoppscotch/cli",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.2",
|
||||
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
||||
"homepage": "https://hoppscotch.io",
|
||||
"main": "dist/index.js",
|
||||
@@ -40,6 +40,9 @@
|
||||
"@hoppscotch/js-sandbox": "workspace:^",
|
||||
"@relmify/jest-fp-ts": "^2.0.2",
|
||||
"@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/lodash": "^4.14.181",
|
||||
"@types/qs": "^6.9.7",
|
||||
@@ -55,7 +58,6 @@
|
||||
"qs": "^6.10.3",
|
||||
"ts-jest": "^27.1.4",
|
||||
"tsup": "^5.12.7",
|
||||
"typescript": "^4.6.4",
|
||||
"zod": "^3.22.2"
|
||||
"typescript": "^4.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,10 @@ import { isHoppCLIError } from "../utils/checks";
|
||||
export const test = (path: string, options: TestCmdOptions) => async () => {
|
||||
try {
|
||||
const delay = options.delay ? parseDelayOption(options.delay) : 0
|
||||
const envs = options.env ? await parseEnvsData(options.env) : <HoppEnvs>{ global: [], selected: [] }
|
||||
const envName = options.envName
|
||||
const envs = options.env ? await parseEnvsData(options.env, envName) : <HoppEnvs>{ global: [], selected: [] }
|
||||
const collections = await parseCollectionData(path)
|
||||
|
||||
|
||||
const report = await collectionsRunner({collections, envs, delay})
|
||||
const hasSucceeded = collectionsRunnerResult(report)
|
||||
collectionsRunnerExit(hasSucceeded)
|
||||
|
||||
@@ -48,14 +48,12 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
|
||||
ERROR_MSG = `Unavailable command: ${error.command}`;
|
||||
break;
|
||||
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":
|
||||
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
|
||||
break;
|
||||
case "ENVIRONMENT_NAME_NOT_FOUND":
|
||||
ERROR_MSG = `\n${parseErrorData(error.data)}`;
|
||||
break;
|
||||
case "NO_FILE_PATH":
|
||||
ERROR_MSG = `Please provide a hoppscotch-collection file path.`;
|
||||
break;
|
||||
@@ -87,4 +85,4 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
|
||||
if (!S.isEmpty(ERROR_MSG)) {
|
||||
console.error(ERROR_CODE, ERROR_MSG);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -50,6 +50,7 @@ program
|
||||
"path to a hoppscotch collection.json file for CI testing"
|
||||
)
|
||||
.option("-e, --env <file_path>", "path to an environment variables json file")
|
||||
.option("-eN, --envName <environment_name>","Specific Name of the environment")
|
||||
.option(
|
||||
"-d, --delay <delay_in_ms>",
|
||||
"delay in milliseconds(ms) between consecutive requests within a collection"
|
||||
|
||||
@@ -33,4 +33,5 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
||||
effectiveFinalHeaders: { key: string; value: string; active: boolean }[];
|
||||
effectiveFinalParams: { key: string; value: string; active: boolean }[];
|
||||
effectiveFinalBody: FormData | string | null;
|
||||
effectiveFinalMaskedURL: string;
|
||||
}
|
||||
|
||||
@@ -1,45 +1,50 @@
|
||||
import { error } from "../../types/errors";
|
||||
import {
|
||||
HoppEnvs,
|
||||
HoppEnvPair,
|
||||
HoppEnvKeyPairObject,
|
||||
HoppEnvExportObject,
|
||||
HoppBulkEnvExportObject,
|
||||
} from "../../types/request";
|
||||
import { HoppEnvs, HoppEnvPair } from "../../types/request";
|
||||
import { readJsonFile } from "../../utils/mutators";
|
||||
|
||||
/**
|
||||
* 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 envName Name of the environment that should be used. If undefined first environment is used.
|
||||
* @returns For successful parsing we get HoppEnvs object.
|
||||
*/
|
||||
export async function parseEnvsData(path: string) {
|
||||
const contents = await readJsonFile(path);
|
||||
const envPairs: Array<HoppEnvPair> = [];
|
||||
const HoppEnvKeyPairResult = HoppEnvKeyPairObject.safeParse(contents);
|
||||
const HoppEnvExportObjectResult = HoppEnvExportObject.safeParse(contents);
|
||||
const HoppBulkEnvExportObjectResult =
|
||||
HoppBulkEnvExportObject.safeParse(contents);
|
||||
|
||||
// CLI doesnt support bulk environments export.
|
||||
// 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 });
|
||||
export async function parseEnvsData(path: string, envName: string | undefined) {
|
||||
const contents = await readJsonFile(path)
|
||||
if(!(contents && typeof contents === "object" && Array.isArray(contents))) {
|
||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: null })
|
||||
}
|
||||
|
||||
// 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 });
|
||||
}
|
||||
const envPairs: Array<HoppEnvPair> = []
|
||||
|
||||
if (HoppEnvKeyPairResult.success) {
|
||||
for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) {
|
||||
envPairs.push({ key, value });
|
||||
const contentEntries = Object.entries(contents)
|
||||
let environmentFound = false;
|
||||
|
||||
for(const [key, obj] of contentEntries) {
|
||||
if(!(typeof obj === "object" && "name" in obj && "variables" in obj)) {
|
||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: { value: obj } })
|
||||
}
|
||||
} else if (HoppEnvExportObjectResult.success) {
|
||||
const { key, value } = HoppEnvExportObjectResult.data.variables[0];
|
||||
envPairs.push({ key, value });
|
||||
if(envName && envName !== obj.name) {
|
||||
continue
|
||||
}
|
||||
environmentFound = true;
|
||||
for(const variable of obj.variables) {
|
||||
if(
|
||||
!(
|
||||
typeof variable === "object" &&
|
||||
"key" in variable &&
|
||||
"value" in variable &&
|
||||
"secret" in variable
|
||||
)
|
||||
) {
|
||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: { value: variable } });
|
||||
}
|
||||
const { key, value, secret } = variable;
|
||||
envPairs.push({ key: key, value: value, secret: secret });
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return <HoppEnvs>{ global: [], selected: envPairs };
|
||||
if(envName && !environmentFound) {
|
||||
throw error({ code: "ENVIRONMENT_NAME_NOT_FOUND", data: envName });
|
||||
}
|
||||
return <HoppEnvs>{ global: [], selected: envPairs }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type TestCmdOptions = {
|
||||
env: string | undefined;
|
||||
delay: string | undefined;
|
||||
envName: string | undefined;
|
||||
};
|
||||
|
||||
export type HOPP_ENV_FILE_EXT = "json";
|
||||
|
||||
@@ -15,6 +15,7 @@ type HoppErrors = {
|
||||
FILE_NOT_FOUND: HoppErrorPath;
|
||||
UNKNOWN_COMMAND: HoppErrorCmd;
|
||||
MALFORMED_COLLECTION: HoppErrorPath & HoppErrorData;
|
||||
ENVIRONMENT_NAME_NOT_FOUND: HoppErrorData;
|
||||
NO_FILE_PATH: {};
|
||||
PRE_REQUEST_SCRIPT_ERROR: HoppErrorData;
|
||||
PARSING_ERROR: HoppErrorData;
|
||||
@@ -24,7 +25,6 @@ type HoppErrors = {
|
||||
REQUEST_ERROR: HoppErrorData;
|
||||
INVALID_ARGUMENT: HoppErrorData;
|
||||
MALFORMED_ENV_FILE: HoppErrorPath & HoppErrorData;
|
||||
BULK_ENV_FILE: HoppErrorPath & HoppErrorData;
|
||||
INVALID_FILE_TYPE: HoppErrorData;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,30 +1,13 @@
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||
import { TestReport } from "../interfaces/response";
|
||||
import { HoppCLIError } from "./errors";
|
||||
import { z } from "zod";
|
||||
|
||||
export type FormDataEntry = {
|
||||
key: string;
|
||||
value: string | Blob;
|
||||
};
|
||||
|
||||
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 HoppEnvPair = { key: string; value: string; secret: boolean };
|
||||
|
||||
export type HoppEnvs = {
|
||||
global: HoppEnvPair[];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { bold } from "chalk";
|
||||
import { groupEnd, group, log } from "console";
|
||||
import { handleError } from "../handlers/error";
|
||||
import { RequestConfig } from "../interfaces/request";
|
||||
import { Method } from "axios";
|
||||
import { RequestRunnerResponse, TestReport } from "../interfaces/response";
|
||||
import { HoppCLIError } from "../types/errors";
|
||||
import {
|
||||
@@ -172,11 +172,12 @@ export const printFailedTestsReport = (
|
||||
export const printRequestRunner = {
|
||||
/**
|
||||
* Request-runner starting message.
|
||||
* @param requestConfig Provides request's method and url.
|
||||
* @param requestMethod Provides request's method
|
||||
* @param maskedURL Provides the URL with secrets masked with asterisks
|
||||
*/
|
||||
start: (requestConfig: RequestConfig) => {
|
||||
const METHOD = BG_INFO(` ${requestConfig.method} `);
|
||||
const ENDPOINT = requestConfig.url;
|
||||
start: (requestMethod: Method | undefined, maskedURL: string) => {
|
||||
const METHOD = BG_INFO(` ${requestMethod} `);
|
||||
const ENDPOINT = maskedURL;
|
||||
|
||||
process.stdout.write(`${METHOD} ${ENDPOINT}`);
|
||||
},
|
||||
|
||||
@@ -50,9 +50,9 @@ export const preRequestScriptRunner = (
|
||||
isHoppCLIError(reason)
|
||||
? reason
|
||||
: error({
|
||||
code: "PRE_REQUEST_SCRIPT_ERROR",
|
||||
data: reason,
|
||||
})
|
||||
code: "PRE_REQUEST_SCRIPT_ERROR",
|
||||
data: reason,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
@@ -151,6 +151,12 @@ export function getEffectiveRESTRequest(
|
||||
request.endpoint,
|
||||
envVariables
|
||||
);
|
||||
|
||||
const maskedEnvVariables = setAllEnvironmentValuesToAsterisk(envVariables)
|
||||
const _effectiveFinalMaskedURL = parseTemplateStringE(
|
||||
request.endpoint,
|
||||
maskedEnvVariables)
|
||||
|
||||
if (E.isLeft(_effectiveFinalURL)) {
|
||||
return E.left(
|
||||
error({
|
||||
@@ -160,6 +166,7 @@ export function getEffectiveRESTRequest(
|
||||
);
|
||||
}
|
||||
const effectiveFinalURL = _effectiveFinalURL.right;
|
||||
const effectiveFinalMaskedURL = E.isLeft(_effectiveFinalMaskedURL) ? request.endpoint : _effectiveFinalMaskedURL.right;
|
||||
|
||||
return E.right({
|
||||
...request,
|
||||
@@ -167,6 +174,7 @@ export function getEffectiveRESTRequest(
|
||||
effectiveFinalHeaders,
|
||||
effectiveFinalParams,
|
||||
effectiveFinalBody,
|
||||
effectiveFinalMaskedURL,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -242,15 +250,15 @@ function getFinalBodyFromRequest(
|
||||
arrayFlatMap((x) =>
|
||||
x.isFile
|
||||
? x.value.map((v) => ({
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: v as string | Blob,
|
||||
}))
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: v as string | Blob,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: parseTemplateString(x.value, envVariables),
|
||||
},
|
||||
]
|
||||
{
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: parseTemplateString(x.value, envVariables),
|
||||
},
|
||||
]
|
||||
),
|
||||
toFormData,
|
||||
E.right
|
||||
@@ -287,3 +295,22 @@ export const getPreRequestMetrics = (
|
||||
hasPreReqErrors ? { failed: 1, passed: 0 } : { failed: 0, passed: 1 },
|
||||
(scripts) => <PreRequestMetrics>{ scripts, duration }
|
||||
);
|
||||
|
||||
/**
|
||||
* Mask all environment values with asterisks
|
||||
* @param variables Environment variable array
|
||||
* @returns Environment variable array with masked values
|
||||
*/
|
||||
const setAllEnvironmentValuesToAsterisk = (
|
||||
variables: Environment["variables"]
|
||||
): Environment["variables"] => {
|
||||
const envVariables: Environment["variables"] = [];
|
||||
for (const variable of variables) {
|
||||
let value = variable.value
|
||||
if (variable.secret) {
|
||||
value = "******"
|
||||
}
|
||||
envVariables.push({ key: variable.key, secret: variable.secret, value: value })
|
||||
}
|
||||
return envVariables
|
||||
}
|
||||
|
||||
@@ -220,6 +220,7 @@ export const processRequest =
|
||||
effectiveFinalHeaders: [],
|
||||
effectiveFinalParams: [],
|
||||
effectiveFinalURL: "",
|
||||
effectiveFinalMaskedURL:"",
|
||||
};
|
||||
|
||||
// Executing pre-request-script
|
||||
@@ -237,8 +238,8 @@ export const processRequest =
|
||||
|
||||
// Creating request-config for request-runner.
|
||||
const requestConfig = createRequest(effectiveRequest);
|
||||
|
||||
printRequestRunner.start(requestConfig);
|
||||
|
||||
printRequestRunner.start(requestConfig.method, effectiveRequest.effectiveFinalMaskedURL);
|
||||
|
||||
// Default value for request-runner's response.
|
||||
let _requestRunnerRes: RequestRunnerResponse = {
|
||||
|
||||
@@ -137,10 +137,10 @@ a {
|
||||
|
||||
.cm-tooltip {
|
||||
.tippy-box {
|
||||
@apply shadow-none #{!important};
|
||||
@apply shadow-none;
|
||||
@apply fixed;
|
||||
@apply inline-flex;
|
||||
@apply -mt-7.5;
|
||||
@apply -mt-8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
"turn_off": "Ausschalten",
|
||||
"turn_on": "Einschalten",
|
||||
"undo": "Rückgängig machen",
|
||||
"yes": "Ja"
|
||||
"yes": "Ja",
|
||||
"secret": "Als Secret speichern"
|
||||
},
|
||||
"add": {
|
||||
"new": "Neue hinzufügen",
|
||||
@@ -880,4 +881,4 @@
|
||||
"team": "Team Workspace",
|
||||
"title": "Workspaces"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"action": {
|
||||
"add": "Add",
|
||||
"autoscroll": "Autoscroll",
|
||||
"cancel": "Cancel",
|
||||
"choose_file": "Choose a file",
|
||||
@@ -49,34 +48,16 @@
|
||||
"turn_off": "Turn off",
|
||||
"turn_on": "Turn on",
|
||||
"undo": "Undo",
|
||||
"yes": "Yes"
|
||||
"yes": "Yes",
|
||||
"secret": "Save as secret"
|
||||
},
|
||||
"add": {
|
||||
"new": "Add new",
|
||||
"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": {
|
||||
"chat_with_us": "Chat with us",
|
||||
"contact_us": "Contact us",
|
||||
"cookies": "Cookies",
|
||||
"copy": "Copy",
|
||||
"copy_user_id": "Copy User Auth Token",
|
||||
"developer_option": "Developer options",
|
||||
@@ -132,7 +113,6 @@
|
||||
},
|
||||
"authorization": {
|
||||
"generate_token": "Generate Token",
|
||||
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init",
|
||||
"include_in_url": "Include in URL",
|
||||
"learn": "Learn how",
|
||||
"pass_key_by": "Pass by",
|
||||
@@ -145,7 +125,6 @@
|
||||
"created": "Collection created",
|
||||
"different_parent": "Cannot reorder collection with different parent",
|
||||
"edit": "Edit Collection",
|
||||
"import_or_create": "Import or create a collection",
|
||||
"invalid_name": "Please provide a name for the collection",
|
||||
"invalid_root_move": "Collection already in the root",
|
||||
"moved": "Moved Successfully",
|
||||
@@ -231,7 +210,6 @@
|
||||
"empty_variables": "No variables",
|
||||
"global": "Global",
|
||||
"global_variables": "Global variables",
|
||||
"import_or_create": "Import or create a environment",
|
||||
"invalid_name": "Please provide a name for the environment",
|
||||
"list": "Environment variables",
|
||||
"my_environments": "My Environments",
|
||||
@@ -257,7 +235,6 @@
|
||||
"error": {
|
||||
"browser_support_sse": "This browser doesn't seems to have Server Sent Events support.",
|
||||
"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",
|
||||
"danger_zone": "Danger zone",
|
||||
"delete_account": "Your account is currently an owner in these teams:",
|
||||
@@ -273,12 +250,9 @@
|
||||
"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_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_environments_to_export": "No environments to export. Please create an environment to get started.",
|
||||
"no_results_found": "No matches 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",
|
||||
"script_fail": "Could not execute pre-request script",
|
||||
"something_went_wrong": "Something went wrong",
|
||||
@@ -483,7 +457,6 @@
|
||||
"enter_curl": "Enter cURL command",
|
||||
"generate_code": "Generate code",
|
||||
"generated_code": "Generated code",
|
||||
"go_to_authorization_tab": "Go to Authorization",
|
||||
"header_list": "Header List",
|
||||
"invalid_name": "Please provide a name for the request",
|
||||
"method": "Method",
|
||||
@@ -771,11 +744,9 @@
|
||||
"disconnected_from": "Disconnected from {name}",
|
||||
"docs_generated": "Documentation generated",
|
||||
"download_started": "Download started",
|
||||
"download_failed": "Download failed",
|
||||
"enabled": "Enabled",
|
||||
"file_imported": "File imported",
|
||||
"finished_in": "Finished in {duration} ms",
|
||||
"hide": "Hide",
|
||||
"history_deleted": "History deleted",
|
||||
"linewrap": "Wrap lines",
|
||||
"loading": "Loading...",
|
||||
@@ -786,7 +757,6 @@
|
||||
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||
"published_message": "Published message: {message} to topic: {topic}",
|
||||
"reconnection_error": "Failed to reconnect",
|
||||
"show": "Show",
|
||||
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
||||
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
||||
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
||||
@@ -868,7 +838,7 @@
|
||||
"new": "New Team",
|
||||
"new_created": "New team created",
|
||||
"new_name": "My New Team",
|
||||
"no_access": "You do not have edit access to this team",
|
||||
"no_access": "You do not have edit access to these collections",
|
||||
"no_invite_found": "Invitation not found. Contact your team owner.",
|
||||
"no_request_found": "Request not found.",
|
||||
"not_found": "Team not found. Contact your team owner.",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"choose_file": "選擇一個檔案",
|
||||
"clear": "清除",
|
||||
"clear_all": "全部清除",
|
||||
"clear_history": "清除所有歷史記錄",
|
||||
"clear_history": "Clear all History",
|
||||
"close": "關閉",
|
||||
"connect": "連線",
|
||||
"connecting": "正在連接",
|
||||
@@ -79,8 +79,8 @@
|
||||
"search": "搜尋",
|
||||
"share": "分享",
|
||||
"shortcuts": "快捷方式",
|
||||
"social_description": "在社交媒體上追蹤我們即可在第一時間得知新聞、更新、以及新版本的消息。",
|
||||
"social_links": "社群連結",
|
||||
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
|
||||
"social_links": "Social links",
|
||||
"spotlight": "聚光燈",
|
||||
"status": "狀態",
|
||||
"status_description": "檢查網站狀態",
|
||||
@@ -135,15 +135,15 @@
|
||||
"renamed": "集合已重新命名",
|
||||
"request_in_use": "請求正在使用中",
|
||||
"save_as": "另存為",
|
||||
"save_to_collection": "儲存到集合",
|
||||
"save_to_collection": "Save to Collection",
|
||||
"select": "選擇一個集合",
|
||||
"select_location": "選擇位置",
|
||||
"select_team": "選擇一個團隊",
|
||||
"team_collections": "團隊集合"
|
||||
},
|
||||
"confirm": {
|
||||
"close_unsaved_tab": "您確定要關閉此分頁嗎?",
|
||||
"close_unsaved_tabs": "您確定要關閉所有分頁嗎?{count} 個未儲存的分頁將會遺失。",
|
||||
"close_unsaved_tab": "Are you sure you want to close this tab?",
|
||||
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
|
||||
"exit_team": "您確定要離開此團隊嗎?",
|
||||
"logout": "您確定要登出嗎?",
|
||||
"remove_collection": "您確定要永久刪除該集合嗎?",
|
||||
@@ -158,9 +158,9 @@
|
||||
"sync": "您想從雲端恢復您的工作區嗎?這將丟棄您的本地進度。"
|
||||
},
|
||||
"context_menu": {
|
||||
"add_parameters": "新增至參數",
|
||||
"open_request_in_new_tab": "在新分頁開啟請求",
|
||||
"set_environment_variable": "設為變數"
|
||||
"add_parameters": "Add to parameters",
|
||||
"open_request_in_new_tab": "Open request in new tab",
|
||||
"set_environment_variable": "Set as variable"
|
||||
},
|
||||
"count": {
|
||||
"header": "請求標頭 {count}",
|
||||
@@ -204,31 +204,31 @@
|
||||
"create_new": "建立新環境",
|
||||
"created": "已建立環境",
|
||||
"deleted": "刪除環境",
|
||||
"duplicated": "已複製環境",
|
||||
"duplicated": "Environment duplicated",
|
||||
"edit": "編輯環境",
|
||||
"empty_variables": "無變數",
|
||||
"global": "全域",
|
||||
"global_variables": "全域變數",
|
||||
"empty_variables": "No variables",
|
||||
"global": "Global",
|
||||
"global_variables": "Global variables",
|
||||
"invalid_name": "請提供有效的環境名稱",
|
||||
"list": "環境變數",
|
||||
"list": "Environment variables",
|
||||
"my_environments": "我的環境",
|
||||
"name": "名稱",
|
||||
"name": "Name",
|
||||
"nested_overflow": "巢狀環境變數不得大於 10 層",
|
||||
"new": "建立環境",
|
||||
"no_active_environment": "無使用中的環境",
|
||||
"no_active_environment": "No active environment",
|
||||
"no_environment": "無環境",
|
||||
"no_environment_description": "未選取任何環境。請選擇要對以下變數進行的動作。",
|
||||
"quick_peek": "快速預覽環境",
|
||||
"replace_with_variable": "以變數替代",
|
||||
"scope": "範圍",
|
||||
"quick_peek": "Environment Quick Peek",
|
||||
"replace_with_variable": "Replace with variable",
|
||||
"scope": "Scope",
|
||||
"select": "選擇環境",
|
||||
"set": "設定環境",
|
||||
"set_as_environment": "設為環境",
|
||||
"set": "Set environment",
|
||||
"set_as_environment": "Set as environment",
|
||||
"team_environments": "團隊環境",
|
||||
"title": "環境",
|
||||
"updated": "更新環境",
|
||||
"value": "數值",
|
||||
"variable": "變數",
|
||||
"value": "Value",
|
||||
"variable": "Variable",
|
||||
"variable_list": "變數列表"
|
||||
},
|
||||
"error": {
|
||||
@@ -252,7 +252,7 @@
|
||||
"no_duration": "無持續時間",
|
||||
"no_results_found": "找不到結果",
|
||||
"page_not_found": "找不到此頁面",
|
||||
"proxy_error": "Proxy 錯誤",
|
||||
"proxy_error": "Proxy error",
|
||||
"script_fail": "無法執行預請求指令碼",
|
||||
"something_went_wrong": "發生了一些錯誤",
|
||||
"test_script_fail": "無法執行測試指令碼"
|
||||
@@ -278,13 +278,13 @@
|
||||
"renamed": "資料夾已重新命名"
|
||||
},
|
||||
"graphql": {
|
||||
"connection_switch_confirm": "您要使用最新的 GraphQL 端點連線嗎?",
|
||||
"connection_switch_new_url": "切換至分頁將斷開使用中的 GraphQL 連線。新的連線網址為 ",
|
||||
"connection_switch_url": "您已連接至 GraphQL 端點。連線網址為 ",
|
||||
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
|
||||
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
|
||||
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
|
||||
"mutations": "變體",
|
||||
"schema": "綱要",
|
||||
"subscriptions": "訂閱",
|
||||
"switch_connection": "切換連線"
|
||||
"switch_connection": "Switch connection"
|
||||
},
|
||||
"group": {
|
||||
"time": "時間",
|
||||
@@ -339,27 +339,27 @@
|
||||
"title": "匯入"
|
||||
},
|
||||
"inspections": {
|
||||
"description": "檢查潛在錯誤",
|
||||
"description": "Inspect possible errors",
|
||||
"environment": {
|
||||
"add_environment": "新增至環境",
|
||||
"not_found": "找不到環境變數 “{environment}”。"
|
||||
"add_environment": "Add to Environment",
|
||||
"not_found": "Environment variable “{environment}” not found."
|
||||
},
|
||||
"header": {
|
||||
"cookie": "瀏覽器不允許 Hoppscotch 設定 Cookie 標頭。在我們推出 Hoppscotch 桌面版前,請先使用 Authorization 標頭。"
|
||||
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
|
||||
},
|
||||
"response": {
|
||||
"401_error": "請檢查您的授權認證。",
|
||||
"404_error": "請檢查您的請求網址和方式類型。",
|
||||
"cors_error": "請檢查您的跨來源資源共用設定。",
|
||||
"default_error": "請檢查您的請求。",
|
||||
"network_error": "請檢查您的網路連線。"
|
||||
"401_error": "Please check your authentication credentials.",
|
||||
"404_error": "Please check your request URL and method type.",
|
||||
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
|
||||
"default_error": "Please check your request.",
|
||||
"network_error": "Please check your network connection."
|
||||
},
|
||||
"title": "檢查工具",
|
||||
"title": "Inspector",
|
||||
"url": {
|
||||
"extension_not_installed": "未安裝擴充套件。",
|
||||
"extension_unknown_origin": "請確認您是否已將 API 端點的來源加入 Hoppscotch 擴充套件的清單。",
|
||||
"extention_enable_action": "啟用瀏覽器擴充套件",
|
||||
"extention_not_enabled": "未啟用擴充套件。"
|
||||
"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.",
|
||||
"extention_enable_action": "Enable Browser Extension",
|
||||
"extention_not_enabled": "Extension not enabled."
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
@@ -472,7 +472,7 @@
|
||||
"payload": "負載",
|
||||
"query": "查詢",
|
||||
"raw_body": "原始請求本體",
|
||||
"rename": "重新命名請求",
|
||||
"rename": "Rename Request",
|
||||
"renamed": "請求已重新命名",
|
||||
"run": "執行",
|
||||
"save": "儲存",
|
||||
@@ -510,7 +510,7 @@
|
||||
"accent_color": "強調色",
|
||||
"account": "帳號",
|
||||
"account_deleted": "已刪除您的帳號",
|
||||
"account_description": "自訂您的帳號設定。",
|
||||
"account_description": "自定義您的帳號設定。",
|
||||
"account_email_description": "您的主要電子郵件地址。",
|
||||
"account_name_description": "這是您的顯示名稱。",
|
||||
"background": "背景",
|
||||
@@ -542,7 +542,7 @@
|
||||
"read_the": "閱讀",
|
||||
"reset_default": "重置為預設",
|
||||
"short_codes": "快捷碼",
|
||||
"short_codes_description": "您建立的快捷碼。",
|
||||
"short_codes_description": "我們為您打造的快捷碼。",
|
||||
"sidebar_on_left": "左側邊欄",
|
||||
"sync": "同步",
|
||||
"sync_collections": "集合",
|
||||
@@ -551,9 +551,9 @@
|
||||
"sync_history": "歷史",
|
||||
"system_mode": "系統",
|
||||
"telemetry": "遙測服務",
|
||||
"telemetry_helps_us": "遙測服務能夠幫助我們進行個人化操作,為您提供最佳體驗。",
|
||||
"telemetry_helps_us": "遙測服務幫助我們進行個性化操作,為您提供最佳體驗。",
|
||||
"theme": "主題",
|
||||
"theme_description": "自訂您的應用程式主題。",
|
||||
"theme_description": "自定義您的應用程式主題。",
|
||||
"use_experimental_url_bar": "使用帶有環境醒目標示的實驗性網址欄",
|
||||
"user": "使用者",
|
||||
"verified_email": "已確認電子郵件地址",
|
||||
@@ -592,26 +592,26 @@
|
||||
"title": "導航"
|
||||
},
|
||||
"others": {
|
||||
"prettify": "美化編輯器的內容",
|
||||
"title": "其他"
|
||||
"prettify": "Prettify Editor's Content",
|
||||
"title": "Others"
|
||||
},
|
||||
"request": {
|
||||
"copy_request_link": "複製請求連結",
|
||||
"delete_method": "選擇 DELETE 方法",
|
||||
"get_method": "選擇 GET 方法",
|
||||
"head_method": "選擇 HEAD 方法",
|
||||
"import_curl": "匯入 cURL",
|
||||
"import_curl": "Import cURL",
|
||||
"method": "方法",
|
||||
"next_method": "選擇下一個方法",
|
||||
"post_method": "選擇 POST 方法",
|
||||
"previous_method": "選擇上一個方法",
|
||||
"put_method": "選擇 PUT 方法",
|
||||
"rename": "重新命名請求",
|
||||
"rename": "Rename Request",
|
||||
"reset_request": "重置請求",
|
||||
"save_request": "儲存請求",
|
||||
"save_request": "Save Request",
|
||||
"save_to_collections": "儲存到集合",
|
||||
"send_request": "傳送請求",
|
||||
"show_code": "產生程式碼片段",
|
||||
"show_code": "Generate code snippet",
|
||||
"title": "請求"
|
||||
},
|
||||
"response": {
|
||||
@@ -642,82 +642,82 @@
|
||||
"url": "網址"
|
||||
},
|
||||
"spotlight": {
|
||||
"change_language": "變更語言",
|
||||
"change_language": "Change Language",
|
||||
"environments": {
|
||||
"delete": "刪除目前環境",
|
||||
"duplicate": "複製目前環境",
|
||||
"duplicate_global": "複製全域環境",
|
||||
"edit": "編輯目前環境",
|
||||
"edit_global": "編輯全域環境",
|
||||
"new": "建立新環境",
|
||||
"new_variable": "建立新環境變數",
|
||||
"title": "環境"
|
||||
"delete": "Delete current environment",
|
||||
"duplicate": "Duplicate current environment",
|
||||
"duplicate_global": "Duplicate global environment",
|
||||
"edit": "Edit current environment",
|
||||
"edit_global": "Edit global environment",
|
||||
"new": "Create new environment",
|
||||
"new_variable": "Create a new environment variable",
|
||||
"title": "Environments"
|
||||
},
|
||||
"general": {
|
||||
"chat": "與客服對話",
|
||||
"help_menu": "幫助與支援",
|
||||
"open_docs": "閱讀說明文件",
|
||||
"open_github": "開啟 GitHub 儲存庫",
|
||||
"open_keybindings": "鍵盤快捷鍵",
|
||||
"social": "社交",
|
||||
"title": "一般"
|
||||
"chat": "Chat with support",
|
||||
"help_menu": "Help and support",
|
||||
"open_docs": "Read Documentation",
|
||||
"open_github": "Open GitHub repository",
|
||||
"open_keybindings": "Keyboard shortcuts",
|
||||
"social": "Social",
|
||||
"title": "General"
|
||||
},
|
||||
"graphql": {
|
||||
"connect": "連接至伺服器",
|
||||
"disconnect": "斷開與伺服器的連線"
|
||||
"connect": "Connect to server",
|
||||
"disconnect": "Disconnect from server"
|
||||
},
|
||||
"miscellaneous": {
|
||||
"invite": "邀請您的朋友使用 Hoppscotch",
|
||||
"title": "雜項"
|
||||
"invite": "Invite your friends to Hoppscotch",
|
||||
"title": "Miscellaneous"
|
||||
},
|
||||
"request": {
|
||||
"save_as_new": "儲存為新請求",
|
||||
"select_method": "選擇方法",
|
||||
"switch_to": "切換至",
|
||||
"tab_authorization": "授權分頁",
|
||||
"tab_body": "本體分頁",
|
||||
"tab_headers": "標頭分頁",
|
||||
"tab_parameters": "參數分頁",
|
||||
"tab_pre_request_script": "預請求腳本分頁",
|
||||
"tab_query": "查詢分頁",
|
||||
"tab_tests": "測試分頁",
|
||||
"tab_variables": "變數分頁"
|
||||
"save_as_new": "Save as new request",
|
||||
"select_method": "Select method",
|
||||
"switch_to": "Switch to",
|
||||
"tab_authorization": "Authorization tab",
|
||||
"tab_body": "Body tab",
|
||||
"tab_headers": "Headers tab",
|
||||
"tab_parameters": "Parameters tab",
|
||||
"tab_pre_request_script": "Pre-request script tab",
|
||||
"tab_query": "Query tab",
|
||||
"tab_tests": "Tests tab",
|
||||
"tab_variables": "Variables tab"
|
||||
},
|
||||
"response": {
|
||||
"copy": "複製回應",
|
||||
"download": "下載回應",
|
||||
"title": "回應"
|
||||
"copy": "Copy response",
|
||||
"download": "Download response as file",
|
||||
"title": "Response"
|
||||
},
|
||||
"section": {
|
||||
"interceptor": "攔截器",
|
||||
"interface": "介面",
|
||||
"theme": "主題",
|
||||
"user": "使用者"
|
||||
"interceptor": "Interceptor",
|
||||
"interface": "Interface",
|
||||
"theme": "Theme",
|
||||
"user": "User"
|
||||
},
|
||||
"settings": {
|
||||
"change_interceptor": "變更攔截器",
|
||||
"change_language": "變更語言",
|
||||
"change_interceptor": "Change Interceptor",
|
||||
"change_language": "Change Language",
|
||||
"theme": {
|
||||
"black": "黑色",
|
||||
"dark": "暗色",
|
||||
"light": "亮色",
|
||||
"system": "跟隨系統"
|
||||
"black": "Black",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"system": "System preference"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"close_current": "關閉目前分頁",
|
||||
"close_others": "關閉所有其他分頁",
|
||||
"duplicate": "複製目前分頁",
|
||||
"new_tab": "開啟新分頁",
|
||||
"title": "分頁"
|
||||
"close_current": "Close current tab",
|
||||
"close_others": "Close all other tabs",
|
||||
"duplicate": "Duplicate current tab",
|
||||
"new_tab": "Open a new tab",
|
||||
"title": "Tabs"
|
||||
},
|
||||
"workspace": {
|
||||
"delete": "刪除目前團隊",
|
||||
"edit": "編輯目前團隊",
|
||||
"invite": "邀請他人加入團隊",
|
||||
"new": "建立新團隊",
|
||||
"switch_to_personal": "切換至您的個人工作區",
|
||||
"title": "團隊"
|
||||
"delete": "Delete current team",
|
||||
"edit": "Edit current team",
|
||||
"invite": "Invite people to team",
|
||||
"new": "Create new team",
|
||||
"switch_to_personal": "Switch to your personal workspace",
|
||||
"title": "Teams"
|
||||
}
|
||||
},
|
||||
"sse": {
|
||||
@@ -777,11 +777,11 @@
|
||||
"tab": {
|
||||
"authorization": "授權",
|
||||
"body": "請求本體",
|
||||
"close": "關閉分頁",
|
||||
"close_others": "關閉其他分頁",
|
||||
"close": "Close Tab",
|
||||
"close_others": "Close other Tabs",
|
||||
"collections": "集合",
|
||||
"documentation": "幫助文件",
|
||||
"duplicate": "複製分頁",
|
||||
"duplicate": "Duplicate Tab",
|
||||
"environments": "環境",
|
||||
"headers": "請求標頭",
|
||||
"history": "歷史記錄",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hoppscotch/common",
|
||||
"private": true,
|
||||
"version": "2023.8.3-1",
|
||||
"version": "2023.8.1",
|
||||
"scripts": {
|
||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||
"test": "vitest --run",
|
||||
@@ -17,22 +17,22 @@
|
||||
"postinstall": "pnpm run gql-codegen",
|
||||
"do-test": "pnpm run test",
|
||||
"do-lint": "pnpm run prod-lint",
|
||||
"do-typecheck": "node type-check.mjs",
|
||||
"do-typecheck": "pnpm run lint",
|
||||
"do-lintfix": "pnpm run lintfix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "^10.1.0",
|
||||
"@codemirror/autocomplete": "^6.10.2",
|
||||
"@codemirror/commands": "^6.3.0",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/autocomplete": "^6.9.0",
|
||||
"@codemirror/commands": "^6.2.4",
|
||||
"@codemirror/lang-javascript": "^6.1.9",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-xml": "^6.0.2",
|
||||
"@codemirror/language": "6.9.0",
|
||||
"@codemirror/language": "^6.9.0",
|
||||
"@codemirror/legacy-modes": "^6.3.3",
|
||||
"@codemirror/lint": "^6.4.2",
|
||||
"@codemirror/search": "^6.5.4",
|
||||
"@codemirror/state": "^6.3.1",
|
||||
"@codemirror/view": "^6.22.0",
|
||||
"@codemirror/lint": "^6.4.0",
|
||||
"@codemirror/search": "^6.5.1",
|
||||
"@codemirror/state": "^6.2.1",
|
||||
"@codemirror/view": "^6.16.0",
|
||||
"@fontsource-variable/inter": "^5.0.8",
|
||||
"@fontsource-variable/material-symbols-rounded": "^5.0.7",
|
||||
"@fontsource-variable/roboto-mono": "^5.0.9",
|
||||
@@ -41,7 +41,9 @@
|
||||
"@hoppscotch/js-sandbox": "workspace:^",
|
||||
"@hoppscotch/ui": "workspace:^",
|
||||
"@hoppscotch/vue-toasted": "^0.1.0",
|
||||
"@lezer/highlight": "1.1.4",
|
||||
"@lezer/highlight": "^1.1.6",
|
||||
"@sentry/tracing": "^7.64.0",
|
||||
"@sentry/vue": "^7.64.0",
|
||||
"@urql/core": "^4.1.1",
|
||||
"@urql/devtools": "^2.0.3",
|
||||
"@urql/exchange-auth": "^2.1.6",
|
||||
@@ -52,7 +54,6 @@
|
||||
"acorn-walk": "^8.2.0",
|
||||
"axios": "^1.4.0",
|
||||
"buffer": "^6.0.3",
|
||||
"cookie-es": "^1.0.0",
|
||||
"dioc": "workspace:^",
|
||||
"esprima": "^4.0.1",
|
||||
"events": "^3.3.0",
|
||||
@@ -77,8 +78,6 @@
|
||||
"process": "^0.11.10",
|
||||
"qs": "^6.11.2",
|
||||
"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-v3": "npm:socket.io-client@^3.1.3",
|
||||
"socket.io-client-v4": "npm:socket.io-client@^4.4.1",
|
||||
@@ -101,8 +100,7 @@
|
||||
"wonka": "^6.3.4",
|
||||
"workbox-window": "^7.0.0",
|
||||
"xml-formatter": "^3.5.0",
|
||||
"yargs-parser": "^21.1.1",
|
||||
"zod": "^3.22.4"
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
@@ -140,7 +138,6 @@
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"glob": "^10.3.10",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"openapi-types": "^12.1.3",
|
||||
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 386 B |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 386 B |
|
Before Width: | Height: | Size: 354 KiB After Width: | Height: | Size: 926 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 462 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 624 B After Width: | Height: | Size: 400 B |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 871 B |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 510 KiB |
|
Before Width: | Height: | Size: 385 KiB After Width: | Height: | Size: 535 KiB |
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 60 KiB |
@@ -1,50 +1 @@
|
||||
<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>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -58,8 +58,6 @@ declare module 'vue' {
|
||||
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
||||
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.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']
|
||||
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
||||
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
||||
@@ -92,6 +90,7 @@ declare module 'vue' {
|
||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
||||
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
|
||||
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
||||
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
||||
@@ -154,7 +153,6 @@ declare module 'vue' {
|
||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||
InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default']
|
||||
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
||||
@@ -187,6 +185,7 @@ declare module 'vue' {
|
||||
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
|
||||
SmartExpand: typeof import('./../../hoppscotch-ui/src/components/smart/Expand.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']
|
||||
SmartIntersection: typeof import('./../../hoppscotch-ui/src/components/smart/Intersection.vue')['default']
|
||||
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
|
||||
|
||||
@@ -2,16 +2,53 @@
|
||||
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
|
||||
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
||||
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
|
||||
|
||||
<HoppSmartConfirmModal
|
||||
:show="confirmRemove"
|
||||
:title="t('confirm.remove_team')"
|
||||
@hide-modal="confirmRemove = false"
|
||||
@resolve="deleteTeam()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
|
||||
const showShortcuts = ref(false)
|
||||
const showShare = 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", () => {
|
||||
showShortcuts.value = !showShortcuts.value
|
||||
})
|
||||
@@ -23,4 +60,9 @@ defineActionHandler("modals.share.toggle", () => {
|
||||
defineActionHandler("modals.login.toggle", () => {
|
||||
showLogin.value = !showLogin.value
|
||||
})
|
||||
|
||||
defineActionHandler("modals.team.delete", ({ teamId }) => {
|
||||
teamID.value = teamId
|
||||
confirmRemove.value = true
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -20,12 +20,6 @@
|
||||
<AppInterceptor />
|
||||
</template>
|
||||
</tippy>
|
||||
<HoppButtonSecondary
|
||||
v-if="platform.platformFeatureFlags.cookiesEnabled ?? false"
|
||||
:label="t('app.cookies')"
|
||||
:icon="IconCookie"
|
||||
@click="showCookiesModal = true"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<tippy
|
||||
@@ -201,17 +195,12 @@
|
||||
:show="showDeveloperOptions"
|
||||
@hide-modal="showDeveloperOptions = false"
|
||||
/>
|
||||
<CookiesAllModal
|
||||
:show="showCookiesModal"
|
||||
@hide-modal="showCookiesModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { version } from "~/../package.json"
|
||||
import IconCookie from "~icons/lucide/cookie"
|
||||
import IconSidebar from "~icons/lucide/sidebar"
|
||||
import IconZap from "~icons/lucide/zap"
|
||||
import IconShare2 from "~icons/lucide/share-2"
|
||||
@@ -234,9 +223,7 @@ import { invokeAction } from "@helpers/actions"
|
||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const showDeveloperOptions = ref(false)
|
||||
const showCookiesModal = ref(false)
|
||||
|
||||
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
||||
const SIDEBAR = useSetting("SIDEBAR")
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<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"
|
||||
@mousedown.prevent="platform.ui?.appHeader?.onHeaderAreaClick?.()"
|
||||
>
|
||||
<div
|
||||
class="inline-flex items-center justify-start flex-1 space-x-2"
|
||||
@@ -233,39 +231,29 @@
|
||||
@invite-team="inviteTeam(editingTeamName, editingTeamID)"
|
||||
@refetch-teams="refetchTeams"
|
||||
/>
|
||||
|
||||
<HoppSmartConfirmModal
|
||||
:show="confirmRemove"
|
||||
:title="t('confirm.remove_team')"
|
||||
@hide-modal="confirmRemove = false"
|
||||
@resolve="deleteTeam"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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 { useReadonlyStream } from "@composables/stream"
|
||||
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 { 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 toast = useToast()
|
||||
@@ -290,9 +278,6 @@ const currentUser = useReadonlyStream(
|
||||
platform.auth.getProbableUser()
|
||||
)
|
||||
|
||||
const confirmRemove = ref(false)
|
||||
const teamID = ref<string | null>(null)
|
||||
|
||||
const selectedTeam = ref<GetMyTeamsQuery["myTeams"][number] | undefined>()
|
||||
|
||||
// TeamList-Adapter
|
||||
@@ -392,24 +377,6 @@ 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
|
||||
const tippyActions = ref<any | null>(null)
|
||||
const profile = ref<any | null>(null)
|
||||
@@ -438,12 +405,6 @@ defineActionHandler(
|
||||
computed(() => !currentUser.value)
|
||||
)
|
||||
|
||||
defineActionHandler("modals.team.delete", ({ teamId }) => {
|
||||
if (selectedTeam.value?.myRole !== TeamMemberRole.Owner) return noPermission()
|
||||
teamID.value = teamId
|
||||
confirmRemove.value = true
|
||||
})
|
||||
|
||||
const noPermission = () => {
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
}
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
import { ref, watch } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
@@ -61,12 +60,11 @@ const emit = defineEmits<{
|
||||
|
||||
const editingName = ref("")
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
watch(
|
||||
() => props.show,
|
||||
(show) => {
|
||||
if (show) {
|
||||
editingName.value = tabs.currentActiveTab.value.document.request.name
|
||||
editingName.value = currentActiveTab.value.document.request.name
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<HoppButtonSecondary
|
||||
v-if="!saveRequest"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconImport"
|
||||
:icon="IconArchive"
|
||||
:title="t('modal.import_export')"
|
||||
@click="emit('display-modal-import-export')"
|
||||
/>
|
||||
@@ -257,27 +257,12 @@
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-secondaryLight text-center">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<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>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="emit('display-modal-add')"
|
||||
/>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
v-else-if="node.data.type === 'collections'"
|
||||
@@ -303,7 +288,8 @@
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.folder')}`"
|
||||
:text="t('empty.folder')"
|
||||
/>
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
</template>
|
||||
</HoppSmartTree>
|
||||
</div>
|
||||
@@ -311,9 +297,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconArchive from "~icons/lucide/archive"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { computed, PropType, Ref, toRef } from "vue"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
@@ -326,8 +312,7 @@ import { useColorMode } from "@composables/theming"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { Picked } from "~/helpers/types/HoppPicked.js"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
|
||||
export type Collection = {
|
||||
type: "collections"
|
||||
@@ -535,8 +520,7 @@ const isSelected = ({
|
||||
}
|
||||
}
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
const active = computed(() => tabs.currentActiveTab.value.document.saveContext)
|
||||
const active = computed(() => currentActiveTab.value.document.saveContext)
|
||||
|
||||
const isActiveRequest = (folderPath: string, requestIndex: number) => {
|
||||
return pipe(
|
||||
|
||||
@@ -82,16 +82,12 @@ import {
|
||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||
import { computedWithControl } from "@vueuse/core"
|
||||
import { platform } from "~/platform"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { currentActiveTab as activeRESTTab } from "~/helpers/rest/tab"
|
||||
import { currentActiveTab as activeGQLTab } from "~/helpers/graphql/tab"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const RESTTabs = useService(RESTTabService)
|
||||
const GQLTabs = useService(GQLTabService)
|
||||
|
||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
||||
|
||||
type CollectionType =
|
||||
@@ -127,13 +123,13 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const gqlRequestName = computedWithControl(
|
||||
() => GQLTabs.currentActiveTab.value,
|
||||
() => GQLTabs.currentActiveTab.value.document.request.name
|
||||
() => activeGQLTab.value,
|
||||
() => activeGQLTab.value.document.request.name
|
||||
)
|
||||
|
||||
const restRequestName = computedWithControl(
|
||||
() => RESTTabs.currentActiveTab.value,
|
||||
() => RESTTabs.currentActiveTab.value.document.request.name
|
||||
() => activeRESTTab.value,
|
||||
() => activeRESTTab.value.document.request.name
|
||||
)
|
||||
|
||||
const reqName = computed(() => {
|
||||
@@ -149,14 +145,12 @@ const reqName = computed(() => {
|
||||
const requestName = ref(reqName.value)
|
||||
|
||||
watch(
|
||||
() => [RESTTabs.currentActiveTab.value, GQLTabs.currentActiveTab.value],
|
||||
() => [activeRESTTab.value, activeGQLTab.value],
|
||||
() => {
|
||||
if (props.mode === "rest") {
|
||||
requestName.value =
|
||||
RESTTabs.currentActiveTab.value?.document.request.name ?? ""
|
||||
requestName.value = activeRESTTab.value?.document.request.name ?? ""
|
||||
} else {
|
||||
requestName.value =
|
||||
GQLTabs.currentActiveTab.value?.document.request.name ?? ""
|
||||
requestName.value = activeGQLTab.value?.document.request.name ?? ""
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -216,8 +210,8 @@ const saveRequestAs = async () => {
|
||||
|
||||
const requestUpdated =
|
||||
props.mode === "rest"
|
||||
? cloneDeep(RESTTabs.currentActiveTab.value.document.request)
|
||||
: cloneDeep(GQLTabs.currentActiveTab.value.document.request)
|
||||
? cloneDeep(activeRESTTab.value.document.request)
|
||||
: cloneDeep(activeGQLTab.value.document.request)
|
||||
|
||||
requestUpdated.name = requestName.value
|
||||
|
||||
@@ -230,7 +224,7 @@ const saveRequestAs = async () => {
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
RESTTabs.currentActiveTab.value.document = {
|
||||
activeRESTTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -257,7 +251,7 @@ const saveRequestAs = async () => {
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
RESTTabs.currentActiveTab.value.document = {
|
||||
activeRESTTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -285,7 +279,7 @@ const saveRequestAs = async () => {
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
RESTTabs.currentActiveTab.value.document = {
|
||||
activeRESTTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -445,7 +439,7 @@ const updateTeamCollectionOrFolder = (
|
||||
(result) => {
|
||||
const { createRequestInCollection } = result
|
||||
|
||||
RESTTabs.currentActiveTab.value.document = {
|
||||
activeRESTTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -466,7 +460,7 @@ const updateTeamCollectionOrFolder = (
|
||||
const requestSaved = () => {
|
||||
toast.success(`${t("request.added")}`)
|
||||
nextTick(() => {
|
||||
RESTTabs.currentActiveTab.value.document.isDirty = false
|
||||
activeRESTTab.value.document.isDirty = false
|
||||
})
|
||||
hideModal()
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
class="!rounded-none"
|
||||
:icon="IconPlus"
|
||||
:title="t('team.no_access')"
|
||||
:label="t('add.new')"
|
||||
:label="t('action.new')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
:icon="IconPlus"
|
||||
:label="t('add.new')"
|
||||
:label="t('action.new')"
|
||||
class="!rounded-none"
|
||||
@click="emit('display-modal-add')"
|
||||
/>
|
||||
@@ -39,7 +39,7 @@
|
||||
collectionsType.type === 'team-collections' &&
|
||||
collectionsType.selectedTeam === undefined
|
||||
"
|
||||
:icon="IconImport"
|
||||
:icon="IconArchive"
|
||||
:title="t('modal.import_export')"
|
||||
@click="emit('display-modal-import-export')"
|
||||
/>
|
||||
@@ -261,68 +261,55 @@
|
||||
/>
|
||||
</template>
|
||||
<template #emptyNode="{ node }">
|
||||
<HoppSmartPlaceholder
|
||||
v-if="node === null"
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
@drop.stop
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-secondaryLight text-center">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex gap-4 flex-col items-stretch">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
<div v-if="node === null">
|
||||
<div @drop="(e) => e.stopPropagation()">
|
||||
<HoppSmartPlaceholder
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-if="hasNoTeamAccess"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
disabled
|
||||
filled
|
||||
outline
|
||||
:disabled="hasNoTeamAccess"
|
||||
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||
@click="
|
||||
hasNoTeamAccess ? null : emit('display-modal-import-export')
|
||||
"
|
||||
:title="t('team.no_access')"
|
||||
:label="t('action.new')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
:icon="IconPlus"
|
||||
:label="t('add.new')"
|
||||
:label="t('action.new')"
|
||||
filled
|
||||
outline
|
||||
:disabled="hasNoTeamAccess"
|
||||
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||
@click="hasNoTeamAccess ? null : emit('display-modal-add')"
|
||||
@click="emit('display-modal-add')"
|
||||
/>
|
||||
</div>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
</div>
|
||||
<div
|
||||
v-else-if="node.data.type === 'collections'"
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
@drop.stop
|
||||
@drop="(e) => e.stopPropagation()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="
|
||||
node.data.type === 'collections' &&
|
||||
emit('add-folder', {
|
||||
path: node.id,
|
||||
folder: node.data.data.data,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
<HoppSmartPlaceholder
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="node.data.type === 'folders'"
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.folder')}`"
|
||||
:text="t('empty.folder')"
|
||||
@drop.stop
|
||||
/>
|
||||
@drop="(e) => e.stopPropagation()"
|
||||
>
|
||||
<HoppSmartPlaceholder
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.folder')}`"
|
||||
:text="t('empty.folder')"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartTree>
|
||||
</div>
|
||||
@@ -330,9 +317,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconArchive from "~icons/lucide/archive"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import { computed, PropType, Ref, toRef } from "vue"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
@@ -348,12 +335,10 @@ import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { Picked } from "~/helpers/types/HoppPicked.js"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { useService } from "dioc/vue"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
||||
|
||||
@@ -551,7 +536,7 @@ const isSelected = ({
|
||||
}
|
||||
}
|
||||
|
||||
const active = computed(() => tabs.currentActiveTab.value.document.saveContext)
|
||||
const active = computed(() => currentActiveTab.value.document.saveContext)
|
||||
|
||||
const isActiveRequest = (requestID: string) => {
|
||||
return pipe(
|
||||
|
||||
@@ -36,14 +36,11 @@
|
||||
import { ref, watch } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { currentActiveTab } from "~/helpers/graphql/tab"
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
folderPath?: string
|
||||
@@ -66,7 +63,7 @@ watch(
|
||||
() => props.show,
|
||||
(show) => {
|
||||
if (show) {
|
||||
editingName.value = tabs.currentActiveTab.value?.document.request.name
|
||||
editingName.value = currentActiveTab.value?.document.request.name
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -220,8 +220,7 @@ import {
|
||||
moveGraphqlRequest,
|
||||
} from "~/newstore/collections"
|
||||
import { Picked } from "~/helpers/types/HoppPicked"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { getTabsRefTo } from "~/helpers/graphql/tab"
|
||||
|
||||
const props = defineProps({
|
||||
picked: { type: Object, default: null },
|
||||
@@ -236,8 +235,6 @@ const colorMode = useColorMode()
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
// TODO: improve types plz
|
||||
const emit = defineEmits<{
|
||||
(e: "select", i: Picked | null): void
|
||||
@@ -298,7 +295,7 @@ const removeCollection = () => {
|
||||
emit("select", null)
|
||||
}
|
||||
|
||||
const possibleTabs = tabs.getTabsRefTo((tab) => {
|
||||
const possibleTabs = getTabsRefTo((tab) => {
|
||||
const ctx = tab.document.saveContext
|
||||
|
||||
if (!ctx) return false
|
||||
|
||||
@@ -203,15 +203,12 @@ import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { removeGraphqlFolder, moveGraphqlRequest } from "~/newstore/collections"
|
||||
import { computed, ref } from "vue"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { getTabsRefTo } from "~/helpers/graphql/tab"
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
const props = defineProps({
|
||||
picked: { type: Object, default: null },
|
||||
// Whether the request is in a selectable mode (activates 'select' event)
|
||||
@@ -280,7 +277,7 @@ const removeFolder = () => {
|
||||
emit("select", { picked: null })
|
||||
}
|
||||
|
||||
const possibleTabs = tabs.getTabsRefTo((tab) => {
|
||||
const possibleTabs = getTabsRefTo((tab) => {
|
||||
const ctx = tab.document.saveContext
|
||||
|
||||
if (!ctx) return false
|
||||
|
||||
@@ -258,42 +258,27 @@ const importFromJSON = () => {
|
||||
inputChooseFileToImportFrom.value.value = ""
|
||||
}
|
||||
|
||||
const exportJSON = async () => {
|
||||
const exportJSON = () => {
|
||||
const dataToWrite = collectionJson.value
|
||||
|
||||
const parsedCollections = JSON.parse(dataToWrite)
|
||||
|
||||
if (!parsedCollections.length) {
|
||||
return toast.error(t("error.no_collections_to_export"))
|
||||
}
|
||||
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
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"],
|
||||
},
|
||||
],
|
||||
platform?.analytics?.logEvent({
|
||||
type: "HOPP_EXPORT_COLLECTION",
|
||||
exporter: "json",
|
||||
platform: "gql",
|
||||
})
|
||||
|
||||
if (result.type === "unknown" || result.type === "saved") {
|
||||
platform?.analytics?.logEvent({
|
||||
type: "HOPP_EXPORT_COLLECTION",
|
||||
exporter: "json",
|
||||
platform: "gql",
|
||||
})
|
||||
|
||||
toast.success(t("state.download_started").toString())
|
||||
}
|
||||
// TODO: get uri from meta
|
||||
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)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -137,8 +137,12 @@ import { useToast } from "@composables/toast"
|
||||
import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { removeGraphqlRequest } from "~/newstore/collections"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import {
|
||||
createNewTab,
|
||||
getTabRefWithSaveContext,
|
||||
currentTabID,
|
||||
currentActiveTab,
|
||||
} from "~/helpers/graphql/tab"
|
||||
|
||||
// Template refs
|
||||
const tippyActions = ref<any | null>(null)
|
||||
@@ -150,8 +154,6 @@ const deleteAction = ref<any | null>(null)
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
const props = defineProps({
|
||||
// Whether the object is selected (show the tick mark)
|
||||
picked: { type: Object, default: null },
|
||||
@@ -163,7 +165,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const isActive = computed(() => {
|
||||
const saveCtx = tabs.currentActiveTab.value?.document.saveContext
|
||||
const saveCtx = currentActiveTab.value?.document.saveContext
|
||||
|
||||
if (!saveCtx) return false
|
||||
|
||||
@@ -199,7 +201,7 @@ const selectRequest = () => {
|
||||
if (props.saveRequest) {
|
||||
pick()
|
||||
} else {
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: props.folderPath,
|
||||
requestIndex: props.requestIndex,
|
||||
@@ -207,11 +209,11 @@ const selectRequest = () => {
|
||||
|
||||
// Switch to that request if that request is open
|
||||
if (possibleTab) {
|
||||
tabs.setActiveTab(possibleTab.value.id)
|
||||
currentTabID.value = possibleTab.value.id
|
||||
return
|
||||
}
|
||||
|
||||
tabs.createNewTab({
|
||||
createNewTab({
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: props.folderPath,
|
||||
@@ -251,7 +253,7 @@ const removeRequest = () => {
|
||||
}
|
||||
|
||||
// Detach the request from any of the tabs
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: props.folderPath,
|
||||
requestIndex: props.requestIndex,
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
v-if="!saveRequest"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('modal.import_export')"
|
||||
:icon="IconImport"
|
||||
:icon="IconArchive"
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
</div>
|
||||
@@ -66,27 +66,12 @@
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-secondaryLight text-center">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<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>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
|
||||
@@ -155,13 +140,12 @@ import {
|
||||
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import IconArchive from "~icons/lucide/archive"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { platform } from "~/platform"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { createNewTab, currentActiveTab } from "~/helpers/graphql/tab"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -174,16 +158,14 @@ export default defineComponent({
|
||||
const collections = useReadonlyStream(graphqlCollections$, [], "deep")
|
||||
const colorMode = useColorMode()
|
||||
const t = useI18n()
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
return {
|
||||
collections,
|
||||
colorMode,
|
||||
t,
|
||||
tabs,
|
||||
IconPlus,
|
||||
IconHelpCircle,
|
||||
IconImport,
|
||||
IconArchive,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -285,13 +267,13 @@ export default defineComponent({
|
||||
},
|
||||
onAddRequest({ name, path, index }) {
|
||||
const newRequest = {
|
||||
...this.tabs.currentActiveTab.value.document.request,
|
||||
...currentActiveTab.value.document.request,
|
||||
name,
|
||||
}
|
||||
|
||||
saveGraphqlRequestAs(path, newRequest)
|
||||
|
||||
this.tabs.createNewTab({
|
||||
createNewTab({
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: path,
|
||||
|
||||
@@ -18,12 +18,13 @@
|
||||
"
|
||||
>
|
||||
<WorkspaceCurrent :section="t('tab.collections')" />
|
||||
<input
|
||||
|
||||
<HoppSmartInput
|
||||
v-model="filterTexts"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="flex w-full p-4 py-2 bg-transparent h-8"
|
||||
:placeholder="t('action.search')"
|
||||
input-styles="py-2 pl-4 pr-2 bg-transparent !border-0"
|
||||
type="search"
|
||||
:autofocus="false"
|
||||
:disabled="collectionsType.type === 'team-collections'"
|
||||
/>
|
||||
</div>
|
||||
@@ -218,6 +219,12 @@ import {
|
||||
import * as E from "fp-ts/Either"
|
||||
import { platform } from "~/platform"
|
||||
import { createCollectionGists } from "~/helpers/gist"
|
||||
import {
|
||||
createNewTab,
|
||||
currentActiveTab,
|
||||
currentTabID,
|
||||
getTabRefWithSaveContext,
|
||||
} from "~/helpers/rest/tab"
|
||||
import {
|
||||
getRequestsByPath,
|
||||
resolveSaveContextOnRequestReorder,
|
||||
@@ -232,11 +239,9 @@ import { currentReorderingStatus$ } from "~/newstore/reordering"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { WorkspaceService } from "~/services/workspace.service"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
const props = defineProps({
|
||||
saveRequest: {
|
||||
@@ -372,26 +377,22 @@ const updateSelectedTeam = (team: SelectedTeam) => {
|
||||
const workspace = workspaceService.currentWorkspace
|
||||
|
||||
// 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 collections and select the team
|
||||
// If there is no teamID, switch to my collections
|
||||
// Check if there is a teamID in the workspace, if yes, switch to team collection and select the team
|
||||
// If there is no teamID, switch to my environment
|
||||
watch(
|
||||
() => {
|
||||
const space = workspace.value
|
||||
return space.type === "personal" ? undefined : space.teamID
|
||||
|
||||
if (space.type === "personal") return undefined
|
||||
else return space.teamID
|
||||
},
|
||||
(teamID) => {
|
||||
if (teamID) {
|
||||
if (!teamID) {
|
||||
switchToMyCollections()
|
||||
} else if (teamID) {
|
||||
const team = myTeams.value?.find((t) => t.id === teamID)
|
||||
if (team) {
|
||||
updateSelectedTeam(team)
|
||||
}
|
||||
return
|
||||
if (team) updateSelectedTeam(team)
|
||||
}
|
||||
|
||||
return switchToMyCollections()
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -649,7 +650,7 @@ const addRequest = (payload: {
|
||||
|
||||
const onAddRequest = (requestName: string) => {
|
||||
const newRequest = {
|
||||
...cloneDeep(tabs.currentActiveTab.value.document.request),
|
||||
...cloneDeep(currentActiveTab.value.document.request),
|
||||
name: requestName,
|
||||
}
|
||||
|
||||
@@ -658,7 +659,7 @@ const onAddRequest = (requestName: string) => {
|
||||
if (!path) return
|
||||
const insertionIndex = saveRESTRequestAs(path, newRequest)
|
||||
|
||||
tabs.createNewTab({
|
||||
createNewTab({
|
||||
request: newRequest,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -707,7 +708,7 @@ const onAddRequest = (requestName: string) => {
|
||||
(result) => {
|
||||
const { createRequestInCollection } = result
|
||||
|
||||
tabs.createNewTab({
|
||||
createNewTab({
|
||||
request: newRequest,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -930,7 +931,7 @@ const updateEditingRequest = (newName: string) => {
|
||||
|
||||
if (folderPath === null || requestIndex === null) return
|
||||
|
||||
const possibleActiveTab = tabs.getTabRefWithSaveContext({
|
||||
const possibleActiveTab = getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
requestIndex,
|
||||
folderPath,
|
||||
@@ -974,7 +975,7 @@ const updateEditingRequest = (newName: string) => {
|
||||
)
|
||||
)()
|
||||
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID,
|
||||
})
|
||||
@@ -1210,7 +1211,7 @@ const onRemoveRequest = () => {
|
||||
emit("select", null)
|
||||
}
|
||||
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath,
|
||||
requestIndex,
|
||||
@@ -1270,7 +1271,7 @@ const onRemoveRequest = () => {
|
||||
)()
|
||||
|
||||
// If there is a tab attached to this request, dissociate its state and mark it dirty
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID,
|
||||
})
|
||||
@@ -1303,14 +1304,14 @@ const selectRequest = (selectedRequest: {
|
||||
let possibleTab = null
|
||||
|
||||
if (collectionsType.value.type === "team-collections") {
|
||||
possibleTab = tabs.getTabRefWithSaveContext({
|
||||
possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
})
|
||||
if (possibleTab) {
|
||||
tabs.setActiveTab(possibleTab.value.id)
|
||||
currentTabID.value = possibleTab.value.id
|
||||
} else {
|
||||
tabs.createNewTab({
|
||||
createNewTab({
|
||||
request: cloneDeep(request),
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -1320,16 +1321,16 @@ const selectRequest = (selectedRequest: {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
possibleTab = tabs.getTabRefWithSaveContext({
|
||||
possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
requestIndex: parseInt(requestIndex),
|
||||
folderPath: folderPath!,
|
||||
})
|
||||
if (possibleTab) {
|
||||
tabs.setActiveTab(possibleTab.value.id)
|
||||
currentTabID.value = possibleTab.value.id
|
||||
} else {
|
||||
// If not, open the request in a new tab
|
||||
tabs.createNewTab({
|
||||
createNewTab({
|
||||
request: cloneDeep(request),
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -1372,7 +1373,7 @@ const dropRequest = (payload: {
|
||||
destinationCollectionIndex
|
||||
)
|
||||
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath,
|
||||
requestIndex: pathToLastIndex(requestIndex),
|
||||
@@ -1421,7 +1422,7 @@ const dropRequest = (payload: {
|
||||
1
|
||||
)
|
||||
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
})
|
||||
@@ -1866,25 +1867,28 @@ const getJSONCollection = async () => {
|
||||
* @param collectionJSON - JSON string of the collection
|
||||
* @param name - Name of the collection set as the file name
|
||||
*/
|
||||
const initializeDownloadCollection = async (
|
||||
const initializeDownloadCollection = (
|
||||
collectionJSON: string,
|
||||
name: string | null
|
||||
) => {
|
||||
const result = await platform.io.saveFileWithDialog({
|
||||
data: collectionJSON,
|
||||
contentType: "application/json",
|
||||
suggestedFilename: `${name ?? "collection"}.json`,
|
||||
filters: [
|
||||
{
|
||||
name: "Hoppscotch Collection JSON file",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
})
|
||||
const file = new Blob([collectionJSON], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
if (result.type === "unknown" || result.type === "saved") {
|
||||
toast.success(t("state.download_started").toString())
|
||||
if (name) {
|
||||
a.download = `${name}.json`
|
||||
} 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)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1913,14 +1917,11 @@ const exportData = async (
|
||||
exportLoading.value = false
|
||||
return
|
||||
},
|
||||
async (coll) => {
|
||||
(coll) => {
|
||||
const hoppColl = teamCollToHoppRESTColl(coll)
|
||||
const collectionJSONString = JSON.stringify(hoppColl)
|
||||
|
||||
await initializeDownloadCollection(
|
||||
collectionJSONString,
|
||||
hoppColl.name
|
||||
)
|
||||
initializeDownloadCollection(collectionJSONString, hoppColl.name)
|
||||
exportLoading.value = false
|
||||
}
|
||||
)
|
||||
@@ -1937,12 +1938,6 @@ const exportJSONCollection = async () => {
|
||||
|
||||
await getJSONCollection()
|
||||
|
||||
const parsedCollections = JSON.parse(collectionJSON.value)
|
||||
|
||||
if (!parsedCollections.length) {
|
||||
return toast.error(t("error.no_collections_to_export"))
|
||||
}
|
||||
|
||||
initializeDownloadCollection(collectionJSON.value, null)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
<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>
|
||||
@@ -1,195 +0,0 @@
|
||||
<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,14 +83,11 @@ import {
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { updateTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { useService } from "dioc/vue"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
position: { top: number; left: number }
|
||||
@@ -192,8 +189,8 @@ const addEnvironment = async () => {
|
||||
//replace the current tab endpoint with the variable name with << and >>
|
||||
const variableName = `<<${editingName.value}>>`
|
||||
//replace the currenttab endpoint containing the value in the text with variablename
|
||||
tabs.currentActiveTab.value.document.request.endpoint =
|
||||
tabs.currentActiveTab.value.document.request.endpoint.replace(
|
||||
currentActiveTab.value.document.request.endpoint =
|
||||
currentActiveTab.value.document.request.endpoint.replace(
|
||||
editingValue.value,
|
||||
variableName
|
||||
)
|
||||
|
||||
@@ -375,37 +375,22 @@ const importFromPostman = ({
|
||||
importFromHoppscotch(environments)
|
||||
}
|
||||
|
||||
const exportJSON = async () => {
|
||||
const exportJSON = () => {
|
||||
const dataToWrite = environmentJson.value
|
||||
|
||||
const parsedCollections = JSON.parse(dataToWrite)
|
||||
|
||||
if (!parsedCollections.length) {
|
||||
return toast.error(t("error.no_environments_to_export"))
|
||||
}
|
||||
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
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())
|
||||
}
|
||||
// TODO: get uri from meta
|
||||
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)
|
||||
}
|
||||
|
||||
const getErrorMessage = (err: GQLError<string>) => {
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
/>
|
||||
<HoppSmartTabs
|
||||
v-model="selectedEnvTab"
|
||||
:styles="`sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-10 top-0 bg-primary ${
|
||||
:styles="`sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-0 top-0 bg-primary ${
|
||||
!isTeamSelected || workspace.type === 'personal'
|
||||
? 'bg-primaryLight'
|
||||
: ''
|
||||
@@ -478,8 +478,7 @@ watch(
|
||||
teamEnvListAdapter.changeTeamID(newVal.teamID)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
}
|
||||
)
|
||||
|
||||
const selectedEnv = computed(() => {
|
||||
|
||||
@@ -60,7 +60,17 @@
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
:envs="liveEnvs"
|
||||
:name="'value' + index"
|
||||
:secret="env.secret"
|
||||
/>
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
id="variable"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.secret')"
|
||||
:icon="env.secret ? IconEyeOff : IconEye"
|
||||
@click="toggleEnvironmentSecret(index)"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
id="variable"
|
||||
@@ -110,6 +120,8 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconDone from "~icons/lucide/check"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconTrash from "~icons/lucide/trash"
|
||||
import IconEye from "~icons/lucide/eye"
|
||||
import IconEyeOff from "~icons/lucide/eye-off"
|
||||
import { clone } from "lodash-es"
|
||||
import { computed, ref, watch } from "vue"
|
||||
import * as E from "fp-ts/Either"
|
||||
@@ -140,6 +152,7 @@ type EnvironmentVariable = {
|
||||
env: {
|
||||
key: string
|
||||
value: string
|
||||
secret: boolean
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +185,7 @@ const idTicker = ref(0)
|
||||
|
||||
const editingName = ref<string | null>(null)
|
||||
const vars = ref<EnvironmentVariable[]>([
|
||||
{ id: idTicker.value++, env: { key: "", value: "" } },
|
||||
{ id: idTicker.value++, env: { key: "", value: "", secret: false } },
|
||||
])
|
||||
|
||||
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
||||
@@ -203,6 +216,8 @@ const workingEnv = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const oldEnvironments = ref<string[]>([])
|
||||
|
||||
const envList = useReadonlyStream(environments$, []) || props.envVars()
|
||||
|
||||
const evnExpandError = computed(() => {
|
||||
@@ -234,6 +249,28 @@ const liveEnvs = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
watch(liveEnvs, (newLiveEnv, oldLiveEnv) => {
|
||||
const oldEnvLength = oldLiveEnv.length
|
||||
const newEnvLength = newLiveEnv.length
|
||||
if (oldEnvLength === newEnvLength) {
|
||||
const _oldEnvironments = []
|
||||
for (let i = 0; i < newEnvLength; i++) {
|
||||
const newVar = newLiveEnv[i]
|
||||
const oldVar = oldLiveEnv[i]
|
||||
let newValue = ""
|
||||
if (!newVar.secret) {
|
||||
newValue = newVar.value
|
||||
} else if (!oldVar.secret) {
|
||||
newValue = oldVar.value
|
||||
} else {
|
||||
newValue = oldEnvironments.value[i]
|
||||
}
|
||||
_oldEnvironments.push(newValue)
|
||||
}
|
||||
oldEnvironments.value = _oldEnvironments
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(show) => {
|
||||
@@ -262,6 +299,7 @@ const addEnvironmentVariable = () => {
|
||||
env: {
|
||||
key: "",
|
||||
value: "",
|
||||
secret: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -270,12 +308,23 @@ const removeEnvironmentVariable = (index: number) => {
|
||||
vars.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const toggleEnvironmentSecret = (index: number) => {
|
||||
vars.value[index].env.secret = !vars.value[index].env.secret
|
||||
}
|
||||
|
||||
const saveEnvironment = () => {
|
||||
if (!editingName.value) {
|
||||
toast.error(`${t("environment.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
|
||||
const _vars = vars.value
|
||||
for (let i = 0; i < vars.value.length; i++) {
|
||||
const value = oldEnvironments.value[i]
|
||||
if (value) {
|
||||
_vars[i].env.value = value
|
||||
}
|
||||
}
|
||||
vars.value = _vars
|
||||
const filterdVariables = pipe(
|
||||
vars.value,
|
||||
A.filterMap(
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
role="menu"
|
||||
@keyup.e="edit!.$el.click()"
|
||||
@keyup.d="duplicate!.$el.click()"
|
||||
@keyup.j="exportAsJsonEl!.$el.click()"
|
||||
@keyup.delete="
|
||||
!(environmentIndex === 'Global')
|
||||
? deleteAction!.$el.click()
|
||||
@@ -78,18 +77,6 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="exportAsJsonEl"
|
||||
:icon="IconEdit"
|
||||
:label="`${t('export.as_json')}`"
|
||||
:shortcut="['J']"
|
||||
@click="
|
||||
() => {
|
||||
exportEnvironmentAsJSON()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="environmentIndex !== 'Global'"
|
||||
ref="deleteAction"
|
||||
@@ -134,7 +121,6 @@ import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -150,18 +136,10 @@ const emit = defineEmits<{
|
||||
|
||||
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 options = ref<TippyComponent | null>(null)
|
||||
const edit = ref<typeof HoppSmartItem>()
|
||||
const duplicate = ref<typeof HoppSmartItem>()
|
||||
const exportAsJsonEl = ref<typeof HoppSmartItem>()
|
||||
const deleteAction = ref<typeof HoppSmartItem>()
|
||||
|
||||
const removeEnvironment = () => {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconImport"
|
||||
:icon="IconArchive"
|
||||
:title="t('modal.import_export')"
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
@@ -33,32 +33,17 @@
|
||||
@edit-environment="editEnvironment(index)"
|
||||
/>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="!environments.length"
|
||||
v-if="environments.length === 0"
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-secondaryLight text-center">
|
||||
{{ t("environment.import_or_create") }}
|
||||
</span>
|
||||
<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>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
</HoppSmartPlaceholder>
|
||||
<EnvironmentsMyDetails
|
||||
:show="showModalDetails"
|
||||
@@ -81,8 +66,8 @@ import { environments$ } from "~/newstore/environments"
|
||||
import { useColorMode } from "~/composables/theming"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import IconArchive from "~icons/lucide/archive"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
|
||||
@@ -63,7 +63,17 @@
|
||||
:envs="liveEnvs"
|
||||
:name="'value' + index"
|
||||
:readonly="isViewer"
|
||||
:secret="env.secret"
|
||||
/>
|
||||
<div v-if="!isViewer" class="flex">
|
||||
<HoppButtonSecondary
|
||||
id="variable"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.secret')"
|
||||
:icon="env.secret ? IconEyeOff : IconEye"
|
||||
@click="toggleEnvironmentSecret(index)"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!isViewer" class="flex">
|
||||
<HoppButtonSecondary
|
||||
id="variable"
|
||||
@@ -139,6 +149,8 @@ import IconTrash from "~icons/lucide/trash"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconDone from "~icons/lucide/check"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconEye from "~icons/lucide/eye"
|
||||
import IconEyeOff from "~icons/lucide/eye-off"
|
||||
import { platform } from "~/platform"
|
||||
|
||||
type EnvironmentVariable = {
|
||||
@@ -146,6 +158,7 @@ type EnvironmentVariable = {
|
||||
env: {
|
||||
key: string
|
||||
value: string
|
||||
secret: boolean
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,9 +195,10 @@ const idTicker = ref(0)
|
||||
|
||||
const editingName = ref<string | null>(null)
|
||||
const vars = ref<EnvironmentVariable[]>([
|
||||
{ id: idTicker.value++, env: { key: "", value: "" } },
|
||||
{ id: idTicker.value++, env: { key: "", value: "", secret: false } },
|
||||
])
|
||||
|
||||
const oldEnvironments = ref<string[]>([])
|
||||
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
||||
IconTrash2,
|
||||
1000
|
||||
@@ -212,6 +226,28 @@ const liveEnvs = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
watch(liveEnvs, (newLiveEnv, oldLiveEnv) => {
|
||||
const oldEnvLength = oldLiveEnv.length
|
||||
const newEnvLength = newLiveEnv.length
|
||||
if (oldEnvLength === newEnvLength) {
|
||||
const _oldEnvironments = []
|
||||
for (let i = 0; i < newEnvLength; i++) {
|
||||
const newVar = newLiveEnv[i]
|
||||
const oldVar = oldLiveEnv[i]
|
||||
let newValue = ""
|
||||
if (!newVar.secret) {
|
||||
newValue = newVar.value
|
||||
} else if (!oldVar.secret) {
|
||||
newValue = oldVar.value
|
||||
} else {
|
||||
newValue = oldEnvironments.value[i]
|
||||
}
|
||||
_oldEnvironments.push(newValue)
|
||||
}
|
||||
oldEnvironments.value = _oldEnvironments
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(show) => {
|
||||
@@ -220,7 +256,7 @@ watch(
|
||||
editingName.value = null
|
||||
vars.value = pipe(
|
||||
props.envVars() ?? [],
|
||||
A.map((e: { key: string; value: string }) => ({
|
||||
A.map((e: { key: string; value: string; secret: boolean }) => ({
|
||||
id: idTicker.value++,
|
||||
env: clone(e),
|
||||
}))
|
||||
@@ -229,7 +265,7 @@ watch(
|
||||
editingName.value = props.editingEnvironment.environment.name ?? null
|
||||
vars.value = pipe(
|
||||
props.editingEnvironment.environment.variables ?? [],
|
||||
A.map((e: { key: string; value: string }) => ({
|
||||
A.map((e: { key: string; value: string; secret: boolean }) => ({
|
||||
id: idTicker.value++,
|
||||
env: clone(e),
|
||||
}))
|
||||
@@ -251,6 +287,7 @@ const addEnvironmentVariable = () => {
|
||||
env: {
|
||||
key: "",
|
||||
value: "",
|
||||
secret: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -259,6 +296,10 @@ const removeEnvironmentVariable = (index: number) => {
|
||||
vars.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const toggleEnvironmentSecret = (index: number) => {
|
||||
vars.value[index].env.secret = !vars.value[index].env.secret
|
||||
}
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
||||
const saveEnvironment = async () => {
|
||||
@@ -269,6 +310,15 @@ const saveEnvironment = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const _vars = vars.value
|
||||
for (let i = 0; i < vars.value.length; i++) {
|
||||
const value = oldEnvironments.value[i]
|
||||
if (value) {
|
||||
_vars[i].env.value = value
|
||||
}
|
||||
}
|
||||
vars.value = _vars
|
||||
|
||||
const filterdVariables = pipe(
|
||||
vars.value,
|
||||
A.filterMap(
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
role="menu"
|
||||
@keyup.e="edit!.$el.click()"
|
||||
@keyup.d="duplicate!.$el.click()"
|
||||
@keyup.j="exportAsJsonEl!.$el.click()"
|
||||
@keyup.delete="deleteAction!.$el.click()"
|
||||
@keyup.escape="options!.tippy().hide()"
|
||||
>
|
||||
@@ -55,7 +54,6 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
||||
<HoppSmartItem
|
||||
ref="duplicate"
|
||||
:icon="IconCopy"
|
||||
@@ -68,18 +66,6 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="exportAsJsonEl"
|
||||
:icon="IconEdit"
|
||||
:label="`${t('export.as_json')}`"
|
||||
:shortcut="['J']"
|
||||
@click="
|
||||
() => {
|
||||
exportEnvironmentAsJSON()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
@@ -123,7 +109,6 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -139,17 +124,11 @@ const emit = defineEmits<{
|
||||
|
||||
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 options = ref<TippyComponent | null>(null)
|
||||
const edit = ref<typeof HoppSmartItem>()
|
||||
const duplicate = ref<typeof HoppSmartItem>()
|
||||
const deleteAction = ref<typeof HoppSmartItem>()
|
||||
const exportAsJsonEl = ref<typeof HoppSmartItem>()
|
||||
|
||||
const removeEnvironment = () => {
|
||||
pipe(
|
||||
|
||||
@@ -31,49 +31,40 @@
|
||||
v-if="team !== undefined && team.myRole === 'VIEWER'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
disabled
|
||||
:icon="IconImport"
|
||||
:icon="IconArchive"
|
||||
:title="t('modal.import_export')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconImport"
|
||||
:icon="IconArchive"
|
||||
:title="t('modal.import_export')"
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="!loading && !teamEnvironments.length && !adapterError"
|
||||
v-if="!loading && teamEnvironments.length === 0 && !adapterError"
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-secondaryLight text-center">
|
||||
{{ t("environment.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex gap-4 flex-col items-stretch">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||
:disabled="isTeamViewer"
|
||||
@click="isTeamViewer ? null : displayModalImportExport(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>
|
||||
<HoppButtonSecondary
|
||||
v-if="team === undefined || team.myRole === 'VIEWER'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
disabled
|
||||
filled
|
||||
:icon="IconPlus"
|
||||
:title="t('team.no_access')"
|
||||
:label="t('action.new')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
</HoppSmartPlaceholder>
|
||||
<div v-else-if="!loading">
|
||||
<EnvironmentsTeamsEnvironment
|
||||
@@ -117,14 +108,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue"
|
||||
import { ref } from "vue"
|
||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { useColorMode } from "~/composables/theming"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconArchive from "~icons/lucide/archive"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
|
||||
@@ -147,8 +138,6 @@ const action = ref<"new" | "edit">("edit")
|
||||
const editingEnvironment = ref<TeamEnvironment | null>(null)
|
||||
const editingVariableName = ref("")
|
||||
|
||||
const isTeamViewer = computed(() => props.team?.myRole === "VIEWER")
|
||||
|
||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||
action.value = "new"
|
||||
showModalDetails.value = shouldDisplay
|
||||
|
||||
@@ -1,55 +1,50 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<div
|
||||
class="field-title flex-1"
|
||||
:class="{ 'field-highlighted': isHighlighted }"
|
||||
>
|
||||
{{ fieldName }}
|
||||
<span v-if="fieldArgs.length > 0">
|
||||
(
|
||||
<span v-for="(field, index) in fieldArgs" :key="`field-${index}`">
|
||||
{{ field.name }}:
|
||||
<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") }}
|
||||
<div class="field-title" :class="{ 'field-highlighted': isHighlighted }">
|
||||
{{ fieldName }}
|
||||
<span v-if="fieldArgs.length > 0">
|
||||
(
|
||||
<span v-for="(field, index) in fieldArgs" :key="`field-${index}`">
|
||||
{{ field.name }}:
|
||||
<GraphqlTypeLink
|
||||
:gql-type="field.type"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
/>
|
||||
<span v-if="index !== fieldArgs.length - 1">, </span>
|
||||
</span>
|
||||
</div>
|
||||
) </span
|
||||
>:
|
||||
<GraphqlTypeLink
|
||||
:gql-type="gqlField.type"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="gqlField.description"
|
||||
class="field-desc py-2 text-secondaryLight"
|
||||
class="py-2 text-secondaryLight field-desc"
|
||||
>
|
||||
{{ gqlField.description }}
|
||||
</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">
|
||||
<h5 class="my-2">Arguments:</h5>
|
||||
<div class="border-l-2 border-divider pl-4">
|
||||
<div class="pl-4 border-l-2 border-divider">
|
||||
<div v-for="(field, index) in fieldArgs" :key="`field-${index}`">
|
||||
<span>
|
||||
{{ field.name }}:
|
||||
<GraphqlTypeLink
|
||||
:gql-type="field.type"
|
||||
@jump-to-type="jumpToType"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
/>
|
||||
</span>
|
||||
<div
|
||||
v-if="field.description"
|
||||
class="field-desc py-2 text-secondaryLight"
|
||||
class="py-2 text-secondaryLight field-desc"
|
||||
>
|
||||
{{ field.description }}
|
||||
</div>
|
||||
@@ -59,41 +54,37 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script>
|
||||
// TypeScript + Script Setup this :)
|
||||
import { defineComponent } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { GraphQLType } from "graphql"
|
||||
import { computed } from "vue"
|
||||
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
||||
|
||||
const t = useI18n()
|
||||
export default defineComponent({
|
||||
props: {
|
||||
gqlField: { type: Object, default: () => ({}) },
|
||||
jumpTypeCallback: { type: Function, default: () => ({}) },
|
||||
isHighlighted: { type: Boolean, default: false },
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
t: useI18n(),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fieldName() {
|
||||
return this.gqlField.name
|
||||
},
|
||||
|
||||
const props = withDefaults(
|
||||
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)
|
||||
}
|
||||
fieldArgs() {
|
||||
return this.gqlField.args || []
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.field-highlighted {
|
||||
@apply border-b-2 border-accent;
|
||||
@apply border-accent border-b-2;
|
||||
}
|
||||
|
||||
.field-title {
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
<script setup lang="ts">
|
||||
import { platform } from "~/platform"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { currentActiveTab } from "~/helpers/graphql/tab"
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { connection } from "~/helpers/graphql/connection"
|
||||
import { connect } from "~/helpers/graphql/connection"
|
||||
@@ -71,10 +72,8 @@ import { disconnect } from "~/helpers/graphql/connection"
|
||||
import { InterceptorService } from "~/services/interceptor.service"
|
||||
import { useService } from "dioc/vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
|
||||
const t = useI18n()
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
const interceptorService = useService(InterceptorService)
|
||||
|
||||
@@ -83,9 +82,9 @@ const connectionSwitchModal = ref(false)
|
||||
const connected = computed(() => connection.state === "CONNECTED")
|
||||
|
||||
const url = computed({
|
||||
get: () => tabs.currentActiveTab.value?.document.request.url ?? "",
|
||||
get: () => currentActiveTab.value?.document.request.url ?? "",
|
||||
set: (value) => {
|
||||
tabs.currentActiveTab.value!.document.request.url = value
|
||||
currentActiveTab.value!.document.request.url = value
|
||||
},
|
||||
})
|
||||
|
||||
@@ -98,7 +97,7 @@ const onConnectClick = () => {
|
||||
}
|
||||
|
||||
const gqlConnect = () => {
|
||||
connect(url.value, tabs.currentActiveTab.value?.document.request.headers)
|
||||
connect(url.value, currentActiveTab.value?.document.request.headers)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_REQUEST_RUN",
|
||||
@@ -115,7 +114,7 @@ const switchConnection = () => {
|
||||
const lastTwoUrls = ref<string[]>([])
|
||||
|
||||
watch(
|
||||
tabs.currentActiveTab,
|
||||
currentActiveTab,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
lastTwoUrls.value.push(newVal.document.request.url)
|
||||
|
||||
@@ -58,7 +58,8 @@ import { computed, ref, watch } from "vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { platform } from "~/platform"
|
||||
import { computedWithControl, useVModel } from "@vueuse/core"
|
||||
import { currentActiveTab } from "~/helpers/graphql/tab"
|
||||
import { computedWithControl } from "@vueuse/core"
|
||||
import {
|
||||
GQLResponseEvent,
|
||||
runGQLOperation,
|
||||
@@ -67,39 +68,26 @@ import {
|
||||
import { useService } from "dioc/vue"
|
||||
import { InterceptorService } from "~/services/interceptor.service"
|
||||
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 t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
// v-model integration with props and emit
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: HoppGQLRequest
|
||||
response?: GQLResponseEvent[] | null
|
||||
optionTab?: GQLOptionTabs
|
||||
tabId: string
|
||||
}>(),
|
||||
{
|
||||
response: null,
|
||||
optionTab: "query",
|
||||
}
|
||||
)
|
||||
const emit = defineEmits(["update:modelValue", "update:response"])
|
||||
const selectedOptionTab = useVModel(props, "optionTab", emit)
|
||||
|
||||
const request = ref(props.modelValue)
|
||||
|
||||
@@ -112,8 +100,8 @@ watch(
|
||||
)
|
||||
|
||||
const url = computedWithControl(
|
||||
() => tabs.currentActiveTab.value,
|
||||
() => tabs.currentActiveTab.value.document.request.url
|
||||
() => currentActiveTab.value,
|
||||
() => currentActiveTab.value.document.request.url
|
||||
)
|
||||
|
||||
const activeGQLHeadersCount = computed(
|
||||
@@ -148,9 +136,6 @@ const runQuery = async (
|
||||
const duration = Date.now() - startTime
|
||||
completePageProgress()
|
||||
toast.success(`${t("state.finished_in", { duration })}`)
|
||||
if (definition?.operation === "subscription" && request.value.auth) {
|
||||
toast.success(t("authorization.graphql_headers"))
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
// response.value = [`${e}`]
|
||||
@@ -197,17 +182,17 @@ const hideRequestModal = () => {
|
||||
}
|
||||
const saveRequest = () => {
|
||||
if (
|
||||
tabs.currentActiveTab.value.document.saveContext &&
|
||||
tabs.currentActiveTab.value.document.saveContext.originLocation ===
|
||||
currentActiveTab.value.document.saveContext &&
|
||||
currentActiveTab.value.document.saveContext.originLocation ===
|
||||
"user-collection"
|
||||
) {
|
||||
editGraphqlRequest(
|
||||
tabs.currentActiveTab.value.document.saveContext.folderPath,
|
||||
tabs.currentActiveTab.value.document.saveContext.requestIndex,
|
||||
tabs.currentActiveTab.value.document.request
|
||||
currentActiveTab.value.document.saveContext.folderPath,
|
||||
currentActiveTab.value.document.saveContext.requestIndex,
|
||||
currentActiveTab.value.document.request
|
||||
)
|
||||
|
||||
tabs.currentActiveTab.value.document.isDirty = false
|
||||
currentActiveTab.value.document.isDirty = false
|
||||
} else {
|
||||
showSaveRequestModal.value = true
|
||||
}
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
<template #primary>
|
||||
<GraphqlRequestOptions
|
||||
v-model="tab.document.request"
|
||||
v-model:response="tab.document.response"
|
||||
v-model:option-tab="tab.document.optionTabPreference"
|
||||
v-model:response="tab.response"
|
||||
:tab-id="tab.id"
|
||||
/>
|
||||
</template>
|
||||
<template #secondary>
|
||||
<GraphqlResponse :response="tab.document.response" />
|
||||
<GraphqlResponse :response="tab.response" />
|
||||
</template>
|
||||
</AppPaneLayout>
|
||||
</template>
|
||||
@@ -19,15 +18,14 @@ import { useVModel } from "@vueuse/core"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { watch } from "vue"
|
||||
import { isEqualHoppGQLRequest } from "~/helpers/graphql"
|
||||
import { HoppGQLDocument } from "~/helpers/graphql/document"
|
||||
import { HoppTab } from "~/services/tab"
|
||||
import { HoppGQLTab } from "~/helpers/graphql/tab"
|
||||
|
||||
// TODO: Move Response and Request execution code to over here
|
||||
|
||||
const props = defineProps<{ modelValue: HoppTab<HoppGQLDocument> }>()
|
||||
const props = defineProps<{ modelValue: HoppGQLTab }>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", val: HoppTab<HoppGQLDocument>): void
|
||||
(e: "update:modelValue", val: HoppGQLTab): void
|
||||
}>()
|
||||
|
||||
const tab = useVModel(props, "modelValue", emit)
|
||||
|
||||
@@ -59,7 +59,6 @@ import { useToast } from "@composables/toast"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||
import { GQLResponseEvent } from "~/helpers/graphql/connection"
|
||||
import { platform } from "~/platform"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -112,31 +111,21 @@ const copyResponse = (str: string) => {
|
||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||
}
|
||||
|
||||
const downloadResponse = async (str: string) => {
|
||||
const downloadResponse = (str: string) => {
|
||||
const dataToWrite = str
|
||||
const file = new Blob([dataToWrite!], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
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: "JSON file",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if (result.type === "unknown" || result.type === "saved") {
|
||||
downloadResponseIcon.value = IconCheck
|
||||
toast.success(`${t("state.download_started")}`)
|
||||
}
|
||||
a.href = url
|
||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
downloadResponseIcon.value = IconCheck
|
||||
toast.success(`${t("state.download_started")}`)
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
defineActionHandler(
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
v-model="graphqlFieldsFilterText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="flex w-full p-4 py-2 bg-transparent h-8"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
class="flex flex-1 p-4 py-2 bg-transparent"
|
||||
/>
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
@@ -58,8 +58,8 @@
|
||||
v-for="(field, index) in filteredQueryFields"
|
||||
:key="`field-${index}`"
|
||||
:gql-field="field"
|
||||
:jump-type-callback="handleJumpToType"
|
||||
class="p-4"
|
||||
@jump-to-type="handleJumpToType"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
@@ -72,8 +72,8 @@
|
||||
v-for="(field, index) in filteredMutationFields"
|
||||
:key="`field-${index}`"
|
||||
:gql-field="field"
|
||||
:jump-type-callback="handleJumpToType"
|
||||
class="p-4"
|
||||
@jump-to-type="handleJumpToType"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
@@ -86,8 +86,8 @@
|
||||
v-for="(field, index) in filteredSubscriptionFields"
|
||||
:key="`field-${index}`"
|
||||
:gql-field="field"
|
||||
:jump-type-callback="handleJumpToType"
|
||||
class="p-4"
|
||||
@jump-to-type="handleJumpToType"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
@@ -103,7 +103,7 @@
|
||||
:gql-types="graphqlTypes"
|
||||
:is-highlighted="isGqlTypeHighlighted(type)"
|
||||
:highlighted-fields="getGqlTypeHighlightedFields(type)"
|
||||
@jump-to-type="handleJumpToType"
|
||||
:jump-type-callback="handleJumpToType"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
@@ -202,7 +202,6 @@ import {
|
||||
schemaString,
|
||||
subscriptionFields,
|
||||
} from "~/helpers/graphql/connection"
|
||||
import { platform } from "~/platform"
|
||||
|
||||
type NavigationTabs = "history" | "collection" | "docs" | "schema"
|
||||
type GqlTabs = "queries" | "mutations" | "subscriptions" | "types"
|
||||
@@ -373,33 +372,21 @@ useCodemirror(
|
||||
})
|
||||
)
|
||||
|
||||
const downloadSchema = async () => {
|
||||
const dataToWrite = schemaString.value
|
||||
const downloadSchema = () => {
|
||||
const dataToWrite = JSON.stringify(schemaString.value, null, 2)
|
||||
const file = new Blob([dataToWrite], { type: "application/graphql" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
|
||||
const filename = `${
|
||||
url.split("/").pop()!.split("#")[0].split("?")[0]
|
||||
}.graphql`
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
const result = await platform.io.saveFileWithDialog({
|
||||
data: dataToWrite,
|
||||
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")}`)
|
||||
}
|
||||
a.href = url
|
||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.graphql`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
downloadSchemaIcon.value = IconCheck
|
||||
toast.success(`${t("state.download_started")}`)
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const copySchema = () => {
|
||||
|
||||
@@ -92,13 +92,12 @@ import IconXCircle from "~icons/lucide/x-circle"
|
||||
import IconXSquare from "~icons/lucide/x-square"
|
||||
import IconFileEdit from "~icons/lucide/file-edit"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import { HoppTab } from "~/services/tab"
|
||||
import { HoppGQLDocument } from "~/helpers/graphql/document"
|
||||
import { HoppGQLTab } from "~/helpers/graphql/tab"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
defineProps<{
|
||||
tab: HoppTab<HoppGQLDocument>
|
||||
tab: HoppGQLTab
|
||||
isRemovable: boolean
|
||||
}>()
|
||||
|
||||
|
||||
@@ -7,31 +7,38 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GraphQLScalarType, GraphQLType } from "graphql"
|
||||
import { computed } from "vue"
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue"
|
||||
import { GraphQLScalarType } from "graphql"
|
||||
|
||||
const props = defineProps<{
|
||||
gqlType: GraphQLType
|
||||
}>()
|
||||
export default defineComponent({
|
||||
props: {
|
||||
// eslint-disable-next-line vue/require-default-prop
|
||||
gqlType: null,
|
||||
// (typeName: string) => void
|
||||
// eslint-disable-next-line vue/require-default-prop
|
||||
jumpTypeCallback: Function,
|
||||
},
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "jump-to-type", type: GraphQLType): void
|
||||
}>()
|
||||
computed: {
|
||||
typeString() {
|
||||
return `${this.gqlType}`
|
||||
},
|
||||
isScalar() {
|
||||
return this.resolveRootType(this.gqlType) instanceof GraphQLScalarType
|
||||
},
|
||||
},
|
||||
|
||||
const typeString = computed(() => `${props.gqlType}`)
|
||||
const isScalar = computed(() => {
|
||||
return resolveRootType(props.gqlType) instanceof GraphQLScalarType
|
||||
methods: {
|
||||
jumpToType() {
|
||||
if (this.isScalar) return
|
||||
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>
|
||||
|
||||
@@ -67,11 +67,9 @@ import IconMaximize2 from "~icons/lucide/maximize-2"
|
||||
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { makeGQLRequest } from "@hoppscotch/data"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { createNewTab } from "~/helpers/graphql/tab"
|
||||
|
||||
const t = useI18n()
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
const props = defineProps<{
|
||||
entry: GQLHistoryEntry
|
||||
@@ -95,7 +93,7 @@ const query = computed(() =>
|
||||
)
|
||||
|
||||
const useEntry = () => {
|
||||
tabs.createNewTab({
|
||||
createNewTab({
|
||||
request: makeGQLRequest({
|
||||
name: props.entry.request.name,
|
||||
url: props.entry.request.url,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
v-model="filterText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="flex w-full p-4 py-2 bg-transparent h-8"
|
||||
class="flex flex-1 p-4 py-2 bg-transparent"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
/>
|
||||
<div class="flex">
|
||||
@@ -176,9 +176,8 @@ import {
|
||||
|
||||
import HistoryRestCard from "./rest/Card.vue"
|
||||
import HistoryGraphqlCard from "./graphql/Card.vue"
|
||||
import { createNewTab } from "~/helpers/rest/tab"
|
||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
|
||||
type HistoryEntry = GQLHistoryEntry | RESTHistoryEntry
|
||||
|
||||
@@ -294,9 +293,8 @@ const clearHistory = () => {
|
||||
|
||||
// NOTE: For GQL, the HistoryGraphqlCard component already implements useEntry
|
||||
// (That is not a really good behaviour tho ¯\_(ツ)_/¯)
|
||||
const tabs = useService(RESTTabService)
|
||||
const useHistory = (entry: RESTHistoryEntry) => {
|
||||
tabs.createNewTab({
|
||||
createNewTab({
|
||||
request: entry.request,
|
||||
isDirty: false,
|
||||
})
|
||||
|
||||
@@ -59,9 +59,7 @@
|
||||
:key="`contentTypeItem-${contentTypeIndex}`"
|
||||
:label="contentTypeItem"
|
||||
:info-icon="
|
||||
contentTypeItem === body.contentType
|
||||
? IconDone
|
||||
: undefined
|
||||
contentTypeItem === body.contentType ? IconDone : null
|
||||
"
|
||||
:active-info-icon="contentTypeItem === body.contentType"
|
||||
@click="
|
||||
@@ -138,7 +136,7 @@ import IconDone from "~icons/lucide/check"
|
||||
import IconExternalLink from "~icons/lucide/external-link"
|
||||
import IconInfo from "~icons/lucide/info"
|
||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||
import { RESTOptionTabs } from "./RequestOptions.vue"
|
||||
import { RequestOptionTabs } from "./RequestOptions.vue"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const t = useI18n()
|
||||
@@ -149,7 +147,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "change-tab", value: RESTOptionTabs): void
|
||||
(e: "change-tab", value: RequestOptionTabs): void
|
||||
(e: "update:headers", value: HoppRESTHeader[]): void
|
||||
(e: "update:body", value: HoppRESTReqBody): void
|
||||
}>()
|
||||
@@ -166,7 +164,7 @@ const overridenContentType = computed(() =>
|
||||
)
|
||||
)
|
||||
|
||||
const contentTypeOverride = (tab: RESTOptionTabs) => {
|
||||
const contentTypeOverride = (tab: RequestOptionTabs) => {
|
||||
emit("change-tab", tab)
|
||||
if (!isContentTypeAlreadyExist()) {
|
||||
// TODO: Fix this
|
||||
|
||||
@@ -157,10 +157,9 @@ import {
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
import cloneDeep from "lodash-es/cloneDeep"
|
||||
import { platform } from "~/platform"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { useService } from "dioc/vue"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -174,8 +173,7 @@ const emit = defineEmits<{
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
const request = ref(cloneDeep(tabs.currentActiveTab.value.document.request))
|
||||
const request = ref(cloneDeep(currentActiveTab.value.document.request))
|
||||
const codegenType = ref<CodegenName>("shell-curl")
|
||||
const errorState = ref(false)
|
||||
|
||||
@@ -244,7 +242,7 @@ watch(
|
||||
() => props.show,
|
||||
(goingToShow) => {
|
||||
if (goingToShow) {
|
||||
request.value = cloneDeep(tabs.currentActiveTab.value.document.request)
|
||||
request.value = cloneDeep(currentActiveTab.value.document.request)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_REST_CODEGEN_OPENED",
|
||||
|
||||
@@ -185,24 +185,18 @@
|
||||
<span>
|
||||
<HoppButtonSecondary
|
||||
v-if="header.source === 'auth'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t(masking ? 'state.show' : 'state.hide')"
|
||||
:icon="masking ? IconEye : IconEyeOff"
|
||||
@click="toggleMask()"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconArrowUpRight"
|
||||
:title="t('request.go_to_authorization_tab')"
|
||||
class="cursor-auto text-primary hover:text-primary"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconArrowUpRight"
|
||||
:title="t('request.go_to_authorization_tab')"
|
||||
@click="changeTab(header.source)"
|
||||
/>
|
||||
</span>
|
||||
@@ -256,7 +250,7 @@ import * as E from "fp-ts/Either"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
import draggable from "vuedraggable-es"
|
||||
import { RESTOptionTabs } from "./RequestOptions.vue"
|
||||
import { RequestOptionTabs } from "./RequestOptions.vue"
|
||||
import { useCodemirror } from "@composables/codemirror"
|
||||
import { commonHeaders } from "~/helpers/headers"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
@@ -273,13 +267,10 @@ import { aggregateEnvs$, getAggregateEnvs } from "~/newstore/environments"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { useService } from "dioc/vue"
|
||||
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { currentTabID } from "~/helpers/rest/tab"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const idTicker = ref(0)
|
||||
@@ -295,7 +286,7 @@ const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||
const props = defineProps<{ modelValue: HoppRESTRequest }>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "change-tab", value: RESTOptionTabs): void
|
||||
(e: "change-tab", value: RequestOptionTabs): void
|
||||
(e: "update:modelValue", value: HoppRESTRequest): void
|
||||
}>()
|
||||
|
||||
@@ -518,13 +509,13 @@ const changeTab = (tab: ComputedHeader["source"]) => {
|
||||
const inspectionService = useService(InspectionService)
|
||||
|
||||
const headerKeyResults = inspectionService.getResultViewFor(
|
||||
tabs.currentTabID.value,
|
||||
currentTabID.value,
|
||||
(result) =>
|
||||
result.locations.type === "header" && result.locations.position === "key"
|
||||
)
|
||||
|
||||
const headerValueResults = inspectionService.getResultViewFor(
|
||||
tabs.currentTabID.value,
|
||||
currentTabID.value,
|
||||
(result) =>
|
||||
result.locations.type === "header" && result.locations.position === "value"
|
||||
)
|
||||
|
||||
@@ -93,16 +93,13 @@ 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 { currentActiveTab } from "~/helpers/rest/tab"
|
||||
import { platform } from "~/platform"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { useService } from "dioc/vue"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
const curl = ref("")
|
||||
|
||||
const curlEditor = ref<any | null>(null)
|
||||
@@ -152,7 +149,7 @@ const handleImport = () => {
|
||||
type: "HOPP_REST_IMPORT_CURL",
|
||||
})
|
||||
|
||||
tabs.currentActiveTab.value.document.request = req
|
||||
currentActiveTab.value.document.request = req
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error(`${t("error.curl_invalid_format")}`)
|
||||
|
||||
@@ -43,7 +43,6 @@ import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { tokenRequest } from "~/helpers/oauth"
|
||||
import { getCombinedEnvVariables } from "~/helpers/preRequest"
|
||||
import * as E from "fp-ts/Either"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -99,11 +98,7 @@ const handleAccessTokenRequest = async () => {
|
||||
clientSecret: parseTemplateString(clientSecret.value, envVars),
|
||||
scope: parseTemplateString(scope.value, envVars),
|
||||
}
|
||||
const res = await tokenRequest(tokenReqParams)
|
||||
|
||||
if (res && E.isLeft(res)) {
|
||||
toast.error(res.left)
|
||||
}
|
||||
await tokenRequest(tokenReqParams)
|
||||
} catch (e) {
|
||||
toast.error(`${e}`)
|
||||
}
|
||||
|
||||
@@ -202,13 +202,12 @@ import { objRemoveKey } from "@functional/object"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { useService } from "dioc/vue"
|
||||
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { currentTabID } from "~/helpers/rest/tab"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
const idTicker = ref(0)
|
||||
|
||||
@@ -411,13 +410,13 @@ const clearContent = () => {
|
||||
const inspectionService = useService(InspectionService)
|
||||
|
||||
const parameterKeyResults = inspectionService.getResultViewFor(
|
||||
tabs.currentTabID.value,
|
||||
currentTabID.value,
|
||||
(result) =>
|
||||
result.locations.type === "parameter" && result.locations.position === "key"
|
||||
)
|
||||
|
||||
const parameterValueResults = inspectionService.getResultViewFor(
|
||||
tabs.currentTabID.value,
|
||||
currentTabID.value,
|
||||
(result) =>
|
||||
result.locations.type === "parameter" &&
|
||||
result.locations.position === "value"
|
||||
|
||||
@@ -217,7 +217,6 @@
|
||||
@hide-modal="showCurlImportModal = false"
|
||||
/>
|
||||
<HttpCodegenModal
|
||||
v-if="showCodegenModal"
|
||||
:show="showCodegenModal"
|
||||
@hide-modal="showCodegenModal = false"
|
||||
/>
|
||||
@@ -258,6 +257,7 @@ import IconLink2 from "~icons/lucide/link-2"
|
||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||
import IconSave from "~icons/lucide/save"
|
||||
import IconShare2 from "~icons/lucide/share-2"
|
||||
import { HoppRESTTab, currentTabID } from "~/helpers/rest/tab"
|
||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||
import { RESTHistoryEntry, restHistory$ } from "~/newstore/history"
|
||||
import { platform } from "~/platform"
|
||||
@@ -265,9 +265,6 @@ import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { useService } from "dioc/vue"
|
||||
import { InspectionService } from "~/services/inspection"
|
||||
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 interceptorService = useService(InterceptorService)
|
||||
@@ -289,7 +286,7 @@ const toast = useToast()
|
||||
|
||||
const { subscribeToStream } = useStreamSubscriber()
|
||||
|
||||
const props = defineProps<{ modelValue: HoppTab<HoppRESTDocument> }>()
|
||||
const props = defineProps<{ modelValue: HoppRESTTab }>()
|
||||
const emit = defineEmits(["update:modelValue"])
|
||||
|
||||
const tab = useVModel(props, "modelValue", emit)
|
||||
@@ -350,6 +347,7 @@ const newSendRequest = async () => {
|
||||
const streamResult = await streamPromise
|
||||
|
||||
requestCancelFunc.value = cancel
|
||||
|
||||
if (E.isRight(streamResult)) {
|
||||
subscribeToStream(
|
||||
streamResult.right,
|
||||
@@ -364,20 +362,6 @@ const newSendRequest = async () => {
|
||||
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
|
||||
}
|
||||
)
|
||||
@@ -442,7 +426,7 @@ const updateMethod = (method: string) => {
|
||||
|
||||
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.
|
||||
updateMethod(e.target.value)
|
||||
updateMethod(e.value)
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
@@ -450,7 +434,7 @@ const clearContent = () => {
|
||||
}
|
||||
|
||||
const updateRESTResponse = (response: HoppRESTResponse | null) => {
|
||||
tab.value.document.response = response
|
||||
tab.value.response = response
|
||||
}
|
||||
|
||||
const copyLinkIcon = refAutoReset<
|
||||
@@ -658,6 +642,5 @@ const COLUMN_LAYOUT = useSetting("COLUMN_LAYOUT")
|
||||
|
||||
const inspectionService = useService(InspectionService)
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
const tabResults = inspectionService.getResultViewFor(tabs.currentTabID.value)
|
||||
const tabResults = inspectionService.getResultViewFor(currentTabID.value)
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<HoppSmartTabs
|
||||
v-model="selectedOptionTab"
|
||||
v-model="selectedOptionsTab"
|
||||
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-upperMobilePrimaryStickyFold sm:top-upperPrimaryStickyFold z-10"
|
||||
render-inactive-tabs
|
||||
>
|
||||
@@ -15,7 +15,7 @@
|
||||
<HttpBody
|
||||
v-model:headers="request.headers"
|
||||
v-model:body="request.body"
|
||||
@change-tab="changeOptionTab"
|
||||
@change-tab="changeTab"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
@@ -23,7 +23,7 @@
|
||||
:label="`${t('tab.headers')}`"
|
||||
:info="`${newActiveHeadersCount$}`"
|
||||
>
|
||||
<HttpHeaders v-model="request" @change-tab="changeOptionTab" />
|
||||
<HttpHeaders v-model="request" @change-tab="changeTab" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
|
||||
<HttpAuthorization v-model="request.auth" />
|
||||
@@ -55,43 +55,31 @@
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { computed } from "vue"
|
||||
import { computed, ref } from "vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
|
||||
const VALID_OPTION_TABS = [
|
||||
"params",
|
||||
"bodyParams",
|
||||
"headers",
|
||||
"authorization",
|
||||
"preRequestScript",
|
||||
"tests",
|
||||
] as const
|
||||
|
||||
export type RESTOptionTabs = (typeof VALID_OPTION_TABS)[number]
|
||||
export type RequestOptionTabs =
|
||||
| "params"
|
||||
| "bodyParams"
|
||||
| "headers"
|
||||
| "authorization"
|
||||
| "preRequestScript"
|
||||
| "tests"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
// v-model integration with props and emit
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: HoppRESTRequest
|
||||
optionTab: RESTOptionTabs
|
||||
}>(),
|
||||
{
|
||||
optionTab: "params",
|
||||
}
|
||||
)
|
||||
|
||||
const props = defineProps<{ modelValue: HoppRESTRequest }>()
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: HoppRESTRequest): void
|
||||
(e: "update:optionTab", value: RESTOptionTabs): void
|
||||
}>()
|
||||
|
||||
const request = useVModel(props, "modelValue", emit)
|
||||
const selectedOptionTab = useVModel(props, "optionTab", emit)
|
||||
|
||||
const changeOptionTab = (e: RESTOptionTabs) => {
|
||||
selectedOptionTab.value = e
|
||||
const selectedOptionsTab = ref<RequestOptionTabs>("params")
|
||||
|
||||
const changeTab = (e: RequestOptionTabs) => {
|
||||
selectedOptionsTab.value = e
|
||||
}
|
||||
|
||||
const newActiveParamsCount$ = computed(() => {
|
||||
@@ -113,6 +101,6 @@ const newActiveHeadersCount$ = computed(() => {
|
||||
})
|
||||
|
||||
defineActionHandler("request.open-tab", ({ tab }) => {
|
||||
selectedOptionTab.value = tab as RESTOptionTabs
|
||||
selectedOptionsTab.value = tab as RequestOptionTabs
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
<AppPaneLayout layout-id="rest-primary">
|
||||
<template #primary>
|
||||
<HttpRequest v-model="tab" />
|
||||
<HttpRequestOptions
|
||||
v-model="tab.document.request"
|
||||
v-model:option-tab="tab.document.optionTabPreference"
|
||||
/>
|
||||
<HttpRequestOptions v-model="tab.document.request" />
|
||||
</template>
|
||||
<template #secondary>
|
||||
<HttpResponse v-model:document="tab.document" />
|
||||
<HttpResponse v-model:tab="tab" />
|
||||
</template>
|
||||
</AppPaneLayout>
|
||||
</template>
|
||||
@@ -16,17 +13,16 @@
|
||||
<script setup lang="ts">
|
||||
import { watch } from "vue"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
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
|
||||
|
||||
const props = defineProps<{ modelValue: HoppTab<HoppRESTDocument> }>()
|
||||
const props = defineProps<{ modelValue: HoppRESTTab }>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", val: HoppTab<HoppRESTDocument>): void
|
||||
(e: "update:modelValue", val: HoppRESTTab): void
|
||||
}>()
|
||||
|
||||
const tab = useVModel(props, "modelValue", emit)
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 relative">
|
||||
<HttpResponseMeta :response="doc.response" />
|
||||
<HttpResponseMeta :response="tab.response" />
|
||||
<LensesResponseBodyRenderer
|
||||
v-if="!loading && hasResponse"
|
||||
v-model:document="doc"
|
||||
v-model:selected-tab-preference="selectedTabPreference"
|
||||
v-model:tab="tab"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue"
|
||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { computed } from "vue"
|
||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||
|
||||
const props = defineProps<{
|
||||
document: HoppRESTDocument
|
||||
tab: HoppRESTTab
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:tab", val: HoppRESTDocument): void
|
||||
(e: "update:tab", val: HoppRESTTab): void
|
||||
}>()
|
||||
|
||||
const doc = useVModel(props, "document", emit)
|
||||
const tab = useVModel(props, "tab", emit)
|
||||
|
||||
const selectedTabPreference = ref<string | null>(null)
|
||||
|
||||
const hasResponse = computed(
|
||||
() =>
|
||||
doc.value.response?.type === "success" ||
|
||||
doc.value.response?.type === "fail"
|
||||
tab.value.response?.type === "success" ||
|
||||
tab.value.response?.type === "fail"
|
||||
)
|
||||
|
||||
const loading = computed(() => doc.value.response?.type === "loading")
|
||||
const loading = computed(() => tab.value.response?.type === "loading")
|
||||
</script>
|
||||
|
||||
@@ -11,12 +11,6 @@
|
||||
<HoppSmartSpinner class="my-4" />
|
||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||
</div>
|
||||
|
||||
<component
|
||||
:is="response.component"
|
||||
v-if="response.type === 'extension_error'"
|
||||
class="flex-1"
|
||||
/>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="response.type === 'network_fail'"
|
||||
:src="`/images/states/${colorMode.value}/youre_lost.svg`"
|
||||
@@ -99,11 +93,10 @@ import { useColorMode } from "@composables/theming"
|
||||
import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
||||
import { useService } from "dioc/vue"
|
||||
import { InspectionService } from "~/services/inspection"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { currentTabID } from "~/helpers/rest/tab"
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
const props = defineProps<{
|
||||
response: HoppRESTResponse | null | undefined
|
||||
@@ -153,7 +146,7 @@ const statusCategory = computed(() => {
|
||||
const inspectionService = useService(InspectionService)
|
||||
|
||||
const tabResults = inspectionService.getResultViewFor(
|
||||
tabs.currentTabID.value,
|
||||
currentTabID.value,
|
||||
(result) => result.locations.type === "response"
|
||||
)
|
||||
</script>
|
||||
|
||||