Compare commits
28 Commits
pr/danithe
...
refactor/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13393e9346 | ||
|
|
685d8105e4 | ||
|
|
07c841796a | ||
|
|
f5db54484c | ||
|
|
8deb6471b9 | ||
|
|
73b3ff8e41 | ||
|
|
016a18d3b2 | ||
|
|
ba31cdabea | ||
|
|
51510566bc | ||
|
|
cabee0ecc8 | ||
|
|
2c2b39a236 | ||
|
|
78450c9316 | ||
|
|
b18fd90b64 | ||
|
|
0188a8d7db | ||
|
|
6c63a8dc28 | ||
|
|
17d6ae15a5 | ||
|
|
40f72278a9 | ||
|
|
f717704731 | ||
|
|
185c225297 | ||
|
|
2694731c36 | ||
|
|
ae89af9978 | ||
|
|
87d617012f | ||
|
|
2420b3fa42 | ||
|
|
175a991ec4 | ||
|
|
0301649aff | ||
|
|
544b045300 | ||
|
|
65884293be | ||
|
|
3cb4861bac |
@@ -5,5 +5,5 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/NicoVIII/devcontainer-features/pnpm:1": {}
|
"ghcr.io/NicoVIII/devcontainer-features/pnpm:1": {}
|
||||||
},
|
},
|
||||||
"postCreateCommand": "mv .env.example .env && pnpm i"
|
"postCreateCommand": "cp .env.example .env && pnpm i"
|
||||||
}
|
}
|
||||||
|
|||||||
3
.github/workflows/release-push-docker.yml
vendored
3
.github/workflows/release-push-docker.yml
vendored
@@ -18,6 +18,9 @@ jobs:
|
|||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/ui.yml
vendored
2
.github/workflows/ui.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
# Deploy the ui site with netlify-cli
|
# Deploy the ui site with netlify-cli
|
||||||
- name: Deploy to Netlify (ui)
|
- name: Deploy to Netlify (ui)
|
||||||
run: npx netlify-cli deploy --dir=packages/hoppscotch-ui/.histoire/dist --prod
|
run: npx netlify-cli@15.11.0 deploy --dir=packages/hoppscotch-ui/.histoire/dist --prod
|
||||||
env:
|
env:
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_UI_SITE_ID }}
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_UI_SITE_ID }}
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -22,16 +22,14 @@
|
|||||||
"workspaces": [
|
"workspaces": [
|
||||||
"./packages/*"
|
"./packages/*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
|
||||||
"husky": "^7.0.4",
|
|
||||||
"lint-staged": "^12.3.8"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^16.2.3",
|
"@commitlint/cli": "^16.2.3",
|
||||||
"@commitlint/config-conventional": "^16.2.1",
|
"@commitlint/config-conventional": "^16.2.1",
|
||||||
"@types/node": "^17.0.24",
|
"@types/node": "17.0.27",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"http-server": "^14.1.1"
|
"http-server": "^14.1.1",
|
||||||
|
"husky": "^7.0.4",
|
||||||
|
"lint-staged": "12.4.0"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"packageExtensions": {
|
"packageExtensions": {
|
||||||
|
|||||||
@@ -24,9 +24,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lezer/generator": "^1.5.0",
|
"@lezer/generator": "^1.5.0",
|
||||||
"mocha": "^9.2.2",
|
"mocha": "^9.2.2",
|
||||||
"rollup": "^2.70.2",
|
"rollup": "^3.29.3",
|
||||||
"rollup-plugin-dts": "^4.2.1",
|
"rollup-plugin-dts": "^6.0.2",
|
||||||
"rollup-plugin-ts": "^2.0.7",
|
"rollup-plugin-ts": "^3.4.5",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-backend",
|
"name": "hoppscotch-backend",
|
||||||
"version": "2023.8.1",
|
"version": "2023.8.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -24,18 +24,17 @@
|
|||||||
"do-test": "pnpm run test"
|
"do-test": "pnpm run test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs-modules/mailer": "^1.8.1",
|
"@apollo/server": "^4.9.4",
|
||||||
"@nestjs/apollo": "^10.1.6",
|
"@nestjs-modules/mailer": "^1.9.1",
|
||||||
"@nestjs/common": "^9.2.1",
|
"@nestjs/apollo": "^12.0.9",
|
||||||
"@nestjs/core": "^9.2.1",
|
"@nestjs/common": "^10.2.6",
|
||||||
"@nestjs/graphql": "^10.1.6",
|
"@nestjs/core": "^10.2.6",
|
||||||
"@nestjs/jwt": "^10.0.1",
|
"@nestjs/graphql": "^12.0.9",
|
||||||
"@nestjs/passport": "^9.0.0",
|
"@nestjs/jwt": "^10.1.1",
|
||||||
"@nestjs/platform-express": "^9.2.1",
|
"@nestjs/passport": "^10.0.2",
|
||||||
"@nestjs/throttler": "^4.0.0",
|
"@nestjs/platform-express": "^10.2.6",
|
||||||
|
"@nestjs/throttler": "^5.0.0",
|
||||||
"@prisma/client": "^4.16.2",
|
"@prisma/client": "^4.16.2",
|
||||||
"apollo-server-express": "^3.11.1",
|
|
||||||
"apollo-server-plugin-base": "^3.7.1",
|
|
||||||
"argon2": "^0.30.3",
|
"argon2": "^0.30.3",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
"cookie": "^0.5.0",
|
"cookie": "^0.5.0",
|
||||||
@@ -43,9 +42,9 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"fp-ts": "^2.13.1",
|
"fp-ts": "^2.13.1",
|
||||||
"graphql": "^15.5.0",
|
"graphql": "^16.8.1",
|
||||||
"graphql-query-complexity": "^0.12.0",
|
"graphql-query-complexity": "^0.12.0",
|
||||||
"graphql-redis-subscriptions": "^2.5.0",
|
"graphql-redis-subscriptions": "^2.6.0",
|
||||||
"graphql-subscriptions": "^2.0.0",
|
"graphql-subscriptions": "^2.0.0",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"io-ts": "^2.2.16",
|
"io-ts": "^2.2.16",
|
||||||
@@ -63,9 +62,9 @@
|
|||||||
"rxjs": "^7.6.0"
|
"rxjs": "^7.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^9.1.5",
|
"@nestjs/cli": "^10.1.18",
|
||||||
"@nestjs/schematics": "^9.0.3",
|
"@nestjs/schematics": "^10.0.2",
|
||||||
"@nestjs/testing": "^9.2.1",
|
"@nestjs/testing": "^10.2.6",
|
||||||
"@relmify/jest-fp-ts": "^2.0.2",
|
"@relmify/jest-fp-ts": "^2.0.2",
|
||||||
"@types/argon2": "^0.15.0",
|
"@types/argon2": "^0.15.0",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
|
|||||||
@@ -27,12 +27,7 @@ import { AppController } from './app.controller';
|
|||||||
buildSchemaOptions: {
|
buildSchemaOptions: {
|
||||||
numberScalarMode: 'integer',
|
numberScalarMode: 'integer',
|
||||||
},
|
},
|
||||||
cors: {
|
|
||||||
origin: process.env.WHITELISTED_ORIGINS.split(','),
|
|
||||||
credentials: true,
|
|
||||||
},
|
|
||||||
playground: process.env.PRODUCTION !== 'true',
|
playground: process.env.PRODUCTION !== 'true',
|
||||||
debug: process.env.PRODUCTION !== 'true',
|
|
||||||
autoSchemaFile: true,
|
autoSchemaFile: true,
|
||||||
installSubscriptionHandlers: true,
|
installSubscriptionHandlers: true,
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
@@ -62,10 +57,12 @@ import { AppController } from './app.controller';
|
|||||||
}),
|
}),
|
||||||
driver: ApolloDriver,
|
driver: ApolloDriver,
|
||||||
}),
|
}),
|
||||||
ThrottlerModule.forRoot({
|
ThrottlerModule.forRoot([
|
||||||
ttl: +process.env.RATE_LIMIT_TTL,
|
{
|
||||||
limit: +process.env.RATE_LIMIT_MAX,
|
ttl: +process.env.RATE_LIMIT_TTL,
|
||||||
}),
|
limit: +process.env.RATE_LIMIT_MAX,
|
||||||
|
},
|
||||||
|
]),
|
||||||
UserModule,
|
UserModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
AdminModule,
|
AdminModule,
|
||||||
|
|||||||
@@ -93,9 +93,7 @@ export async function emitGQLSchemaFile() {
|
|||||||
numberScalarMode: 'integer',
|
numberScalarMode: 'integer',
|
||||||
});
|
});
|
||||||
|
|
||||||
const schemaString = printSchema(schema, {
|
const schemaString = printSchema(schema);
|
||||||
commentDescriptions: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.log(`Writing schema to GQL_SCHEMA_EMIT_LOCATION (${destination})`);
|
logger.log(`Writing schema to GQL_SCHEMA_EMIT_LOCATION (${destination})`);
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
|
export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
|
||||||
protected getTracker(req: Record<string, any>): string {
|
protected async getTracker(req: Record<string, any>): Promise<string> {
|
||||||
return req.ips.length ? req.ips[0] : req.ip; // individualize IP extraction to meet your own needs
|
return req.ips.length ? req.ips[0] : req.ip; // individualize IP extraction to meet your own needs
|
||||||
// learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#directives
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { GraphQLSchemaHost } from '@nestjs/graphql';
|
import { GraphQLSchemaHost } from '@nestjs/graphql';
|
||||||
import {
|
import {
|
||||||
ApolloServerPlugin,
|
ApolloServerPlugin,
|
||||||
|
BaseContext,
|
||||||
GraphQLRequestListener,
|
GraphQLRequestListener,
|
||||||
} from 'apollo-server-plugin-base';
|
} from '@apollo/server';
|
||||||
import { Plugin } from '@nestjs/apollo';
|
import { Plugin } from '@nestjs/apollo';
|
||||||
import { GraphQLError } from 'graphql';
|
import { GraphQLError } from 'graphql';
|
||||||
import {
|
import {
|
||||||
@@ -17,7 +18,7 @@ const COMPLEXITY_LIMIT = 50;
|
|||||||
export class GQLComplexityPlugin implements ApolloServerPlugin {
|
export class GQLComplexityPlugin implements ApolloServerPlugin {
|
||||||
constructor(private gqlSchemaHost: GraphQLSchemaHost) {}
|
constructor(private gqlSchemaHost: GraphQLSchemaHost) {}
|
||||||
|
|
||||||
async requestDidStart(): Promise<GraphQLRequestListener> {
|
async requestDidStart(): Promise<GraphQLRequestListener<BaseContext>> {
|
||||||
const { schema } = this.gqlSchemaHost;
|
const { schema } = this.gqlSchemaHost;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Team, TeamCollection as DBTeamCollection } from '@prisma/client';
|
import { Team, TeamCollection as DBTeamCollection } from '@prisma/client';
|
||||||
import { mock, mockDeep, mockReset } from 'jest-mock-extended';
|
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||||
import {
|
import {
|
||||||
TEAM_COLL_DEST_SAME,
|
TEAM_COLL_DEST_SAME,
|
||||||
TEAM_COLL_INVALID_JSON,
|
TEAM_COLL_INVALID_JSON,
|
||||||
@@ -17,9 +17,6 @@ import { PrismaService } from 'src/prisma/prisma.service';
|
|||||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||||
import { AuthUser } from 'src/types/AuthUser';
|
import { AuthUser } from 'src/types/AuthUser';
|
||||||
import { TeamCollectionService } from './team-collection.service';
|
import { TeamCollectionService } from './team-collection.service';
|
||||||
import { TeamCollection } from './team-collection.model';
|
|
||||||
import { TeamCollectionModule } from './team-collection.module';
|
|
||||||
import * as E from 'fp-ts/Either';
|
|
||||||
|
|
||||||
const mockPrisma = mockDeep<PrismaService>();
|
const mockPrisma = mockDeep<PrismaService>();
|
||||||
const mockPubSub = mockDeep<PubSubService>();
|
const mockPubSub = mockDeep<PubSubService>();
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
|
|
||||||
describe('createDuplicateEnvironment', () => {
|
describe('createDuplicateEnvironment', () => {
|
||||||
test('should successfully duplicate an existing team environment', async () => {
|
test('should successfully duplicate an existing team environment', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce(
|
||||||
teamEnvironment,
|
teamEnvironment,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -322,7 +322,9 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirst.mockRejectedValue('NotFoundError');
|
mockPrisma.teamEnvironment.findFirstOrThrow.mockRejectedValue(
|
||||||
|
'NotFoundError',
|
||||||
|
);
|
||||||
|
|
||||||
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
@@ -332,7 +334,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should send pubsub message to "team_environment/<teamID>/created" if team environment is updated successfully', async () => {
|
test('should send pubsub message to "team_environment/<teamID>/created" if team environment is updated successfully', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce(
|
||||||
teamEnvironment,
|
teamEnvironment,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -183,11 +183,10 @@ export class TeamEnvironmentsService {
|
|||||||
*/
|
*/
|
||||||
async createDuplicateEnvironment(id: string) {
|
async createDuplicateEnvironment(id: string) {
|
||||||
try {
|
try {
|
||||||
const environment = await this.prisma.teamEnvironment.findFirst({
|
const environment = await this.prisma.teamEnvironment.findFirstOrThrow({
|
||||||
where: {
|
where: {
|
||||||
id: id,
|
id: id,
|
||||||
},
|
},
|
||||||
rejectOnNotFound: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await this.prisma.teamEnvironment.create({
|
const result = await this.prisma.teamEnvironment.create({
|
||||||
|
|||||||
@@ -142,13 +142,15 @@ describe('UserHistoryService', () => {
|
|||||||
});
|
});
|
||||||
describe('createUserHistory', () => {
|
describe('createUserHistory', () => {
|
||||||
test('Should resolve right and create a REST request to users history and return a `UserHistory` object', async () => {
|
test('Should resolve right and create a REST request to users history and return a `UserHistory` object', async () => {
|
||||||
|
const executedOn = new Date();
|
||||||
|
|
||||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||||
userUid: 'abc',
|
userUid: 'abc',
|
||||||
id: '1',
|
id: '1',
|
||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -158,7 +160,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: JSON.stringify([{}]),
|
request: JSON.stringify([{}]),
|
||||||
responseMetadata: JSON.stringify([{}]),
|
responseMetadata: JSON.stringify([{}]),
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -172,13 +174,15 @@ describe('UserHistoryService', () => {
|
|||||||
).toEqualRight(userHistory);
|
).toEqualRight(userHistory);
|
||||||
});
|
});
|
||||||
test('Should resolve right and create a GQL request to users history and return a `UserHistory` object', async () => {
|
test('Should resolve right and create a GQL request to users history and return a `UserHistory` object', async () => {
|
||||||
|
const executedOn = new Date();
|
||||||
|
|
||||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||||
userUid: 'abc',
|
userUid: 'abc',
|
||||||
id: '1',
|
id: '1',
|
||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.GQL,
|
reqType: ReqType.GQL,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -188,7 +192,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: JSON.stringify([{}]),
|
request: JSON.stringify([{}]),
|
||||||
responseMetadata: JSON.stringify([{}]),
|
responseMetadata: JSON.stringify([{}]),
|
||||||
reqType: ReqType.GQL,
|
reqType: ReqType.GQL,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -212,13 +216,15 @@ describe('UserHistoryService', () => {
|
|||||||
).toEqualLeft(USER_HISTORY_INVALID_REQ_TYPE);
|
).toEqualLeft(USER_HISTORY_INVALID_REQ_TYPE);
|
||||||
});
|
});
|
||||||
test('Should create a GQL request to users history and publish a created subscription', async () => {
|
test('Should create a GQL request to users history and publish a created subscription', async () => {
|
||||||
|
const executedOn = new Date();
|
||||||
|
|
||||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||||
userUid: 'abc',
|
userUid: 'abc',
|
||||||
id: '1',
|
id: '1',
|
||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.GQL,
|
reqType: ReqType.GQL,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -228,7 +234,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: JSON.stringify([{}]),
|
request: JSON.stringify([{}]),
|
||||||
responseMetadata: JSON.stringify([{}]),
|
responseMetadata: JSON.stringify([{}]),
|
||||||
reqType: ReqType.GQL,
|
reqType: ReqType.GQL,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -245,13 +251,15 @@ describe('UserHistoryService', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('Should create a REST request to users history and publish a created subscription', async () => {
|
test('Should create a REST request to users history and publish a created subscription', async () => {
|
||||||
|
const executedOn = new Date();
|
||||||
|
|
||||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||||
userUid: 'abc',
|
userUid: 'abc',
|
||||||
id: '1',
|
id: '1',
|
||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -261,7 +269,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: JSON.stringify([{}]),
|
request: JSON.stringify([{}]),
|
||||||
responseMetadata: JSON.stringify([{}]),
|
responseMetadata: JSON.stringify([{}]),
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -323,13 +331,15 @@ describe('UserHistoryService', () => {
|
|||||||
).toEqualLeft(USER_HISTORY_NOT_FOUND);
|
).toEqualLeft(USER_HISTORY_NOT_FOUND);
|
||||||
});
|
});
|
||||||
test('Should star/unstar a request in the history and publish a updated subscription', async () => {
|
test('Should star/unstar a request in the history and publish a updated subscription', async () => {
|
||||||
|
const executedOn = new Date();
|
||||||
|
|
||||||
mockPrisma.userHistory.findFirst.mockResolvedValueOnce({
|
mockPrisma.userHistory.findFirst.mockResolvedValueOnce({
|
||||||
userUid: 'abc',
|
userUid: 'abc',
|
||||||
id: '1',
|
id: '1',
|
||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -339,7 +349,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: [{}],
|
request: [{}],
|
||||||
responseMetadata: [{}],
|
responseMetadata: [{}],
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: true,
|
isStarred: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -349,7 +359,7 @@ describe('UserHistoryService', () => {
|
|||||||
request: JSON.stringify([{}]),
|
request: JSON.stringify([{}]),
|
||||||
responseMetadata: JSON.stringify([{}]),
|
responseMetadata: JSON.stringify([{}]),
|
||||||
reqType: ReqType.REST,
|
reqType: ReqType.REST,
|
||||||
executedOn: new Date(),
|
executedOn,
|
||||||
isStarred: true,
|
isStarred: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/cli",
|
"name": "@hoppscotch/cli",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
||||||
"homepage": "https://hoppscotch.io",
|
"homepage": "https://hoppscotch.io",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -40,9 +40,6 @@
|
|||||||
"@hoppscotch/js-sandbox": "workspace:^",
|
"@hoppscotch/js-sandbox": "workspace:^",
|
||||||
"@relmify/jest-fp-ts": "^2.0.2",
|
"@relmify/jest-fp-ts": "^2.0.2",
|
||||||
"@swc/core": "^1.2.181",
|
"@swc/core": "^1.2.181",
|
||||||
"@types/axios": "^0.14.0",
|
|
||||||
"@types/chalk": "^2.2.0",
|
|
||||||
"@types/commander": "^2.12.2",
|
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^27.4.1",
|
||||||
"@types/lodash": "^4.14.181",
|
"@types/lodash": "^4.14.181",
|
||||||
"@types/qs": "^6.9.7",
|
"@types/qs": "^6.9.7",
|
||||||
@@ -58,6 +55,7 @@
|
|||||||
"qs": "^6.10.3",
|
"qs": "^6.10.3",
|
||||||
"ts-jest": "^27.1.4",
|
"ts-jest": "^27.1.4",
|
||||||
"tsup": "^5.12.7",
|
"tsup": "^5.12.7",
|
||||||
"typescript": "^4.6.4"
|
"typescript": "^4.6.4",
|
||||||
|
"zod": "^3.22.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ import { isHoppCLIError } from "../utils/checks";
|
|||||||
export const test = (path: string, options: TestCmdOptions) => async () => {
|
export const test = (path: string, options: TestCmdOptions) => async () => {
|
||||||
try {
|
try {
|
||||||
const delay = options.delay ? parseDelayOption(options.delay) : 0
|
const delay = options.delay ? parseDelayOption(options.delay) : 0
|
||||||
const envName = options.envName
|
const envs = options.env ? await parseEnvsData(options.env) : <HoppEnvs>{ global: [], selected: [] }
|
||||||
const envs = options.env ? await parseEnvsData(options.env, envName) : <HoppEnvs>{ global: [], selected: [] }
|
|
||||||
const collections = await parseCollectionData(path)
|
const collections = await parseCollectionData(path)
|
||||||
|
|
||||||
const report = await collectionsRunner({collections, envs, delay})
|
const report = await collectionsRunner({collections, envs, delay})
|
||||||
|
|||||||
@@ -48,12 +48,14 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
|
|||||||
ERROR_MSG = `Unavailable command: ${error.command}`;
|
ERROR_MSG = `Unavailable command: ${error.command}`;
|
||||||
break;
|
break;
|
||||||
case "MALFORMED_ENV_FILE":
|
case "MALFORMED_ENV_FILE":
|
||||||
|
ERROR_MSG = `The environment file is not of the correct format.`;
|
||||||
|
break;
|
||||||
|
case "BULK_ENV_FILE":
|
||||||
|
ERROR_MSG = `CLI doesn't support bulk environments export.`;
|
||||||
|
break;
|
||||||
case "MALFORMED_COLLECTION":
|
case "MALFORMED_COLLECTION":
|
||||||
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
|
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
|
||||||
break;
|
break;
|
||||||
case "ENVIRONMENT_NAME_NOT_FOUND":
|
|
||||||
ERROR_MSG = `\n${parseErrorData(error.data)}`;
|
|
||||||
break;
|
|
||||||
case "NO_FILE_PATH":
|
case "NO_FILE_PATH":
|
||||||
ERROR_MSG = `Please provide a hoppscotch-collection file path.`;
|
ERROR_MSG = `Please provide a hoppscotch-collection file path.`;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ program
|
|||||||
"path to a hoppscotch collection.json file for CI testing"
|
"path to a hoppscotch collection.json file for CI testing"
|
||||||
)
|
)
|
||||||
.option("-e, --env <file_path>", "path to an environment variables json file")
|
.option("-e, --env <file_path>", "path to an environment variables json file")
|
||||||
.option("-eN, --envName <environment_name>","Specific Name of the environment")
|
|
||||||
.option(
|
.option(
|
||||||
"-d, --delay <delay_in_ms>",
|
"-d, --delay <delay_in_ms>",
|
||||||
"delay in milliseconds(ms) between consecutive requests within a collection"
|
"delay in milliseconds(ms) between consecutive requests within a collection"
|
||||||
|
|||||||
@@ -33,5 +33,4 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
|||||||
effectiveFinalHeaders: { key: string; value: string; active: boolean }[];
|
effectiveFinalHeaders: { key: string; value: string; active: boolean }[];
|
||||||
effectiveFinalParams: { key: string; value: string; active: boolean }[];
|
effectiveFinalParams: { key: string; value: string; active: boolean }[];
|
||||||
effectiveFinalBody: FormData | string | null;
|
effectiveFinalBody: FormData | string | null;
|
||||||
effectiveFinalMaskedURL: string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,45 @@
|
|||||||
import { error } from "../../types/errors";
|
import { error } from "../../types/errors";
|
||||||
import { HoppEnvs, HoppEnvPair } from "../../types/request";
|
import {
|
||||||
|
HoppEnvs,
|
||||||
|
HoppEnvPair,
|
||||||
|
HoppEnvKeyPairObject,
|
||||||
|
HoppEnvExportObject,
|
||||||
|
HoppBulkEnvExportObject,
|
||||||
|
} from "../../types/request";
|
||||||
import { readJsonFile } from "../../utils/mutators";
|
import { readJsonFile } from "../../utils/mutators";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses env json file for given path and validates the parsed env json object.
|
* Parses env json file for given path and validates the parsed env json object.
|
||||||
* @param path Path of env.json file to be parsed.
|
* @param path Path of env.json file to be parsed.
|
||||||
* @param envName Name of the environment that should be used. If undefined first environment is used.
|
|
||||||
* @returns For successful parsing we get HoppEnvs object.
|
* @returns For successful parsing we get HoppEnvs object.
|
||||||
*/
|
*/
|
||||||
export async function parseEnvsData(path: string, envName: string | undefined) {
|
export async function parseEnvsData(path: string) {
|
||||||
const contents = await readJsonFile(path)
|
const contents = await readJsonFile(path);
|
||||||
if(!(contents && typeof contents === "object" && Array.isArray(contents))) {
|
const envPairs: Array<HoppEnvPair> = [];
|
||||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: null })
|
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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const envPairs: Array<HoppEnvPair> = []
|
// Checks if the environment file is of the correct format.
|
||||||
|
// If it doesnt match either of them, we throw an error.
|
||||||
const contentEntries = Object.entries(contents)
|
if (!(HoppEnvKeyPairResult.success || HoppEnvExportObjectResult.success)) {
|
||||||
let environmentFound = false;
|
throw error({ code: "MALFORMED_ENV_FILE", path, data: error });
|
||||||
|
|
||||||
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 } })
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
if(envName && !environmentFound) {
|
|
||||||
throw error({ code: "ENVIRONMENT_NAME_NOT_FOUND", data: envName });
|
if (HoppEnvKeyPairResult.success) {
|
||||||
|
for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) {
|
||||||
|
envPairs.push({ key, value });
|
||||||
|
}
|
||||||
|
} else if (HoppEnvExportObjectResult.success) {
|
||||||
|
const { key, value } = HoppEnvExportObjectResult.data.variables[0];
|
||||||
|
envPairs.push({ key, value });
|
||||||
}
|
}
|
||||||
return <HoppEnvs>{ global: [], selected: envPairs }
|
|
||||||
|
return <HoppEnvs>{ global: [], selected: envPairs };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export type TestCmdOptions = {
|
export type TestCmdOptions = {
|
||||||
env: string | undefined;
|
env: string | undefined;
|
||||||
delay: string | undefined;
|
delay: string | undefined;
|
||||||
envName: string | undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HOPP_ENV_FILE_EXT = "json";
|
export type HOPP_ENV_FILE_EXT = "json";
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ type HoppErrors = {
|
|||||||
FILE_NOT_FOUND: HoppErrorPath;
|
FILE_NOT_FOUND: HoppErrorPath;
|
||||||
UNKNOWN_COMMAND: HoppErrorCmd;
|
UNKNOWN_COMMAND: HoppErrorCmd;
|
||||||
MALFORMED_COLLECTION: HoppErrorPath & HoppErrorData;
|
MALFORMED_COLLECTION: HoppErrorPath & HoppErrorData;
|
||||||
ENVIRONMENT_NAME_NOT_FOUND: HoppErrorData;
|
|
||||||
NO_FILE_PATH: {};
|
NO_FILE_PATH: {};
|
||||||
PRE_REQUEST_SCRIPT_ERROR: HoppErrorData;
|
PRE_REQUEST_SCRIPT_ERROR: HoppErrorData;
|
||||||
PARSING_ERROR: HoppErrorData;
|
PARSING_ERROR: HoppErrorData;
|
||||||
@@ -25,6 +24,7 @@ type HoppErrors = {
|
|||||||
REQUEST_ERROR: HoppErrorData;
|
REQUEST_ERROR: HoppErrorData;
|
||||||
INVALID_ARGUMENT: HoppErrorData;
|
INVALID_ARGUMENT: HoppErrorData;
|
||||||
MALFORMED_ENV_FILE: HoppErrorPath & HoppErrorData;
|
MALFORMED_ENV_FILE: HoppErrorPath & HoppErrorData;
|
||||||
|
BULK_ENV_FILE: HoppErrorPath & HoppErrorData;
|
||||||
INVALID_FILE_TYPE: HoppErrorData;
|
INVALID_FILE_TYPE: HoppErrorData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,30 @@
|
|||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||||
import { TestReport } from "../interfaces/response";
|
import { TestReport } from "../interfaces/response";
|
||||||
import { HoppCLIError } from "./errors";
|
import { HoppCLIError } from "./errors";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export type FormDataEntry = {
|
export type FormDataEntry = {
|
||||||
key: string;
|
key: string;
|
||||||
value: string | Blob;
|
value: string | Blob;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HoppEnvPair = { key: string; value: string; secret: boolean };
|
export type HoppEnvPair = { key: string; value: string };
|
||||||
|
|
||||||
|
export const HoppEnvKeyPairObject = z.record(z.string(), z.string());
|
||||||
|
|
||||||
|
// Shape of the single environment export object that is exported from the app.
|
||||||
|
export const HoppEnvExportObject = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
variables: z.array(
|
||||||
|
z.object({
|
||||||
|
key: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shape of the bulk environment export object that is exported from the app.
|
||||||
|
export const HoppBulkEnvExportObject = z.array(HoppEnvExportObject);
|
||||||
|
|
||||||
export type HoppEnvs = {
|
export type HoppEnvs = {
|
||||||
global: HoppEnvPair[];
|
global: HoppEnvPair[];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { bold } from "chalk";
|
import { bold } from "chalk";
|
||||||
import { groupEnd, group, log } from "console";
|
import { groupEnd, group, log } from "console";
|
||||||
import { handleError } from "../handlers/error";
|
import { handleError } from "../handlers/error";
|
||||||
import { Method } from "axios";
|
import { RequestConfig } from "../interfaces/request";
|
||||||
import { RequestRunnerResponse, TestReport } from "../interfaces/response";
|
import { RequestRunnerResponse, TestReport } from "../interfaces/response";
|
||||||
import { HoppCLIError } from "../types/errors";
|
import { HoppCLIError } from "../types/errors";
|
||||||
import {
|
import {
|
||||||
@@ -172,12 +172,11 @@ export const printFailedTestsReport = (
|
|||||||
export const printRequestRunner = {
|
export const printRequestRunner = {
|
||||||
/**
|
/**
|
||||||
* Request-runner starting message.
|
* Request-runner starting message.
|
||||||
* @param requestMethod Provides request's method
|
* @param requestConfig Provides request's method and url.
|
||||||
* @param maskedURL Provides the URL with secrets masked with asterisks
|
|
||||||
*/
|
*/
|
||||||
start: (requestMethod: Method | undefined, maskedURL: string) => {
|
start: (requestConfig: RequestConfig) => {
|
||||||
const METHOD = BG_INFO(` ${requestMethod} `);
|
const METHOD = BG_INFO(` ${requestConfig.method} `);
|
||||||
const ENDPOINT = maskedURL;
|
const ENDPOINT = requestConfig.url;
|
||||||
|
|
||||||
process.stdout.write(`${METHOD} ${ENDPOINT}`);
|
process.stdout.write(`${METHOD} ${ENDPOINT}`);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ export const preRequestScriptRunner = (
|
|||||||
isHoppCLIError(reason)
|
isHoppCLIError(reason)
|
||||||
? reason
|
? reason
|
||||||
: error({
|
: error({
|
||||||
code: "PRE_REQUEST_SCRIPT_ERROR",
|
code: "PRE_REQUEST_SCRIPT_ERROR",
|
||||||
data: reason,
|
data: reason,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -151,12 +151,6 @@ export function getEffectiveRESTRequest(
|
|||||||
request.endpoint,
|
request.endpoint,
|
||||||
envVariables
|
envVariables
|
||||||
);
|
);
|
||||||
|
|
||||||
const maskedEnvVariables = setAllEnvironmentValuesToAsterisk(envVariables)
|
|
||||||
const _effectiveFinalMaskedURL = parseTemplateStringE(
|
|
||||||
request.endpoint,
|
|
||||||
maskedEnvVariables)
|
|
||||||
|
|
||||||
if (E.isLeft(_effectiveFinalURL)) {
|
if (E.isLeft(_effectiveFinalURL)) {
|
||||||
return E.left(
|
return E.left(
|
||||||
error({
|
error({
|
||||||
@@ -166,7 +160,6 @@ export function getEffectiveRESTRequest(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const effectiveFinalURL = _effectiveFinalURL.right;
|
const effectiveFinalURL = _effectiveFinalURL.right;
|
||||||
const effectiveFinalMaskedURL = E.isLeft(_effectiveFinalMaskedURL) ? request.endpoint : _effectiveFinalMaskedURL.right;
|
|
||||||
|
|
||||||
return E.right({
|
return E.right({
|
||||||
...request,
|
...request,
|
||||||
@@ -174,7 +167,6 @@ export function getEffectiveRESTRequest(
|
|||||||
effectiveFinalHeaders,
|
effectiveFinalHeaders,
|
||||||
effectiveFinalParams,
|
effectiveFinalParams,
|
||||||
effectiveFinalBody,
|
effectiveFinalBody,
|
||||||
effectiveFinalMaskedURL,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,15 +242,15 @@ function getFinalBodyFromRequest(
|
|||||||
arrayFlatMap((x) =>
|
arrayFlatMap((x) =>
|
||||||
x.isFile
|
x.isFile
|
||||||
? x.value.map((v) => ({
|
? x.value.map((v) => ({
|
||||||
key: parseTemplateString(x.key, envVariables),
|
|
||||||
value: v as string | Blob,
|
|
||||||
}))
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
key: parseTemplateString(x.key, envVariables),
|
key: parseTemplateString(x.key, envVariables),
|
||||||
value: parseTemplateString(x.value, envVariables),
|
value: v as string | Blob,
|
||||||
},
|
}))
|
||||||
]
|
: [
|
||||||
|
{
|
||||||
|
key: parseTemplateString(x.key, envVariables),
|
||||||
|
value: parseTemplateString(x.value, envVariables),
|
||||||
|
},
|
||||||
|
]
|
||||||
),
|
),
|
||||||
toFormData,
|
toFormData,
|
||||||
E.right
|
E.right
|
||||||
@@ -295,22 +287,3 @@ export const getPreRequestMetrics = (
|
|||||||
hasPreReqErrors ? { failed: 1, passed: 0 } : { failed: 0, passed: 1 },
|
hasPreReqErrors ? { failed: 1, passed: 0 } : { failed: 0, passed: 1 },
|
||||||
(scripts) => <PreRequestMetrics>{ scripts, duration }
|
(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,7 +220,6 @@ export const processRequest =
|
|||||||
effectiveFinalHeaders: [],
|
effectiveFinalHeaders: [],
|
||||||
effectiveFinalParams: [],
|
effectiveFinalParams: [],
|
||||||
effectiveFinalURL: "",
|
effectiveFinalURL: "",
|
||||||
effectiveFinalMaskedURL:"",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Executing pre-request-script
|
// Executing pre-request-script
|
||||||
@@ -239,7 +238,7 @@ export const processRequest =
|
|||||||
// Creating request-config for request-runner.
|
// Creating request-config for request-runner.
|
||||||
const requestConfig = createRequest(effectiveRequest);
|
const requestConfig = createRequest(effectiveRequest);
|
||||||
|
|
||||||
printRequestRunner.start(requestConfig.method, effectiveRequest.effectiveFinalMaskedURL);
|
printRequestRunner.start(requestConfig);
|
||||||
|
|
||||||
// Default value for request-runner's response.
|
// Default value for request-runner's response.
|
||||||
let _requestRunnerRes: RequestRunnerResponse = {
|
let _requestRunnerRes: RequestRunnerResponse = {
|
||||||
|
|||||||
@@ -137,10 +137,10 @@ a {
|
|||||||
|
|
||||||
.cm-tooltip {
|
.cm-tooltip {
|
||||||
.tippy-box {
|
.tippy-box {
|
||||||
@apply shadow-none;
|
@apply shadow-none #{!important};
|
||||||
@apply fixed;
|
@apply fixed;
|
||||||
@apply inline-flex;
|
@apply inline-flex;
|
||||||
@apply -mt-8;
|
@apply -mt-7.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,7 @@
|
|||||||
"turn_off": "Ausschalten",
|
"turn_off": "Ausschalten",
|
||||||
"turn_on": "Einschalten",
|
"turn_on": "Einschalten",
|
||||||
"undo": "Rückgängig machen",
|
"undo": "Rückgängig machen",
|
||||||
"yes": "Ja",
|
"yes": "Ja"
|
||||||
"secret": "Als Secret speichern"
|
|
||||||
},
|
},
|
||||||
"add": {
|
"add": {
|
||||||
"new": "Neue hinzufügen",
|
"new": "Neue hinzufügen",
|
||||||
|
|||||||
@@ -48,8 +48,7 @@
|
|||||||
"turn_off": "Turn off",
|
"turn_off": "Turn off",
|
||||||
"turn_on": "Turn on",
|
"turn_on": "Turn on",
|
||||||
"undo": "Undo",
|
"undo": "Undo",
|
||||||
"yes": "Yes",
|
"yes": "Yes"
|
||||||
"secret": "Save as secret"
|
|
||||||
},
|
},
|
||||||
"add": {
|
"add": {
|
||||||
"new": "Add new",
|
"new": "Add new",
|
||||||
@@ -113,6 +112,7 @@
|
|||||||
},
|
},
|
||||||
"authorization": {
|
"authorization": {
|
||||||
"generate_token": "Generate Token",
|
"generate_token": "Generate Token",
|
||||||
|
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init",
|
||||||
"include_in_url": "Include in URL",
|
"include_in_url": "Include in URL",
|
||||||
"learn": "Learn how",
|
"learn": "Learn how",
|
||||||
"pass_key_by": "Pass by",
|
"pass_key_by": "Pass by",
|
||||||
@@ -125,6 +125,7 @@
|
|||||||
"created": "Collection created",
|
"created": "Collection created",
|
||||||
"different_parent": "Cannot reorder collection with different parent",
|
"different_parent": "Cannot reorder collection with different parent",
|
||||||
"edit": "Edit Collection",
|
"edit": "Edit Collection",
|
||||||
|
"import_or_create": "Import or create a collection",
|
||||||
"invalid_name": "Please provide a name for the collection",
|
"invalid_name": "Please provide a name for the collection",
|
||||||
"invalid_root_move": "Collection already in the root",
|
"invalid_root_move": "Collection already in the root",
|
||||||
"moved": "Moved Successfully",
|
"moved": "Moved Successfully",
|
||||||
@@ -210,6 +211,7 @@
|
|||||||
"empty_variables": "No variables",
|
"empty_variables": "No variables",
|
||||||
"global": "Global",
|
"global": "Global",
|
||||||
"global_variables": "Global variables",
|
"global_variables": "Global variables",
|
||||||
|
"import_or_create": "Import or create a environment",
|
||||||
"invalid_name": "Please provide a name for the environment",
|
"invalid_name": "Please provide a name for the environment",
|
||||||
"list": "Environment variables",
|
"list": "Environment variables",
|
||||||
"my_environments": "My Environments",
|
"my_environments": "My Environments",
|
||||||
@@ -250,7 +252,9 @@
|
|||||||
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
|
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
|
||||||
"network_error": "There seems to be a network error. Please try again.",
|
"network_error": "There seems to be a network error. Please try again.",
|
||||||
"network_fail": "Could not send request",
|
"network_fail": "Could not send request",
|
||||||
|
"no_collections_to_export": "No collections to export. Please create a collection to get started.",
|
||||||
"no_duration": "No duration",
|
"no_duration": "No duration",
|
||||||
|
"no_environments_to_export": "No environments to export. Please create an environment to get started.",
|
||||||
"no_results_found": "No matches found",
|
"no_results_found": "No matches found",
|
||||||
"page_not_found": "This page could not be found",
|
"page_not_found": "This page could not be found",
|
||||||
"proxy_error": "Proxy error",
|
"proxy_error": "Proxy error",
|
||||||
@@ -457,6 +461,7 @@
|
|||||||
"enter_curl": "Enter cURL command",
|
"enter_curl": "Enter cURL command",
|
||||||
"generate_code": "Generate code",
|
"generate_code": "Generate code",
|
||||||
"generated_code": "Generated code",
|
"generated_code": "Generated code",
|
||||||
|
"go_to_authorization_tab": "Go to Authorization",
|
||||||
"header_list": "Header List",
|
"header_list": "Header List",
|
||||||
"invalid_name": "Please provide a name for the request",
|
"invalid_name": "Please provide a name for the request",
|
||||||
"method": "Method",
|
"method": "Method",
|
||||||
@@ -744,9 +749,11 @@
|
|||||||
"disconnected_from": "Disconnected from {name}",
|
"disconnected_from": "Disconnected from {name}",
|
||||||
"docs_generated": "Documentation generated",
|
"docs_generated": "Documentation generated",
|
||||||
"download_started": "Download started",
|
"download_started": "Download started",
|
||||||
|
"download_failed": "Download failed",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"file_imported": "File imported",
|
"file_imported": "File imported",
|
||||||
"finished_in": "Finished in {duration} ms",
|
"finished_in": "Finished in {duration} ms",
|
||||||
|
"hide": "Hide",
|
||||||
"history_deleted": "History deleted",
|
"history_deleted": "History deleted",
|
||||||
"linewrap": "Wrap lines",
|
"linewrap": "Wrap lines",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
@@ -757,6 +764,7 @@
|
|||||||
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||||
"published_message": "Published message: {message} to topic: {topic}",
|
"published_message": "Published message: {message} to topic: {topic}",
|
||||||
"reconnection_error": "Failed to reconnect",
|
"reconnection_error": "Failed to reconnect",
|
||||||
|
"show":"Show",
|
||||||
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
||||||
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
||||||
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
||||||
@@ -838,7 +846,7 @@
|
|||||||
"new": "New Team",
|
"new": "New Team",
|
||||||
"new_created": "New team created",
|
"new_created": "New team created",
|
||||||
"new_name": "My New Team",
|
"new_name": "My New Team",
|
||||||
"no_access": "You do not have edit access to these collections",
|
"no_access": "You do not have edit access to this team",
|
||||||
"no_invite_found": "Invitation not found. Contact your team owner.",
|
"no_invite_found": "Invitation not found. Contact your team owner.",
|
||||||
"no_request_found": "Request not found.",
|
"no_request_found": "Request not found.",
|
||||||
"not_found": "Team not found. Contact your team owner.",
|
"not_found": "Team not found. Contact your team owner.",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"choose_file": "選擇一個檔案",
|
"choose_file": "選擇一個檔案",
|
||||||
"clear": "清除",
|
"clear": "清除",
|
||||||
"clear_all": "全部清除",
|
"clear_all": "全部清除",
|
||||||
"clear_history": "Clear all History",
|
"clear_history": "清除所有歷史記錄",
|
||||||
"close": "關閉",
|
"close": "關閉",
|
||||||
"connect": "連線",
|
"connect": "連線",
|
||||||
"connecting": "正在連接",
|
"connecting": "正在連接",
|
||||||
@@ -79,8 +79,8 @@
|
|||||||
"search": "搜尋",
|
"search": "搜尋",
|
||||||
"share": "分享",
|
"share": "分享",
|
||||||
"shortcuts": "快捷方式",
|
"shortcuts": "快捷方式",
|
||||||
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
|
"social_description": "在社交媒體上追蹤我們即可在第一時間得知新聞、更新、以及新版本的消息。",
|
||||||
"social_links": "Social links",
|
"social_links": "社群連結",
|
||||||
"spotlight": "聚光燈",
|
"spotlight": "聚光燈",
|
||||||
"status": "狀態",
|
"status": "狀態",
|
||||||
"status_description": "檢查網站狀態",
|
"status_description": "檢查網站狀態",
|
||||||
@@ -135,15 +135,15 @@
|
|||||||
"renamed": "集合已重新命名",
|
"renamed": "集合已重新命名",
|
||||||
"request_in_use": "請求正在使用中",
|
"request_in_use": "請求正在使用中",
|
||||||
"save_as": "另存為",
|
"save_as": "另存為",
|
||||||
"save_to_collection": "Save to Collection",
|
"save_to_collection": "儲存到集合",
|
||||||
"select": "選擇一個集合",
|
"select": "選擇一個集合",
|
||||||
"select_location": "選擇位置",
|
"select_location": "選擇位置",
|
||||||
"select_team": "選擇一個團隊",
|
"select_team": "選擇一個團隊",
|
||||||
"team_collections": "團隊集合"
|
"team_collections": "團隊集合"
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"close_unsaved_tab": "Are you sure you want to close this tab?",
|
"close_unsaved_tab": "您確定要關閉此分頁嗎?",
|
||||||
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
|
"close_unsaved_tabs": "您確定要關閉所有分頁嗎?{count} 個未儲存的分頁將會遺失。",
|
||||||
"exit_team": "您確定要離開此團隊嗎?",
|
"exit_team": "您確定要離開此團隊嗎?",
|
||||||
"logout": "您確定要登出嗎?",
|
"logout": "您確定要登出嗎?",
|
||||||
"remove_collection": "您確定要永久刪除該集合嗎?",
|
"remove_collection": "您確定要永久刪除該集合嗎?",
|
||||||
@@ -158,9 +158,9 @@
|
|||||||
"sync": "您想從雲端恢復您的工作區嗎?這將丟棄您的本地進度。"
|
"sync": "您想從雲端恢復您的工作區嗎?這將丟棄您的本地進度。"
|
||||||
},
|
},
|
||||||
"context_menu": {
|
"context_menu": {
|
||||||
"add_parameters": "Add to parameters",
|
"add_parameters": "新增至參數",
|
||||||
"open_request_in_new_tab": "Open request in new tab",
|
"open_request_in_new_tab": "在新分頁開啟請求",
|
||||||
"set_environment_variable": "Set as variable"
|
"set_environment_variable": "設為變數"
|
||||||
},
|
},
|
||||||
"count": {
|
"count": {
|
||||||
"header": "請求標頭 {count}",
|
"header": "請求標頭 {count}",
|
||||||
@@ -204,31 +204,31 @@
|
|||||||
"create_new": "建立新環境",
|
"create_new": "建立新環境",
|
||||||
"created": "已建立環境",
|
"created": "已建立環境",
|
||||||
"deleted": "刪除環境",
|
"deleted": "刪除環境",
|
||||||
"duplicated": "Environment duplicated",
|
"duplicated": "已複製環境",
|
||||||
"edit": "編輯環境",
|
"edit": "編輯環境",
|
||||||
"empty_variables": "No variables",
|
"empty_variables": "無變數",
|
||||||
"global": "Global",
|
"global": "全域",
|
||||||
"global_variables": "Global variables",
|
"global_variables": "全域變數",
|
||||||
"invalid_name": "請提供有效的環境名稱",
|
"invalid_name": "請提供有效的環境名稱",
|
||||||
"list": "Environment variables",
|
"list": "環境變數",
|
||||||
"my_environments": "我的環境",
|
"my_environments": "我的環境",
|
||||||
"name": "Name",
|
"name": "名稱",
|
||||||
"nested_overflow": "巢狀環境變數不得大於 10 層",
|
"nested_overflow": "巢狀環境變數不得大於 10 層",
|
||||||
"new": "建立環境",
|
"new": "建立環境",
|
||||||
"no_active_environment": "No active environment",
|
"no_active_environment": "無使用中的環境",
|
||||||
"no_environment": "無環境",
|
"no_environment": "無環境",
|
||||||
"no_environment_description": "未選取任何環境。請選擇要對以下變數進行的動作。",
|
"no_environment_description": "未選取任何環境。請選擇要對以下變數進行的動作。",
|
||||||
"quick_peek": "Environment Quick Peek",
|
"quick_peek": "快速預覽環境",
|
||||||
"replace_with_variable": "Replace with variable",
|
"replace_with_variable": "以變數替代",
|
||||||
"scope": "Scope",
|
"scope": "範圍",
|
||||||
"select": "選擇環境",
|
"select": "選擇環境",
|
||||||
"set": "Set environment",
|
"set": "設定環境",
|
||||||
"set_as_environment": "Set as environment",
|
"set_as_environment": "設為環境",
|
||||||
"team_environments": "團隊環境",
|
"team_environments": "團隊環境",
|
||||||
"title": "環境",
|
"title": "環境",
|
||||||
"updated": "更新環境",
|
"updated": "更新環境",
|
||||||
"value": "Value",
|
"value": "數值",
|
||||||
"variable": "Variable",
|
"variable": "變數",
|
||||||
"variable_list": "變數列表"
|
"variable_list": "變數列表"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
"no_duration": "無持續時間",
|
"no_duration": "無持續時間",
|
||||||
"no_results_found": "找不到結果",
|
"no_results_found": "找不到結果",
|
||||||
"page_not_found": "找不到此頁面",
|
"page_not_found": "找不到此頁面",
|
||||||
"proxy_error": "Proxy error",
|
"proxy_error": "Proxy 錯誤",
|
||||||
"script_fail": "無法執行預請求指令碼",
|
"script_fail": "無法執行預請求指令碼",
|
||||||
"something_went_wrong": "發生了一些錯誤",
|
"something_went_wrong": "發生了一些錯誤",
|
||||||
"test_script_fail": "無法執行測試指令碼"
|
"test_script_fail": "無法執行測試指令碼"
|
||||||
@@ -278,13 +278,13 @@
|
|||||||
"renamed": "資料夾已重新命名"
|
"renamed": "資料夾已重新命名"
|
||||||
},
|
},
|
||||||
"graphql": {
|
"graphql": {
|
||||||
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
|
"connection_switch_confirm": "您要使用最新的 GraphQL 端點連線嗎?",
|
||||||
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
|
"connection_switch_new_url": "切換至分頁將斷開使用中的 GraphQL 連線。新的連線網址為 ",
|
||||||
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
|
"connection_switch_url": "您已連接至 GraphQL 端點。連線網址為 ",
|
||||||
"mutations": "變體",
|
"mutations": "變體",
|
||||||
"schema": "綱要",
|
"schema": "綱要",
|
||||||
"subscriptions": "訂閱",
|
"subscriptions": "訂閱",
|
||||||
"switch_connection": "Switch connection"
|
"switch_connection": "切換連線"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"time": "時間",
|
"time": "時間",
|
||||||
@@ -339,27 +339,27 @@
|
|||||||
"title": "匯入"
|
"title": "匯入"
|
||||||
},
|
},
|
||||||
"inspections": {
|
"inspections": {
|
||||||
"description": "Inspect possible errors",
|
"description": "檢查潛在錯誤",
|
||||||
"environment": {
|
"environment": {
|
||||||
"add_environment": "Add to Environment",
|
"add_environment": "新增至環境",
|
||||||
"not_found": "Environment variable “{environment}” not found."
|
"not_found": "找不到環境變數 “{environment}”。"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
|
"cookie": "瀏覽器不允許 Hoppscotch 設定 Cookie 標頭。在我們推出 Hoppscotch 桌面版前,請先使用 Authorization 標頭。"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"401_error": "Please check your authentication credentials.",
|
"401_error": "請檢查您的授權認證。",
|
||||||
"404_error": "Please check your request URL and method type.",
|
"404_error": "請檢查您的請求網址和方式類型。",
|
||||||
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
|
"cors_error": "請檢查您的跨來源資源共用設定。",
|
||||||
"default_error": "Please check your request.",
|
"default_error": "請檢查您的請求。",
|
||||||
"network_error": "Please check your network connection."
|
"network_error": "請檢查您的網路連線。"
|
||||||
},
|
},
|
||||||
"title": "Inspector",
|
"title": "檢查工具",
|
||||||
"url": {
|
"url": {
|
||||||
"extension_not_installed": "Extension not installed.",
|
"extension_not_installed": "未安裝擴充套件。",
|
||||||
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
|
"extension_unknown_origin": "請確認您是否已將 API 端點的來源加入 Hoppscotch 擴充套件的清單。",
|
||||||
"extention_enable_action": "Enable Browser Extension",
|
"extention_enable_action": "啟用瀏覽器擴充套件",
|
||||||
"extention_not_enabled": "Extension not enabled."
|
"extention_not_enabled": "未啟用擴充套件。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
@@ -472,7 +472,7 @@
|
|||||||
"payload": "負載",
|
"payload": "負載",
|
||||||
"query": "查詢",
|
"query": "查詢",
|
||||||
"raw_body": "原始請求本體",
|
"raw_body": "原始請求本體",
|
||||||
"rename": "Rename Request",
|
"rename": "重新命名請求",
|
||||||
"renamed": "請求已重新命名",
|
"renamed": "請求已重新命名",
|
||||||
"run": "執行",
|
"run": "執行",
|
||||||
"save": "儲存",
|
"save": "儲存",
|
||||||
@@ -510,7 +510,7 @@
|
|||||||
"accent_color": "強調色",
|
"accent_color": "強調色",
|
||||||
"account": "帳號",
|
"account": "帳號",
|
||||||
"account_deleted": "已刪除您的帳號",
|
"account_deleted": "已刪除您的帳號",
|
||||||
"account_description": "自定義您的帳號設定。",
|
"account_description": "自訂您的帳號設定。",
|
||||||
"account_email_description": "您的主要電子郵件地址。",
|
"account_email_description": "您的主要電子郵件地址。",
|
||||||
"account_name_description": "這是您的顯示名稱。",
|
"account_name_description": "這是您的顯示名稱。",
|
||||||
"background": "背景",
|
"background": "背景",
|
||||||
@@ -542,7 +542,7 @@
|
|||||||
"read_the": "閱讀",
|
"read_the": "閱讀",
|
||||||
"reset_default": "重置為預設",
|
"reset_default": "重置為預設",
|
||||||
"short_codes": "快捷碼",
|
"short_codes": "快捷碼",
|
||||||
"short_codes_description": "我們為您打造的快捷碼。",
|
"short_codes_description": "您建立的快捷碼。",
|
||||||
"sidebar_on_left": "左側邊欄",
|
"sidebar_on_left": "左側邊欄",
|
||||||
"sync": "同步",
|
"sync": "同步",
|
||||||
"sync_collections": "集合",
|
"sync_collections": "集合",
|
||||||
@@ -551,9 +551,9 @@
|
|||||||
"sync_history": "歷史",
|
"sync_history": "歷史",
|
||||||
"system_mode": "系統",
|
"system_mode": "系統",
|
||||||
"telemetry": "遙測服務",
|
"telemetry": "遙測服務",
|
||||||
"telemetry_helps_us": "遙測服務幫助我們進行個性化操作,為您提供最佳體驗。",
|
"telemetry_helps_us": "遙測服務能夠幫助我們進行個人化操作,為您提供最佳體驗。",
|
||||||
"theme": "主題",
|
"theme": "主題",
|
||||||
"theme_description": "自定義您的應用程式主題。",
|
"theme_description": "自訂您的應用程式主題。",
|
||||||
"use_experimental_url_bar": "使用帶有環境醒目標示的實驗性網址欄",
|
"use_experimental_url_bar": "使用帶有環境醒目標示的實驗性網址欄",
|
||||||
"user": "使用者",
|
"user": "使用者",
|
||||||
"verified_email": "已確認電子郵件地址",
|
"verified_email": "已確認電子郵件地址",
|
||||||
@@ -592,26 +592,26 @@
|
|||||||
"title": "導航"
|
"title": "導航"
|
||||||
},
|
},
|
||||||
"others": {
|
"others": {
|
||||||
"prettify": "Prettify Editor's Content",
|
"prettify": "美化編輯器的內容",
|
||||||
"title": "Others"
|
"title": "其他"
|
||||||
},
|
},
|
||||||
"request": {
|
"request": {
|
||||||
"copy_request_link": "複製請求連結",
|
"copy_request_link": "複製請求連結",
|
||||||
"delete_method": "選擇 DELETE 方法",
|
"delete_method": "選擇 DELETE 方法",
|
||||||
"get_method": "選擇 GET 方法",
|
"get_method": "選擇 GET 方法",
|
||||||
"head_method": "選擇 HEAD 方法",
|
"head_method": "選擇 HEAD 方法",
|
||||||
"import_curl": "Import cURL",
|
"import_curl": "匯入 cURL",
|
||||||
"method": "方法",
|
"method": "方法",
|
||||||
"next_method": "選擇下一個方法",
|
"next_method": "選擇下一個方法",
|
||||||
"post_method": "選擇 POST 方法",
|
"post_method": "選擇 POST 方法",
|
||||||
"previous_method": "選擇上一個方法",
|
"previous_method": "選擇上一個方法",
|
||||||
"put_method": "選擇 PUT 方法",
|
"put_method": "選擇 PUT 方法",
|
||||||
"rename": "Rename Request",
|
"rename": "重新命名請求",
|
||||||
"reset_request": "重置請求",
|
"reset_request": "重置請求",
|
||||||
"save_request": "Save Request",
|
"save_request": "儲存請求",
|
||||||
"save_to_collections": "儲存到集合",
|
"save_to_collections": "儲存到集合",
|
||||||
"send_request": "傳送請求",
|
"send_request": "傳送請求",
|
||||||
"show_code": "Generate code snippet",
|
"show_code": "產生程式碼片段",
|
||||||
"title": "請求"
|
"title": "請求"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
@@ -642,82 +642,82 @@
|
|||||||
"url": "網址"
|
"url": "網址"
|
||||||
},
|
},
|
||||||
"spotlight": {
|
"spotlight": {
|
||||||
"change_language": "Change Language",
|
"change_language": "變更語言",
|
||||||
"environments": {
|
"environments": {
|
||||||
"delete": "Delete current environment",
|
"delete": "刪除目前環境",
|
||||||
"duplicate": "Duplicate current environment",
|
"duplicate": "複製目前環境",
|
||||||
"duplicate_global": "Duplicate global environment",
|
"duplicate_global": "複製全域環境",
|
||||||
"edit": "Edit current environment",
|
"edit": "編輯目前環境",
|
||||||
"edit_global": "Edit global environment",
|
"edit_global": "編輯全域環境",
|
||||||
"new": "Create new environment",
|
"new": "建立新環境",
|
||||||
"new_variable": "Create a new environment variable",
|
"new_variable": "建立新環境變數",
|
||||||
"title": "Environments"
|
"title": "環境"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"chat": "Chat with support",
|
"chat": "與客服對話",
|
||||||
"help_menu": "Help and support",
|
"help_menu": "幫助與支援",
|
||||||
"open_docs": "Read Documentation",
|
"open_docs": "閱讀說明文件",
|
||||||
"open_github": "Open GitHub repository",
|
"open_github": "開啟 GitHub 儲存庫",
|
||||||
"open_keybindings": "Keyboard shortcuts",
|
"open_keybindings": "鍵盤快捷鍵",
|
||||||
"social": "Social",
|
"social": "社交",
|
||||||
"title": "General"
|
"title": "一般"
|
||||||
},
|
},
|
||||||
"graphql": {
|
"graphql": {
|
||||||
"connect": "Connect to server",
|
"connect": "連接至伺服器",
|
||||||
"disconnect": "Disconnect from server"
|
"disconnect": "斷開與伺服器的連線"
|
||||||
},
|
},
|
||||||
"miscellaneous": {
|
"miscellaneous": {
|
||||||
"invite": "Invite your friends to Hoppscotch",
|
"invite": "邀請您的朋友使用 Hoppscotch",
|
||||||
"title": "Miscellaneous"
|
"title": "雜項"
|
||||||
},
|
},
|
||||||
"request": {
|
"request": {
|
||||||
"save_as_new": "Save as new request",
|
"save_as_new": "儲存為新請求",
|
||||||
"select_method": "Select method",
|
"select_method": "選擇方法",
|
||||||
"switch_to": "Switch to",
|
"switch_to": "切換至",
|
||||||
"tab_authorization": "Authorization tab",
|
"tab_authorization": "授權分頁",
|
||||||
"tab_body": "Body tab",
|
"tab_body": "本體分頁",
|
||||||
"tab_headers": "Headers tab",
|
"tab_headers": "標頭分頁",
|
||||||
"tab_parameters": "Parameters tab",
|
"tab_parameters": "參數分頁",
|
||||||
"tab_pre_request_script": "Pre-request script tab",
|
"tab_pre_request_script": "預請求腳本分頁",
|
||||||
"tab_query": "Query tab",
|
"tab_query": "查詢分頁",
|
||||||
"tab_tests": "Tests tab",
|
"tab_tests": "測試分頁",
|
||||||
"tab_variables": "Variables tab"
|
"tab_variables": "變數分頁"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"copy": "Copy response",
|
"copy": "複製回應",
|
||||||
"download": "Download response as file",
|
"download": "下載回應",
|
||||||
"title": "Response"
|
"title": "回應"
|
||||||
},
|
},
|
||||||
"section": {
|
"section": {
|
||||||
"interceptor": "Interceptor",
|
"interceptor": "攔截器",
|
||||||
"interface": "Interface",
|
"interface": "介面",
|
||||||
"theme": "Theme",
|
"theme": "主題",
|
||||||
"user": "User"
|
"user": "使用者"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"change_interceptor": "Change Interceptor",
|
"change_interceptor": "變更攔截器",
|
||||||
"change_language": "Change Language",
|
"change_language": "變更語言",
|
||||||
"theme": {
|
"theme": {
|
||||||
"black": "Black",
|
"black": "黑色",
|
||||||
"dark": "Dark",
|
"dark": "暗色",
|
||||||
"light": "Light",
|
"light": "亮色",
|
||||||
"system": "System preference"
|
"system": "跟隨系統"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tab": {
|
"tab": {
|
||||||
"close_current": "Close current tab",
|
"close_current": "關閉目前分頁",
|
||||||
"close_others": "Close all other tabs",
|
"close_others": "關閉所有其他分頁",
|
||||||
"duplicate": "Duplicate current tab",
|
"duplicate": "複製目前分頁",
|
||||||
"new_tab": "Open a new tab",
|
"new_tab": "開啟新分頁",
|
||||||
"title": "Tabs"
|
"title": "分頁"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"delete": "Delete current team",
|
"delete": "刪除目前團隊",
|
||||||
"edit": "Edit current team",
|
"edit": "編輯目前團隊",
|
||||||
"invite": "Invite people to team",
|
"invite": "邀請他人加入團隊",
|
||||||
"new": "Create new team",
|
"new": "建立新團隊",
|
||||||
"switch_to_personal": "Switch to your personal workspace",
|
"switch_to_personal": "切換至您的個人工作區",
|
||||||
"title": "Teams"
|
"title": "團隊"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sse": {
|
"sse": {
|
||||||
@@ -777,11 +777,11 @@
|
|||||||
"tab": {
|
"tab": {
|
||||||
"authorization": "授權",
|
"authorization": "授權",
|
||||||
"body": "請求本體",
|
"body": "請求本體",
|
||||||
"close": "Close Tab",
|
"close": "關閉分頁",
|
||||||
"close_others": "Close other Tabs",
|
"close_others": "關閉其他分頁",
|
||||||
"collections": "集合",
|
"collections": "集合",
|
||||||
"documentation": "幫助文件",
|
"documentation": "幫助文件",
|
||||||
"duplicate": "Duplicate Tab",
|
"duplicate": "複製分頁",
|
||||||
"environments": "環境",
|
"environments": "環境",
|
||||||
"headers": "請求標頭",
|
"headers": "請求標頭",
|
||||||
"history": "歷史記錄",
|
"history": "歷史記錄",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/common",
|
"name": "@hoppscotch/common",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.8.1",
|
"version": "2023.8.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
"test": "vitest --run",
|
"test": "vitest --run",
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ declare module 'vue' {
|
|||||||
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
||||||
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
||||||
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
||||||
|
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
|
||||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||||
@@ -142,6 +143,7 @@ declare module 'vue' {
|
|||||||
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
||||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||||
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
||||||
|
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
|
||||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||||
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
||||||
@@ -151,8 +153,10 @@ declare module 'vue' {
|
|||||||
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
||||||
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
||||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||||
|
IconLucideRss: typeof import('~icons/lucide/rss')['default']
|
||||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||||
|
IconLucideVerified: typeof import('~icons/lucide/verified')['default']
|
||||||
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
||||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||||
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
||||||
|
|||||||
@@ -2,53 +2,16 @@
|
|||||||
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
|
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
|
||||||
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
||||||
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
|
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
|
||||||
|
|
||||||
<HoppSmartConfirmModal
|
|
||||||
:show="confirmRemove"
|
|
||||||
:title="t('confirm.remove_team')"
|
|
||||||
@hide-modal="confirmRemove = false"
|
|
||||||
@resolve="deleteTeam()"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { pipe } from "fp-ts/function"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
|
||||||
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
|
||||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
|
||||||
import { useToast } from "~/composables/toast"
|
|
||||||
import { useI18n } from "~/composables/i18n"
|
|
||||||
|
|
||||||
const toast = useToast()
|
|
||||||
const t = useI18n()
|
|
||||||
|
|
||||||
const showShortcuts = ref(false)
|
const showShortcuts = ref(false)
|
||||||
const showShare = ref(false)
|
const showShare = ref(false)
|
||||||
const showLogin = ref(false)
|
const showLogin = ref(false)
|
||||||
|
|
||||||
const confirmRemove = ref(false)
|
|
||||||
|
|
||||||
const teamID = ref<string | null>(null)
|
|
||||||
|
|
||||||
const deleteTeam = () => {
|
|
||||||
if (!teamID.value) return
|
|
||||||
pipe(
|
|
||||||
backendDeleteTeam(teamID.value),
|
|
||||||
TE.match(
|
|
||||||
(err) => {
|
|
||||||
// TODO: Better errors ? We know the possible errors now
|
|
||||||
toast.error(`${t("error.something_went_wrong")}`)
|
|
||||||
console.error(err)
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
invokeAction("workspace.switch.personal")
|
|
||||||
toast.success(`${t("team.deleted")}`)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)() // Tasks (and TEs) are lazy, so call the function returned
|
|
||||||
}
|
|
||||||
|
|
||||||
defineActionHandler("flyouts.keybinds.toggle", () => {
|
defineActionHandler("flyouts.keybinds.toggle", () => {
|
||||||
showShortcuts.value = !showShortcuts.value
|
showShortcuts.value = !showShortcuts.value
|
||||||
})
|
})
|
||||||
@@ -60,9 +23,4 @@ defineActionHandler("modals.share.toggle", () => {
|
|||||||
defineActionHandler("modals.login.toggle", () => {
|
defineActionHandler("modals.login.toggle", () => {
|
||||||
showLogin.value = !showLogin.value
|
showLogin.value = !showLogin.value
|
||||||
})
|
})
|
||||||
|
|
||||||
defineActionHandler("modals.team.delete", ({ teamId }) => {
|
|
||||||
teamID.value = teamId
|
|
||||||
confirmRemove.value = true
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -231,29 +231,39 @@
|
|||||||
@invite-team="inviteTeam(editingTeamName, editingTeamID)"
|
@invite-team="inviteTeam(editingTeamName, editingTeamID)"
|
||||||
@refetch-teams="refetchTeams"
|
@refetch-teams="refetchTeams"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<HoppSmartConfirmModal
|
||||||
|
:show="confirmRemove"
|
||||||
|
:title="t('confirm.remove_team')"
|
||||||
|
@hide-modal="confirmRemove = false"
|
||||||
|
@resolve="deleteTeam"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive, ref, watch } from "vue"
|
|
||||||
import IconUser from "~icons/lucide/user"
|
|
||||||
import IconUsers from "~icons/lucide/users"
|
|
||||||
import IconSettings from "~icons/lucide/settings"
|
|
||||||
import IconDownload from "~icons/lucide/download"
|
|
||||||
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
|
||||||
import IconUserPlus from "~icons/lucide/user-plus"
|
|
||||||
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
|
||||||
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
|
||||||
import { pwaDefferedPrompt, installPWA } from "@modules/pwa"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
|
||||||
import { useToast } from "~/composables/toast"
|
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
|
import { installPWA, pwaDefferedPrompt } from "@modules/pwa"
|
||||||
|
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
||||||
|
import { computed, reactive, ref, watch } from "vue"
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql"
|
||||||
|
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
import IconDownload from "~icons/lucide/download"
|
||||||
|
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
||||||
|
import IconSettings from "~icons/lucide/settings"
|
||||||
|
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
||||||
|
import IconUser from "~icons/lucide/user"
|
||||||
|
import IconUserPlus from "~icons/lucide/user-plus"
|
||||||
|
import IconUsers from "~icons/lucide/users"
|
||||||
|
import { pipe } from "fp-ts/function"
|
||||||
|
import * as TE from "fp-ts/TaskEither"
|
||||||
|
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -278,6 +288,9 @@ const currentUser = useReadonlyStream(
|
|||||||
platform.auth.getProbableUser()
|
platform.auth.getProbableUser()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const confirmRemove = ref(false)
|
||||||
|
const teamID = ref<string | null>(null)
|
||||||
|
|
||||||
const selectedTeam = ref<GetMyTeamsQuery["myTeams"][number] | undefined>()
|
const selectedTeam = ref<GetMyTeamsQuery["myTeams"][number] | undefined>()
|
||||||
|
|
||||||
// TeamList-Adapter
|
// TeamList-Adapter
|
||||||
@@ -377,6 +390,24 @@ const handleTeamEdit = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteTeam = () => {
|
||||||
|
if (!teamID.value) return
|
||||||
|
pipe(
|
||||||
|
backendDeleteTeam(teamID.value),
|
||||||
|
TE.match(
|
||||||
|
(err) => {
|
||||||
|
// TODO: Better errors ? We know the possible errors now
|
||||||
|
toast.error(`${t("error.something_went_wrong")}`)
|
||||||
|
console.error(err)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
invokeAction("workspace.switch.personal")
|
||||||
|
toast.success(`${t("team.deleted")}`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)() // Tasks (and TEs) are lazy, so call the function returned
|
||||||
|
}
|
||||||
|
|
||||||
// Template refs
|
// Template refs
|
||||||
const tippyActions = ref<any | null>(null)
|
const tippyActions = ref<any | null>(null)
|
||||||
const profile = ref<any | null>(null)
|
const profile = ref<any | null>(null)
|
||||||
@@ -405,6 +436,12 @@ defineActionHandler(
|
|||||||
computed(() => !currentUser.value)
|
computed(() => !currentUser.value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
defineActionHandler("modals.team.delete", ({ teamId }) => {
|
||||||
|
if (selectedTeam.value?.myRole !== TeamMemberRole.Owner) return noPermission()
|
||||||
|
teamID.value = teamId
|
||||||
|
confirmRemove.value = true
|
||||||
|
})
|
||||||
|
|
||||||
const noPermission = () => {
|
const noPermission = () => {
|
||||||
toast.error(`${t("profile.no_permission")}`)
|
toast.error(`${t("profile.no_permission")}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,8 @@
|
|||||||
import { ref, watch } from "vue"
|
import { ref, watch } from "vue"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@@ -60,11 +61,12 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const editingName = ref("")
|
const editingName = ref("")
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
editingName.value = currentActiveTab.value.document.request.name
|
editingName.value = tabs.currentActiveTab.value.document.request.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="!saveRequest"
|
v-if="!saveRequest"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
@click="emit('display-modal-import-export')"
|
@click="emit('display-modal-import-export')"
|
||||||
/>
|
/>
|
||||||
@@ -257,12 +257,27 @@
|
|||||||
:alt="`${t('empty.collections')}`"
|
:alt="`${t('empty.collections')}`"
|
||||||
:text="t('empty.collections')"
|
:text="t('empty.collections')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div class="flex flex-col items-center space-y-4">
|
||||||
:label="t('add.new')"
|
<span class="text-secondaryLight text-center">
|
||||||
filled
|
{{ t("collection.import_or_create") }}
|
||||||
outline
|
</span>
|
||||||
@click="emit('display-modal-add')"
|
<div class="flex gap-4 flex-col items-stretch">
|
||||||
/>
|
<HoppButtonPrimary
|
||||||
|
:icon="IconImport"
|
||||||
|
:label="t('import.title')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="emit('display-modal-import-export')"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:icon="IconPlus"
|
||||||
|
:label="t('add.new')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="emit('display-modal-add')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-else-if="node.data.type === 'collections'"
|
v-else-if="node.data.type === 'collections'"
|
||||||
@@ -288,8 +303,7 @@
|
|||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
:alt="`${t('empty.folder')}`"
|
:alt="`${t('empty.folder')}`"
|
||||||
:text="t('empty.folder')"
|
:text="t('empty.folder')"
|
||||||
>
|
/>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartTree>
|
</HoppSmartTree>
|
||||||
</div>
|
</div>
|
||||||
@@ -297,9 +311,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import IconArchive from "~icons/lucide/archive"
|
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { computed, PropType, Ref, toRef } from "vue"
|
import { computed, PropType, Ref, toRef } from "vue"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
@@ -312,7 +326,8 @@ import { useColorMode } from "@composables/theming"
|
|||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked.js"
|
import { Picked } from "~/helpers/types/HoppPicked.js"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
export type Collection = {
|
export type Collection = {
|
||||||
type: "collections"
|
type: "collections"
|
||||||
@@ -520,7 +535,8 @@ const isSelected = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const active = computed(() => currentActiveTab.value.document.saveContext)
|
const tabs = useService(RESTTabService)
|
||||||
|
const active = computed(() => tabs.currentActiveTab.value.document.saveContext)
|
||||||
|
|
||||||
const isActiveRequest = (folderPath: string, requestIndex: number) => {
|
const isActiveRequest = (folderPath: string, requestIndex: number) => {
|
||||||
return pipe(
|
return pipe(
|
||||||
|
|||||||
@@ -82,12 +82,16 @@ import {
|
|||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { computedWithControl } from "@vueuse/core"
|
import { computedWithControl } from "@vueuse/core"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { currentActiveTab as activeRESTTab } from "~/helpers/rest/tab"
|
import { useService } from "dioc/vue"
|
||||||
import { currentActiveTab as activeGQLTab } from "~/helpers/graphql/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const RESTTabs = useService(RESTTabService)
|
||||||
|
const GQLTabs = useService(GQLTabService)
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
||||||
|
|
||||||
type CollectionType =
|
type CollectionType =
|
||||||
@@ -123,13 +127,13 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const gqlRequestName = computedWithControl(
|
const gqlRequestName = computedWithControl(
|
||||||
() => activeGQLTab.value,
|
() => GQLTabs.currentActiveTab.value,
|
||||||
() => activeGQLTab.value.document.request.name
|
() => GQLTabs.currentActiveTab.value.document.request.name
|
||||||
)
|
)
|
||||||
|
|
||||||
const restRequestName = computedWithControl(
|
const restRequestName = computedWithControl(
|
||||||
() => activeRESTTab.value,
|
() => RESTTabs.currentActiveTab.value,
|
||||||
() => activeRESTTab.value.document.request.name
|
() => RESTTabs.currentActiveTab.value.document.request.name
|
||||||
)
|
)
|
||||||
|
|
||||||
const reqName = computed(() => {
|
const reqName = computed(() => {
|
||||||
@@ -145,12 +149,14 @@ const reqName = computed(() => {
|
|||||||
const requestName = ref(reqName.value)
|
const requestName = ref(reqName.value)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [activeRESTTab.value, activeGQLTab.value],
|
() => [RESTTabs.currentActiveTab.value, GQLTabs.currentActiveTab.value],
|
||||||
() => {
|
() => {
|
||||||
if (props.mode === "rest") {
|
if (props.mode === "rest") {
|
||||||
requestName.value = activeRESTTab.value?.document.request.name ?? ""
|
requestName.value =
|
||||||
|
RESTTabs.currentActiveTab.value?.document.request.name ?? ""
|
||||||
} else {
|
} else {
|
||||||
requestName.value = activeGQLTab.value?.document.request.name ?? ""
|
requestName.value =
|
||||||
|
GQLTabs.currentActiveTab.value?.document.request.name ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -210,8 +216,8 @@ const saveRequestAs = async () => {
|
|||||||
|
|
||||||
const requestUpdated =
|
const requestUpdated =
|
||||||
props.mode === "rest"
|
props.mode === "rest"
|
||||||
? cloneDeep(activeRESTTab.value.document.request)
|
? cloneDeep(RESTTabs.currentActiveTab.value.document.request)
|
||||||
: cloneDeep(activeGQLTab.value.document.request)
|
: cloneDeep(GQLTabs.currentActiveTab.value.document.request)
|
||||||
|
|
||||||
requestUpdated.name = requestName.value
|
requestUpdated.name = requestName.value
|
||||||
|
|
||||||
@@ -224,7 +230,7 @@ const saveRequestAs = async () => {
|
|||||||
requestUpdated
|
requestUpdated
|
||||||
)
|
)
|
||||||
|
|
||||||
activeRESTTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: requestUpdated,
|
request: requestUpdated,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -251,7 +257,7 @@ const saveRequestAs = async () => {
|
|||||||
requestUpdated
|
requestUpdated
|
||||||
)
|
)
|
||||||
|
|
||||||
activeRESTTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: requestUpdated,
|
request: requestUpdated,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -279,7 +285,7 @@ const saveRequestAs = async () => {
|
|||||||
requestUpdated
|
requestUpdated
|
||||||
)
|
)
|
||||||
|
|
||||||
activeRESTTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: requestUpdated,
|
request: requestUpdated,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -439,7 +445,7 @@ const updateTeamCollectionOrFolder = (
|
|||||||
(result) => {
|
(result) => {
|
||||||
const { createRequestInCollection } = result
|
const { createRequestInCollection } = result
|
||||||
|
|
||||||
activeRESTTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: requestUpdated,
|
request: requestUpdated,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -460,7 +466,7 @@ const updateTeamCollectionOrFolder = (
|
|||||||
const requestSaved = () => {
|
const requestSaved = () => {
|
||||||
toast.success(`${t("request.added")}`)
|
toast.success(`${t("request.added")}`)
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
activeRESTTab.value.document.isDirty = false
|
RESTTabs.currentActiveTab.value.document.isDirty = false
|
||||||
})
|
})
|
||||||
hideModal()
|
hideModal()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
class="!rounded-none"
|
class="!rounded-none"
|
||||||
:icon="IconPlus"
|
:icon="IconPlus"
|
||||||
:title="t('team.no_access')"
|
:title="t('team.no_access')"
|
||||||
:label="t('action.new')"
|
:label="t('add.new')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-else
|
v-else
|
||||||
:icon="IconPlus"
|
:icon="IconPlus"
|
||||||
:label="t('action.new')"
|
:label="t('add.new')"
|
||||||
class="!rounded-none"
|
class="!rounded-none"
|
||||||
@click="emit('display-modal-add')"
|
@click="emit('display-modal-add')"
|
||||||
/>
|
/>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
collectionsType.type === 'team-collections' &&
|
collectionsType.type === 'team-collections' &&
|
||||||
collectionsType.selectedTeam === undefined
|
collectionsType.selectedTeam === undefined
|
||||||
"
|
"
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
@click="emit('display-modal-import-export')"
|
@click="emit('display-modal-import-export')"
|
||||||
/>
|
/>
|
||||||
@@ -261,55 +261,68 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #emptyNode="{ node }">
|
<template #emptyNode="{ node }">
|
||||||
<div v-if="node === null">
|
<HoppSmartPlaceholder
|
||||||
<div @drop="(e) => e.stopPropagation()">
|
v-if="node === null"
|
||||||
<HoppSmartPlaceholder
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:alt="`${t('empty.collections')}`"
|
||||||
:alt="`${t('empty.collections')}`"
|
:text="t('empty.collections')"
|
||||||
:text="t('empty.collections')"
|
@drop.stop
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div class="flex flex-col items-center space-y-4">
|
||||||
v-if="hasNoTeamAccess"
|
<span class="text-secondaryLight text-center">
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
{{ t("collection.import_or_create") }}
|
||||||
disabled
|
</span>
|
||||||
|
<div class="flex gap-4 flex-col items-stretch">
|
||||||
|
<HoppButtonPrimary
|
||||||
|
:icon="IconImport"
|
||||||
|
:label="t('import.title')"
|
||||||
filled
|
filled
|
||||||
outline
|
outline
|
||||||
:title="t('team.no_access')"
|
:disabled="hasNoTeamAccess"
|
||||||
:label="t('action.new')"
|
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||||
|
@click="
|
||||||
|
hasNoTeamAccess ? null : emit('display-modal-import-export')
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-else
|
|
||||||
:icon="IconPlus"
|
:icon="IconPlus"
|
||||||
:label="t('action.new')"
|
:label="t('add.new')"
|
||||||
filled
|
filled
|
||||||
outline
|
outline
|
||||||
@click="emit('display-modal-add')"
|
:disabled="hasNoTeamAccess"
|
||||||
|
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||||
|
@click="hasNoTeamAccess ? null : emit('display-modal-add')"
|
||||||
/>
|
/>
|
||||||
</HoppSmartPlaceholder>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</HoppSmartPlaceholder>
|
||||||
<div
|
<HoppSmartPlaceholder
|
||||||
v-else-if="node.data.type === 'collections'"
|
v-else-if="node.data.type === 'collections'"
|
||||||
@drop="(e) => e.stopPropagation()"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
|
:alt="`${t('empty.collections')}`"
|
||||||
|
:text="t('empty.collections')"
|
||||||
|
@drop.stop
|
||||||
>
|
>
|
||||||
<HoppSmartPlaceholder
|
<HoppButtonSecondary
|
||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:label="t('add.new')"
|
||||||
:alt="`${t('empty.collections')}`"
|
filled
|
||||||
:text="t('empty.collections')"
|
outline
|
||||||
>
|
@click="
|
||||||
</HoppSmartPlaceholder>
|
node.data.type === 'collections' &&
|
||||||
</div>
|
emit('add-folder', {
|
||||||
<div
|
path: node.id,
|
||||||
|
folder: node.data.data.data,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</HoppSmartPlaceholder>
|
||||||
|
<HoppSmartPlaceholder
|
||||||
v-else-if="node.data.type === 'folders'"
|
v-else-if="node.data.type === 'folders'"
|
||||||
@drop="(e) => e.stopPropagation()"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
>
|
:alt="`${t('empty.folder')}`"
|
||||||
<HoppSmartPlaceholder
|
:text="t('empty.folder')"
|
||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
@drop.stop
|
||||||
:alt="`${t('empty.folder')}`"
|
/>
|
||||||
:text="t('empty.folder')"
|
|
||||||
>
|
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartTree>
|
</HoppSmartTree>
|
||||||
</div>
|
</div>
|
||||||
@@ -317,9 +330,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import IconArchive from "~icons/lucide/archive"
|
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import { computed, PropType, Ref, toRef } from "vue"
|
import { computed, PropType, Ref, toRef } from "vue"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
@@ -335,10 +348,12 @@ import { HoppRESTRequest } from "@hoppscotch/data"
|
|||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked.js"
|
import { Picked } from "~/helpers/types/HoppPicked.js"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
||||||
|
|
||||||
@@ -536,7 +551,7 @@ const isSelected = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const active = computed(() => currentActiveTab.value.document.saveContext)
|
const active = computed(() => tabs.currentActiveTab.value.document.saveContext)
|
||||||
|
|
||||||
const isActiveRequest = (requestID: string) => {
|
const isActiveRequest = (requestID: string) => {
|
||||||
return pipe(
|
return pipe(
|
||||||
|
|||||||
@@ -36,11 +36,14 @@
|
|||||||
import { ref, watch } from "vue"
|
import { ref, watch } from "vue"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { currentActiveTab } from "~/helpers/graphql/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
folderPath?: string
|
folderPath?: string
|
||||||
@@ -63,7 +66,7 @@ watch(
|
|||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
editingName.value = currentActiveTab.value?.document.request.name
|
editingName.value = tabs.currentActiveTab.value?.document.request.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -220,7 +220,8 @@ import {
|
|||||||
moveGraphqlRequest,
|
moveGraphqlRequest,
|
||||||
} from "~/newstore/collections"
|
} from "~/newstore/collections"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked"
|
import { Picked } from "~/helpers/types/HoppPicked"
|
||||||
import { getTabsRefTo } from "~/helpers/graphql/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
picked: { type: Object, default: null },
|
picked: { type: Object, default: null },
|
||||||
@@ -235,6 +236,8 @@ const colorMode = useColorMode()
|
|||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
// TODO: improve types plz
|
// TODO: improve types plz
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "select", i: Picked | null): void
|
(e: "select", i: Picked | null): void
|
||||||
@@ -295,7 +298,7 @@ const removeCollection = () => {
|
|||||||
emit("select", null)
|
emit("select", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const possibleTabs = getTabsRefTo((tab) => {
|
const possibleTabs = tabs.getTabsRefTo((tab) => {
|
||||||
const ctx = tab.document.saveContext
|
const ctx = tab.document.saveContext
|
||||||
|
|
||||||
if (!ctx) return false
|
if (!ctx) return false
|
||||||
|
|||||||
@@ -203,12 +203,15 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { removeGraphqlFolder, moveGraphqlRequest } from "~/newstore/collections"
|
import { removeGraphqlFolder, moveGraphqlRequest } from "~/newstore/collections"
|
||||||
import { computed, ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
import { getTabsRefTo } from "~/helpers/graphql/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
picked: { type: Object, default: null },
|
picked: { type: Object, default: null },
|
||||||
// Whether the request is in a selectable mode (activates 'select' event)
|
// Whether the request is in a selectable mode (activates 'select' event)
|
||||||
@@ -277,7 +280,7 @@ const removeFolder = () => {
|
|||||||
emit("select", { picked: null })
|
emit("select", { picked: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
const possibleTabs = getTabsRefTo((tab) => {
|
const possibleTabs = tabs.getTabsRefTo((tab) => {
|
||||||
const ctx = tab.document.saveContext
|
const ctx = tab.document.saveContext
|
||||||
|
|
||||||
if (!ctx) return false
|
if (!ctx) return false
|
||||||
|
|||||||
@@ -260,6 +260,13 @@ const importFromJSON = () => {
|
|||||||
|
|
||||||
const exportJSON = () => {
|
const exportJSON = () => {
|
||||||
const dataToWrite = collectionJson.value
|
const dataToWrite = collectionJson.value
|
||||||
|
|
||||||
|
const parsedCollections = JSON.parse(dataToWrite)
|
||||||
|
|
||||||
|
if (!parsedCollections.length) {
|
||||||
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
|
}
|
||||||
|
|
||||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
const a = document.createElement("a")
|
const a = document.createElement("a")
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
|||||||
@@ -137,12 +137,8 @@ import { useToast } from "@composables/toast"
|
|||||||
import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
|
import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { removeGraphqlRequest } from "~/newstore/collections"
|
import { removeGraphqlRequest } from "~/newstore/collections"
|
||||||
import {
|
import { useService } from "dioc/vue"
|
||||||
createNewTab,
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
getTabRefWithSaveContext,
|
|
||||||
currentTabID,
|
|
||||||
currentActiveTab,
|
|
||||||
} from "~/helpers/graphql/tab"
|
|
||||||
|
|
||||||
// Template refs
|
// Template refs
|
||||||
const tippyActions = ref<any | null>(null)
|
const tippyActions = ref<any | null>(null)
|
||||||
@@ -154,6 +150,8 @@ const deleteAction = ref<any | null>(null)
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// Whether the object is selected (show the tick mark)
|
// Whether the object is selected (show the tick mark)
|
||||||
picked: { type: Object, default: null },
|
picked: { type: Object, default: null },
|
||||||
@@ -165,7 +163,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isActive = computed(() => {
|
const isActive = computed(() => {
|
||||||
const saveCtx = currentActiveTab.value?.document.saveContext
|
const saveCtx = tabs.currentActiveTab.value?.document.saveContext
|
||||||
|
|
||||||
if (!saveCtx) return false
|
if (!saveCtx) return false
|
||||||
|
|
||||||
@@ -201,7 +199,7 @@ const selectRequest = () => {
|
|||||||
if (props.saveRequest) {
|
if (props.saveRequest) {
|
||||||
pick()
|
pick()
|
||||||
} else {
|
} else {
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: props.folderPath,
|
folderPath: props.folderPath,
|
||||||
requestIndex: props.requestIndex,
|
requestIndex: props.requestIndex,
|
||||||
@@ -209,11 +207,11 @@ const selectRequest = () => {
|
|||||||
|
|
||||||
// Switch to that request if that request is open
|
// Switch to that request if that request is open
|
||||||
if (possibleTab) {
|
if (possibleTab) {
|
||||||
currentTabID.value = possibleTab.value.id
|
tabs.setActiveTab(possibleTab.value.id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
saveContext: {
|
saveContext: {
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: props.folderPath,
|
folderPath: props.folderPath,
|
||||||
@@ -253,7 +251,7 @@ const removeRequest = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Detach the request from any of the tabs
|
// Detach the request from any of the tabs
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: props.folderPath,
|
folderPath: props.folderPath,
|
||||||
requestIndex: props.requestIndex,
|
requestIndex: props.requestIndex,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
v-if="!saveRequest"
|
v-if="!saveRequest"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
@click="displayModalImportExport(true)"
|
@click="displayModalImportExport(true)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,12 +66,27 @@
|
|||||||
:alt="`${t('empty.collections')}`"
|
:alt="`${t('empty.collections')}`"
|
||||||
:text="t('empty.collections')"
|
:text="t('empty.collections')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div class="flex flex-col items-center space-y-4">
|
||||||
:label="t('add.new')"
|
<span class="text-secondaryLight text-center">
|
||||||
filled
|
{{ t("collection.import_or_create") }}
|
||||||
outline
|
</span>
|
||||||
@click="displayModalAdd(true)"
|
<div class="flex gap-4 flex-col items-stretch">
|
||||||
/>
|
<HoppButtonPrimary
|
||||||
|
:icon="IconImport"
|
||||||
|
:label="t('import.title')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="displayModalImportExport(true)"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="t('add.new')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
:icon="IconPlus"
|
||||||
|
@click="displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
|
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
|
||||||
@@ -140,12 +155,13 @@ import {
|
|||||||
|
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
import IconArchive from "~icons/lucide/archive"
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { createNewTab, currentActiveTab } from "~/helpers/graphql/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -158,14 +174,16 @@ export default defineComponent({
|
|||||||
const collections = useReadonlyStream(graphqlCollections$, [], "deep")
|
const collections = useReadonlyStream(graphqlCollections$, [], "deep")
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
collections,
|
collections,
|
||||||
colorMode,
|
colorMode,
|
||||||
t,
|
t,
|
||||||
|
tabs,
|
||||||
IconPlus,
|
IconPlus,
|
||||||
IconHelpCircle,
|
IconHelpCircle,
|
||||||
IconArchive,
|
IconImport,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -267,13 +285,13 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
onAddRequest({ name, path, index }) {
|
onAddRequest({ name, path, index }) {
|
||||||
const newRequest = {
|
const newRequest = {
|
||||||
...currentActiveTab.value.document.request,
|
...this.tabs.currentActiveTab.value.document.request,
|
||||||
name,
|
name,
|
||||||
}
|
}
|
||||||
|
|
||||||
saveGraphqlRequestAs(path, newRequest)
|
saveGraphqlRequestAs(path, newRequest)
|
||||||
|
|
||||||
createNewTab({
|
this.tabs.createNewTab({
|
||||||
saveContext: {
|
saveContext: {
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: path,
|
folderPath: path,
|
||||||
|
|||||||
@@ -219,12 +219,6 @@ import {
|
|||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { createCollectionGists } from "~/helpers/gist"
|
import { createCollectionGists } from "~/helpers/gist"
|
||||||
import {
|
|
||||||
createNewTab,
|
|
||||||
currentActiveTab,
|
|
||||||
currentTabID,
|
|
||||||
getTabRefWithSaveContext,
|
|
||||||
} from "~/helpers/rest/tab"
|
|
||||||
import {
|
import {
|
||||||
getRequestsByPath,
|
getRequestsByPath,
|
||||||
resolveSaveContextOnRequestReorder,
|
resolveSaveContextOnRequestReorder,
|
||||||
@@ -239,9 +233,11 @@ import { currentReorderingStatus$ } from "~/newstore/reordering"
|
|||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
saveRequest: {
|
saveRequest: {
|
||||||
@@ -377,22 +373,26 @@ const updateSelectedTeam = (team: SelectedTeam) => {
|
|||||||
const workspace = workspaceService.currentWorkspace
|
const workspace = workspaceService.currentWorkspace
|
||||||
|
|
||||||
// Used to switch collection type and team when user switch workspace in the global workspace switcher
|
// Used to switch collection type and team when user switch workspace in the global workspace switcher
|
||||||
// Check if there is a teamID in the workspace, if yes, switch to team collection and select the team
|
// Check if there is a teamID in the workspace, if yes, switch to team collections and select the team
|
||||||
// If there is no teamID, switch to my environment
|
// If there is no teamID, switch to my collections
|
||||||
watch(
|
watch(
|
||||||
() => {
|
() => {
|
||||||
const space = workspace.value
|
const space = workspace.value
|
||||||
|
return space.type === "personal" ? undefined : space.teamID
|
||||||
if (space.type === "personal") return undefined
|
|
||||||
else return space.teamID
|
|
||||||
},
|
},
|
||||||
(teamID) => {
|
(teamID) => {
|
||||||
if (!teamID) {
|
if (teamID) {
|
||||||
switchToMyCollections()
|
|
||||||
} else if (teamID) {
|
|
||||||
const team = myTeams.value?.find((t) => t.id === teamID)
|
const team = myTeams.value?.find((t) => t.id === teamID)
|
||||||
if (team) updateSelectedTeam(team)
|
if (team) {
|
||||||
|
updateSelectedTeam(team)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return switchToMyCollections()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -650,7 +650,7 @@ const addRequest = (payload: {
|
|||||||
|
|
||||||
const onAddRequest = (requestName: string) => {
|
const onAddRequest = (requestName: string) => {
|
||||||
const newRequest = {
|
const newRequest = {
|
||||||
...cloneDeep(currentActiveTab.value.document.request),
|
...cloneDeep(tabs.currentActiveTab.value.document.request),
|
||||||
name: requestName,
|
name: requestName,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,7 +659,7 @@ const onAddRequest = (requestName: string) => {
|
|||||||
if (!path) return
|
if (!path) return
|
||||||
const insertionIndex = saveRESTRequestAs(path, newRequest)
|
const insertionIndex = saveRESTRequestAs(path, newRequest)
|
||||||
|
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: newRequest,
|
request: newRequest,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -708,7 +708,7 @@ const onAddRequest = (requestName: string) => {
|
|||||||
(result) => {
|
(result) => {
|
||||||
const { createRequestInCollection } = result
|
const { createRequestInCollection } = result
|
||||||
|
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: newRequest,
|
request: newRequest,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -931,7 +931,7 @@ const updateEditingRequest = (newName: string) => {
|
|||||||
|
|
||||||
if (folderPath === null || requestIndex === null) return
|
if (folderPath === null || requestIndex === null) return
|
||||||
|
|
||||||
const possibleActiveTab = getTabRefWithSaveContext({
|
const possibleActiveTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
requestIndex,
|
requestIndex,
|
||||||
folderPath,
|
folderPath,
|
||||||
@@ -975,7 +975,7 @@ const updateEditingRequest = (newName: string) => {
|
|||||||
)
|
)
|
||||||
)()
|
)()
|
||||||
|
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID,
|
requestID,
|
||||||
})
|
})
|
||||||
@@ -1211,7 +1211,7 @@ const onRemoveRequest = () => {
|
|||||||
emit("select", null)
|
emit("select", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath,
|
folderPath,
|
||||||
requestIndex,
|
requestIndex,
|
||||||
@@ -1271,7 +1271,7 @@ const onRemoveRequest = () => {
|
|||||||
)()
|
)()
|
||||||
|
|
||||||
// If there is a tab attached to this request, dissociate its state and mark it dirty
|
// If there is a tab attached to this request, dissociate its state and mark it dirty
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID,
|
requestID,
|
||||||
})
|
})
|
||||||
@@ -1304,14 +1304,14 @@ const selectRequest = (selectedRequest: {
|
|||||||
let possibleTab = null
|
let possibleTab = null
|
||||||
|
|
||||||
if (collectionsType.value.type === "team-collections") {
|
if (collectionsType.value.type === "team-collections") {
|
||||||
possibleTab = getTabRefWithSaveContext({
|
possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID: requestIndex,
|
requestID: requestIndex,
|
||||||
})
|
})
|
||||||
if (possibleTab) {
|
if (possibleTab) {
|
||||||
currentTabID.value = possibleTab.value.id
|
tabs.setActiveTab(possibleTab.value.id)
|
||||||
} else {
|
} else {
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: cloneDeep(request),
|
request: cloneDeep(request),
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -1321,16 +1321,16 @@ const selectRequest = (selectedRequest: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
possibleTab = getTabRefWithSaveContext({
|
possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
requestIndex: parseInt(requestIndex),
|
requestIndex: parseInt(requestIndex),
|
||||||
folderPath: folderPath!,
|
folderPath: folderPath!,
|
||||||
})
|
})
|
||||||
if (possibleTab) {
|
if (possibleTab) {
|
||||||
currentTabID.value = possibleTab.value.id
|
tabs.setActiveTab(possibleTab.value.id)
|
||||||
} else {
|
} else {
|
||||||
// If not, open the request in a new tab
|
// If not, open the request in a new tab
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: cloneDeep(request),
|
request: cloneDeep(request),
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
@@ -1373,7 +1373,7 @@ const dropRequest = (payload: {
|
|||||||
destinationCollectionIndex
|
destinationCollectionIndex
|
||||||
)
|
)
|
||||||
|
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath,
|
folderPath,
|
||||||
requestIndex: pathToLastIndex(requestIndex),
|
requestIndex: pathToLastIndex(requestIndex),
|
||||||
@@ -1422,7 +1422,7 @@ const dropRequest = (payload: {
|
|||||||
1
|
1
|
||||||
)
|
)
|
||||||
|
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID: requestIndex,
|
requestID: requestIndex,
|
||||||
})
|
})
|
||||||
@@ -1938,6 +1938,12 @@ const exportJSONCollection = async () => {
|
|||||||
|
|
||||||
await getJSONCollection()
|
await getJSONCollection()
|
||||||
|
|
||||||
|
const parsedCollections = JSON.parse(collectionJSON.value)
|
||||||
|
|
||||||
|
if (!parsedCollections.length) {
|
||||||
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
|
}
|
||||||
|
|
||||||
initializeDownloadCollection(collectionJSON.value, null)
|
initializeDownloadCollection(collectionJSON.value, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,11 +83,14 @@ import {
|
|||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import { updateTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
import { updateTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
position: { top: number; left: number }
|
position: { top: number; left: number }
|
||||||
@@ -189,8 +192,8 @@ const addEnvironment = async () => {
|
|||||||
//replace the current tab endpoint with the variable name with << and >>
|
//replace the current tab endpoint with the variable name with << and >>
|
||||||
const variableName = `<<${editingName.value}>>`
|
const variableName = `<<${editingName.value}>>`
|
||||||
//replace the currenttab endpoint containing the value in the text with variablename
|
//replace the currenttab endpoint containing the value in the text with variablename
|
||||||
currentActiveTab.value.document.request.endpoint =
|
tabs.currentActiveTab.value.document.request.endpoint =
|
||||||
currentActiveTab.value.document.request.endpoint.replace(
|
tabs.currentActiveTab.value.document.request.endpoint.replace(
|
||||||
editingValue.value,
|
editingValue.value,
|
||||||
variableName
|
variableName
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -377,6 +377,13 @@ const importFromPostman = ({
|
|||||||
|
|
||||||
const exportJSON = () => {
|
const exportJSON = () => {
|
||||||
const dataToWrite = environmentJson.value
|
const dataToWrite = environmentJson.value
|
||||||
|
|
||||||
|
const parsedCollections = JSON.parse(dataToWrite)
|
||||||
|
|
||||||
|
if (!parsedCollections.length) {
|
||||||
|
return toast.error(t("error.no_environments_to_export"))
|
||||||
|
}
|
||||||
|
|
||||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
const a = document.createElement("a")
|
const a = document.createElement("a")
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
|||||||
@@ -478,7 +478,8 @@ watch(
|
|||||||
teamEnvListAdapter.changeTeamID(newVal.teamID)
|
teamEnvListAdapter.changeTeamID(newVal.teamID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const selectedEnv = computed(() => {
|
const selectedEnv = computed(() => {
|
||||||
|
|||||||
@@ -60,17 +60,7 @@
|
|||||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||||
:envs="liveEnvs"
|
:envs="liveEnvs"
|
||||||
:name="'value' + index"
|
: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">
|
<div class="flex">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
id="variable"
|
id="variable"
|
||||||
@@ -120,8 +110,6 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
|||||||
import IconDone from "~icons/lucide/check"
|
import IconDone from "~icons/lucide/check"
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconTrash from "~icons/lucide/trash"
|
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 { clone } from "lodash-es"
|
||||||
import { computed, ref, watch } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
@@ -152,7 +140,6 @@ type EnvironmentVariable = {
|
|||||||
env: {
|
env: {
|
||||||
key: string
|
key: string
|
||||||
value: string
|
value: string
|
||||||
secret: boolean
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +172,7 @@ const idTicker = ref(0)
|
|||||||
|
|
||||||
const editingName = ref<string | null>(null)
|
const editingName = ref<string | null>(null)
|
||||||
const vars = ref<EnvironmentVariable[]>([
|
const vars = ref<EnvironmentVariable[]>([
|
||||||
{ id: idTicker.value++, env: { key: "", value: "", secret: false } },
|
{ id: idTicker.value++, env: { key: "", value: "" } },
|
||||||
])
|
])
|
||||||
|
|
||||||
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
||||||
@@ -216,8 +203,6 @@ const workingEnv = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const oldEnvironments = ref<string[]>([])
|
|
||||||
|
|
||||||
const envList = useReadonlyStream(environments$, []) || props.envVars()
|
const envList = useReadonlyStream(environments$, []) || props.envVars()
|
||||||
|
|
||||||
const evnExpandError = computed(() => {
|
const evnExpandError = computed(() => {
|
||||||
@@ -249,28 +234,6 @@ 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(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
@@ -299,7 +262,6 @@ const addEnvironmentVariable = () => {
|
|||||||
env: {
|
env: {
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: "",
|
||||||
secret: false,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -308,23 +270,12 @@ const removeEnvironmentVariable = (index: number) => {
|
|||||||
vars.value.splice(index, 1)
|
vars.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleEnvironmentSecret = (index: number) => {
|
|
||||||
vars.value[index].env.secret = !vars.value[index].env.secret
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveEnvironment = () => {
|
const saveEnvironment = () => {
|
||||||
if (!editingName.value) {
|
if (!editingName.value) {
|
||||||
toast.error(`${t("environment.invalid_name")}`)
|
toast.error(`${t("environment.invalid_name")}`)
|
||||||
return
|
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(
|
const filterdVariables = pipe(
|
||||||
vars.value,
|
vars.value,
|
||||||
A.filterMap(
|
A.filterMap(
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
role="menu"
|
role="menu"
|
||||||
@keyup.e="edit!.$el.click()"
|
@keyup.e="edit!.$el.click()"
|
||||||
@keyup.d="duplicate!.$el.click()"
|
@keyup.d="duplicate!.$el.click()"
|
||||||
|
@keyup.j="exportAsJsonEl!.$el.click()"
|
||||||
@keyup.delete="
|
@keyup.delete="
|
||||||
!(environmentIndex === 'Global')
|
!(environmentIndex === 'Global')
|
||||||
? deleteAction!.$el.click()
|
? deleteAction!.$el.click()
|
||||||
@@ -77,6 +78,18 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="exportAsJsonEl"
|
||||||
|
:icon="IconEdit"
|
||||||
|
:label="`${t('export.as_json')}`"
|
||||||
|
:shortcut="['J']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
exportEnvironmentAsJSON()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
v-if="environmentIndex !== 'Global'"
|
v-if="environmentIndex !== 'Global'"
|
||||||
ref="deleteAction"
|
ref="deleteAction"
|
||||||
@@ -121,6 +134,7 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||||
|
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -136,10 +150,18 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const confirmRemove = ref(false)
|
const confirmRemove = ref(false)
|
||||||
|
|
||||||
|
const exportEnvironmentAsJSON = () => {
|
||||||
|
const { environment, environmentIndex } = props
|
||||||
|
exportAsJSON(environment, environmentIndex)
|
||||||
|
? toast.success(t("state.download_started"))
|
||||||
|
: toast.error(t("state.download_failed"))
|
||||||
|
}
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
const options = ref<TippyComponent | null>(null)
|
const options = ref<TippyComponent | null>(null)
|
||||||
const edit = ref<typeof HoppSmartItem>()
|
const edit = ref<typeof HoppSmartItem>()
|
||||||
const duplicate = ref<typeof HoppSmartItem>()
|
const duplicate = ref<typeof HoppSmartItem>()
|
||||||
|
const exportAsJsonEl = ref<typeof HoppSmartItem>()
|
||||||
const deleteAction = ref<typeof HoppSmartItem>()
|
const deleteAction = ref<typeof HoppSmartItem>()
|
||||||
|
|
||||||
const removeEnvironment = () => {
|
const removeEnvironment = () => {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
@click="displayModalImportExport(true)"
|
@click="displayModalImportExport(true)"
|
||||||
/>
|
/>
|
||||||
@@ -33,17 +33,32 @@
|
|||||||
@edit-environment="editEnvironment(index)"
|
@edit-environment="editEnvironment(index)"
|
||||||
/>
|
/>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="environments.length === 0"
|
v-if="!environments.length"
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div class="flex flex-col items-center space-y-4">
|
||||||
:label="`${t('add.new')}`"
|
<span class="text-secondaryLight text-center">
|
||||||
filled
|
{{ t("environment.import_or_create") }}
|
||||||
outline
|
</span>
|
||||||
@click="displayModalAdd(true)"
|
<div class="flex gap-4 flex-col items-stretch">
|
||||||
/>
|
<HoppButtonPrimary
|
||||||
|
:icon="IconImport"
|
||||||
|
:label="t('import.title')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="displayModalImportExport(true)"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:icon="IconPlus"
|
||||||
|
:label="`${t('add.new')}`"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<EnvironmentsMyDetails
|
<EnvironmentsMyDetails
|
||||||
:show="showModalDetails"
|
:show="showModalDetails"
|
||||||
@@ -66,8 +81,8 @@ import { environments$ } from "~/newstore/environments"
|
|||||||
import { useColorMode } from "~/composables/theming"
|
import { useColorMode } from "~/composables/theming"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import IconArchive from "~icons/lucide/archive"
|
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
import { Environment } from "@hoppscotch/data"
|
import { Environment } from "@hoppscotch/data"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
|||||||
@@ -63,17 +63,7 @@
|
|||||||
:envs="liveEnvs"
|
:envs="liveEnvs"
|
||||||
:name="'value' + index"
|
:name="'value' + index"
|
||||||
:readonly="isViewer"
|
: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">
|
<div v-if="!isViewer" class="flex">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
id="variable"
|
id="variable"
|
||||||
@@ -149,8 +139,6 @@ import IconTrash from "~icons/lucide/trash"
|
|||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
import IconDone from "~icons/lucide/check"
|
import IconDone from "~icons/lucide/check"
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconEye from "~icons/lucide/eye"
|
|
||||||
import IconEyeOff from "~icons/lucide/eye-off"
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
type EnvironmentVariable = {
|
type EnvironmentVariable = {
|
||||||
@@ -158,7 +146,6 @@ type EnvironmentVariable = {
|
|||||||
env: {
|
env: {
|
||||||
key: string
|
key: string
|
||||||
value: string
|
value: string
|
||||||
secret: boolean
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,10 +182,9 @@ const idTicker = ref(0)
|
|||||||
|
|
||||||
const editingName = ref<string | null>(null)
|
const editingName = ref<string | null>(null)
|
||||||
const vars = ref<EnvironmentVariable[]>([
|
const vars = ref<EnvironmentVariable[]>([
|
||||||
{ id: idTicker.value++, env: { key: "", value: "", secret: false } },
|
{ id: idTicker.value++, env: { key: "", value: "" } },
|
||||||
])
|
])
|
||||||
|
|
||||||
const oldEnvironments = ref<string[]>([])
|
|
||||||
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
||||||
IconTrash2,
|
IconTrash2,
|
||||||
1000
|
1000
|
||||||
@@ -226,28 +212,6 @@ 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(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
@@ -256,7 +220,7 @@ watch(
|
|||||||
editingName.value = null
|
editingName.value = null
|
||||||
vars.value = pipe(
|
vars.value = pipe(
|
||||||
props.envVars() ?? [],
|
props.envVars() ?? [],
|
||||||
A.map((e: { key: string; value: string; secret: boolean }) => ({
|
A.map((e: { key: string; value: string }) => ({
|
||||||
id: idTicker.value++,
|
id: idTicker.value++,
|
||||||
env: clone(e),
|
env: clone(e),
|
||||||
}))
|
}))
|
||||||
@@ -265,7 +229,7 @@ watch(
|
|||||||
editingName.value = props.editingEnvironment.environment.name ?? null
|
editingName.value = props.editingEnvironment.environment.name ?? null
|
||||||
vars.value = pipe(
|
vars.value = pipe(
|
||||||
props.editingEnvironment.environment.variables ?? [],
|
props.editingEnvironment.environment.variables ?? [],
|
||||||
A.map((e: { key: string; value: string; secret: boolean }) => ({
|
A.map((e: { key: string; value: string }) => ({
|
||||||
id: idTicker.value++,
|
id: idTicker.value++,
|
||||||
env: clone(e),
|
env: clone(e),
|
||||||
}))
|
}))
|
||||||
@@ -287,7 +251,6 @@ const addEnvironmentVariable = () => {
|
|||||||
env: {
|
env: {
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: "",
|
||||||
secret: false,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -296,10 +259,6 @@ const removeEnvironmentVariable = (index: number) => {
|
|||||||
vars.value.splice(index, 1)
|
vars.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleEnvironmentSecret = (index: number) => {
|
|
||||||
vars.value[index].env.secret = !vars.value[index].env.secret
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
|
||||||
const saveEnvironment = async () => {
|
const saveEnvironment = async () => {
|
||||||
@@ -310,15 +269,6 @@ const saveEnvironment = async () => {
|
|||||||
return
|
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(
|
const filterdVariables = pipe(
|
||||||
vars.value,
|
vars.value,
|
||||||
A.filterMap(
|
A.filterMap(
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
role="menu"
|
role="menu"
|
||||||
@keyup.e="edit!.$el.click()"
|
@keyup.e="edit!.$el.click()"
|
||||||
@keyup.d="duplicate!.$el.click()"
|
@keyup.d="duplicate!.$el.click()"
|
||||||
|
@keyup.j="exportAsJsonEl!.$el.click()"
|
||||||
@keyup.delete="deleteAction!.$el.click()"
|
@keyup.delete="deleteAction!.$el.click()"
|
||||||
@keyup.escape="options!.tippy().hide()"
|
@keyup.escape="options!.tippy().hide()"
|
||||||
>
|
>
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
ref="duplicate"
|
ref="duplicate"
|
||||||
:icon="IconCopy"
|
:icon="IconCopy"
|
||||||
@@ -66,6 +68,18 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="exportAsJsonEl"
|
||||||
|
:icon="IconEdit"
|
||||||
|
:label="`${t('export.as_json')}`"
|
||||||
|
:shortcut="['J']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
exportEnvironmentAsJSON()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
ref="deleteAction"
|
ref="deleteAction"
|
||||||
:icon="IconTrash2"
|
:icon="IconTrash2"
|
||||||
@@ -109,6 +123,7 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
|||||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||||
|
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -124,11 +139,17 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const confirmRemove = ref(false)
|
const confirmRemove = ref(false)
|
||||||
|
|
||||||
|
const exportEnvironmentAsJSON = () =>
|
||||||
|
exportAsJSON(props.environment)
|
||||||
|
? toast.success(t("state.download_started"))
|
||||||
|
: toast.error(t("state.download_failed"))
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
const options = ref<TippyComponent | null>(null)
|
const options = ref<TippyComponent | null>(null)
|
||||||
const edit = ref<typeof HoppSmartItem>()
|
const edit = ref<typeof HoppSmartItem>()
|
||||||
const duplicate = ref<typeof HoppSmartItem>()
|
const duplicate = ref<typeof HoppSmartItem>()
|
||||||
const deleteAction = ref<typeof HoppSmartItem>()
|
const deleteAction = ref<typeof HoppSmartItem>()
|
||||||
|
const exportAsJsonEl = ref<typeof HoppSmartItem>()
|
||||||
|
|
||||||
const removeEnvironment = () => {
|
const removeEnvironment = () => {
|
||||||
pipe(
|
pipe(
|
||||||
|
|||||||
@@ -31,40 +31,49 @@
|
|||||||
v-if="team !== undefined && team.myRole === 'VIEWER'"
|
v-if="team !== undefined && team.myRole === 'VIEWER'"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
disabled
|
disabled
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-else
|
v-else
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconArchive"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
@click="displayModalImportExport(true)"
|
@click="displayModalImportExport(true)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="!loading && teamEnvironments.length === 0 && !adapterError"
|
v-if="!loading && !teamEnvironments.length && !adapterError"
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div class="flex flex-col items-center space-y-4">
|
||||||
v-if="team === undefined || team.myRole === 'VIEWER'"
|
<span class="text-secondaryLight text-center">
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
{{ t("environment.import_or_create") }}
|
||||||
disabled
|
</span>
|
||||||
filled
|
<div class="flex gap-4 flex-col items-stretch">
|
||||||
:icon="IconPlus"
|
<HoppButtonPrimary
|
||||||
:title="t('team.no_access')"
|
:icon="IconImport"
|
||||||
:label="t('action.new')"
|
:label="t('import.title')"
|
||||||
/>
|
filled
|
||||||
<HoppButtonSecondary
|
outline
|
||||||
v-else
|
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||||
:label="`${t('add.new')}`"
|
:disabled="isTeamViewer"
|
||||||
filled
|
@click="isTeamViewer ? null : displayModalImportExport(true)"
|
||||||
outline
|
/>
|
||||||
@click="displayModalAdd(true)"
|
<HoppButtonSecondary
|
||||||
/>
|
:label="`${t('add.new')}`"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
:icon="IconPlus"
|
||||||
|
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||||
|
:disabled="isTeamViewer"
|
||||||
|
@click="isTeamViewer ? null : displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div v-else-if="!loading">
|
<div v-else-if="!loading">
|
||||||
<EnvironmentsTeamsEnvironment
|
<EnvironmentsTeamsEnvironment
|
||||||
@@ -108,14 +117,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { useColorMode } from "~/composables/theming"
|
import { useColorMode } from "~/composables/theming"
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconArchive from "~icons/lucide/archive"
|
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
|
|
||||||
@@ -138,6 +147,8 @@ const action = ref<"new" | "edit">("edit")
|
|||||||
const editingEnvironment = ref<TeamEnvironment | null>(null)
|
const editingEnvironment = ref<TeamEnvironment | null>(null)
|
||||||
const editingVariableName = ref("")
|
const editingVariableName = ref("")
|
||||||
|
|
||||||
|
const isTeamViewer = computed(() => props.team?.myRole === "VIEWER")
|
||||||
|
|
||||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||||
action.value = "new"
|
action.value = "new"
|
||||||
showModalDetails.value = shouldDisplay
|
showModalDetails.value = shouldDisplay
|
||||||
|
|||||||
@@ -64,7 +64,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { currentActiveTab } from "~/helpers/graphql/tab"
|
|
||||||
import { computed, ref, watch } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import { connection } from "~/helpers/graphql/connection"
|
import { connection } from "~/helpers/graphql/connection"
|
||||||
import { connect } from "~/helpers/graphql/connection"
|
import { connect } from "~/helpers/graphql/connection"
|
||||||
@@ -72,8 +71,10 @@ import { disconnect } from "~/helpers/graphql/connection"
|
|||||||
import { InterceptorService } from "~/services/interceptor.service"
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
const interceptorService = useService(InterceptorService)
|
const interceptorService = useService(InterceptorService)
|
||||||
|
|
||||||
@@ -82,9 +83,9 @@ const connectionSwitchModal = ref(false)
|
|||||||
const connected = computed(() => connection.state === "CONNECTED")
|
const connected = computed(() => connection.state === "CONNECTED")
|
||||||
|
|
||||||
const url = computed({
|
const url = computed({
|
||||||
get: () => currentActiveTab.value?.document.request.url ?? "",
|
get: () => tabs.currentActiveTab.value?.document.request.url ?? "",
|
||||||
set: (value) => {
|
set: (value) => {
|
||||||
currentActiveTab.value!.document.request.url = value
|
tabs.currentActiveTab.value!.document.request.url = value
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ const onConnectClick = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const gqlConnect = () => {
|
const gqlConnect = () => {
|
||||||
connect(url.value, currentActiveTab.value?.document.request.headers)
|
connect(url.value, tabs.currentActiveTab.value?.document.request.headers)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_REQUEST_RUN",
|
type: "HOPP_REQUEST_RUN",
|
||||||
@@ -114,7 +115,7 @@ const switchConnection = () => {
|
|||||||
const lastTwoUrls = ref<string[]>([])
|
const lastTwoUrls = ref<string[]>([])
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
currentActiveTab,
|
tabs.currentActiveTab,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
lastTwoUrls.value.push(newVal.document.request.url)
|
lastTwoUrls.value.push(newVal.document.request.url)
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ import { computed, ref, watch } from "vue"
|
|||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { currentActiveTab } from "~/helpers/graphql/tab"
|
import { computedWithControl, useVModel } from "@vueuse/core"
|
||||||
import { computedWithControl } from "@vueuse/core"
|
|
||||||
import {
|
import {
|
||||||
GQLResponseEvent,
|
GQLResponseEvent,
|
||||||
runGQLOperation,
|
runGQLOperation,
|
||||||
@@ -68,26 +67,39 @@ import {
|
|||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
import { editGraphqlRequest } from "~/newstore/collections"
|
import { editGraphqlRequest } from "~/newstore/collections"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
|
const VALID_GQL_OPERATIONS = [
|
||||||
|
"query",
|
||||||
|
"headers",
|
||||||
|
"variables",
|
||||||
|
"authorization",
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type GQLOptionTabs = (typeof VALID_GQL_OPERATIONS)[number]
|
||||||
|
|
||||||
export type GQLOptionTabs = "query" | "headers" | "variables" | "authorization"
|
|
||||||
const selectedOptionTab = ref<GQLOptionTabs>("query")
|
|
||||||
const interceptorService = useService(InterceptorService)
|
const interceptorService = useService(InterceptorService)
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
// v-model integration with props and emit
|
// v-model integration with props and emit
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
modelValue: HoppGQLRequest
|
modelValue: HoppGQLRequest
|
||||||
response?: GQLResponseEvent[] | null
|
response?: GQLResponseEvent[] | null
|
||||||
|
optionTab?: GQLOptionTabs
|
||||||
tabId: string
|
tabId: string
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
response: null,
|
response: null,
|
||||||
|
optionTab: "query",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const emit = defineEmits(["update:modelValue", "update:response"])
|
const emit = defineEmits(["update:modelValue", "update:response"])
|
||||||
|
const selectedOptionTab = useVModel(props, "optionTab", emit)
|
||||||
|
|
||||||
const request = ref(props.modelValue)
|
const request = ref(props.modelValue)
|
||||||
|
|
||||||
@@ -100,8 +112,8 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const url = computedWithControl(
|
const url = computedWithControl(
|
||||||
() => currentActiveTab.value,
|
() => tabs.currentActiveTab.value,
|
||||||
() => currentActiveTab.value.document.request.url
|
() => tabs.currentActiveTab.value.document.request.url
|
||||||
)
|
)
|
||||||
|
|
||||||
const activeGQLHeadersCount = computed(
|
const activeGQLHeadersCount = computed(
|
||||||
@@ -136,6 +148,9 @@ const runQuery = async (
|
|||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
completePageProgress()
|
completePageProgress()
|
||||||
toast.success(`${t("state.finished_in", { duration })}`)
|
toast.success(`${t("state.finished_in", { duration })}`)
|
||||||
|
if (definition?.operation === "subscription" && request.value.auth) {
|
||||||
|
toast.success(t("authorization.graphql_headers"))
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
// response.value = [`${e}`]
|
// response.value = [`${e}`]
|
||||||
@@ -182,17 +197,17 @@ const hideRequestModal = () => {
|
|||||||
}
|
}
|
||||||
const saveRequest = () => {
|
const saveRequest = () => {
|
||||||
if (
|
if (
|
||||||
currentActiveTab.value.document.saveContext &&
|
tabs.currentActiveTab.value.document.saveContext &&
|
||||||
currentActiveTab.value.document.saveContext.originLocation ===
|
tabs.currentActiveTab.value.document.saveContext.originLocation ===
|
||||||
"user-collection"
|
"user-collection"
|
||||||
) {
|
) {
|
||||||
editGraphqlRequest(
|
editGraphqlRequest(
|
||||||
currentActiveTab.value.document.saveContext.folderPath,
|
tabs.currentActiveTab.value.document.saveContext.folderPath,
|
||||||
currentActiveTab.value.document.saveContext.requestIndex,
|
tabs.currentActiveTab.value.document.saveContext.requestIndex,
|
||||||
currentActiveTab.value.document.request
|
tabs.currentActiveTab.value.document.request
|
||||||
)
|
)
|
||||||
|
|
||||||
currentActiveTab.value.document.isDirty = false
|
tabs.currentActiveTab.value.document.isDirty = false
|
||||||
} else {
|
} else {
|
||||||
showSaveRequestModal.value = true
|
showSaveRequestModal.value = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
<template #primary>
|
<template #primary>
|
||||||
<GraphqlRequestOptions
|
<GraphqlRequestOptions
|
||||||
v-model="tab.document.request"
|
v-model="tab.document.request"
|
||||||
v-model:response="tab.response"
|
v-model:response="tab.document.response"
|
||||||
|
v-model:option-tab="tab.document.optionTabPreference"
|
||||||
:tab-id="tab.id"
|
:tab-id="tab.id"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #secondary>
|
<template #secondary>
|
||||||
<GraphqlResponse :response="tab.response" />
|
<GraphqlResponse :response="tab.document.response" />
|
||||||
</template>
|
</template>
|
||||||
</AppPaneLayout>
|
</AppPaneLayout>
|
||||||
</template>
|
</template>
|
||||||
@@ -18,14 +19,15 @@ import { useVModel } from "@vueuse/core"
|
|||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { watch } from "vue"
|
import { watch } from "vue"
|
||||||
import { isEqualHoppGQLRequest } from "~/helpers/graphql"
|
import { isEqualHoppGQLRequest } from "~/helpers/graphql"
|
||||||
import { HoppGQLTab } from "~/helpers/graphql/tab"
|
import { HoppGQLDocument } from "~/helpers/graphql/document"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
|
||||||
// TODO: Move Response and Request execution code to over here
|
// TODO: Move Response and Request execution code to over here
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: HoppGQLTab }>()
|
const props = defineProps<{ modelValue: HoppTab<HoppGQLDocument> }>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:modelValue", val: HoppGQLTab): void
|
(e: "update:modelValue", val: HoppTab<HoppGQLDocument>): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tab = useVModel(props, "modelValue", emit)
|
const tab = useVModel(props, "modelValue", emit)
|
||||||
|
|||||||
@@ -92,12 +92,13 @@ import IconXCircle from "~icons/lucide/x-circle"
|
|||||||
import IconXSquare from "~icons/lucide/x-square"
|
import IconXSquare from "~icons/lucide/x-square"
|
||||||
import IconFileEdit from "~icons/lucide/file-edit"
|
import IconFileEdit from "~icons/lucide/file-edit"
|
||||||
import IconCopy from "~icons/lucide/copy"
|
import IconCopy from "~icons/lucide/copy"
|
||||||
import { HoppGQLTab } from "~/helpers/graphql/tab"
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppGQLDocument } from "~/helpers/graphql/document"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
tab: HoppGQLTab
|
tab: HoppTab<HoppGQLDocument>
|
||||||
isRemovable: boolean
|
isRemovable: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -67,9 +67,11 @@ import IconMaximize2 from "~icons/lucide/maximize-2"
|
|||||||
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { makeGQLRequest } from "@hoppscotch/data"
|
import { makeGQLRequest } from "@hoppscotch/data"
|
||||||
import { createNewTab } from "~/helpers/graphql/tab"
|
import { useService } from "dioc/vue"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
entry: GQLHistoryEntry
|
entry: GQLHistoryEntry
|
||||||
@@ -93,7 +95,7 @@ const query = computed(() =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
const useEntry = () => {
|
const useEntry = () => {
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: makeGQLRequest({
|
request: makeGQLRequest({
|
||||||
name: props.entry.request.name,
|
name: props.entry.request.name,
|
||||||
url: props.entry.request.url,
|
url: props.entry.request.url,
|
||||||
|
|||||||
@@ -176,8 +176,9 @@ import {
|
|||||||
|
|
||||||
import HistoryRestCard from "./rest/Card.vue"
|
import HistoryRestCard from "./rest/Card.vue"
|
||||||
import HistoryGraphqlCard from "./graphql/Card.vue"
|
import HistoryGraphqlCard from "./graphql/Card.vue"
|
||||||
import { createNewTab } from "~/helpers/rest/tab"
|
|
||||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
type HistoryEntry = GQLHistoryEntry | RESTHistoryEntry
|
type HistoryEntry = GQLHistoryEntry | RESTHistoryEntry
|
||||||
|
|
||||||
@@ -293,8 +294,9 @@ const clearHistory = () => {
|
|||||||
|
|
||||||
// NOTE: For GQL, the HistoryGraphqlCard component already implements useEntry
|
// NOTE: For GQL, the HistoryGraphqlCard component already implements useEntry
|
||||||
// (That is not a really good behaviour tho ¯\_(ツ)_/¯)
|
// (That is not a really good behaviour tho ¯\_(ツ)_/¯)
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
const useHistory = (entry: RESTHistoryEntry) => {
|
const useHistory = (entry: RESTHistoryEntry) => {
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: entry.request,
|
request: entry.request,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -59,7 +59,9 @@
|
|||||||
:key="`contentTypeItem-${contentTypeIndex}`"
|
:key="`contentTypeItem-${contentTypeIndex}`"
|
||||||
:label="contentTypeItem"
|
:label="contentTypeItem"
|
||||||
:info-icon="
|
:info-icon="
|
||||||
contentTypeItem === body.contentType ? IconDone : null
|
contentTypeItem === body.contentType
|
||||||
|
? IconDone
|
||||||
|
: undefined
|
||||||
"
|
"
|
||||||
:active-info-icon="contentTypeItem === body.contentType"
|
:active-info-icon="contentTypeItem === body.contentType"
|
||||||
@click="
|
@click="
|
||||||
@@ -136,7 +138,7 @@ import IconDone from "~icons/lucide/check"
|
|||||||
import IconExternalLink from "~icons/lucide/external-link"
|
import IconExternalLink from "~icons/lucide/external-link"
|
||||||
import IconInfo from "~icons/lucide/info"
|
import IconInfo from "~icons/lucide/info"
|
||||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||||
import { RequestOptionTabs } from "./RequestOptions.vue"
|
import { RESTOptionTabs } from "./RequestOptions.vue"
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@@ -147,7 +149,7 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "change-tab", value: RequestOptionTabs): void
|
(e: "change-tab", value: RESTOptionTabs): void
|
||||||
(e: "update:headers", value: HoppRESTHeader[]): void
|
(e: "update:headers", value: HoppRESTHeader[]): void
|
||||||
(e: "update:body", value: HoppRESTReqBody): void
|
(e: "update:body", value: HoppRESTReqBody): void
|
||||||
}>()
|
}>()
|
||||||
@@ -164,7 +166,7 @@ const overridenContentType = computed(() =>
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const contentTypeOverride = (tab: RequestOptionTabs) => {
|
const contentTypeOverride = (tab: RESTOptionTabs) => {
|
||||||
emit("change-tab", tab)
|
emit("change-tab", tab)
|
||||||
if (!isContentTypeAlreadyExist()) {
|
if (!isContentTypeAlreadyExist()) {
|
||||||
// TODO: Fix this
|
// TODO: Fix this
|
||||||
|
|||||||
@@ -157,9 +157,10 @@ import {
|
|||||||
import IconCopy from "~icons/lucide/copy"
|
import IconCopy from "~icons/lucide/copy"
|
||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
import IconWrapText from "~icons/lucide/wrap-text"
|
import IconWrapText from "~icons/lucide/wrap-text"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
|
||||||
import cloneDeep from "lodash-es/cloneDeep"
|
import cloneDeep from "lodash-es/cloneDeep"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -173,7 +174,8 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const request = ref(cloneDeep(currentActiveTab.value.document.request))
|
const tabs = useService(RESTTabService)
|
||||||
|
const request = ref(cloneDeep(tabs.currentActiveTab.value.document.request))
|
||||||
const codegenType = ref<CodegenName>("shell-curl")
|
const codegenType = ref<CodegenName>("shell-curl")
|
||||||
const errorState = ref(false)
|
const errorState = ref(false)
|
||||||
|
|
||||||
@@ -242,7 +244,7 @@ watch(
|
|||||||
() => props.show,
|
() => props.show,
|
||||||
(goingToShow) => {
|
(goingToShow) => {
|
||||||
if (goingToShow) {
|
if (goingToShow) {
|
||||||
request.value = cloneDeep(currentActiveTab.value.document.request)
|
request.value = cloneDeep(tabs.currentActiveTab.value.document.request)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_REST_CODEGEN_OPENED",
|
type: "HOPP_REST_CODEGEN_OPENED",
|
||||||
|
|||||||
@@ -185,18 +185,24 @@
|
|||||||
<span>
|
<span>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="header.source === 'auth'"
|
v-if="header.source === 'auth'"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t(masking ? 'state.show' : 'state.hide')"
|
||||||
:icon="masking ? IconEye : IconEyeOff"
|
:icon="masking ? IconEye : IconEyeOff"
|
||||||
@click="toggleMask()"
|
@click="toggleMask()"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-else
|
v-else
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconArrowUpRight"
|
:icon="IconArrowUpRight"
|
||||||
|
:title="t('request.go_to_authorization_tab')"
|
||||||
class="cursor-auto text-primary hover:text-primary"
|
class="cursor-auto text-primary hover:text-primary"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconArrowUpRight"
|
:icon="IconArrowUpRight"
|
||||||
|
:title="t('request.go_to_authorization_tab')"
|
||||||
@click="changeTab(header.source)"
|
@click="changeTab(header.source)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -267,10 +273,13 @@ import { aggregateEnvs$, getAggregateEnvs } from "~/newstore/environments"
|
|||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService, InspectorResult } from "~/services/inspection"
|
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||||
import { currentTabID } from "~/helpers/rest/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const idTicker = ref(0)
|
const idTicker = ref(0)
|
||||||
@@ -509,13 +518,13 @@ const changeTab = (tab: ComputedHeader["source"]) => {
|
|||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const headerKeyResults = inspectionService.getResultViewFor(
|
const headerKeyResults = inspectionService.getResultViewFor(
|
||||||
currentTabID.value,
|
tabs.currentTabID.value,
|
||||||
(result) =>
|
(result) =>
|
||||||
result.locations.type === "header" && result.locations.position === "key"
|
result.locations.type === "header" && result.locations.position === "key"
|
||||||
)
|
)
|
||||||
|
|
||||||
const headerValueResults = inspectionService.getResultViewFor(
|
const headerValueResults = inspectionService.getResultViewFor(
|
||||||
currentTabID.value,
|
tabs.currentTabID.value,
|
||||||
(result) =>
|
(result) =>
|
||||||
result.locations.type === "header" && result.locations.position === "value"
|
result.locations.type === "header" && result.locations.position === "value"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -93,13 +93,16 @@ import IconWrapText from "~icons/lucide/wrap-text"
|
|||||||
import IconClipboard from "~icons/lucide/clipboard"
|
import IconClipboard from "~icons/lucide/clipboard"
|
||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const curl = ref("")
|
const curl = ref("")
|
||||||
|
|
||||||
const curlEditor = ref<any | null>(null)
|
const curlEditor = ref<any | null>(null)
|
||||||
@@ -149,7 +152,7 @@ const handleImport = () => {
|
|||||||
type: "HOPP_REST_IMPORT_CURL",
|
type: "HOPP_REST_IMPORT_CURL",
|
||||||
})
|
})
|
||||||
|
|
||||||
currentActiveTab.value.document.request = req
|
tabs.currentActiveTab.value.document.request = req
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast.error(`${t("error.curl_invalid_format")}`)
|
toast.error(`${t("error.curl_invalid_format")}`)
|
||||||
|
|||||||
@@ -202,12 +202,13 @@ import { objRemoveKey } from "@functional/object"
|
|||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService, InspectorResult } from "~/services/inspection"
|
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||||
import { currentTabID } from "~/helpers/rest/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const idTicker = ref(0)
|
const idTicker = ref(0)
|
||||||
|
|
||||||
@@ -410,13 +411,13 @@ const clearContent = () => {
|
|||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const parameterKeyResults = inspectionService.getResultViewFor(
|
const parameterKeyResults = inspectionService.getResultViewFor(
|
||||||
currentTabID.value,
|
tabs.currentTabID.value,
|
||||||
(result) =>
|
(result) =>
|
||||||
result.locations.type === "parameter" && result.locations.position === "key"
|
result.locations.type === "parameter" && result.locations.position === "key"
|
||||||
)
|
)
|
||||||
|
|
||||||
const parameterValueResults = inspectionService.getResultViewFor(
|
const parameterValueResults = inspectionService.getResultViewFor(
|
||||||
currentTabID.value,
|
tabs.currentTabID.value,
|
||||||
(result) =>
|
(result) =>
|
||||||
result.locations.type === "parameter" &&
|
result.locations.type === "parameter" &&
|
||||||
result.locations.position === "value"
|
result.locations.position === "value"
|
||||||
|
|||||||
@@ -217,6 +217,7 @@
|
|||||||
@hide-modal="showCurlImportModal = false"
|
@hide-modal="showCurlImportModal = false"
|
||||||
/>
|
/>
|
||||||
<HttpCodegenModal
|
<HttpCodegenModal
|
||||||
|
v-if="showCodegenModal"
|
||||||
:show="showCodegenModal"
|
:show="showCodegenModal"
|
||||||
@hide-modal="showCodegenModal = false"
|
@hide-modal="showCodegenModal = false"
|
||||||
/>
|
/>
|
||||||
@@ -257,7 +258,6 @@ import IconLink2 from "~icons/lucide/link-2"
|
|||||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||||
import IconSave from "~icons/lucide/save"
|
import IconSave from "~icons/lucide/save"
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
import { HoppRESTTab, currentTabID } from "~/helpers/rest/tab"
|
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { RESTHistoryEntry, restHistory$ } from "~/newstore/history"
|
import { RESTHistoryEntry, restHistory$ } from "~/newstore/history"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
@@ -265,6 +265,9 @@ import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
|||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const interceptorService = useService(InterceptorService)
|
const interceptorService = useService(InterceptorService)
|
||||||
@@ -286,7 +289,7 @@ const toast = useToast()
|
|||||||
|
|
||||||
const { subscribeToStream } = useStreamSubscriber()
|
const { subscribeToStream } = useStreamSubscriber()
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: HoppRESTTab }>()
|
const props = defineProps<{ modelValue: HoppTab<HoppRESTDocument> }>()
|
||||||
const emit = defineEmits(["update:modelValue"])
|
const emit = defineEmits(["update:modelValue"])
|
||||||
|
|
||||||
const tab = useVModel(props, "modelValue", emit)
|
const tab = useVModel(props, "modelValue", emit)
|
||||||
@@ -426,7 +429,7 @@ const updateMethod = (method: string) => {
|
|||||||
|
|
||||||
const onSelectMethod = (e: Event | any) => {
|
const onSelectMethod = (e: Event | any) => {
|
||||||
// type any because of value property not being recognized by TS in the event.target object. It is a valid property though.
|
// type any because of value property not being recognized by TS in the event.target object. It is a valid property though.
|
||||||
updateMethod(e.value)
|
updateMethod(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearContent = () => {
|
const clearContent = () => {
|
||||||
@@ -434,7 +437,7 @@ const clearContent = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateRESTResponse = (response: HoppRESTResponse | null) => {
|
const updateRESTResponse = (response: HoppRESTResponse | null) => {
|
||||||
tab.value.response = response
|
tab.value.document.response = response
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyLinkIcon = refAutoReset<
|
const copyLinkIcon = refAutoReset<
|
||||||
@@ -642,5 +645,6 @@ const COLUMN_LAYOUT = useSetting("COLUMN_LAYOUT")
|
|||||||
|
|
||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const tabResults = inspectionService.getResultViewFor(currentTabID.value)
|
const tabs = useService(RESTTabService)
|
||||||
|
const tabResults = inspectionService.getResultViewFor(tabs.currentTabID.value)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<HoppSmartTabs
|
<HoppSmartTabs
|
||||||
v-model="selectedOptionsTab"
|
v-model="selectedOptionTab"
|
||||||
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-upperMobilePrimaryStickyFold sm:top-upperPrimaryStickyFold z-10"
|
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-upperMobilePrimaryStickyFold sm:top-upperPrimaryStickyFold z-10"
|
||||||
render-inactive-tabs
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<HttpBody
|
<HttpBody
|
||||||
v-model:headers="request.headers"
|
v-model:headers="request.headers"
|
||||||
v-model:body="request.body"
|
v-model:body="request.body"
|
||||||
@change-tab="changeTab"
|
@change-tab="changeOptionTab"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
:label="`${t('tab.headers')}`"
|
:label="`${t('tab.headers')}`"
|
||||||
:info="`${newActiveHeadersCount$}`"
|
:info="`${newActiveHeadersCount$}`"
|
||||||
>
|
>
|
||||||
<HttpHeaders v-model="request" @change-tab="changeTab" />
|
<HttpHeaders v-model="request" @change-tab="changeOptionTab" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
|
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
|
||||||
<HttpAuthorization v-model="request.auth" />
|
<HttpAuthorization v-model="request.auth" />
|
||||||
@@ -55,31 +55,43 @@
|
|||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { computed, ref } from "vue"
|
import { computed } from "vue"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
|
||||||
export type RequestOptionTabs =
|
const VALID_OPTION_TABS = [
|
||||||
| "params"
|
"params",
|
||||||
| "bodyParams"
|
"bodyParams",
|
||||||
| "headers"
|
"headers",
|
||||||
| "authorization"
|
"authorization",
|
||||||
| "preRequestScript"
|
"preRequestScript",
|
||||||
| "tests"
|
"tests",
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type RESTOptionTabs = (typeof VALID_OPTION_TABS)[number]
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
// v-model integration with props and emit
|
// v-model integration with props and emit
|
||||||
const props = defineProps<{ modelValue: HoppRESTRequest }>()
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: HoppRESTRequest
|
||||||
|
optionTab: RESTOptionTabs
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
optionTab: "params",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:modelValue", value: HoppRESTRequest): void
|
(e: "update:modelValue", value: HoppRESTRequest): void
|
||||||
|
(e: "update:optionTab", value: RESTOptionTabs): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const request = useVModel(props, "modelValue", emit)
|
const request = useVModel(props, "modelValue", emit)
|
||||||
|
const selectedOptionTab = useVModel(props, "optionTab", emit)
|
||||||
|
|
||||||
const selectedOptionsTab = ref<RequestOptionTabs>("params")
|
const changeOptionTab = (e: RESTOptionTabs) => {
|
||||||
|
selectedOptionTab.value = e
|
||||||
const changeTab = (e: RequestOptionTabs) => {
|
|
||||||
selectedOptionsTab.value = e
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newActiveParamsCount$ = computed(() => {
|
const newActiveParamsCount$ = computed(() => {
|
||||||
@@ -101,6 +113,6 @@ const newActiveHeadersCount$ = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
defineActionHandler("request.open-tab", ({ tab }) => {
|
defineActionHandler("request.open-tab", ({ tab }) => {
|
||||||
selectedOptionsTab.value = tab as RequestOptionTabs
|
selectedOptionTab.value = tab as RESTOptionTabs
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
<AppPaneLayout layout-id="rest-primary">
|
<AppPaneLayout layout-id="rest-primary">
|
||||||
<template #primary>
|
<template #primary>
|
||||||
<HttpRequest v-model="tab" />
|
<HttpRequest v-model="tab" />
|
||||||
<HttpRequestOptions v-model="tab.document.request" />
|
<HttpRequestOptions
|
||||||
|
v-model="tab.document.request"
|
||||||
|
v-model:option-tab="tab.document.optionTabPreference"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #secondary>
|
<template #secondary>
|
||||||
<HttpResponse v-model:tab="tab" />
|
<HttpResponse v-model:document="tab.document" />
|
||||||
</template>
|
</template>
|
||||||
</AppPaneLayout>
|
</AppPaneLayout>
|
||||||
</template>
|
</template>
|
||||||
@@ -13,16 +16,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { watch } from "vue"
|
import { watch } from "vue"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { isEqualHoppRESTRequest } from "@hoppscotch/data"
|
import { isEqualHoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
// TODO: Move Response and Request execution code to over here
|
// TODO: Move Response and Request execution code to over here
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: HoppRESTTab }>()
|
const props = defineProps<{ modelValue: HoppTab<HoppRESTDocument> }>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:modelValue", val: HoppRESTTab): void
|
(e: "update:modelValue", val: HoppTab<HoppRESTDocument>): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tab = useVModel(props, "modelValue", emit)
|
const tab = useVModel(props, "modelValue", emit)
|
||||||
|
|||||||
@@ -1,36 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col flex-1 relative">
|
<div class="flex flex-col flex-1 relative">
|
||||||
<HttpResponseMeta :response="tab.response" />
|
<HttpResponseMeta :response="doc.response" />
|
||||||
<LensesResponseBodyRenderer
|
<LensesResponseBodyRenderer
|
||||||
v-if="!loading && hasResponse"
|
v-if="!loading && hasResponse"
|
||||||
v-model:selected-tab-preference="selectedTabPreference"
|
v-model:document="doc"
|
||||||
v-model:tab="tab"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from "vue"
|
|
||||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tab: HoppRESTTab
|
document: HoppRESTDocument
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:tab", val: HoppRESTTab): void
|
(e: "update:tab", val: HoppRESTDocument): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tab = useVModel(props, "tab", emit)
|
const doc = useVModel(props, "document", emit)
|
||||||
|
|
||||||
const selectedTabPreference = ref<string | null>(null)
|
|
||||||
|
|
||||||
const hasResponse = computed(
|
const hasResponse = computed(
|
||||||
() =>
|
() =>
|
||||||
tab.value.response?.type === "success" ||
|
doc.value.response?.type === "success" ||
|
||||||
tab.value.response?.type === "fail"
|
doc.value.response?.type === "fail"
|
||||||
)
|
)
|
||||||
|
|
||||||
const loading = computed(() => tab.value.response?.type === "loading")
|
const loading = computed(() => doc.value.response?.type === "loading")
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -93,10 +93,11 @@ import { useColorMode } from "@composables/theming"
|
|||||||
import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
import { currentTabID } from "~/helpers/rest/tab"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
response: HoppRESTResponse | null | undefined
|
response: HoppRESTResponse | null | undefined
|
||||||
@@ -146,7 +147,7 @@ const statusCategory = computed(() => {
|
|||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const tabResults = inspectionService.getResultViewFor(
|
const tabResults = inspectionService.getResultViewFor(
|
||||||
currentTabID.value,
|
tabs.currentTabID.value,
|
||||||
(result) => result.locations.type === "response"
|
(result) => result.locations.type === "response"
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -96,16 +96,17 @@ import { ref } from "vue"
|
|||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
|
||||||
import IconXCircle from "~icons/lucide/x-circle"
|
import IconXCircle from "~icons/lucide/x-circle"
|
||||||
import IconXSquare from "~icons/lucide/x-square"
|
import IconXSquare from "~icons/lucide/x-square"
|
||||||
import IconFileEdit from "~icons/lucide/file-edit"
|
import IconFileEdit from "~icons/lucide/file-edit"
|
||||||
import IconCopy from "~icons/lucide/copy"
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
tab: HoppRESTTab
|
tab: HoppTab<HoppRESTDocument>
|
||||||
isRemovable: boolean
|
isRemovable: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -279,7 +279,6 @@ const globalEnvVars = useReadonlyStream(globalEnv$, []) as Ref<
|
|||||||
Array<{
|
Array<{
|
||||||
key: string
|
key: string
|
||||||
value: string
|
value: string
|
||||||
secret: boolean
|
|
||||||
}>
|
}>
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ type Props = {
|
|||||||
env: {
|
env: {
|
||||||
key: string
|
key: string
|
||||||
value: string
|
value: string
|
||||||
secret: boolean
|
|
||||||
previousValue?: string
|
previousValue?: string
|
||||||
}
|
}
|
||||||
status: Status
|
status: Status
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<HoppSmartTabs
|
<HoppSmartTabs
|
||||||
v-if="tab.response"
|
v-if="doc.response"
|
||||||
v-model="selectedLensTab"
|
v-model="selectedLensTab"
|
||||||
styles="sticky overflow-x-auto flex-shrink-0 z-10 bg-primary top-lowerPrimaryStickyFold"
|
styles="sticky overflow-x-auto flex-shrink-0 z-10 bg-primary top-lowerPrimaryStickyFold"
|
||||||
>
|
>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="lensRendererFor(lens.renderer)"
|
:is="lensRendererFor(lens.renderer)"
|
||||||
:response="tab.response"
|
:response="doc.response"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
@@ -28,19 +28,10 @@
|
|||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
id="results"
|
id="results"
|
||||||
:label="t('test.results')"
|
:label="t('test.results')"
|
||||||
:indicator="
|
:indicator="showIndicator"
|
||||||
tab.testResults &&
|
|
||||||
(tab.testResults.expectResults.length ||
|
|
||||||
tab.testResults.tests.length ||
|
|
||||||
tab.testResults.envDiff.selected.additions.length ||
|
|
||||||
tab.testResults.envDiff.selected.updations.length ||
|
|
||||||
tab.testResults.envDiff.global.updations.length)
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
"
|
|
||||||
class="flex flex-col flex-1"
|
class="flex flex-col flex-1"
|
||||||
>
|
>
|
||||||
<HttpTestResult v-model="tab.testResults" />
|
<HttpTestResult v-model="doc.testResults" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
</HoppSmartTabs>
|
</HoppSmartTabs>
|
||||||
</template>
|
</template>
|
||||||
@@ -54,20 +45,30 @@ import {
|
|||||||
} from "~/helpers/lenses/lenses"
|
} from "~/helpers/lenses/lenses"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tab: HoppRESTTab
|
document: HoppRESTDocument
|
||||||
selectedTabPreference: string | null
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:tab", val: HoppRESTTab): void
|
(e: "update:document", document: HoppRESTDocument): void
|
||||||
(e: "update:selectedTabPreference", newTab: string): void
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tab = useVModel(props, "tab", emit)
|
const doc = useVModel(props, "document", emit)
|
||||||
const selectedTabPreference = useVModel(props, "selectedTabPreference", emit)
|
|
||||||
|
const showIndicator = computed(() => {
|
||||||
|
if (!doc.value.testResults) return false
|
||||||
|
|
||||||
|
const { expectResults, tests, envDiff } = doc.value.testResults
|
||||||
|
return Boolean(
|
||||||
|
expectResults.length ||
|
||||||
|
tests.length ||
|
||||||
|
envDiff.selected.additions.length ||
|
||||||
|
envDiff.selected.updations.length ||
|
||||||
|
envDiff.global.updations.length
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const allLensRenderers = getLensRenderers()
|
const allLensRenderers = getLensRenderers()
|
||||||
|
|
||||||
@@ -81,19 +82,19 @@ const selectedLensTab = ref("")
|
|||||||
|
|
||||||
const maybeHeaders = computed(() => {
|
const maybeHeaders = computed(() => {
|
||||||
if (
|
if (
|
||||||
!tab.value.response ||
|
!doc.value.response ||
|
||||||
!(
|
!(
|
||||||
tab.value.response.type === "success" ||
|
doc.value.response.type === "success" ||
|
||||||
tab.value.response.type === "fail"
|
doc.value.response.type === "fail"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
return tab.value.response.headers
|
return doc.value.response.headers
|
||||||
})
|
})
|
||||||
|
|
||||||
const validLenses = computed(() => {
|
const validLenses = computed(() => {
|
||||||
if (!tab.value.response) return []
|
if (!doc.value.response) return []
|
||||||
return getSuitableLenses(tab.value.response)
|
return getSuitableLenses(doc.value.response)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -107,11 +108,13 @@ watch(
|
|||||||
"results",
|
"results",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const { responseTabPreference } = doc.value
|
||||||
|
|
||||||
if (
|
if (
|
||||||
selectedTabPreference.value &&
|
responseTabPreference &&
|
||||||
validRenderers.includes(selectedTabPreference.value)
|
validRenderers.includes(responseTabPreference)
|
||||||
) {
|
) {
|
||||||
selectedLensTab.value = selectedTabPreference.value
|
selectedLensTab.value = responseTabPreference
|
||||||
} else {
|
} else {
|
||||||
selectedLensTab.value = newLenses[0].renderer
|
selectedLensTab.value = newLenses[0].renderer
|
||||||
}
|
}
|
||||||
@@ -120,6 +123,6 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
watch(selectedLensTab, (newLensID) => {
|
watch(selectedLensTab, (newLensID) => {
|
||||||
selectedTabPreference.value = newLensID
|
doc.value.responseTabPreference = newLensID
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -55,12 +55,7 @@ import {
|
|||||||
keymap,
|
keymap,
|
||||||
tooltips,
|
tooltips,
|
||||||
} from "@codemirror/view"
|
} from "@codemirror/view"
|
||||||
import {
|
import { EditorSelection, EditorState, Extension } from "@codemirror/state"
|
||||||
EditorSelection,
|
|
||||||
EditorState,
|
|
||||||
Extension,
|
|
||||||
StateEffect,
|
|
||||||
} from "@codemirror/state"
|
|
||||||
import { clone } from "lodash-es"
|
import { clone } from "lodash-es"
|
||||||
import { history, historyKeymap } from "@codemirror/commands"
|
import { history, historyKeymap } from "@codemirror/commands"
|
||||||
import { inputTheme } from "~/helpers/editor/themes/baseTheme"
|
import { inputTheme } from "~/helpers/editor/themes/baseTheme"
|
||||||
@@ -77,17 +72,13 @@ const props = withDefaults(
|
|||||||
modelValue?: string
|
modelValue?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
styles?: string
|
styles?: string
|
||||||
envs?:
|
envs?: { key: string; value: string; source: string }[] | null
|
||||||
| { key: string; value: string; secret: boolean; source: string }[]
|
|
||||||
| null
|
|
||||||
focus?: boolean
|
focus?: boolean
|
||||||
selectTextOnMount?: boolean
|
selectTextOnMount?: boolean
|
||||||
environmentHighlights?: boolean
|
environmentHighlights?: boolean
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
autoCompleteSource?: string[]
|
autoCompleteSource?: string[]
|
||||||
inspectionResults?: InspectorResult[] | undefined
|
inspectionResults?: InspectorResult[] | undefined
|
||||||
defaultValue?: string
|
|
||||||
secret?: boolean
|
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
modelValue: "",
|
modelValue: "",
|
||||||
@@ -100,8 +91,6 @@ const props = withDefaults(
|
|||||||
autoCompleteSource: undefined,
|
autoCompleteSource: undefined,
|
||||||
inspectionResult: undefined,
|
inspectionResult: undefined,
|
||||||
inspectionResults: undefined,
|
inspectionResults: undefined,
|
||||||
defaultValue: "",
|
|
||||||
secret: false,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -115,8 +104,6 @@ const emit = defineEmits<{
|
|||||||
(e: "click", ev: any): void
|
(e: "click", ev: any): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const asterikedText = computed(() => "*".repeat(props.modelValue.length))
|
|
||||||
|
|
||||||
const cachedValue = ref(props.modelValue)
|
const cachedValue = ref(props.modelValue)
|
||||||
|
|
||||||
const view = ref<EditorView>()
|
const view = ref<EditorView>()
|
||||||
@@ -276,22 +263,6 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const prevModelValue = ref(props.modelValue)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.secret,
|
|
||||||
(newValue) => {
|
|
||||||
let visibleValue = asterikedText.value
|
|
||||||
if (!newValue) {
|
|
||||||
visibleValue = prevModelValue.value
|
|
||||||
} else {
|
|
||||||
prevModelValue.value = props.modelValue
|
|
||||||
}
|
|
||||||
emit("update:modelValue", visibleValue)
|
|
||||||
updateEditorViewTheme(newValue)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to scroll the active suggestion into view
|
* Used to scroll the active suggestion into view
|
||||||
*/
|
*/
|
||||||
@@ -313,6 +284,7 @@ watch(
|
|||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
const singleLinedText = newVal.replaceAll("\n", "")
|
const singleLinedText = newVal.replaceAll("\n", "")
|
||||||
|
|
||||||
const currDoc = view.value?.state.doc
|
const currDoc = view.value?.state.doc
|
||||||
.toJSON()
|
.toJSON()
|
||||||
.join(view.value.state.lineBreak)
|
.join(view.value.state.lineBreak)
|
||||||
@@ -348,7 +320,6 @@ const envVars = computed(() =>
|
|||||||
? props.envs.map((x) => ({
|
? props.envs.map((x) => ({
|
||||||
key: x.key,
|
key: x.key,
|
||||||
value: x.value,
|
value: x.value,
|
||||||
secret: x.secret,
|
|
||||||
sourceEnv: x.source,
|
sourceEnv: x.source,
|
||||||
}))
|
}))
|
||||||
: aggregateEnvs.value
|
: aggregateEnvs.value
|
||||||
@@ -383,14 +354,6 @@ function handleTextSelection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const updateEditorViewTheme = (readonly: boolean) => {
|
|
||||||
if (view.value) {
|
|
||||||
const extensions: Extension = getExtensions(readonly)
|
|
||||||
view.value.dispatch({
|
|
||||||
effects: [StateEffect.reconfigure.of(extensions)],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const initView = (el: any) => {
|
const initView = (el: any) => {
|
||||||
// Debounce to prevent double click from selecting the word
|
// Debounce to prevent double click from selecting the word
|
||||||
@@ -401,31 +364,17 @@ const initView = (el: any) => {
|
|||||||
el.addEventListener("mouseup", debounceFn)
|
el.addEventListener("mouseup", debounceFn)
|
||||||
el.addEventListener("keyup", debounceFn)
|
el.addEventListener("keyup", debounceFn)
|
||||||
|
|
||||||
if (props.secret) {
|
|
||||||
emit("update:modelValue", asterikedText.value)
|
|
||||||
}
|
|
||||||
const extensions: Extension = getExtensions(props.readonly || props.secret)
|
|
||||||
view.value = new EditorView({
|
|
||||||
parent: el,
|
|
||||||
state: EditorState.create({
|
|
||||||
doc: props.modelValue,
|
|
||||||
extensions,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getExtensions = (readonly: boolean): Extension => {
|
|
||||||
const extensions: Extension = [
|
const extensions: Extension = [
|
||||||
EditorView.contentAttributes.of({ "aria-label": props.placeholder }),
|
EditorView.contentAttributes.of({ "aria-label": props.placeholder }),
|
||||||
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
|
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
|
||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
if (readonly) {
|
if (props.readonly) {
|
||||||
update.view.contentDOM.inputMode = "none"
|
update.view.contentDOM.inputMode = "none"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
EditorState.changeFilter.of(() => !readonly),
|
EditorState.changeFilter.of(() => !props.readonly),
|
||||||
inputTheme,
|
inputTheme,
|
||||||
readonly
|
props.readonly
|
||||||
? EditorView.theme({
|
? EditorView.theme({
|
||||||
".cm-content": {
|
".cm-content": {
|
||||||
caretColor: "var(--secondary-dark-color)",
|
caretColor: "var(--secondary-dark-color)",
|
||||||
@@ -457,7 +406,7 @@ const getExtensions = (readonly: boolean): Extension => {
|
|||||||
ViewPlugin.fromClass(
|
ViewPlugin.fromClass(
|
||||||
class {
|
class {
|
||||||
update(update: ViewUpdate) {
|
update(update: ViewUpdate) {
|
||||||
if (readonly) return
|
if (props.readonly) return
|
||||||
|
|
||||||
if (update.docChanged) {
|
if (update.docChanged) {
|
||||||
const prevValue = clone(cachedValue.value)
|
const prevValue = clone(cachedValue.value)
|
||||||
@@ -469,14 +418,9 @@ const getExtensions = (readonly: boolean): Extension => {
|
|||||||
// We do not update the cache directly in this case (to trigger value watcher to dispatch)
|
// We do not update the cache directly in this case (to trigger value watcher to dispatch)
|
||||||
// So, we desync cachedValue a bit so we can trigger updates
|
// So, we desync cachedValue a bit so we can trigger updates
|
||||||
const value = clone(cachedValue.value).replaceAll("\n", "")
|
const value = clone(cachedValue.value).replaceAll("\n", "")
|
||||||
if (props.secret) {
|
|
||||||
const asterikedValue = "*".repeat(value.length)
|
emit("update:modelValue", value)
|
||||||
emit("update:modelValue", asterikedValue)
|
emit("change", value)
|
||||||
emit("change", asterikedValue)
|
|
||||||
} else {
|
|
||||||
emit("update:modelValue", value)
|
|
||||||
emit("change", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pasted = !!update.transactions.find((txn) =>
|
const pasted = !!update.transactions.find((txn) =>
|
||||||
txn.isUserEvent("input.paste")
|
txn.isUserEvent("input.paste")
|
||||||
@@ -501,7 +445,14 @@ const getExtensions = (readonly: boolean): Extension => {
|
|||||||
history(),
|
history(),
|
||||||
keymap.of([...historyKeymap]),
|
keymap.of([...historyKeymap]),
|
||||||
]
|
]
|
||||||
return extensions
|
|
||||||
|
view.value = new EditorView({
|
||||||
|
parent: el,
|
||||||
|
state: EditorState.create({
|
||||||
|
doc: props.modelValue,
|
||||||
|
extensions,
|
||||||
|
}),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const triggerTextSelection = () => {
|
const triggerTextSelection = () => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
WatchStopHandle,
|
WatchStopHandle,
|
||||||
watchSyncEffect,
|
watchSyncEffect,
|
||||||
watch,
|
watch,
|
||||||
|
nextTick,
|
||||||
} from "vue"
|
} from "vue"
|
||||||
import {
|
import {
|
||||||
client,
|
client,
|
||||||
@@ -101,7 +102,7 @@ export const useGQLQuery = <
|
|||||||
|
|
||||||
const rerunQuery = () => {
|
const rerunQuery = () => {
|
||||||
source.value = !isPaused.value
|
source.value = !isPaused.value
|
||||||
? client.value.executeQuery<DocType, DocVarType>(request.value, {
|
? client.value?.executeQuery<DocType, DocVarType>(request.value, {
|
||||||
requestPolicy: "network-only",
|
requestPolicy: "network-only",
|
||||||
})
|
})
|
||||||
: undefined
|
: undefined
|
||||||
@@ -126,7 +127,7 @@ export const useGQLQuery = <
|
|||||||
|
|
||||||
const invalidateStops = args.updateSubs!.map((sub) => {
|
const invalidateStops = args.updateSubs!.map((sub) => {
|
||||||
return wonkaPipe(
|
return wonkaPipe(
|
||||||
client.value.executeSubscription(sub),
|
client.value!.executeSubscription(sub),
|
||||||
onEnd(() => {
|
onEnd(() => {
|
||||||
if (source.value) execute()
|
if (source.value) execute()
|
||||||
}),
|
}),
|
||||||
@@ -191,10 +192,12 @@ export const useGQLQuery = <
|
|||||||
} else {
|
} else {
|
||||||
args.variables = updatedVars
|
args.variables = updatedVars
|
||||||
}
|
}
|
||||||
|
nextTick(rerunQuery)
|
||||||
|
} else {
|
||||||
|
rerunQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
isPaused.value = false
|
isPaused.value = false
|
||||||
rerunQuery()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pause = () => {
|
const pause = () => {
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ import {
|
|||||||
setGlobalEnvVariables,
|
setGlobalEnvVariables,
|
||||||
updateEnvironment,
|
updateEnvironment,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import { HoppRESTTab } from "./rest/tab"
|
|
||||||
import { Ref } from "vue"
|
import { Ref } from "vue"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "./rest/document"
|
||||||
|
|
||||||
const getTestableBody = (
|
const getTestableBody = (
|
||||||
res: HoppRESTResponse & { type: "success" | "fail" }
|
res: HoppRESTResponse & { type: "success" | "fail" }
|
||||||
@@ -69,7 +70,7 @@ export const executedResponses$ = new Subject<
|
|||||||
>()
|
>()
|
||||||
|
|
||||||
export function runRESTRequest$(
|
export function runRESTRequest$(
|
||||||
tab: Ref<HoppRESTTab>
|
tab: Ref<HoppTab<HoppRESTDocument>>
|
||||||
): [
|
): [
|
||||||
() => void,
|
() => void,
|
||||||
Promise<
|
Promise<
|
||||||
@@ -127,7 +128,7 @@ export function runRESTRequest$(
|
|||||||
)()
|
)()
|
||||||
|
|
||||||
if (E.isRight(runResult)) {
|
if (E.isRight(runResult)) {
|
||||||
tab.value.testResults = translateToSandboxTestResults(
|
tab.value.document.testResults = translateToSandboxTestResults(
|
||||||
runResult.right
|
runResult.right
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -163,7 +164,7 @@ export function runRESTRequest$(
|
|||||||
)()
|
)()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tab.value.testResults = {
|
tab.value.document.testResults = {
|
||||||
description: "",
|
description: "",
|
||||||
expectResults: [],
|
expectResults: [],
|
||||||
tests: [],
|
tests: [],
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { getTabsRefTo } from "../rest/tab"
|
|
||||||
import { getAffectedIndexes } from "./affectedIndex"
|
import { getAffectedIndexes } from "./affectedIndex"
|
||||||
import { GetSingleRequestDocument } from "../backend/graphql"
|
import { GetSingleRequestDocument } from "../backend/graphql"
|
||||||
import { runGQLQuery } from "../backend/GQLClient"
|
import { runGQLQuery } from "../backend/GQLClient"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve save context on reorder
|
* Resolve save context on reorder
|
||||||
@@ -56,7 +57,9 @@ export function resolveSaveContextOnCollectionReorder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = getTabsRefTo((tab) => {
|
const tabService = getService(RESTTabService)
|
||||||
|
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return (
|
return (
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
affectedPaths.has(tab.document.saveContext.folderPath)
|
affectedPaths.has(tab.document.saveContext.folderPath)
|
||||||
@@ -84,7 +87,8 @@ export function updateSaveContextForAffectedRequests(
|
|||||||
oldFolderPath: string,
|
oldFolderPath: string,
|
||||||
newFolderPath: string
|
newFolderPath: string
|
||||||
) {
|
) {
|
||||||
const tabs = getTabsRefTo((tab) => {
|
const tabService = getService(RESTTabService)
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return (
|
return (
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
tab.document.saveContext.folderPath.startsWith(oldFolderPath)
|
tab.document.saveContext.folderPath.startsWith(oldFolderPath)
|
||||||
@@ -105,7 +109,8 @@ export function updateSaveContextForAffectedRequests(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resetSaveContextForAffectedRequests(folderPath: string) {
|
function resetSaveContextForAffectedRequests(folderPath: string) {
|
||||||
const tabs = getTabsRefTo((tab) => {
|
const tabService = getService(RESTTabService)
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return (
|
return (
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
tab.document.saveContext.folderPath.startsWith(folderPath)
|
tab.document.saveContext.folderPath.startsWith(folderPath)
|
||||||
@@ -124,7 +129,8 @@ function resetSaveContextForAffectedRequests(folderPath: string) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export async function resetTeamRequestsContext() {
|
export async function resetTeamRequestsContext() {
|
||||||
const tabs = getTabsRefTo((tab) => {
|
const tabService = getService(RESTTabService)
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return tab.document.saveContext?.originLocation === "team-collection"
|
return tab.document.saveContext?.originLocation === "team-collection"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { getTabsRefTo } from "../rest/tab"
|
|
||||||
import { getAffectedIndexes } from "./affectedIndex"
|
import { getAffectedIndexes } from "./affectedIndex"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve save context on reorder
|
* Resolve save context on reorder
|
||||||
@@ -32,7 +33,8 @@ export function resolveSaveContextOnRequestReorder(payload: {
|
|||||||
// if (newIndex === -1) remove it from the map because it will be deleted
|
// if (newIndex === -1) remove it from the map because it will be deleted
|
||||||
if (newIndex === -1) affectedIndexes.delete(lastIndex)
|
if (newIndex === -1) affectedIndexes.delete(lastIndex)
|
||||||
|
|
||||||
const tabs = getTabsRefTo((tab) => {
|
const tabService = getService(RESTTabService)
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return (
|
return (
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
tab.document.saveContext.folderPath === folderPath &&
|
tab.document.saveContext.folderPath === folderPath &&
|
||||||
|
|||||||
@@ -66,10 +66,7 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||||||
|
|
||||||
const envName = tooltipEnv?.sourceEnv ?? "Choose an Environment"
|
const envName = tooltipEnv?.sourceEnv ?? "Choose an Environment"
|
||||||
|
|
||||||
let envValue = tooltipEnv?.value ?? "Not found"
|
const envValue = tooltipEnv?.value ?? "Not found"
|
||||||
if (tooltipEnv?.secret) {
|
|
||||||
envValue = "******"
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = parseTemplateStringE(envValue, aggregateEnvs)
|
const result = parseTemplateStringE(envValue, aggregateEnvs)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { GQLHeader, HoppGQLAuth, makeGQLRequest } from "@hoppscotch/data"
|
import { GQLHeader, HoppGQLAuth, makeGQLRequest } from "@hoppscotch/data"
|
||||||
import { OperationType } from "@urql/core"
|
import { OperationType } from "@urql/core"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
import {
|
import {
|
||||||
GraphQLEnumType,
|
GraphQLEnumType,
|
||||||
GraphQLInputObjectType,
|
GraphQLInputObjectType,
|
||||||
@@ -11,11 +12,12 @@ import {
|
|||||||
printSchema,
|
printSchema,
|
||||||
} from "graphql"
|
} from "graphql"
|
||||||
import { computed, reactive, ref } from "vue"
|
import { computed, reactive, ref } from "vue"
|
||||||
import { addGraphqlHistoryEntry, makeGQLHistoryEntry } from "~/newstore/history"
|
|
||||||
import { currentTabID } from "./tab"
|
|
||||||
import { getService } from "~/modules/dioc"
|
import { getService } from "~/modules/dioc"
|
||||||
|
|
||||||
|
import { addGraphqlHistoryEntry, makeGQLHistoryEntry } from "~/newstore/history"
|
||||||
|
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
import * as E from "fp-ts/Either"
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const GQL_SCHEMA_POLL_INTERVAL = 7000
|
const GQL_SCHEMA_POLL_INTERVAL = 7000
|
||||||
|
|
||||||
@@ -61,6 +63,9 @@ type Connection = {
|
|||||||
schema: GraphQLSchema | null
|
schema: GraphQLSchema | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabs = getService(GQLTabService)
|
||||||
|
const currentTabID = computed(() => tabs.currentTabID.value)
|
||||||
|
|
||||||
export const connection = reactive<Connection>({
|
export const connection = reactive<Connection>({
|
||||||
state: "DISCONNECTED",
|
state: "DISCONNECTED",
|
||||||
subscriptionState: new Map<string, SubscriptionState>(),
|
subscriptionState: new Map<string, SubscriptionState>(),
|
||||||
@@ -268,7 +273,7 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (operationType === "subscription") {
|
if (operationType === "subscription") {
|
||||||
return runSubscription(options)
|
return runSubscription(options, finalHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
const interceptorService = getService(InterceptorService)
|
const interceptorService = getService(InterceptorService)
|
||||||
@@ -299,7 +304,10 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||||||
return responseText
|
return responseText
|
||||||
}
|
}
|
||||||
|
|
||||||
export const runSubscription = (options: RunQueryOptions) => {
|
export const runSubscription = (
|
||||||
|
options: RunQueryOptions,
|
||||||
|
headers?: Record<string, string>
|
||||||
|
) => {
|
||||||
const { url, query, operationName } = options
|
const { url, query, operationName } = options
|
||||||
const wsUrl = url.replace(/^http/, "ws")
|
const wsUrl = url.replace(/^http/, "ws")
|
||||||
|
|
||||||
@@ -309,10 +317,11 @@ export const runSubscription = (options: RunQueryOptions) => {
|
|||||||
|
|
||||||
connection.socket.onopen = (event) => {
|
connection.socket.onopen = (event) => {
|
||||||
console.log("WebSocket is open now.", event)
|
console.log("WebSocket is open now.", event)
|
||||||
|
|
||||||
connection.socket?.send(
|
connection.socket?.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: GQL.CONNECTION_INIT,
|
type: GQL.CONNECTION_INIT,
|
||||||
payload: {},
|
payload: headers ?? {},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||||
|
import { GQLResponseEvent } from "./connection"
|
||||||
|
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
||||||
|
|
||||||
export type HoppGQLSaveContext =
|
export type HoppGQLSaveContext =
|
||||||
| {
|
| {
|
||||||
@@ -55,4 +57,20 @@ export type HoppGQLDocument = {
|
|||||||
* This contains where the request is originated from basically.
|
* This contains where the request is originated from basically.
|
||||||
*/
|
*/
|
||||||
saveContext?: HoppGQLSaveContext
|
saveContext?: HoppGQLSaveContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The response as it is in the document
|
||||||
|
* (if any)
|
||||||
|
*/
|
||||||
|
response?: GQLResponseEvent[] | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response tab preference for the current tab's document
|
||||||
|
*/
|
||||||
|
responseTabPreference?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options tab preference for the current tab's document
|
||||||
|
*/
|
||||||
|
optionTabPreference?: GQLOptionTabs
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,226 +0,0 @@
|
|||||||
import { refWithControl } from "@vueuse/core"
|
|
||||||
import { isEqual } from "lodash-es"
|
|
||||||
import { v4 as uuidV4 } from "uuid"
|
|
||||||
import { computed, reactive, ref, shallowReadonly, watch } from "vue"
|
|
||||||
import { HoppTestResult } from "../types/HoppTestResult"
|
|
||||||
import { GQLResponseEvent } from "./connection"
|
|
||||||
import { getDefaultGQLRequest } from "./default"
|
|
||||||
import { HoppGQLDocument, HoppGQLSaveContext } from "./document"
|
|
||||||
|
|
||||||
export type HoppGQLTab = {
|
|
||||||
id: string
|
|
||||||
document: HoppGQLDocument
|
|
||||||
response?: GQLResponseEvent[] | null
|
|
||||||
testResults?: HoppTestResult | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PersistableGQLTabState = {
|
|
||||||
lastActiveTabID: string
|
|
||||||
orderedDocs: Array<{
|
|
||||||
tabID: string
|
|
||||||
doc: HoppGQLDocument
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const currentTabID = refWithControl("test", {
|
|
||||||
onBeforeChange(newTabID) {
|
|
||||||
if (!newTabID || !tabMap.has(newTabID)) {
|
|
||||||
console.warn(
|
|
||||||
`Tried to set current tab id to an invalid value. (value: ${newTabID})`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Don't allow change
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const tabMap = reactive(
|
|
||||||
new Map<string, HoppGQLTab>([
|
|
||||||
[
|
|
||||||
"test",
|
|
||||||
{
|
|
||||||
id: "test",
|
|
||||||
document: {
|
|
||||||
request: getDefaultGQLRequest(),
|
|
||||||
isDirty: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
])
|
|
||||||
)
|
|
||||||
const tabOrdering = ref<string[]>(["test"])
|
|
||||||
|
|
||||||
watch(
|
|
||||||
tabOrdering,
|
|
||||||
(newOrdering) => {
|
|
||||||
if (!currentTabID.value || !newOrdering.includes(currentTabID.value)) {
|
|
||||||
currentTabID.value = newOrdering[newOrdering.length - 1] // newOrdering should always be non-empty
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
export const persistableTabState = computed<PersistableGQLTabState>(() => ({
|
|
||||||
lastActiveTabID: currentTabID.value,
|
|
||||||
orderedDocs: tabOrdering.value.map((tabID) => {
|
|
||||||
const tab = tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
|
|
||||||
return {
|
|
||||||
tabID: tab.id,
|
|
||||||
doc: tab.document,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const currentActiveTab = computed(() => tabMap.get(currentTabID.value)!) // Guaranteed to not be undefined
|
|
||||||
|
|
||||||
// TODO: Mark this unknown and do validations
|
|
||||||
export function loadTabsFromPersistedState(data: PersistableGQLTabState) {
|
|
||||||
if (data) {
|
|
||||||
tabMap.clear()
|
|
||||||
tabOrdering.value = []
|
|
||||||
|
|
||||||
for (const doc of data.orderedDocs) {
|
|
||||||
tabMap.set(doc.tabID, {
|
|
||||||
id: doc.tabID,
|
|
||||||
document: doc.doc,
|
|
||||||
})
|
|
||||||
|
|
||||||
tabOrdering.value.push(doc.tabID)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTabID.value = data.lastActiveTabID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the active Tab IDs in order
|
|
||||||
*/
|
|
||||||
export function getActiveTabs() {
|
|
||||||
return shallowReadonly(
|
|
||||||
computed(() => tabOrdering.value.map((x) => tabMap.get(x)!))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTabRef(tabID: string) {
|
|
||||||
return computed({
|
|
||||||
get() {
|
|
||||||
const result = tabMap.get(tabID)
|
|
||||||
|
|
||||||
if (result === undefined) throw new Error(`Invalid tab id: ${tabID}`)
|
|
||||||
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
return tabMap.set(tabID, value)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateNewTabID() {
|
|
||||||
while (true) {
|
|
||||||
const id = uuidV4()
|
|
||||||
|
|
||||||
if (!tabMap.has(id)) return id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTab(tabUpdate: HoppGQLTab) {
|
|
||||||
if (!tabMap.has(tabUpdate.id)) {
|
|
||||||
console.warn(
|
|
||||||
`Cannot update tab as tab with that tab id does not exist (id: ${tabUpdate.id})`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
tabMap.set(tabUpdate.id, tabUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createNewTab(document: HoppGQLDocument, switchToIt = true) {
|
|
||||||
const id = generateNewTabID()
|
|
||||||
|
|
||||||
const tab: HoppGQLTab = { id, document }
|
|
||||||
|
|
||||||
tabMap.set(id, tab)
|
|
||||||
tabOrdering.value.push(id)
|
|
||||||
|
|
||||||
if (switchToIt) {
|
|
||||||
currentTabID.value = id
|
|
||||||
}
|
|
||||||
|
|
||||||
return tab
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTabOrdering(fromIndex: number, toIndex: number) {
|
|
||||||
tabOrdering.value.splice(
|
|
||||||
toIndex,
|
|
||||||
0,
|
|
||||||
tabOrdering.value.splice(fromIndex, 1)[0]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeTab(tabID: string) {
|
|
||||||
if (!tabMap.has(tabID)) {
|
|
||||||
console.warn(`Tried to close a tab which does not exist (tab id: ${tabID})`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tabOrdering.value.length === 1) {
|
|
||||||
console.warn(
|
|
||||||
`Tried to close the only tab open, which is not allowed. (tab id: ${tabID})`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tabOrdering.value.splice(tabOrdering.value.indexOf(tabID), 1)
|
|
||||||
|
|
||||||
tabMap.delete(tabID)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTabRefWithSaveContext(ctx: HoppGQLSaveContext) {
|
|
||||||
for (const tab of tabMap.values()) {
|
|
||||||
// For `team-collection` request id can be considered unique
|
|
||||||
if (ctx && ctx.originLocation === "team-collection") {
|
|
||||||
if (
|
|
||||||
tab.document.saveContext?.originLocation === "team-collection" &&
|
|
||||||
tab.document.saveContext.requestID === ctx.requestID
|
|
||||||
) {
|
|
||||||
return getTabRef(tab.id)
|
|
||||||
}
|
|
||||||
} else if (isEqual(ctx, tab.document.saveContext)) return getTabRef(tab.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTabsRefTo(func: (tab: HoppGQLTab) => boolean) {
|
|
||||||
return Array.from(tabMap.values())
|
|
||||||
.filter(func)
|
|
||||||
.map((tab) => getTabRef(tab.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeOtherTabs(tabID: string) {
|
|
||||||
if (!tabMap.has(tabID)) {
|
|
||||||
console.warn(
|
|
||||||
`The tab to close other tabs does not exist (tab id: ${tabID})`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tabOrdering.value = [tabID]
|
|
||||||
|
|
||||||
tabMap.forEach((_, id) => {
|
|
||||||
if (id !== tabID) tabMap.delete(id)
|
|
||||||
})
|
|
||||||
|
|
||||||
currentTabID.value = tabID
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDirtyTabsCount() {
|
|
||||||
let count = 0
|
|
||||||
|
|
||||||
for (const tab of tabMap.values()) {
|
|
||||||
if (tab.document.isDirty) count++
|
|
||||||
}
|
|
||||||
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Environment } from "@hoppscotch/data"
|
||||||
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
|
import { cloneDeep } from "lodash-es"
|
||||||
|
|
||||||
|
const getEnvironmentJson = (
|
||||||
|
environmentObj: TeamEnvironment | Environment,
|
||||||
|
environmentIndex?: number | "Global" | null
|
||||||
|
) => {
|
||||||
|
const newEnvironment =
|
||||||
|
"environment" in environmentObj
|
||||||
|
? cloneDeep(environmentObj.environment)
|
||||||
|
: cloneDeep(environmentObj)
|
||||||
|
|
||||||
|
delete newEnvironment.id
|
||||||
|
|
||||||
|
const environmentId =
|
||||||
|
environmentIndex || environmentIndex === 0
|
||||||
|
? environmentIndex
|
||||||
|
: environmentObj.id
|
||||||
|
|
||||||
|
return environmentId !== null
|
||||||
|
? JSON.stringify(newEnvironment, null, 2)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exportAsJSON = (
|
||||||
|
environmentObj: Environment | TeamEnvironment,
|
||||||
|
environmentIndex?: number | "Global" | null
|
||||||
|
): boolean => {
|
||||||
|
const dataToWrite = getEnvironmentJson(environmentObj, environmentIndex)
|
||||||
|
|
||||||
|
if (!dataToWrite) return false
|
||||||
|
|
||||||
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
|
const a = document.createElement("a")
|
||||||
|
const url = URL.createObjectURL(file)
|
||||||
|
a.href = url
|
||||||
|
|
||||||
|
// Extracts the path from url, removes fragment identifier and query parameters if any, appends the ".json" extension, and assigns it
|
||||||
|
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
}, 0)
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -131,7 +131,8 @@ function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPressedKey(ev: KeyboardEvent): Key | null {
|
function getPressedKey(ev: KeyboardEvent): Key | null {
|
||||||
const val = ev.code.toLowerCase()
|
// Sometimes the property code is not available on the KeyboardEvent object
|
||||||
|
const val = (ev.code ?? "").toLowerCase()
|
||||||
|
|
||||||
// Check arrow keys
|
// Check arrow keys
|
||||||
if (val === "arrowup") return "up"
|
if (val === "arrowup") return "up"
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
||||||
|
import { HoppTestResult } from "../types/HoppTestResult"
|
||||||
|
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||||
|
|
||||||
export type HoppRESTSaveContext =
|
export type HoppRESTSaveContext =
|
||||||
| {
|
| {
|
||||||
@@ -55,4 +58,26 @@ export type HoppRESTDocument = {
|
|||||||
* This contains where the request is originated from basically.
|
* This contains where the request is originated from basically.
|
||||||
*/
|
*/
|
||||||
saveContext?: HoppRESTSaveContext
|
saveContext?: HoppRESTSaveContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The response as it is in the document
|
||||||
|
* (if any)
|
||||||
|
*/
|
||||||
|
response?: HoppRESTResponse | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The test results as it is in the document
|
||||||
|
* (if any)
|
||||||
|
*/
|
||||||
|
testResults?: HoppTestResult | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response tab preference for the current tab's document
|
||||||
|
*/
|
||||||
|
responseTabPreference?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options tab preference for the current tab's document
|
||||||
|
*/
|
||||||
|
optionTabPreference?: RESTOptionTabs
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,234 +0,0 @@
|
|||||||
import { v4 as uuidV4 } from "uuid"
|
|
||||||
import { isEqual } from "lodash-es"
|
|
||||||
import { reactive, watch, computed, ref, shallowReadonly } from "vue"
|
|
||||||
import { HoppRESTDocument, HoppRESTSaveContext } from "./document"
|
|
||||||
import { refWithControl } from "@vueuse/core"
|
|
||||||
import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
|
||||||
import { getDefaultRESTRequest } from "./default"
|
|
||||||
import { HoppTestResult } from "../types/HoppTestResult"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { nextTick } from "vue"
|
|
||||||
|
|
||||||
export type HoppRESTTab = {
|
|
||||||
id: string
|
|
||||||
document: HoppRESTDocument
|
|
||||||
response?: HoppRESTResponse | null
|
|
||||||
testResults?: HoppTestResult | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PersistableRESTTabState = {
|
|
||||||
lastActiveTabID: string
|
|
||||||
orderedDocs: Array<{
|
|
||||||
tabID: string
|
|
||||||
doc: HoppRESTDocument
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const currentTabID = refWithControl("test", {
|
|
||||||
onBeforeChange(newTabID) {
|
|
||||||
if (!newTabID || !tabMap.has(newTabID)) {
|
|
||||||
console.warn(
|
|
||||||
`Tried to set current tab id to an invalid value. (value: ${newTabID})`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Don't allow change
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const tabMap = reactive(
|
|
||||||
new Map<string, HoppRESTTab>([
|
|
||||||
[
|
|
||||||
"test",
|
|
||||||
{
|
|
||||||
id: "test",
|
|
||||||
document: {
|
|
||||||
request: getDefaultRESTRequest(),
|
|
||||||
isDirty: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
])
|
|
||||||
)
|
|
||||||
const tabOrdering = ref<string[]>(["test"])
|
|
||||||
|
|
||||||
watch(
|
|
||||||
tabOrdering,
|
|
||||||
(newOrdering) => {
|
|
||||||
if (!currentTabID.value || !newOrdering.includes(currentTabID.value)) {
|
|
||||||
currentTabID.value = newOrdering[newOrdering.length - 1] // newOrdering should always be non-empty
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
export const persistableTabState = computed<PersistableRESTTabState>(() => ({
|
|
||||||
lastActiveTabID: currentTabID.value,
|
|
||||||
orderedDocs: tabOrdering.value.map((tabID) => {
|
|
||||||
const tab = tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
|
|
||||||
return {
|
|
||||||
tabID: tab.id,
|
|
||||||
doc: tab.document,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const currentActiveTab = computed(() => tabMap.get(currentTabID.value)!) // Guaranteed to not be undefined
|
|
||||||
|
|
||||||
// TODO: Mark this unknown and do validations
|
|
||||||
export function loadTabsFromPersistedState(data: PersistableRESTTabState) {
|
|
||||||
if (data) {
|
|
||||||
tabMap.clear()
|
|
||||||
tabOrdering.value = []
|
|
||||||
|
|
||||||
for (const doc of data.orderedDocs) {
|
|
||||||
tabMap.set(doc.tabID, {
|
|
||||||
id: doc.tabID,
|
|
||||||
document: doc.doc,
|
|
||||||
})
|
|
||||||
|
|
||||||
tabOrdering.value.push(doc.tabID)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTabID.value = data.lastActiveTabID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the active Tab IDs in order
|
|
||||||
*/
|
|
||||||
export function getActiveTabs() {
|
|
||||||
return shallowReadonly(
|
|
||||||
computed(() => tabOrdering.value.map((x) => tabMap.get(x)!))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTabRef(tabID: string) {
|
|
||||||
return computed({
|
|
||||||
get() {
|
|
||||||
const result = tabMap.get(tabID)
|
|
||||||
|
|
||||||
if (result === undefined) throw new Error(`Invalid tab id: ${tabID}`)
|
|
||||||
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
return tabMap.set(tabID, value)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateNewTabID() {
|
|
||||||
while (true) {
|
|
||||||
const id = uuidV4()
|
|
||||||
|
|
||||||
if (!tabMap.has(id)) return id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTab(tabUpdate: HoppRESTTab) {
|
|
||||||
if (!tabMap.has(tabUpdate.id)) {
|
|
||||||
console.warn(
|
|
||||||
`Cannot update tab as tab with that tab id does not exist (id: ${tabUpdate.id})`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
tabMap.set(tabUpdate.id, tabUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createNewTab(document: HoppRESTDocument, switchToIt = true) {
|
|
||||||
const id = generateNewTabID()
|
|
||||||
|
|
||||||
const tab: HoppRESTTab = { id, document }
|
|
||||||
|
|
||||||
tabMap.set(id, tab)
|
|
||||||
tabOrdering.value.push(id)
|
|
||||||
|
|
||||||
if (switchToIt) {
|
|
||||||
currentTabID.value = id
|
|
||||||
}
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_REST_NEW_TAB_OPENED",
|
|
||||||
})
|
|
||||||
|
|
||||||
return tab
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTabOrdering(fromIndex: number, toIndex: number) {
|
|
||||||
tabOrdering.value.splice(
|
|
||||||
toIndex,
|
|
||||||
0,
|
|
||||||
tabOrdering.value.splice(fromIndex, 1)[0]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeTab(tabID: string) {
|
|
||||||
if (!tabMap.has(tabID)) {
|
|
||||||
console.warn(`Tried to close a tab which does not exist (tab id: ${tabID})`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tabOrdering.value.length === 1) {
|
|
||||||
console.warn(
|
|
||||||
`Tried to close the only tab open, which is not allowed. (tab id: ${tabID})`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tabOrdering.value.splice(tabOrdering.value.indexOf(tabID), 1)
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
tabMap.delete(tabID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeOtherTabs(tabID: string) {
|
|
||||||
if (!tabMap.has(tabID)) {
|
|
||||||
console.warn(
|
|
||||||
`The tab to close other tabs does not exist (tab id: ${tabID})`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tabOrdering.value = [tabID]
|
|
||||||
|
|
||||||
tabMap.forEach((_, id) => {
|
|
||||||
if (id !== tabID) tabMap.delete(id)
|
|
||||||
})
|
|
||||||
|
|
||||||
currentTabID.value = tabID
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDirtyTabsCount() {
|
|
||||||
let count = 0
|
|
||||||
|
|
||||||
for (const tab of tabMap.values()) {
|
|
||||||
if (tab.document.isDirty) count++
|
|
||||||
}
|
|
||||||
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTabRefWithSaveContext(ctx: HoppRESTSaveContext) {
|
|
||||||
for (const tab of tabMap.values()) {
|
|
||||||
// For `team-collection` request id can be considered unique
|
|
||||||
if (ctx && ctx.originLocation === "team-collection") {
|
|
||||||
if (
|
|
||||||
tab.document.saveContext?.originLocation === "team-collection" &&
|
|
||||||
tab.document.saveContext.requestID === ctx.requestID
|
|
||||||
) {
|
|
||||||
return getTabRef(tab.id)
|
|
||||||
}
|
|
||||||
} else if (isEqual(ctx, tab.document.saveContext)) return getTabRef(tab.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTabsRefTo(func: (tab: HoppRESTTab) => boolean) {
|
|
||||||
return Array.from(tabMap.values())
|
|
||||||
.filter(func)
|
|
||||||
.map((tab) => getTabRef(tab.id))
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"!name": "pw-pre",
|
"!name": "pw-pre",
|
||||||
"pw": {
|
"pw": {
|
||||||
"env": {
|
"env": {
|
||||||
"set": "fn(key: string, value: string, secret: boolean)",
|
"set": "fn(key: string, value: string)",
|
||||||
"get": "fn(key: string) -> string",
|
"get": "fn(key: string) -> string",
|
||||||
"getResolve": "fn(key: string) -> string",
|
"getResolve": "fn(key: string) -> string",
|
||||||
"resolve": "fn(value: string) -> string"
|
"resolve": "fn(value: string) -> string"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"body": "?"
|
"body": "?"
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"set": "fn(key: string, value: string, secret:boolean)",
|
"set": "fn(key: string, value: string)",
|
||||||
"get": "fn(key: string) -> string",
|
"get": "fn(key: string) -> string",
|
||||||
"getResolve": "fn(key: string) -> string",
|
"getResolve": "fn(key: string) -> string",
|
||||||
"resolve": "fn(value: string) -> string"
|
"resolve": "fn(value: string) -> string"
|
||||||
|
|||||||
@@ -198,11 +198,11 @@ export const resolvesEnvsInBody = (
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
return {
|
|
||||||
contentType: body.contentType,
|
return {
|
||||||
body: parseTemplateString(body.body, env.variables),
|
contentType: body.contentType,
|
||||||
}
|
body: parseTemplateString(body.body ?? "", env.variables),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,9 +210,7 @@ function getFinalBodyFromRequest(
|
|||||||
request: HoppRESTRequest,
|
request: HoppRESTRequest,
|
||||||
envVariables: Environment["variables"]
|
envVariables: Environment["variables"]
|
||||||
): FormData | string | null {
|
): FormData | string | null {
|
||||||
if (request.body.contentType === null) {
|
if (request.body.contentType === null) return null
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.body.contentType === "application/x-www-form-urlencoded") {
|
if (request.body.contentType === "application/x-www-form-urlencoded") {
|
||||||
const parsedBodyRecord = pipe(
|
const parsedBodyRecord = pipe(
|
||||||
@@ -280,7 +278,10 @@ function getFinalBodyFromRequest(
|
|||||||
),
|
),
|
||||||
toFormData
|
toFormData
|
||||||
)
|
)
|
||||||
} else return parseBodyEnvVariables(request.body.body, envVariables)
|
}
|
||||||
|
|
||||||
|
// body can be null if the content-type is not set
|
||||||
|
return parseBodyEnvVariables(request.body.body ?? "", envVariables)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import {
|
|||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { getTabRefWithSaveContext } from "~/helpers/rest/tab"
|
|
||||||
import { resolveSaveContextOnRequestReorder } from "~/helpers/collection/request"
|
import { resolveSaveContextOnRequestReorder } from "~/helpers/collection/request"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const defaultRESTCollectionState = {
|
const defaultRESTCollectionState = {
|
||||||
state: [
|
state: [
|
||||||
@@ -454,7 +455,10 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
|
|
||||||
// Deal with situations where a tab with the given thing is deleted
|
// Deal with situations where a tab with the given thing is deleted
|
||||||
// We are just going to dissociate the save context of the tab and mark it dirty
|
// We are just going to dissociate the save context of the tab and mark it dirty
|
||||||
const tab = getTabRefWithSaveContext({
|
|
||||||
|
const tabService = getService(RESTTabService)
|
||||||
|
|
||||||
|
const tab = tabService.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: path,
|
folderPath: path,
|
||||||
requestIndex: requestIndex,
|
requestIndex: requestIndex,
|
||||||
@@ -512,7 +516,8 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
destLocation.requests.push(req)
|
destLocation.requests.push(req)
|
||||||
targetLocation.requests.splice(requestIndex, 1)
|
targetLocation.requests.splice(requestIndex, 1)
|
||||||
|
|
||||||
const possibleTab = getTabRefWithSaveContext({
|
const tabService = getService(RESTTabService)
|
||||||
|
const possibleTab = tabService.getTabRefWithSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
folderPath: path,
|
folderPath: path,
|
||||||
requestIndex,
|
requestIndex,
|
||||||
|
|||||||
@@ -186,19 +186,14 @@ const dispatchers = defineDispatchers({
|
|||||||
},
|
},
|
||||||
addEnvironmentVariable(
|
addEnvironmentVariable(
|
||||||
{ environments }: EnvironmentStore,
|
{ environments }: EnvironmentStore,
|
||||||
{
|
{ envIndex, key, value }: { envIndex: number; key: string; value: string }
|
||||||
envIndex,
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
secret,
|
|
||||||
}: { envIndex: number; key: string; value: string; secret: boolean }
|
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
environments: environments.map((env, index) =>
|
environments: environments.map((env, index) =>
|
||||||
index === envIndex
|
index === envIndex
|
||||||
? {
|
? {
|
||||||
...env,
|
...env,
|
||||||
variables: [...env.variables, { key, value, secret }],
|
variables: [...env.variables, { key, value }],
|
||||||
}
|
}
|
||||||
: env
|
: env
|
||||||
),
|
),
|
||||||
@@ -226,10 +221,7 @@ const dispatchers = defineDispatchers({
|
|||||||
{
|
{
|
||||||
envIndex,
|
envIndex,
|
||||||
vars,
|
vars,
|
||||||
}: {
|
}: { envIndex: number; vars: { key: string; value: string }[] }
|
||||||
envIndex: number
|
|
||||||
vars: { key: string; value: string; secret: boolean }[]
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
environments: environments.map((env, index) =>
|
environments: environments.map((env, index) =>
|
||||||
@@ -249,13 +241,11 @@ const dispatchers = defineDispatchers({
|
|||||||
variableIndex,
|
variableIndex,
|
||||||
updatedKey,
|
updatedKey,
|
||||||
updatedValue,
|
updatedValue,
|
||||||
updatedSecret,
|
|
||||||
}: {
|
}: {
|
||||||
envIndex: number
|
envIndex: number
|
||||||
variableIndex: number
|
variableIndex: number
|
||||||
updatedKey: string
|
updatedKey: string
|
||||||
updatedValue: string
|
updatedValue: string
|
||||||
updatedSecret: boolean
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
@@ -265,11 +255,7 @@ const dispatchers = defineDispatchers({
|
|||||||
...env,
|
...env,
|
||||||
variables: env.variables.map((v, vIndex) =>
|
variables: env.variables.map((v, vIndex) =>
|
||||||
vIndex === variableIndex
|
vIndex === variableIndex
|
||||||
? {
|
? { key: updatedKey, value: updatedValue }
|
||||||
key: updatedKey,
|
|
||||||
value: updatedValue,
|
|
||||||
secret: updatedSecret,
|
|
||||||
}
|
|
||||||
: v
|
: v
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -373,7 +359,6 @@ export const currentEnvironment$: Observable<Environment | undefined> =
|
|||||||
export type AggregateEnvironment = {
|
export type AggregateEnvironment = {
|
||||||
key: string
|
key: string
|
||||||
value: string
|
value: string
|
||||||
secret: boolean
|
|
||||||
sourceEnv: string
|
sourceEnv: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,11 +373,11 @@ export const aggregateEnvs$: Observable<AggregateEnvironment[]> = combineLatest(
|
|||||||
map(([selectedEnv, globalVars]) => {
|
map(([selectedEnv, globalVars]) => {
|
||||||
const results: AggregateEnvironment[] = []
|
const results: AggregateEnvironment[] = []
|
||||||
|
|
||||||
selectedEnv?.variables.forEach(({ key, value, secret }) =>
|
selectedEnv?.variables.forEach(({ key, value }) =>
|
||||||
results.push({ key, value, secret, sourceEnv: selectedEnv.name })
|
results.push({ key, value, sourceEnv: selectedEnv.name })
|
||||||
)
|
)
|
||||||
globalVars.forEach(({ key, value, secret }) =>
|
globalVars.forEach(({ key, value }) =>
|
||||||
results.push({ key, value, secret, sourceEnv: "Global" })
|
results.push({ key, value, sourceEnv: "Global" })
|
||||||
)
|
)
|
||||||
|
|
||||||
return results
|
return results
|
||||||
@@ -608,7 +593,7 @@ export function updateEnvironment(envIndex: number, updatedEnv: Environment) {
|
|||||||
|
|
||||||
export function setEnvironmentVariables(
|
export function setEnvironmentVariables(
|
||||||
envIndex: number,
|
envIndex: number,
|
||||||
vars: { key: string; value: string; secret: boolean }[]
|
vars: { key: string; value: string }[]
|
||||||
) {
|
) {
|
||||||
environmentsStore.dispatch({
|
environmentsStore.dispatch({
|
||||||
dispatcher: "setEnvironmentVariables",
|
dispatcher: "setEnvironmentVariables",
|
||||||
@@ -621,7 +606,7 @@ export function setEnvironmentVariables(
|
|||||||
|
|
||||||
export function addEnvironmentVariable(
|
export function addEnvironmentVariable(
|
||||||
envIndex: number,
|
envIndex: number,
|
||||||
{ key, value, secret }: { key: string; value: string; secret: boolean }
|
{ key, value }: { key: string; value: string }
|
||||||
) {
|
) {
|
||||||
environmentsStore.dispatch({
|
environmentsStore.dispatch({
|
||||||
dispatcher: "addEnvironmentVariable",
|
dispatcher: "addEnvironmentVariable",
|
||||||
@@ -629,7 +614,6 @@ export function addEnvironmentVariable(
|
|||||||
envIndex,
|
envIndex,
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
secret,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -650,7 +634,7 @@ export function removeEnvironmentVariable(
|
|||||||
export function updateEnvironmentVariable(
|
export function updateEnvironmentVariable(
|
||||||
envIndex: number,
|
envIndex: number,
|
||||||
variableIndex: number,
|
variableIndex: number,
|
||||||
{ key, value, secret }: { key: string; value: string; secret: boolean }
|
{ key, value }: { key: string; value: string }
|
||||||
) {
|
) {
|
||||||
environmentsStore.dispatch({
|
environmentsStore.dispatch({
|
||||||
dispatcher: "updateEnvironmentVariable",
|
dispatcher: "updateEnvironmentVariable",
|
||||||
@@ -659,7 +643,6 @@ export function updateEnvironmentVariable(
|
|||||||
variableIndex,
|
variableIndex,
|
||||||
updatedKey: key,
|
updatedKey: key,
|
||||||
updatedValue: value,
|
updatedValue: value,
|
||||||
updatedSecret: secret,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,14 +44,9 @@ import { SSERequest$, setSSERequest } from "./SSESession"
|
|||||||
import { MQTTRequest$, setMQTTRequest } from "./MQTTSession"
|
import { MQTTRequest$, setMQTTRequest } from "./MQTTSession"
|
||||||
import { bulkApplyLocalState, localStateStore } from "./localstate"
|
import { bulkApplyLocalState, localStateStore } from "./localstate"
|
||||||
import { StorageLike, watchDebounced } from "@vueuse/core"
|
import { StorageLike, watchDebounced } from "@vueuse/core"
|
||||||
import {
|
import { getService } from "~/modules/dioc"
|
||||||
loadTabsFromPersistedState,
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
persistableTabState,
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
} from "~/helpers/rest/tab"
|
|
||||||
import {
|
|
||||||
loadTabsFromPersistedState as loadGQLTabsFromPersistedState,
|
|
||||||
persistableTabState as persistableGQLTabState,
|
|
||||||
} from "~/helpers/graphql/tab"
|
|
||||||
|
|
||||||
function checkAndMigrateOldSettings() {
|
function checkAndMigrateOldSettings() {
|
||||||
if (window.localStorage.getItem("selectedEnvIndex")) {
|
if (window.localStorage.getItem("selectedEnvIndex")) {
|
||||||
@@ -320,11 +315,13 @@ function setupGlobalEnvsPersistence() {
|
|||||||
|
|
||||||
// TODO: Graceful error handling ?
|
// TODO: Graceful error handling ?
|
||||||
export function setupRESTTabsPersistence() {
|
export function setupRESTTabsPersistence() {
|
||||||
|
const tabService = getService(RESTTabService)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const state = window.localStorage.getItem("restTabState")
|
const state = window.localStorage.getItem("restTabState")
|
||||||
if (state) {
|
if (state) {
|
||||||
const data = JSON.parse(state)
|
const data = JSON.parse(state)
|
||||||
loadTabsFromPersistedState(data)
|
tabService.loadTabsFromPersistedState(data)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -334,7 +331,7 @@ export function setupRESTTabsPersistence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watchDebounced(
|
watchDebounced(
|
||||||
persistableTabState,
|
tabService.persistableTabState,
|
||||||
(state) => {
|
(state) => {
|
||||||
window.localStorage.setItem("restTabState", JSON.stringify(state))
|
window.localStorage.setItem("restTabState", JSON.stringify(state))
|
||||||
},
|
},
|
||||||
@@ -343,11 +340,13 @@ export function setupRESTTabsPersistence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupGQLTabsPersistence() {
|
function setupGQLTabsPersistence() {
|
||||||
|
const tabService = getService(GQLTabService)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const state = window.localStorage.getItem("gqlTabState")
|
const state = window.localStorage.getItem("gqlTabState")
|
||||||
if (state) {
|
if (state) {
|
||||||
const data = JSON.parse(state)
|
const data = JSON.parse(state)
|
||||||
loadGQLTabsFromPersistedState(data)
|
tabService.loadTabsFromPersistedState(data)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -357,7 +356,7 @@ function setupGQLTabsPersistence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watchDebounced(
|
watchDebounced(
|
||||||
persistableGQLTabState,
|
tabService.persistableTabState,
|
||||||
(state) => {
|
(state) => {
|
||||||
window.localStorage.setItem("gqlTabState", JSON.stringify(state))
|
window.localStorage.setItem("gqlTabState", JSON.stringify(state))
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,23 +7,24 @@
|
|||||||
<HoppSmartWindows
|
<HoppSmartWindows
|
||||||
v-if="currentTabID"
|
v-if="currentTabID"
|
||||||
:id="'gql_windows'"
|
:id="'gql_windows'"
|
||||||
v-model="currentTabID"
|
:model-value="currentTabID"
|
||||||
|
@update:model-value="(tabID) => tabs.setActiveTab(tabID)"
|
||||||
@remove-tab="removeTab"
|
@remove-tab="removeTab"
|
||||||
@add-tab="addNewTab"
|
@add-tab="addNewTab"
|
||||||
@sort="sortTabs"
|
@sort="sortTabs"
|
||||||
>
|
>
|
||||||
<HoppSmartWindow
|
<HoppSmartWindow
|
||||||
v-for="tab in tabs"
|
v-for="tab in activeTabs"
|
||||||
:id="tab.id"
|
:id="tab.id"
|
||||||
:key="'removable_tab_' + tab.id"
|
:key="'removable_tab_' + tab.id"
|
||||||
:label="tab.document.request.name"
|
:label="tab.document.request.name"
|
||||||
:is-removable="tabs.length > 1"
|
:is-removable="activeTabs.length > 1"
|
||||||
:close-visibility="'hover'"
|
:close-visibility="'hover'"
|
||||||
>
|
>
|
||||||
<template #tabhead>
|
<template #tabhead>
|
||||||
<GraphqlTabHead
|
<GraphqlTabHead
|
||||||
:tab="tab"
|
:tab="tab"
|
||||||
:is-removable="tabs.length > 1"
|
:is-removable="activeTabs.length > 1"
|
||||||
@open-rename-modal="openReqRenameModal(tab)"
|
@open-rename-modal="openReqRenameModal(tab)"
|
||||||
@close-tab="removeTab(tab.id)"
|
@close-tab="removeTab(tab.id)"
|
||||||
@close-other-tabs="closeOtherTabsAction(tab.id)"
|
@close-other-tabs="closeOtherTabsAction(tab.id)"
|
||||||
@@ -89,21 +90,15 @@ import { computed, onBeforeUnmount, ref } from "vue"
|
|||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { connection, disconnect } from "~/helpers/graphql/connection"
|
import { connection, disconnect } from "~/helpers/graphql/connection"
|
||||||
import { getDefaultGQLRequest } from "~/helpers/graphql/default"
|
import { getDefaultGQLRequest } from "~/helpers/graphql/default"
|
||||||
import {
|
import { HoppGQLDocument } from "~/helpers/graphql/document"
|
||||||
HoppGQLTab,
|
|
||||||
closeOtherTabs,
|
|
||||||
closeTab,
|
|
||||||
createNewTab,
|
|
||||||
currentTabID,
|
|
||||||
getActiveTabs,
|
|
||||||
getDirtyTabsCount,
|
|
||||||
getTabRef,
|
|
||||||
updateTab,
|
|
||||||
updateTabOrdering,
|
|
||||||
} from "~/helpers/graphql/tab"
|
|
||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
const tabs = useService(GQLTabService)
|
||||||
|
|
||||||
|
const currentTabID = computed(() => tabs.currentTabID.value)
|
||||||
|
|
||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
@@ -113,27 +108,27 @@ usePageHead({
|
|||||||
title: computed(() => t("navigation.graphql")),
|
title: computed(() => t("navigation.graphql")),
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabs = getActiveTabs()
|
const activeTabs = tabs.getActiveTabs()
|
||||||
|
|
||||||
const addNewTab = () => {
|
const addNewTab = () => {
|
||||||
const tab = createNewTab({
|
const tab = tabs.createNewTab({
|
||||||
request: getDefaultGQLRequest(),
|
request: getDefaultGQLRequest(),
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
currentTabID.value = tab.id
|
tabs.setActiveTab(tab.id)
|
||||||
}
|
}
|
||||||
const sortTabs = (e: { oldIndex: number; newIndex: number }) => {
|
const sortTabs = (e: { oldIndex: number; newIndex: number }) => {
|
||||||
updateTabOrdering(e.oldIndex, e.newIndex)
|
tabs.updateTabOrdering(e.oldIndex, e.newIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeTab = (tabID: string) => {
|
const removeTab = (tabID: string) => {
|
||||||
const tabState = getTabRef(tabID).value
|
const tabState = tabs.getTabRef(tabID).value
|
||||||
|
|
||||||
if (tabState.document.isDirty) {
|
if (tabState.document.isDirty) {
|
||||||
confirmingCloseForTabID.value = tabID
|
confirmingCloseForTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
closeTab(tabState.id)
|
tabs.closeTab(tabState.id)
|
||||||
inspectionService.deleteTabInspectorResult(tabState.id)
|
inspectionService.deleteTabInspectorResult(tabState.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +145,7 @@ const onCloseConfirm = () => {
|
|||||||
*/
|
*/
|
||||||
const onResolveConfirm = () => {
|
const onResolveConfirm = () => {
|
||||||
if (confirmingCloseForTabID.value) {
|
if (confirmingCloseForTabID.value) {
|
||||||
closeTab(confirmingCloseForTabID.value)
|
tabs.closeTab(confirmingCloseForTabID.value)
|
||||||
confirmingCloseForTabID.value = null
|
confirmingCloseForTabID.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,24 +155,24 @@ const unsavedTabsCount = ref(0)
|
|||||||
const exceptedTabID = ref<string | null>(null)
|
const exceptedTabID = ref<string | null>(null)
|
||||||
|
|
||||||
const closeOtherTabsAction = (tabID: string) => {
|
const closeOtherTabsAction = (tabID: string) => {
|
||||||
const dirtyTabCount = getDirtyTabsCount()
|
const dirtyTabCount = tabs.getDirtyTabsCount()
|
||||||
// If there are dirty tabs, show the confirm modal
|
// If there are dirty tabs, show the confirm modal
|
||||||
if (dirtyTabCount > 0) {
|
if (dirtyTabCount > 0) {
|
||||||
confirmingCloseAllTabs.value = true
|
confirmingCloseAllTabs.value = true
|
||||||
unsavedTabsCount.value = dirtyTabCount
|
unsavedTabsCount.value = dirtyTabCount
|
||||||
exceptedTabID.value = tabID
|
exceptedTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
closeOtherTabs(tabID)
|
tabs.closeOtherTabs(tabID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onResolveConfirmCloseAllTabs = () => {
|
const onResolveConfirmCloseAllTabs = () => {
|
||||||
if (exceptedTabID.value) closeOtherTabs(exceptedTabID.value)
|
if (exceptedTabID.value) tabs.closeOtherTabs(exceptedTabID.value)
|
||||||
confirmingCloseAllTabs.value = false
|
confirmingCloseAllTabs.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const onTabUpdate = (tab: HoppGQLTab) => {
|
const onTabUpdate = (tab: HoppTab<HoppGQLDocument>) => {
|
||||||
updateTab(tab)
|
tabs.updateTab(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@@ -189,33 +184,33 @@ onBeforeUnmount(() => {
|
|||||||
const editReqModalReqName = ref("")
|
const editReqModalReqName = ref("")
|
||||||
const showRenamingReqNameModalForTabID = ref<string>()
|
const showRenamingReqNameModalForTabID = ref<string>()
|
||||||
|
|
||||||
const openReqRenameModal = (tab: HoppGQLTab) => {
|
const openReqRenameModal = (tab: HoppTab<HoppGQLDocument>) => {
|
||||||
editReqModalReqName.value = tab.document.request.name
|
editReqModalReqName.value = tab.document.request.name
|
||||||
showRenamingReqNameModalForTabID.value = tab.id
|
showRenamingReqNameModalForTabID.value = tab.id
|
||||||
}
|
}
|
||||||
|
|
||||||
const renameReqName = () => {
|
const renameReqName = () => {
|
||||||
const tab = getTabRef(showRenamingReqNameModalForTabID.value!)
|
const tab = tabs.getTabRef(showRenamingReqNameModalForTabID.value!)
|
||||||
if (tab.value) {
|
if (tab.value) {
|
||||||
tab.value.document.request.name = editReqModalReqName.value
|
tab.value.document.request.name = editReqModalReqName.value
|
||||||
updateTab(tab.value)
|
tabs.updateTab(tab.value)
|
||||||
}
|
}
|
||||||
showRenamingReqNameModalForTabID.value = undefined
|
showRenamingReqNameModalForTabID.value = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateTab = (tabID: string) => {
|
const duplicateTab = (tabID: string) => {
|
||||||
const tab = getTabRef(tabID)
|
const tab = tabs.getTabRef(tabID)
|
||||||
if (tab.value) {
|
if (tab.value) {
|
||||||
const newTab = createNewTab({
|
const newTab = tabs.createNewTab({
|
||||||
request: tab.value.document.request,
|
request: tab.value.document.request,
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
})
|
})
|
||||||
currentTabID.value = newTab.id
|
tabs.setActiveTab(newTab.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defineActionHandler("gql.request.open", ({ request, saveContext }) => {
|
defineActionHandler("gql.request.open", ({ request, saveContext }) => {
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
saveContext,
|
saveContext,
|
||||||
request: request,
|
request: request,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
@@ -223,7 +218,7 @@ defineActionHandler("gql.request.open", ({ request, saveContext }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
defineActionHandler("request.rename", () => {
|
defineActionHandler("request.rename", () => {
|
||||||
openReqRenameModal(getTabRef(currentTabID.value).value!)
|
openReqRenameModal(tabs.getTabRef(currentTabID.value).value!)
|
||||||
})
|
})
|
||||||
|
|
||||||
defineActionHandler("tab.duplicate-tab", ({ tabID }) => {
|
defineActionHandler("tab.duplicate-tab", ({ tabID }) => {
|
||||||
@@ -233,7 +228,7 @@ defineActionHandler("tab.close-current", () => {
|
|||||||
removeTab(currentTabID.value)
|
removeTab(currentTabID.value)
|
||||||
})
|
})
|
||||||
defineActionHandler("tab.close-other", () => {
|
defineActionHandler("tab.close-other", () => {
|
||||||
closeOtherTabs(currentTabID.value)
|
tabs.closeOtherTabs(currentTabID.value)
|
||||||
})
|
})
|
||||||
defineActionHandler("tab.open-new", addNewTab)
|
defineActionHandler("tab.open-new", addNewTab)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -11,17 +11,17 @@
|
|||||||
@sort="sortTabs"
|
@sort="sortTabs"
|
||||||
>
|
>
|
||||||
<HoppSmartWindow
|
<HoppSmartWindow
|
||||||
v-for="tab in tabs"
|
v-for="tab in activeTabs"
|
||||||
:id="tab.id"
|
:id="tab.id"
|
||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
:label="tab.document.request.name"
|
:label="tab.document.request.name"
|
||||||
:is-removable="tabs.length > 1"
|
:is-removable="activeTabs.length > 1"
|
||||||
:close-visibility="'hover'"
|
:close-visibility="'hover'"
|
||||||
>
|
>
|
||||||
<template #tabhead>
|
<template #tabhead>
|
||||||
<HttpTabHead
|
<HttpTabHead
|
||||||
:tab="tab"
|
:tab="tab"
|
||||||
:is-removable="tabs.length > 1"
|
:is-removable="activeTabs.length > 1"
|
||||||
@open-rename-modal="openReqRenameModal(tab.id)"
|
@open-rename-modal="openReqRenameModal(tab.id)"
|
||||||
@close-tab="removeTab(tab.id)"
|
@close-tab="removeTab(tab.id)"
|
||||||
@close-other-tabs="closeOtherTabsAction(tab.id)"
|
@close-other-tabs="closeOtherTabsAction(tab.id)"
|
||||||
@@ -99,21 +99,6 @@ import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
|||||||
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
||||||
import { useRoute } from "vue-router"
|
import { useRoute } from "vue-router"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import {
|
|
||||||
closeTab,
|
|
||||||
closeOtherTabs,
|
|
||||||
createNewTab,
|
|
||||||
currentActiveTab,
|
|
||||||
currentTabID,
|
|
||||||
getActiveTabs,
|
|
||||||
getTabRef,
|
|
||||||
HoppRESTTab,
|
|
||||||
loadTabsFromPersistedState,
|
|
||||||
persistableTabState,
|
|
||||||
updateTab,
|
|
||||||
updateTabOrdering,
|
|
||||||
getDirtyTabsCount,
|
|
||||||
} from "~/helpers/rest/tab"
|
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
import { onLoggedIn } from "~/composables/auth"
|
import { onLoggedIn } from "~/composables/auth"
|
||||||
@@ -128,7 +113,6 @@ import {
|
|||||||
Subscription,
|
Subscription,
|
||||||
} from "rxjs"
|
} from "rxjs"
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { PersistableRESTTabState } from "~/helpers/rest/tab"
|
|
||||||
import { watchDebounced } from "@vueuse/core"
|
import { watchDebounced } from "@vueuse/core"
|
||||||
import { oauthRedirect } from "~/helpers/oauth"
|
import { oauthRedirect } from "~/helpers/oauth"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
@@ -142,6 +126,9 @@ import { HeaderInspectorService } from "~/services/inspection/inspectors/header.
|
|||||||
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
||||||
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { HoppTab, PersistableTabState } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
const savingRequest = ref(false)
|
const savingRequest = ref(false)
|
||||||
const confirmingCloseForTabID = ref<string | null>(null)
|
const confirmingCloseForTabID = ref<string | null>(null)
|
||||||
@@ -155,6 +142,10 @@ const renameTabID = ref<string | null>(null)
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
|
const currentTabID = tabs.currentTabID
|
||||||
|
|
||||||
type PopupDetails = {
|
type PopupDetails = {
|
||||||
show: boolean
|
show: boolean
|
||||||
position: {
|
position: {
|
||||||
@@ -173,13 +164,13 @@ const contextMenu = ref<PopupDetails>({
|
|||||||
text: null,
|
text: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabs = getActiveTabs()
|
const activeTabs = tabs.getActiveTabs()
|
||||||
|
|
||||||
const confirmSync = useReadonlyStream(currentSyncingStatus$, {
|
const confirmSync = useReadonlyStream(currentSyncingStatus$, {
|
||||||
isInitialSync: false,
|
isInitialSync: false,
|
||||||
shouldSync: true,
|
shouldSync: true,
|
||||||
})
|
})
|
||||||
const tabStateForSync = ref<PersistableRESTTabState | null>(null)
|
const tabStateForSync = ref<PersistableTabState<HoppRESTDocument> | null>(null)
|
||||||
|
|
||||||
function bindRequestToURLParams() {
|
function bindRequestToURLParams() {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -190,91 +181,92 @@ function bindRequestToURLParams() {
|
|||||||
// We skip URL params parsing
|
// We skip URL params parsing
|
||||||
if (Object.keys(query).length === 0 || query.code || query.error) return
|
if (Object.keys(query).length === 0 || query.code || query.error) return
|
||||||
|
|
||||||
const request = currentActiveTab.value.document.request
|
const request = tabs.currentActiveTab.value.document.request
|
||||||
|
|
||||||
currentActiveTab.value.document.request = safelyExtractRESTRequest(
|
tabs.currentActiveTab.value.document.request = safelyExtractRESTRequest(
|
||||||
translateExtURLParams(query, request),
|
translateExtURLParams(query, request),
|
||||||
getDefaultRESTRequest()
|
getDefaultRESTRequest()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onTabUpdate = (tab: HoppRESTTab) => {
|
const onTabUpdate = (tab: HoppTab<HoppRESTDocument>) => {
|
||||||
updateTab(tab)
|
tabs.updateTab(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
const addNewTab = () => {
|
const addNewTab = () => {
|
||||||
const tab = createNewTab({
|
const tab = tabs.createNewTab({
|
||||||
request: getDefaultRESTRequest(),
|
request: getDefaultRESTRequest(),
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
currentTabID.value = tab.id
|
tabs.setActiveTab(tab.id)
|
||||||
}
|
}
|
||||||
const sortTabs = (e: { oldIndex: number; newIndex: number }) => {
|
const sortTabs = (e: { oldIndex: number; newIndex: number }) => {
|
||||||
updateTabOrdering(e.oldIndex, e.newIndex)
|
tabs.updateTabOrdering(e.oldIndex, e.newIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const removeTab = (tabID: string) => {
|
const removeTab = (tabID: string) => {
|
||||||
const tabState = getTabRef(tabID).value
|
const tabState = tabs.getTabRef(tabID).value
|
||||||
|
|
||||||
if (tabState.document.isDirty) {
|
if (tabState.document.isDirty) {
|
||||||
confirmingCloseForTabID.value = tabID
|
confirmingCloseForTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
closeTab(tabState.id)
|
tabs.closeTab(tabState.id)
|
||||||
inspectionService.deleteTabInspectorResult(tabState.id)
|
inspectionService.deleteTabInspectorResult(tabState.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeOtherTabsAction = (tabID: string) => {
|
const closeOtherTabsAction = (tabID: string) => {
|
||||||
const isTabDirty = getTabRef(tabID).value?.document.isDirty
|
const isTabDirty = tabs.getTabRef(tabID).value?.document.isDirty
|
||||||
const dirtyTabCount = getDirtyTabsCount()
|
const dirtyTabCount = tabs.getDirtyTabsCount()
|
||||||
// If current tab is dirty, so we need to subtract 1 from the dirty tab count
|
// If current tab is dirty, so we need to subtract 1 from the dirty tab count
|
||||||
const balanceDirtyTabCount = isTabDirty ? dirtyTabCount - 1 : dirtyTabCount
|
const balanceDirtyTabCount = isTabDirty ? dirtyTabCount - 1 : dirtyTabCount
|
||||||
|
|
||||||
// If there are dirty tabs, show the confirm modal
|
// If there are dirty tabs, show the confirm modal
|
||||||
if (balanceDirtyTabCount > 0) {
|
if (balanceDirtyTabCount > 0) {
|
||||||
confirmingCloseAllTabs.value = true
|
confirmingCloseAllTabs.value = true
|
||||||
unsavedTabsCount.value = balanceDirtyTabCount
|
unsavedTabsCount.value = balanceDirtyTabCount
|
||||||
exceptedTabID.value = tabID
|
exceptedTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
closeOtherTabs(tabID)
|
tabs.closeOtherTabs(tabID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateTab = (tabID: string) => {
|
const duplicateTab = (tabID: string) => {
|
||||||
const tab = getTabRef(tabID)
|
const tab = tabs.getTabRef(tabID)
|
||||||
if (tab.value) {
|
if (tab.value) {
|
||||||
const newTab = createNewTab({
|
const newTab = tabs.createNewTab({
|
||||||
request: cloneDeep(tab.value.document.request),
|
request: cloneDeep(tab.value.document.request),
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
})
|
})
|
||||||
currentTabID.value = newTab.id
|
tabs.setActiveTab(newTab.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onResolveConfirmCloseAllTabs = () => {
|
const onResolveConfirmCloseAllTabs = () => {
|
||||||
if (exceptedTabID.value) closeOtherTabs(exceptedTabID.value)
|
if (exceptedTabID.value) tabs.closeOtherTabs(exceptedTabID.value)
|
||||||
confirmingCloseAllTabs.value = false
|
confirmingCloseAllTabs.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const openReqRenameModal = (tabID?: string) => {
|
const openReqRenameModal = (tabID?: string) => {
|
||||||
if (tabID) {
|
if (tabID) {
|
||||||
const tab = getTabRef(tabID)
|
const tab = tabs.getTabRef(tabID)
|
||||||
reqName.value = tab.value.document.request.name
|
reqName.value = tab.value.document.request.name
|
||||||
renameTabID.value = tabID
|
renameTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
reqName.value = currentActiveTab.value.document.request.name
|
reqName.value = tabs.currentActiveTab.value.document.request.name
|
||||||
}
|
}
|
||||||
showRenamingReqNameModal.value = true
|
showRenamingReqNameModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const renameReqName = () => {
|
const renameReqName = () => {
|
||||||
const tab = getTabRef(renameTabID.value ?? currentTabID.value)
|
const tab = tabs.getTabRef(renameTabID.value ?? currentTabID.value)
|
||||||
if (tab.value) {
|
if (tab.value) {
|
||||||
tab.value.document.request.name = reqName.value
|
tab.value.document.request.name = reqName.value
|
||||||
updateTab(tab.value)
|
tabs.updateTab(tab.value)
|
||||||
}
|
}
|
||||||
showRenamingReqNameModal.value = false
|
showRenamingReqNameModal.value = false
|
||||||
}
|
}
|
||||||
@@ -284,7 +276,7 @@ const renameReqName = () => {
|
|||||||
*/
|
*/
|
||||||
const onCloseConfirmSaveTab = () => {
|
const onCloseConfirmSaveTab = () => {
|
||||||
if (!savingRequest.value && confirmingCloseForTabID.value) {
|
if (!savingRequest.value && confirmingCloseForTabID.value) {
|
||||||
closeTab(confirmingCloseForTabID.value)
|
tabs.closeTab(confirmingCloseForTabID.value)
|
||||||
inspectionService.deleteTabInspectorResult(confirmingCloseForTabID.value)
|
inspectionService.deleteTabInspectorResult(confirmingCloseForTabID.value)
|
||||||
confirmingCloseForTabID.value = null
|
confirmingCloseForTabID.value = null
|
||||||
}
|
}
|
||||||
@@ -294,11 +286,11 @@ const onCloseConfirmSaveTab = () => {
|
|||||||
* Called when the user confirms they want to save the tab
|
* Called when the user confirms they want to save the tab
|
||||||
*/
|
*/
|
||||||
const onResolveConfirmSaveTab = () => {
|
const onResolveConfirmSaveTab = () => {
|
||||||
if (currentActiveTab.value.document.saveContext) {
|
if (tabs.currentActiveTab.value.document.saveContext) {
|
||||||
invokeAction("request.save")
|
invokeAction("request.save")
|
||||||
|
|
||||||
if (confirmingCloseForTabID.value) {
|
if (confirmingCloseForTabID.value) {
|
||||||
closeTab(confirmingCloseForTabID.value)
|
tabs.closeTab(confirmingCloseForTabID.value)
|
||||||
confirmingCloseForTabID.value = null
|
confirmingCloseForTabID.value = null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -312,13 +304,14 @@ const onResolveConfirmSaveTab = () => {
|
|||||||
const onSaveModalClose = () => {
|
const onSaveModalClose = () => {
|
||||||
savingRequest.value = false
|
savingRequest.value = false
|
||||||
if (confirmingCloseForTabID.value) {
|
if (confirmingCloseForTabID.value) {
|
||||||
closeTab(confirmingCloseForTabID.value)
|
tabs.closeTab(confirmingCloseForTabID.value)
|
||||||
confirmingCloseForTabID.value = null
|
confirmingCloseForTabID.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncTabState = () => {
|
const syncTabState = () => {
|
||||||
if (tabStateForSync.value) loadTabsFromPersistedState(tabStateForSync.value)
|
if (tabStateForSync.value)
|
||||||
|
tabs.loadTabsFromPersistedState(tabStateForSync.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -329,10 +322,11 @@ const syncTabState = () => {
|
|||||||
*/
|
*/
|
||||||
function startTabStateSync(): Subscription {
|
function startTabStateSync(): Subscription {
|
||||||
const currentUser$ = platform.auth.getCurrentUserStream()
|
const currentUser$ = platform.auth.getCurrentUserStream()
|
||||||
const tabState$ = new BehaviorSubject<PersistableRESTTabState | null>(null)
|
const tabState$ =
|
||||||
|
new BehaviorSubject<PersistableTabState<HoppRESTDocument> | null>(null)
|
||||||
|
|
||||||
watchDebounced(
|
watchDebounced(
|
||||||
persistableTabState,
|
tabs.persistableTabState,
|
||||||
(state) => {
|
(state) => {
|
||||||
tabState$.next(state)
|
tabState$.next(state)
|
||||||
},
|
},
|
||||||
@@ -429,9 +423,10 @@ function oAuthURL() {
|
|||||||
tokenInfo.hasOwnProperty("access_token")
|
tokenInfo.hasOwnProperty("access_token")
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
currentActiveTab.value.document.request.auth.authType === "oauth-2"
|
tabs.currentActiveTab.value.document.request.auth.authType ===
|
||||||
|
"oauth-2"
|
||||||
) {
|
) {
|
||||||
currentActiveTab.value.document.request.auth.token =
|
tabs.currentActiveTab.value.document.request.auth.token =
|
||||||
tokenInfo.access_token
|
tokenInfo.access_token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,7 +457,7 @@ bindRequestToURLParams()
|
|||||||
oAuthURL()
|
oAuthURL()
|
||||||
|
|
||||||
defineActionHandler("rest.request.open", ({ doc }) => {
|
defineActionHandler("rest.request.open", ({ doc }) => {
|
||||||
createNewTab(doc)
|
tabs.createNewTab(doc)
|
||||||
})
|
})
|
||||||
|
|
||||||
defineActionHandler("request.rename", openReqRenameModal)
|
defineActionHandler("request.rename", openReqRenameModal)
|
||||||
@@ -473,7 +468,7 @@ defineActionHandler("tab.close-current", () => {
|
|||||||
removeTab(currentTabID.value)
|
removeTab(currentTabID.value)
|
||||||
})
|
})
|
||||||
defineActionHandler("tab.close-other", () => {
|
defineActionHandler("tab.close-other", () => {
|
||||||
closeOtherTabs(currentTabID.value)
|
tabs.closeOtherTabs(currentTabID.value)
|
||||||
})
|
})
|
||||||
defineActionHandler("tab.open-new", addNewTab)
|
defineActionHandler("tab.open-new", addNewTab)
|
||||||
|
|
||||||
|
|||||||
@@ -82,15 +82,18 @@ import {
|
|||||||
|
|
||||||
import IconHome from "~icons/lucide/home"
|
import IconHome from "~icons/lucide/home"
|
||||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||||
import { createNewTab } from "~/helpers/rest/tab"
|
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
const invalidLink = ref(false)
|
const invalidLink = ref(false)
|
||||||
const shortcodeID = ref("")
|
const shortcodeID = ref("")
|
||||||
|
|
||||||
@@ -127,7 +130,7 @@ const addRequestToTab = () => {
|
|||||||
|
|
||||||
const request: unknown = JSON.parse(data.right.shortcode?.request as string)
|
const request: unknown = JSON.parse(data.right.shortcode?.request as string)
|
||||||
|
|
||||||
createNewTab({
|
tabs.createNewTab({
|
||||||
request: safelyExtractRESTRequest(request, getDefaultRESTRequest()),
|
request: safelyExtractRESTRequest(request, getDefaultRESTRequest()),
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { PersistableRESTTabState } from "~/helpers/rest/tab"
|
import { PersistableTabState } from "~/services/tab"
|
||||||
import { HoppUser } from "./auth"
|
import { HoppUser } from "./auth"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
export type TabStatePlatformDef = {
|
export type TabStatePlatformDef = {
|
||||||
loadTabStateFromSync: () => Promise<PersistableRESTTabState | null>
|
loadTabStateFromSync: () => Promise<PersistableTabState<HoppRESTDocument> | null>
|
||||||
writeCurrentTabState: (
|
writeCurrentTabState: (
|
||||||
user: HoppUser,
|
user: HoppUser,
|
||||||
persistableTabState: PersistableRESTTabState
|
persistableTabState: PersistableTabState<HoppRESTDocument>
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,6 @@ vi.mock("~/modules/i18n", () => ({
|
|||||||
getI18n: () => (x: string) => x,
|
getI18n: () => (x: string) => x,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const tabMock = vi.hoisted(() => ({
|
|
||||||
currentActiveTab: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock("~/helpers/rest/tab", () => ({
|
|
||||||
__esModule: true,
|
|
||||||
currentActiveTab: tabMock.currentActiveTab,
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe("ParameterMenuService", () => {
|
describe("ParameterMenuService", () => {
|
||||||
it("registers with the contextmenu service upon initialization", () => {
|
it("registers with the contextmenu service upon initialization", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { TestContainer } from "dioc/testing"
|
import { TestContainer } from "dioc/testing"
|
||||||
import { describe, expect, it, vi } from "vitest"
|
import { describe, expect, it, vi } from "vitest"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { ContextMenuService } from "../.."
|
import { ContextMenuService } from "../.."
|
||||||
import { URLMenuService } from "../url.menu"
|
import { URLMenuService } from "../url.menu"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
@@ -9,15 +10,6 @@ vi.mock("~/modules/i18n", () => ({
|
|||||||
getI18n: () => (x: string) => x,
|
getI18n: () => (x: string) => x,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const tabMock = vi.hoisted(() => ({
|
|
||||||
createNewTab: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock("~/helpers/rest/tab", () => ({
|
|
||||||
__esModule: true,
|
|
||||||
createNewTab: tabMock.createNewTab,
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe("URLMenuService", () => {
|
describe("URLMenuService", () => {
|
||||||
it("registers with the contextmenu service upon initialization", () => {
|
it("registers with the contextmenu service upon initialization", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
@@ -64,6 +56,10 @@ describe("URLMenuService", () => {
|
|||||||
|
|
||||||
it("should call the openNewTab function when action is called and a new hoppscotch tab is opened", () => {
|
it("should call the openNewTab function when action is called and a new hoppscotch tab is opened", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
|
const createNewTabFn = vi.fn()
|
||||||
|
container.bindMock(RESTTabService, {
|
||||||
|
createNewTab: createNewTabFn,
|
||||||
|
})
|
||||||
const url = container.bind(URLMenuService)
|
const url = container.bind(URLMenuService)
|
||||||
|
|
||||||
const test = "https://hoppscotch.io"
|
const test = "https://hoppscotch.io"
|
||||||
@@ -76,8 +72,8 @@ describe("URLMenuService", () => {
|
|||||||
endpoint: test,
|
endpoint: test,
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(tabMock.createNewTab).toHaveBeenCalledOnce()
|
expect(createNewTabFn).toHaveBeenCalledOnce()
|
||||||
expect(tabMock.createNewTab).toHaveBeenCalledWith({
|
expect(createNewTabFn).toHaveBeenCalledWith({
|
||||||
request: request,
|
request: request,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import {
|
|||||||
} from "../"
|
} from "../"
|
||||||
import { markRaw, ref } from "vue"
|
import { markRaw, ref } from "vue"
|
||||||
import IconArrowDownRight from "~icons/lucide/arrow-down-right"
|
import IconArrowDownRight from "~icons/lucide/arrow-down-right"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
|
||||||
import { getI18n } from "~/modules/i18n"
|
import { getI18n } from "~/modules/i18n"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
|
||||||
//regex containing both url and parameter
|
//regex containing both url and parameter
|
||||||
const urlAndParameterRegex = new RegExp("[^&?]*?=[^&?]*")
|
const urlAndParameterRegex = new RegExp("[^&?]*?=[^&?]*")
|
||||||
@@ -88,20 +89,23 @@ export class ParameterMenuService extends Service implements ContextMenu {
|
|||||||
queryParams.push({ key, value, active: true })
|
queryParams.push({ key, value, active: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabService = getService(RESTTabService)
|
||||||
|
|
||||||
// add the parameters to the current request parameters
|
// add the parameters to the current request parameters
|
||||||
currentActiveTab.value.document.request.params = [
|
tabService.currentActiveTab.value.document.request.params = [
|
||||||
...currentActiveTab.value.document.request.params,
|
...tabService.currentActiveTab.value.document.request.params,
|
||||||
...queryParams,
|
...queryParams,
|
||||||
]
|
]
|
||||||
|
|
||||||
if (newURL) {
|
if (newURL) {
|
||||||
currentActiveTab.value.document.request.endpoint = newURL
|
tabService.currentActiveTab.value.document.request.endpoint = newURL
|
||||||
} else {
|
} else {
|
||||||
// remove the parameter from the URL
|
// remove the parameter from the URL
|
||||||
const textRegex = new RegExp(`\\b${text.replace(/\?/g, "")}\\b`, "gi")
|
const textRegex = new RegExp(`\\b${text.replace(/\?/g, "")}\\b`, "gi")
|
||||||
const sanitizedWord = currentActiveTab.value.document.request.endpoint
|
const sanitizedWord =
|
||||||
|
tabService.currentActiveTab.value.document.request.endpoint
|
||||||
const newURL = sanitizedWord.replace(textRegex, "")
|
const newURL = sanitizedWord.replace(textRegex, "")
|
||||||
currentActiveTab.value.document.request.endpoint = newURL
|
tabService.currentActiveTab.value.document.request.endpoint = newURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user