Compare commits
31 Commits
2023.8.1
...
feature/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e20904e896 | ||
|
|
9dcbc4a126 | ||
|
|
a215860782 | ||
|
|
59b5a50a97 | ||
|
|
d1c9c3583f | ||
|
|
2462492c86 | ||
|
|
7a9f0c8756 | ||
|
|
46caf9b198 | ||
|
|
f5db54484c | ||
|
|
8deb6471b9 | ||
|
|
73b3ff8e41 | ||
|
|
016a18d3b2 | ||
|
|
ba31cdabea | ||
|
|
51510566bc | ||
|
|
cabee0ecc8 | ||
|
|
2c2b39a236 | ||
|
|
78450c9316 | ||
|
|
b18fd90b64 | ||
|
|
0188a8d7db | ||
|
|
6c63a8dc28 | ||
|
|
17d6ae15a5 | ||
|
|
40f72278a9 | ||
|
|
f717704731 | ||
|
|
185c225297 | ||
|
|
2694731c36 | ||
|
|
ae89af9978 | ||
|
|
87d617012f | ||
|
|
2420b3fa42 | ||
|
|
175a991ec4 | ||
|
|
0301649aff | ||
|
|
544b045300 |
@@ -5,5 +5,5 @@
|
||||
"features": {
|
||||
"ghcr.io/NicoVIII/devcontainer-features/pnpm:1": {}
|
||||
},
|
||||
"postCreateCommand": "mv .env.example .env && pnpm i"
|
||||
"postCreateCommand": "cp .env.example .env && pnpm i"
|
||||
}
|
||||
|
||||
10
package.json
10
package.json
@@ -22,16 +22,14 @@
|
||||
"workspaces": [
|
||||
"./packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^16.2.3",
|
||||
"@commitlint/config-conventional": "^16.2.1",
|
||||
"@types/node": "^17.0.24",
|
||||
"@types/node": "17.0.27",
|
||||
"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": {
|
||||
"packageExtensions": {
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
"devDependencies": {
|
||||
"@lezer/generator": "^1.5.0",
|
||||
"mocha": "^9.2.2",
|
||||
"rollup": "^2.70.2",
|
||||
"rollup-plugin-dts": "^4.2.1",
|
||||
"rollup-plugin-ts": "^2.0.7",
|
||||
"typescript": "^4.6.3"
|
||||
"rollup": "^3.29.3",
|
||||
"rollup-plugin-dts": "^6.0.2",
|
||||
"rollup-plugin-ts": "^3.4.5",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hoppscotch-backend",
|
||||
"version": "2023.8.1",
|
||||
"version": "2023.8.2",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
@@ -24,18 +24,17 @@
|
||||
"do-test": "pnpm run test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs-modules/mailer": "^1.8.1",
|
||||
"@nestjs/apollo": "^10.1.6",
|
||||
"@nestjs/common": "^9.2.1",
|
||||
"@nestjs/core": "^9.2.1",
|
||||
"@nestjs/graphql": "^10.1.6",
|
||||
"@nestjs/jwt": "^10.0.1",
|
||||
"@nestjs/passport": "^9.0.0",
|
||||
"@nestjs/platform-express": "^9.2.1",
|
||||
"@nestjs/throttler": "^4.0.0",
|
||||
"@apollo/server": "^4.9.4",
|
||||
"@nestjs-modules/mailer": "^1.9.1",
|
||||
"@nestjs/apollo": "^12.0.9",
|
||||
"@nestjs/common": "^10.2.6",
|
||||
"@nestjs/core": "^10.2.6",
|
||||
"@nestjs/graphql": "^12.0.9",
|
||||
"@nestjs/jwt": "^10.1.1",
|
||||
"@nestjs/passport": "^10.0.2",
|
||||
"@nestjs/platform-express": "^10.2.6",
|
||||
"@nestjs/throttler": "^5.0.0",
|
||||
"@prisma/client": "^4.16.2",
|
||||
"apollo-server-express": "^3.11.1",
|
||||
"apollo-server-plugin-base": "^3.7.1",
|
||||
"argon2": "^0.30.3",
|
||||
"bcrypt": "^5.1.0",
|
||||
"cookie": "^0.5.0",
|
||||
@@ -43,10 +42,11 @@
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.3",
|
||||
"fp-ts": "^2.13.1",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-query-complexity": "^0.12.0",
|
||||
"graphql-redis-subscriptions": "^2.5.0",
|
||||
"graphql-redis-subscriptions": "^2.6.0",
|
||||
"graphql-subscriptions": "^2.0.0",
|
||||
"graphql-ws": "^5.14.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"io-ts": "^2.2.16",
|
||||
"luxon": "^3.2.1",
|
||||
@@ -63,11 +63,10 @@
|
||||
"rxjs": "^7.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.1.5",
|
||||
"@nestjs/schematics": "^9.0.3",
|
||||
"@nestjs/testing": "^9.2.1",
|
||||
"@nestjs/cli": "^10.1.18",
|
||||
"@nestjs/schematics": "^10.0.2",
|
||||
"@nestjs/testing": "^10.2.6",
|
||||
"@relmify/jest-fp-ts": "^2.0.2",
|
||||
"@types/argon2": "^0.15.0",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { ObjectType } from '@nestjs/graphql';
|
||||
import { ObjectType, OmitType } from '@nestjs/graphql';
|
||||
import { User } from 'src/user/user.model';
|
||||
|
||||
@ObjectType()
|
||||
export class Admin {}
|
||||
export class Admin extends OmitType(User, [
|
||||
'isAdmin',
|
||||
'currentRESTSession',
|
||||
'currentGQLSession',
|
||||
]) {}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { TeamInvitationModule } from '../team-invitation/team-invitation.module'
|
||||
import { TeamEnvironmentsModule } from '../team-environments/team-environments.module';
|
||||
import { TeamCollectionModule } from '../team-collection/team-collection.module';
|
||||
import { TeamRequestModule } from '../team-request/team-request.module';
|
||||
import { InfraResolver } from './infra.resolver';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -23,7 +24,7 @@ import { TeamRequestModule } from '../team-request/team-request.module';
|
||||
TeamCollectionModule,
|
||||
TeamRequestModule,
|
||||
],
|
||||
providers: [AdminResolver, AdminService],
|
||||
providers: [InfraResolver, AdminResolver, AdminService],
|
||||
exports: [AdminService],
|
||||
})
|
||||
export class AdminModule {}
|
||||
|
||||
@@ -21,15 +21,15 @@ import { InvitedUser } from './invited-user.model';
|
||||
import { GqlUser } from '../decorators/gql-user.decorator';
|
||||
import { PubSubService } from '../pubsub/pubsub.service';
|
||||
import { Team, TeamMember } from '../team/team.model';
|
||||
import { User } from '../user/user.model';
|
||||
import { TeamInvitation } from '../team-invitation/team-invitation.model';
|
||||
import { PaginationArgs } from '../types/input-types.args';
|
||||
import {
|
||||
AddUserToTeamArgs,
|
||||
ChangeUserRoleInTeamArgs,
|
||||
} from './input-types.args';
|
||||
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
|
||||
import { SkipThrottle } from '@nestjs/throttler';
|
||||
import { User } from 'src/user/user.model';
|
||||
import { PaginationArgs } from 'src/types/input-types.args';
|
||||
import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
|
||||
|
||||
@UseGuards(GqlThrottlerGuard)
|
||||
@Resolver(() => Admin)
|
||||
@@ -51,6 +51,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => [User], {
|
||||
description: 'Returns a list of all admin users in infra',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard, GqlAdminGuard)
|
||||
async admins() {
|
||||
@@ -59,6 +60,7 @@ export class AdminResolver {
|
||||
}
|
||||
@ResolveField(() => User, {
|
||||
description: 'Returns a user info by UID',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard, GqlAdminGuard)
|
||||
async userInfo(
|
||||
@@ -76,6 +78,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => [User], {
|
||||
description: 'Returns a list of all the users in infra',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard, GqlAdminGuard)
|
||||
async allUsers(
|
||||
@@ -88,6 +91,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => [InvitedUser], {
|
||||
description: 'Returns a list of all the invited users',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async invitedUsers(@Parent() admin: Admin): Promise<InvitedUser[]> {
|
||||
const users = await this.adminService.fetchInvitedUsers();
|
||||
@@ -96,6 +100,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => [Team], {
|
||||
description: 'Returns a list of all the teams in the infra',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async allTeams(
|
||||
@Parent() admin: Admin,
|
||||
@@ -106,6 +111,7 @@ export class AdminResolver {
|
||||
}
|
||||
@ResolveField(() => Team, {
|
||||
description: 'Returns a team info by ID when requested by Admin',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async teamInfo(
|
||||
@Parent() admin: Admin,
|
||||
@@ -123,6 +129,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return count of all the members in a team',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async membersCountInTeam(
|
||||
@Parent() admin: Admin,
|
||||
@@ -140,6 +147,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return count of all the stored collections in a team',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async collectionCountInTeam(
|
||||
@Parent() admin: Admin,
|
||||
@@ -155,6 +163,7 @@ export class AdminResolver {
|
||||
}
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return count of all the stored requests in a team',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async requestCountInTeam(
|
||||
@Parent() admin: Admin,
|
||||
@@ -171,6 +180,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return count of all the stored environments in a team',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async environmentCountInTeam(
|
||||
@Parent() admin: Admin,
|
||||
@@ -187,6 +197,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => [TeamInvitation], {
|
||||
description: 'Return all the pending invitations in a team',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async pendingInvitationCountInTeam(
|
||||
@Parent() admin: Admin,
|
||||
@@ -205,6 +216,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return total number of Users in organization',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async usersCount() {
|
||||
return this.adminService.getUsersCount();
|
||||
@@ -212,6 +224,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return total number of Teams in organization',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async teamsCount() {
|
||||
return this.adminService.getTeamsCount();
|
||||
@@ -219,6 +232,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return total number of Team Collections in organization',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async teamCollectionsCount() {
|
||||
return this.adminService.getTeamCollectionsCount();
|
||||
@@ -226,6 +240,7 @@ export class AdminResolver {
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return total number of Team Requests in organization',
|
||||
deprecationReason: 'Use `infra` query instead',
|
||||
})
|
||||
async teamRequestsCount() {
|
||||
return this.adminService.getTeamRequestsCount();
|
||||
|
||||
10
packages/hoppscotch-backend/src/admin/infra.model.ts
Normal file
10
packages/hoppscotch-backend/src/admin/infra.model.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
import { Admin } from './admin.model';
|
||||
|
||||
@ObjectType()
|
||||
export class Infra {
|
||||
@Field(() => Admin, {
|
||||
description: 'Admin who executed the action',
|
||||
})
|
||||
executedBy: Admin;
|
||||
}
|
||||
205
packages/hoppscotch-backend/src/admin/infra.resolver.ts
Normal file
205
packages/hoppscotch-backend/src/admin/infra.resolver.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Args, ID, Query, ResolveField, Resolver } from '@nestjs/graphql';
|
||||
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
|
||||
import { Infra } from './infra.model';
|
||||
import { AdminService } from './admin.service';
|
||||
import { GqlAuthGuard } from 'src/guards/gql-auth.guard';
|
||||
import { GqlAdminGuard } from './guards/gql-admin.guard';
|
||||
import { User } from 'src/user/user.model';
|
||||
import { AuthUser } from 'src/types/AuthUser';
|
||||
import { throwErr } from 'src/utils';
|
||||
import * as E from 'fp-ts/Either';
|
||||
import { Admin } from './admin.model';
|
||||
import { PaginationArgs } from 'src/types/input-types.args';
|
||||
import { InvitedUser } from './invited-user.model';
|
||||
import { Team } from 'src/team/team.model';
|
||||
import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
|
||||
import { GqlAdmin } from './decorators/gql-admin.decorator';
|
||||
|
||||
@UseGuards(GqlThrottlerGuard)
|
||||
@Resolver(() => Infra)
|
||||
export class InfraResolver {
|
||||
constructor(private adminService: AdminService) {}
|
||||
|
||||
@Query(() => Infra, {
|
||||
description: 'Fetch details of the Infrastructure',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard, GqlAdminGuard)
|
||||
infra(@GqlAdmin() admin: Admin) {
|
||||
const infra: Infra = { executedBy: admin };
|
||||
return infra;
|
||||
}
|
||||
|
||||
@ResolveField(() => [User], {
|
||||
description: 'Returns a list of all admin users in infra',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard, GqlAdminGuard)
|
||||
async admins() {
|
||||
const admins = await this.adminService.fetchAdmins();
|
||||
return admins;
|
||||
}
|
||||
|
||||
@ResolveField(() => User, {
|
||||
description: 'Returns a user info by UID',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard, GqlAdminGuard)
|
||||
async userInfo(
|
||||
@Args({
|
||||
name: 'userUid',
|
||||
type: () => ID,
|
||||
description: 'The user UID',
|
||||
})
|
||||
userUid: string,
|
||||
): Promise<AuthUser> {
|
||||
const user = await this.adminService.fetchUserInfo(userUid);
|
||||
if (E.isLeft(user)) throwErr(user.left);
|
||||
return user.right;
|
||||
}
|
||||
|
||||
@ResolveField(() => [User], {
|
||||
description: 'Returns a list of all the users in infra',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard, GqlAdminGuard)
|
||||
async allUsers(@Args() args: PaginationArgs): Promise<AuthUser[]> {
|
||||
const users = await this.adminService.fetchUsers(args.cursor, args.take);
|
||||
return users;
|
||||
}
|
||||
|
||||
@ResolveField(() => [InvitedUser], {
|
||||
description: 'Returns a list of all the invited users',
|
||||
})
|
||||
async invitedUsers(): Promise<InvitedUser[]> {
|
||||
const users = await this.adminService.fetchInvitedUsers();
|
||||
return users;
|
||||
}
|
||||
|
||||
@ResolveField(() => [Team], {
|
||||
description: 'Returns a list of all the teams in the infra',
|
||||
})
|
||||
async allTeams(@Args() args: PaginationArgs): Promise<Team[]> {
|
||||
const teams = await this.adminService.fetchAllTeams(args.cursor, args.take);
|
||||
return teams;
|
||||
}
|
||||
|
||||
@ResolveField(() => Team, {
|
||||
description: 'Returns a team info by ID when requested by Admin',
|
||||
})
|
||||
async teamInfo(
|
||||
@Args({
|
||||
name: 'teamID',
|
||||
type: () => ID,
|
||||
description: 'Team ID for which info to fetch',
|
||||
})
|
||||
teamID: string,
|
||||
): Promise<Team> {
|
||||
const team = await this.adminService.getTeamInfo(teamID);
|
||||
if (E.isLeft(team)) throwErr(team.left);
|
||||
return team.right;
|
||||
}
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return count of all the members in a team',
|
||||
})
|
||||
async membersCountInTeam(
|
||||
@Args({
|
||||
name: 'teamID',
|
||||
type: () => ID,
|
||||
description: 'Team ID for which team members to fetch',
|
||||
nullable: false,
|
||||
})
|
||||
teamID: string,
|
||||
): Promise<number> {
|
||||
const teamMembersCount = await this.adminService.membersCountInTeam(teamID);
|
||||
return teamMembersCount;
|
||||
}
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return count of all the stored collections in a team',
|
||||
})
|
||||
async collectionCountInTeam(
|
||||
@Args({
|
||||
name: 'teamID',
|
||||
type: () => ID,
|
||||
description: 'Team ID for which team members to fetch',
|
||||
})
|
||||
teamID: string,
|
||||
): Promise<number> {
|
||||
const teamCollCount = await this.adminService.collectionCountInTeam(teamID);
|
||||
return teamCollCount;
|
||||
}
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return count of all the stored requests in a team',
|
||||
})
|
||||
async requestCountInTeam(
|
||||
@Args({
|
||||
name: 'teamID',
|
||||
type: () => ID,
|
||||
description: 'Team ID for which team members to fetch',
|
||||
})
|
||||
teamID: string,
|
||||
): Promise<number> {
|
||||
const teamReqCount = await this.adminService.requestCountInTeam(teamID);
|
||||
return teamReqCount;
|
||||
}
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return count of all the stored environments in a team',
|
||||
})
|
||||
async environmentCountInTeam(
|
||||
@Args({
|
||||
name: 'teamID',
|
||||
type: () => ID,
|
||||
description: 'Team ID for which team members to fetch',
|
||||
})
|
||||
teamID: string,
|
||||
): Promise<number> {
|
||||
const envsCount = await this.adminService.environmentCountInTeam(teamID);
|
||||
return envsCount;
|
||||
}
|
||||
|
||||
@ResolveField(() => [TeamInvitation], {
|
||||
description: 'Return all the pending invitations in a team',
|
||||
})
|
||||
async pendingInvitationCountInTeam(
|
||||
@Args({
|
||||
name: 'teamID',
|
||||
type: () => ID,
|
||||
description: 'Team ID for which team members to fetch',
|
||||
})
|
||||
teamID: string,
|
||||
) {
|
||||
const invitations = await this.adminService.pendingInvitationCountInTeam(
|
||||
teamID,
|
||||
);
|
||||
return invitations;
|
||||
}
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return total number of Users in organization',
|
||||
})
|
||||
async usersCount() {
|
||||
return this.adminService.getUsersCount();
|
||||
}
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return total number of Teams in organization',
|
||||
})
|
||||
async teamsCount() {
|
||||
return this.adminService.getTeamsCount();
|
||||
}
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return total number of Team Collections in organization',
|
||||
})
|
||||
async teamCollectionsCount() {
|
||||
return this.adminService.getTeamCollectionsCount();
|
||||
}
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'Return total number of Team Requests in organization',
|
||||
})
|
||||
async teamRequestsCount() {
|
||||
return this.adminService.getTeamRequestsCount();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ForbiddenException, HttpException, Module } from '@nestjs/common';
|
||||
import { HttpException, Module } from '@nestjs/common';
|
||||
import { GraphQLModule } from '@nestjs/graphql';
|
||||
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
||||
import { UserModule } from './user/user.module';
|
||||
@@ -20,33 +20,35 @@ import { ShortcodeModule } from './shortcode/shortcode.module';
|
||||
import { COOKIES_NOT_FOUND } from './errors';
|
||||
import { ThrottlerModule } from '@nestjs/throttler';
|
||||
import { AppController } from './app.controller';
|
||||
import { Context } from 'graphql-ws';
|
||||
import {
|
||||
ApolloServerPluginLandingPageLocalDefault,
|
||||
ApolloServerPluginLandingPageProductionDefault,
|
||||
} from '@apollo/server/plugin/landingPage/default';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
GraphQLModule.forRoot<ApolloDriverConfig>({
|
||||
driver: ApolloDriver,
|
||||
buildSchemaOptions: {
|
||||
numberScalarMode: 'integer',
|
||||
},
|
||||
cors: {
|
||||
origin: process.env.WHITELISTED_ORIGINS.split(','),
|
||||
credentials: true,
|
||||
},
|
||||
playground: process.env.PRODUCTION !== 'true',
|
||||
debug: process.env.PRODUCTION !== 'true',
|
||||
playground: false,
|
||||
plugins: [
|
||||
process.env.PRODUCTION !== 'true'
|
||||
? ApolloServerPluginLandingPageLocalDefault()
|
||||
: ApolloServerPluginLandingPageProductionDefault(),
|
||||
],
|
||||
autoSchemaFile: true,
|
||||
installSubscriptionHandlers: true,
|
||||
subscriptions: {
|
||||
'subscriptions-transport-ws': {
|
||||
'graphql-ws': {
|
||||
path: '/graphql',
|
||||
onConnect: (_, websocket) => {
|
||||
onConnect: (context: Context<any, any>) => {
|
||||
try {
|
||||
const cookies = subscriptionContextCookieParser(
|
||||
websocket.upgradeReq.headers.cookie,
|
||||
context.extra.request.headers.cookie,
|
||||
);
|
||||
|
||||
return {
|
||||
headers: { ...websocket?.upgradeReq?.headers, cookies },
|
||||
};
|
||||
context['cookies'] = cookies;
|
||||
} catch (error) {
|
||||
throw new HttpException(COOKIES_NOT_FOUND, 400, {
|
||||
cause: new Error(COOKIES_NOT_FOUND),
|
||||
@@ -60,12 +62,13 @@ import { AppController } from './app.controller';
|
||||
res,
|
||||
connection,
|
||||
}),
|
||||
driver: ApolloDriver,
|
||||
}),
|
||||
ThrottlerModule.forRoot({
|
||||
ttl: +process.env.RATE_LIMIT_TTL,
|
||||
limit: +process.env.RATE_LIMIT_MAX,
|
||||
}),
|
||||
ThrottlerModule.forRoot([
|
||||
{
|
||||
ttl: +process.env.RATE_LIMIT_TTL,
|
||||
limit: +process.env.RATE_LIMIT_MAX,
|
||||
},
|
||||
]),
|
||||
UserModule,
|
||||
AuthModule,
|
||||
AdminModule,
|
||||
|
||||
@@ -60,13 +60,13 @@ export const authCookieHandler = (
|
||||
res.cookie(AuthTokenType.ACCESS_TOKEN, authTokens.access_token, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'lax',
|
||||
sameSite: process.env.PRODUCTION !== 'true' ? 'none' : 'lax',
|
||||
maxAge: accessTokenValidity,
|
||||
});
|
||||
res.cookie(AuthTokenType.REFRESH_TOKEN, authTokens.refresh_token, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'lax',
|
||||
sameSite: process.env.PRODUCTION !== 'true' ? 'none' : 'lax',
|
||||
maxAge: refreshTokenValidity,
|
||||
});
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import { UserRequestUserCollectionResolver } from './user-request/resolvers/user
|
||||
import { UserEnvsUserResolver } from './user-environment/user.resolver';
|
||||
import { UserHistoryUserResolver } from './user-history/user.resolver';
|
||||
import { UserSettingsUserResolver } from './user-settings/user.resolver';
|
||||
import { InfraResolver } from './admin/infra.resolver';
|
||||
|
||||
/**
|
||||
* All the resolvers present in the application.
|
||||
@@ -34,6 +35,7 @@ import { UserSettingsUserResolver } from './user-settings/user.resolver';
|
||||
* NOTE: This needs to be KEPT UP-TO-DATE to keep the schema accurate
|
||||
*/
|
||||
const RESOLVERS = [
|
||||
InfraResolver,
|
||||
AdminResolver,
|
||||
ShortcodeResolver,
|
||||
TeamResolver,
|
||||
@@ -93,9 +95,7 @@ export async function emitGQLSchemaFile() {
|
||||
numberScalarMode: 'integer',
|
||||
});
|
||||
|
||||
const schemaString = printSchema(schema, {
|
||||
commentDescriptions: true,
|
||||
});
|
||||
const schemaString = printSchema(schema);
|
||||
|
||||
logger.log(`Writing schema to GQL_SCHEMA_EMIT_LOCATION (${destination})`);
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
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
|
||||
// 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 {
|
||||
ApolloServerPlugin,
|
||||
BaseContext,
|
||||
GraphQLRequestListener,
|
||||
} from 'apollo-server-plugin-base';
|
||||
} from '@apollo/server';
|
||||
import { Plugin } from '@nestjs/apollo';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import {
|
||||
@@ -17,7 +18,7 @@ const COMPLEXITY_LIMIT = 50;
|
||||
export class GQLComplexityPlugin implements ApolloServerPlugin {
|
||||
constructor(private gqlSchemaHost: GraphQLSchemaHost) {}
|
||||
|
||||
async requestDidStart(): Promise<GraphQLRequestListener> {
|
||||
async requestDidStart(): Promise<GraphQLRequestListener<BaseContext>> {
|
||||
const { schema } = this.gqlSchemaHost;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Team, TeamCollection as DBTeamCollection } from '@prisma/client';
|
||||
import { mock, mockDeep, mockReset } from 'jest-mock-extended';
|
||||
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||
import {
|
||||
TEAM_COLL_DEST_SAME,
|
||||
TEAM_COLL_INVALID_JSON,
|
||||
@@ -17,9 +17,6 @@ import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||
import { AuthUser } from 'src/types/AuthUser';
|
||||
import { TeamCollectionService } from './team-collection.service';
|
||||
import { TeamCollection } from './team-collection.model';
|
||||
import { TeamCollectionModule } from './team-collection.module';
|
||||
import * as E from 'fp-ts/Either';
|
||||
|
||||
const mockPrisma = mockDeep<PrismaService>();
|
||||
const mockPubSub = mockDeep<PubSubService>();
|
||||
|
||||
@@ -301,7 +301,7 @@ describe('TeamEnvironmentsService', () => {
|
||||
|
||||
describe('createDuplicateEnvironment', () => {
|
||||
test('should successfully duplicate an existing team environment', async () => {
|
||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
||||
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce(
|
||||
teamEnvironment,
|
||||
);
|
||||
|
||||
@@ -322,7 +322,9 @@ describe('TeamEnvironmentsService', () => {
|
||||
});
|
||||
|
||||
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(
|
||||
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 () => {
|
||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
||||
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce(
|
||||
teamEnvironment,
|
||||
);
|
||||
|
||||
|
||||
@@ -183,11 +183,10 @@ export class TeamEnvironmentsService {
|
||||
*/
|
||||
async createDuplicateEnvironment(id: string) {
|
||||
try {
|
||||
const environment = await this.prisma.teamEnvironment.findFirst({
|
||||
const environment = await this.prisma.teamEnvironment.findFirstOrThrow({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
rejectOnNotFound: true,
|
||||
});
|
||||
|
||||
const result = await this.prisma.teamEnvironment.create({
|
||||
|
||||
@@ -142,13 +142,15 @@ describe('UserHistoryService', () => {
|
||||
});
|
||||
describe('createUserHistory', () => {
|
||||
test('Should resolve right and create a REST request to users history and return a `UserHistory` object', async () => {
|
||||
const executedOn = new Date();
|
||||
|
||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||
userUid: 'abc',
|
||||
id: '1',
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.REST,
|
||||
executedOn: new Date(),
|
||||
executedOn,
|
||||
isStarred: false,
|
||||
});
|
||||
|
||||
@@ -158,7 +160,7 @@ describe('UserHistoryService', () => {
|
||||
request: JSON.stringify([{}]),
|
||||
responseMetadata: JSON.stringify([{}]),
|
||||
reqType: ReqType.REST,
|
||||
executedOn: new Date(),
|
||||
executedOn,
|
||||
isStarred: false,
|
||||
};
|
||||
|
||||
@@ -172,13 +174,15 @@ describe('UserHistoryService', () => {
|
||||
).toEqualRight(userHistory);
|
||||
});
|
||||
test('Should resolve right and create a GQL request to users history and return a `UserHistory` object', async () => {
|
||||
const executedOn = new Date();
|
||||
|
||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||
userUid: 'abc',
|
||||
id: '1',
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.GQL,
|
||||
executedOn: new Date(),
|
||||
executedOn,
|
||||
isStarred: false,
|
||||
});
|
||||
|
||||
@@ -188,7 +192,7 @@ describe('UserHistoryService', () => {
|
||||
request: JSON.stringify([{}]),
|
||||
responseMetadata: JSON.stringify([{}]),
|
||||
reqType: ReqType.GQL,
|
||||
executedOn: new Date(),
|
||||
executedOn,
|
||||
isStarred: false,
|
||||
};
|
||||
|
||||
@@ -212,13 +216,15 @@ describe('UserHistoryService', () => {
|
||||
).toEqualLeft(USER_HISTORY_INVALID_REQ_TYPE);
|
||||
});
|
||||
test('Should create a GQL request to users history and publish a created subscription', async () => {
|
||||
const executedOn = new Date();
|
||||
|
||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||
userUid: 'abc',
|
||||
id: '1',
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.GQL,
|
||||
executedOn: new Date(),
|
||||
executedOn,
|
||||
isStarred: false,
|
||||
});
|
||||
|
||||
@@ -228,7 +234,7 @@ describe('UserHistoryService', () => {
|
||||
request: JSON.stringify([{}]),
|
||||
responseMetadata: JSON.stringify([{}]),
|
||||
reqType: ReqType.GQL,
|
||||
executedOn: new Date(),
|
||||
executedOn,
|
||||
isStarred: false,
|
||||
};
|
||||
|
||||
@@ -245,13 +251,15 @@ describe('UserHistoryService', () => {
|
||||
);
|
||||
});
|
||||
test('Should create a REST request to users history and publish a created subscription', async () => {
|
||||
const executedOn = new Date();
|
||||
|
||||
mockPrisma.userHistory.create.mockResolvedValueOnce({
|
||||
userUid: 'abc',
|
||||
id: '1',
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.REST,
|
||||
executedOn: new Date(),
|
||||
executedOn,
|
||||
isStarred: false,
|
||||
});
|
||||
|
||||
@@ -261,7 +269,7 @@ describe('UserHistoryService', () => {
|
||||
request: JSON.stringify([{}]),
|
||||
responseMetadata: JSON.stringify([{}]),
|
||||
reqType: ReqType.REST,
|
||||
executedOn: new Date(),
|
||||
executedOn,
|
||||
isStarred: false,
|
||||
};
|
||||
|
||||
@@ -323,13 +331,15 @@ describe('UserHistoryService', () => {
|
||||
).toEqualLeft(USER_HISTORY_NOT_FOUND);
|
||||
});
|
||||
test('Should star/unstar a request in the history and publish a updated subscription', async () => {
|
||||
const executedOn = new Date();
|
||||
|
||||
mockPrisma.userHistory.findFirst.mockResolvedValueOnce({
|
||||
userUid: 'abc',
|
||||
id: '1',
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.REST,
|
||||
executedOn: new Date(),
|
||||
executedOn,
|
||||
isStarred: false,
|
||||
});
|
||||
|
||||
@@ -339,7 +349,7 @@ describe('UserHistoryService', () => {
|
||||
request: [{}],
|
||||
responseMetadata: [{}],
|
||||
reqType: ReqType.REST,
|
||||
executedOn: new Date(),
|
||||
executedOn,
|
||||
isStarred: true,
|
||||
});
|
||||
|
||||
@@ -349,7 +359,7 @@ describe('UserHistoryService', () => {
|
||||
request: JSON.stringify([{}]),
|
||||
responseMetadata: JSON.stringify([{}]),
|
||||
reqType: ReqType.REST,
|
||||
executedOn: new Date(),
|
||||
executedOn,
|
||||
isStarred: true,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hoppscotch/cli",
|
||||
"version": "0.3.2",
|
||||
"version": "0.4.0",
|
||||
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
||||
"homepage": "https://hoppscotch.io",
|
||||
"main": "dist/index.js",
|
||||
@@ -10,6 +10,9 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm exec tsup",
|
||||
"dev": "pnpm exec tsup --watch",
|
||||
@@ -38,26 +41,24 @@
|
||||
"devDependencies": {
|
||||
"@hoppscotch/data": "workspace:^",
|
||||
"@hoppscotch/js-sandbox": "workspace:^",
|
||||
"@relmify/jest-fp-ts": "^2.0.2",
|
||||
"@swc/core": "^1.2.181",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/chalk": "^2.2.0",
|
||||
"@types/commander": "^2.12.2",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/lodash": "^4.14.181",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@relmify/jest-fp-ts": "^2.1.1",
|
||||
"@swc/core": "^1.3.92",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/lodash": "^4.14.199",
|
||||
"@types/qs": "^6.9.8",
|
||||
"axios": "^0.21.4",
|
||||
"chalk": "^4.1.1",
|
||||
"commander": "^8.0.0",
|
||||
"chalk": "^4.1.2",
|
||||
"commander": "^11.0.0",
|
||||
"esm": "^3.2.25",
|
||||
"fp-ts": "^2.12.1",
|
||||
"io-ts": "^2.2.16",
|
||||
"jest": "^27.5.1",
|
||||
"fp-ts": "^2.16.1",
|
||||
"io-ts": "^2.2.20",
|
||||
"jest": "^29.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"prettier": "^2.8.4",
|
||||
"qs": "^6.10.3",
|
||||
"ts-jest": "^27.1.4",
|
||||
"tsup": "^5.12.7",
|
||||
"typescript": "^4.6.4"
|
||||
"prettier": "^3.0.3",
|
||||
"qs": "^6.11.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"tsup": "^7.2.0",
|
||||
"typescript": "^5.2.2",
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,11 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
|
||||
ERROR_MSG = `Unavailable command: ${error.command}`;
|
||||
break;
|
||||
case "MALFORMED_ENV_FILE":
|
||||
ERROR_MSG = `The environment file is not of the correct format.`;
|
||||
break;
|
||||
case "BULK_ENV_FILE":
|
||||
ERROR_MSG = `CLI doesn't support bulk environments export.`;
|
||||
break;
|
||||
case "MALFORMED_COLLECTION":
|
||||
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
|
||||
break;
|
||||
@@ -82,4 +87,4 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
|
||||
if (!S.isEmpty(ERROR_MSG)) {
|
||||
console.error(ERROR_CODE, ERROR_MSG);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,27 +1,45 @@
|
||||
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";
|
||||
|
||||
/**
|
||||
* Parses env json file for given path and validates the parsed env json object.
|
||||
* @param path Path of env.json file to be parsed.
|
||||
* @returns For successful parsing we get HoppEnvs object.
|
||||
*/
|
||||
export async function parseEnvsData(path: string) {
|
||||
const contents = await readJsonFile(path)
|
||||
const contents = await readJsonFile(path);
|
||||
const envPairs: Array<HoppEnvPair> = [];
|
||||
const HoppEnvKeyPairResult = HoppEnvKeyPairObject.safeParse(contents);
|
||||
const HoppEnvExportObjectResult = HoppEnvExportObject.safeParse(contents);
|
||||
const HoppBulkEnvExportObjectResult =
|
||||
HoppBulkEnvExportObject.safeParse(contents);
|
||||
|
||||
if(!(contents && typeof contents === "object" && !Array.isArray(contents))) {
|
||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: null })
|
||||
// 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.
|
||||
if (!(HoppEnvKeyPairResult.success || HoppEnvExportObjectResult.success)) {
|
||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: error });
|
||||
}
|
||||
|
||||
for( const [key,value] of Object.entries(contents)) {
|
||||
if(typeof value !== "string") {
|
||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: {value: value} })
|
||||
if (HoppEnvKeyPairResult.success) {
|
||||
for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) {
|
||||
envPairs.push({ key, value });
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ type HoppErrors = {
|
||||
REQUEST_ERROR: HoppErrorData;
|
||||
INVALID_ARGUMENT: HoppErrorData;
|
||||
MALFORMED_ENV_FILE: HoppErrorPath & HoppErrorData;
|
||||
BULK_ENV_FILE: HoppErrorPath & HoppErrorData;
|
||||
INVALID_FILE_TYPE: HoppErrorData;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||
import { TestReport } from "../interfaces/response";
|
||||
import { HoppCLIError } from "./errors";
|
||||
import { z } from "zod";
|
||||
|
||||
export type FormDataEntry = {
|
||||
key: string;
|
||||
@@ -9,6 +10,22 @@ export type FormDataEntry = {
|
||||
|
||||
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 = {
|
||||
global: HoppEnvPair[];
|
||||
selected: HoppEnvPair[];
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
singleQuote: false,
|
||||
printWidth: 80,
|
||||
useTabs: false,
|
||||
tabWidth: 2
|
||||
tabWidth: 2,
|
||||
plugins: ["prettier-plugin-tailwindcss"],
|
||||
}
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
/*
|
||||
* Write hoppscotch-common related custom styles in this file.
|
||||
* If styles are sharable across all package then write into hoppscotch-ui/assets/scss/styles.scss file.
|
||||
*/
|
||||
|
||||
* {
|
||||
@apply backface-hidden;
|
||||
@apply before:backface-hidden;
|
||||
@apply after:backface-hidden;
|
||||
backface-visibility: hidden;
|
||||
-moz-backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
|
||||
&::before {
|
||||
backface-visibility: hidden;
|
||||
-moz-backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
}
|
||||
|
||||
&::after {
|
||||
backface-visibility: hidden;
|
||||
-moz-backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
}
|
||||
|
||||
@apply selection:bg-accentDark;
|
||||
@apply selection:text-accentContrast;
|
||||
@apply overscroll-none;
|
||||
@@ -11,17 +29,25 @@
|
||||
@apply antialiased;
|
||||
accent-color: var(--accent-color);
|
||||
font-variant-ligatures: common-ligatures;
|
||||
|
||||
// Colors
|
||||
--info-color: #ec4899;
|
||||
--success-color: #10b981;
|
||||
--blue-color: #3b82f6;
|
||||
--warning-color: #f59e0b;
|
||||
--cl-error-color: #ef4444;
|
||||
--sv-error-color: #dc2626;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-transparent;
|
||||
@apply border-solid border-l border-dividerLight border-t-0 border-b-0 border-r-0;
|
||||
@apply border-b-0 border-l border-r-0 border-t-0 border-solid border-dividerLight;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-divider bg-clip-content;
|
||||
@apply rounded-full;
|
||||
@apply border-solid border-transparent border-4;
|
||||
@apply border-4 border-solid border-transparent;
|
||||
@apply hover:bg-dividerDark;
|
||||
@apply hover:bg-clip-content;
|
||||
}
|
||||
@@ -54,7 +80,7 @@ html {
|
||||
|
||||
body {
|
||||
@apply bg-primary;
|
||||
@apply text-secondary text-body;
|
||||
@apply text-body text-secondary;
|
||||
@apply font-medium;
|
||||
@apply select-none;
|
||||
@apply overflow-x-hidden;
|
||||
@@ -124,8 +150,8 @@ a {
|
||||
|
||||
&.link {
|
||||
@apply items-center;
|
||||
@apply py-0.5 px-1;
|
||||
@apply -my-0.5 -mx-1;
|
||||
@apply px-1 py-0.5;
|
||||
@apply -mx-1 -my-0.5;
|
||||
@apply text-accent;
|
||||
@apply rounded;
|
||||
@apply hover:text-accentDark;
|
||||
@@ -137,7 +163,7 @@ a {
|
||||
|
||||
.cm-tooltip {
|
||||
.tippy-box {
|
||||
@apply shadow-none;
|
||||
@apply shadow-none #{!important};
|
||||
@apply fixed;
|
||||
@apply inline-flex;
|
||||
@apply -mt-8;
|
||||
@@ -154,7 +180,7 @@ a {
|
||||
@apply flex;
|
||||
@apply text-tiny text-primary;
|
||||
@apply font-semibold;
|
||||
@apply py-1 px-2;
|
||||
@apply px-2 py-1;
|
||||
@apply truncate;
|
||||
@apply leading-normal;
|
||||
@apply items-center;
|
||||
@@ -162,7 +188,7 @@ a {
|
||||
kbd {
|
||||
@apply hidden;
|
||||
@apply font-sans;
|
||||
@apply bg-gray-500/45;
|
||||
background-color: rgba(107, 114, 128, 0.45);
|
||||
@apply text-primaryLight;
|
||||
@apply rounded-sm;
|
||||
@apply px-1;
|
||||
@@ -170,6 +196,12 @@ a {
|
||||
@apply truncate;
|
||||
@apply sm:inline-flex;
|
||||
}
|
||||
|
||||
.env-icon {
|
||||
@apply transition;
|
||||
@apply inline-flex;
|
||||
@apply items-center;
|
||||
}
|
||||
}
|
||||
|
||||
.tippy-svg-arrow {
|
||||
@@ -195,7 +227,7 @@ a {
|
||||
@apply max-h-[45vh];
|
||||
@apply items-stretch;
|
||||
@apply overflow-y-auto;
|
||||
@apply text-secondary text-body;
|
||||
@apply text-body text-secondary;
|
||||
@apply p-2;
|
||||
@apply leading-normal;
|
||||
@apply focus:outline-none;
|
||||
@@ -234,7 +266,7 @@ hr {
|
||||
|
||||
.heading {
|
||||
@apply font-bold;
|
||||
@apply text-secondaryDark text-lg;
|
||||
@apply text-lg text-secondaryDark;
|
||||
@apply tracking-tight;
|
||||
}
|
||||
|
||||
@@ -243,7 +275,7 @@ hr {
|
||||
.textarea {
|
||||
@apply flex;
|
||||
@apply w-full;
|
||||
@apply py-2 px-4;
|
||||
@apply px-4 py-2;
|
||||
@apply bg-transparent;
|
||||
@apply rounded;
|
||||
@apply text-secondaryDark;
|
||||
@@ -284,7 +316,7 @@ button {
|
||||
@apply transform;
|
||||
@apply origin-top-left;
|
||||
@apply scale-75;
|
||||
@apply translate-x-1 -translate-y-4;
|
||||
@apply -translate-y-4 translate-x-1;
|
||||
}
|
||||
|
||||
.floating-input:focus-within ~ label {
|
||||
@@ -293,7 +325,7 @@ button {
|
||||
|
||||
.floating-input ~ .end-actions {
|
||||
@apply absolute;
|
||||
@apply right-0.2;
|
||||
@apply right-[.05rem];
|
||||
@apply inset-y-0;
|
||||
@apply flex;
|
||||
@apply items-center;
|
||||
@@ -335,23 +367,23 @@ pre.ace_editor {
|
||||
}
|
||||
|
||||
.info-response {
|
||||
@apply text-pink-500;
|
||||
color: var(--info-color);
|
||||
}
|
||||
|
||||
.success-response {
|
||||
@apply text-green-500;
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.redir-response {
|
||||
@apply text-yellow-500;
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.cl-error-response {
|
||||
@apply text-red-500;
|
||||
color: var(--cl-error-color);
|
||||
}
|
||||
|
||||
.sv-error-response {
|
||||
@apply text-red-600;
|
||||
color: var(--sv-error-color);
|
||||
}
|
||||
|
||||
.missing-data-response {
|
||||
@@ -366,7 +398,7 @@ pre.ace_editor {
|
||||
@apply px-4 py-2;
|
||||
@apply bg-tooltip;
|
||||
@apply border-secondaryDark;
|
||||
@apply text-primary text-body;
|
||||
@apply text-body text-primary;
|
||||
@apply justify-between;
|
||||
@apply shadow-lg;
|
||||
@apply font-semibold;
|
||||
@@ -394,7 +426,7 @@ pre.ace_editor {
|
||||
@apply before:opacity-10;
|
||||
@apply before:inset-0;
|
||||
@apply before:transition;
|
||||
@apply before:content-DEFAULT;
|
||||
@apply before:content-[''];
|
||||
@apply hover:no-underline;
|
||||
@apply hover:before:opacity-20;
|
||||
}
|
||||
@@ -428,7 +460,7 @@ pre.ace_editor {
|
||||
@apply before:opacity-0;
|
||||
@apply before:z-20;
|
||||
@apply before:transition;
|
||||
@apply before:content-DEFAULT;
|
||||
@apply before:content-[''];
|
||||
@apply hover:before:opacity-100;
|
||||
}
|
||||
|
||||
@@ -501,22 +533,6 @@ pre.ace_editor {
|
||||
}
|
||||
}
|
||||
|
||||
.cm-panel.cm-search [name="close"] {
|
||||
@apply flex;
|
||||
@apply items-center;
|
||||
@apply justify-center;
|
||||
@apply min-h-5;
|
||||
@apply min-w-5;
|
||||
@apply bg-primaryDark #{!important};
|
||||
@apply sticky #{!important};
|
||||
@apply right-0 #{!important};
|
||||
@apply ml-auto #{!important};
|
||||
@apply my-auto #{!important};
|
||||
@apply rounded #{!important};
|
||||
@apply outline #{!important};
|
||||
@apply outline-divider #{!important};
|
||||
}
|
||||
|
||||
.shortcut-key {
|
||||
@apply inline-flex;
|
||||
@apply font-sans;
|
||||
|
||||
3
packages/hoppscotch-common/assets/scss/tailwind.scss
Normal file
3
packages/hoppscotch-common/assets/scss/tailwind.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -1,274 +0,0 @@
|
||||
@mixin base-theme {
|
||||
--font-sans: "Inter Variable", sans-serif;
|
||||
--font-icon: "Material Symbols Rounded Variable";
|
||||
--font-mono: "Roboto Mono Variable", monospace;
|
||||
--font-size-body: 0.75rem;
|
||||
--font-size-tiny: 0.688rem;
|
||||
--line-height-body: 1rem;
|
||||
--upper-primary-sticky-fold: 4.125rem;
|
||||
--upper-secondary-sticky-fold: 6.188rem;
|
||||
--upper-tertiary-sticky-fold: 8.25rem;
|
||||
--upper-fourth-sticky-fold: 10.2rem;
|
||||
--upper-mobile-primary-sticky-fold: 6.625rem;
|
||||
--upper-mobile-secondary-sticky-fold: 8.688rem;
|
||||
--upper-mobile-sticky-fold: 10.75rem;
|
||||
--upper-mobile-tertiary-sticky-fold: 8.25rem;
|
||||
--lower-primary-sticky-fold: 3rem;
|
||||
--lower-secondary-sticky-fold: 5.063rem;
|
||||
--lower-tertiary-sticky-fold: 7.125rem;
|
||||
--lower-fourth-sticky-fold: 9.188rem;
|
||||
--sidebar-primary-sticky-fold: 2rem;
|
||||
}
|
||||
|
||||
@mixin dark-theme {
|
||||
--primary-color: theme("colors.dark.800");
|
||||
--primary-light-color: theme("colors.dark.600");
|
||||
--primary-dark-color: theme("colors.neutral.800");
|
||||
--primary-contrast-color: theme("colors.neutral.900");
|
||||
|
||||
--secondary-color: theme("colors.neutral.400");
|
||||
--secondary-light-color: theme("colors.neutral.500");
|
||||
--secondary-dark-color: theme("colors.neutral.50");
|
||||
|
||||
--divider-color: theme("colors.neutral.800");
|
||||
--divider-light-color: theme("colors.dark.500");
|
||||
--divider-dark-color: theme("colors.dark.300");
|
||||
|
||||
--error-color: theme("colors.stone.800");
|
||||
--tooltip-color: theme("colors.neutral.100");
|
||||
--popover-color: theme("colors.dark.700");
|
||||
--editor-theme: "merbivore_soft";
|
||||
}
|
||||
|
||||
@mixin light-theme {
|
||||
--primary-color: theme("colors.white");
|
||||
--primary-light-color: theme("colors.gray.50");
|
||||
--primary-dark-color: theme("colors.gray.100");
|
||||
--primary-contrast-color: theme("colors.light.50");
|
||||
|
||||
--secondary-color: theme("colors.gray.500");
|
||||
--secondary-light-color: theme("colors.gray.400");
|
||||
--secondary-dark-color: theme("colors.gray.900");
|
||||
|
||||
--divider-color: theme("colors.gray.100");
|
||||
--divider-light-color: theme("colors.gray.100");
|
||||
--divider-dark-color: theme("colors.gray.300");
|
||||
|
||||
--error-color: theme("colors.yellow.100");
|
||||
--tooltip-color: theme("colors.neutral.800");
|
||||
--popover-color: theme("colors.white");
|
||||
--editor-theme: "textmate";
|
||||
}
|
||||
|
||||
@mixin black-theme {
|
||||
--primary-color: theme("colors.dark.900");
|
||||
--primary-light-color: theme("colors.neutral.900");
|
||||
--primary-dark-color: theme("colors.dark.800");
|
||||
--primary-contrast-color: theme("colors.dark.900");
|
||||
|
||||
--secondary-color: theme("colors.neutral.400");
|
||||
--secondary-light-color: theme("colors.neutral.500");
|
||||
--secondary-dark-color: theme("colors.neutral.100");
|
||||
|
||||
--divider-color: theme("colors.dark.600");
|
||||
--divider-light-color: theme("colors.dark.800");
|
||||
--divider-dark-color: theme("colors.dark.200");
|
||||
|
||||
--error-color: theme("colors.stone.900");
|
||||
--tooltip-color: theme("colors.neutral.100");
|
||||
--popover-color: theme("colors.dark.900");
|
||||
--editor-theme: "twilight";
|
||||
}
|
||||
|
||||
@mixin dark-editor-theme {
|
||||
--editor-type-color: theme("colors.purple.400");
|
||||
--editor-name-color: theme("colors.blue.400");
|
||||
--editor-operator-color: theme("colors.indigo.400");
|
||||
--editor-invalid-color: theme("colors.red.400");
|
||||
--editor-separator-color: theme("colors.gray.400");
|
||||
--editor-meta-color: theme("colors.gray.400");
|
||||
--editor-variable-color: theme("colors.green.400");
|
||||
--editor-link-color: theme("colors.cyan.400");
|
||||
--editor-process-color: theme("colors.fuchsia.400");
|
||||
--editor-constant-color: theme("colors.violet.400");
|
||||
--editor-keyword-color: theme("colors.pink.400");
|
||||
}
|
||||
|
||||
@mixin light-editor-theme {
|
||||
--editor-type-color: theme("colors.purple.600");
|
||||
--editor-name-color: theme("colors.red.600");
|
||||
--editor-operator-color: theme("colors.indigo.600");
|
||||
--editor-invalid-color: theme("colors.red.600");
|
||||
--editor-separator-color: theme("colors.gray.600");
|
||||
--editor-meta-color: theme("colors.gray.600");
|
||||
--editor-variable-color: theme("colors.green.600");
|
||||
--editor-link-color: theme("colors.cyan.600");
|
||||
--editor-process-color: theme("colors.blue.600");
|
||||
--editor-constant-color: theme("colors.fuchsia.600");
|
||||
--editor-keyword-color: theme("colors.pink.600");
|
||||
}
|
||||
|
||||
@mixin black-editor-theme {
|
||||
--editor-type-color: theme("colors.purple.400");
|
||||
--editor-name-color: theme("colors.fuchsia.400");
|
||||
--editor-operator-color: theme("colors.indigo.400");
|
||||
--editor-invalid-color: theme("colors.red.400");
|
||||
--editor-separator-color: theme("colors.gray.400");
|
||||
--editor-meta-color: theme("colors.gray.400");
|
||||
--editor-variable-color: theme("colors.green.400");
|
||||
--editor-link-color: theme("colors.cyan.400");
|
||||
--editor-process-color: theme("colors.violet.400");
|
||||
--editor-constant-color: theme("colors.blue.400");
|
||||
--editor-keyword-color: theme("colors.pink.400");
|
||||
}
|
||||
|
||||
@mixin green-theme {
|
||||
--accent-color: theme("colors.green.500");
|
||||
--accent-light-color: theme("colors.green.400");
|
||||
--accent-dark-color: theme("colors.green.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.green.200");
|
||||
--gradient-via-color: theme("colors.green.400");
|
||||
--gradient-to-color: theme("colors.green.600");
|
||||
}
|
||||
|
||||
@mixin teal-theme {
|
||||
--accent-color: theme("colors.teal.500");
|
||||
--accent-light-color: theme("colors.teal.400");
|
||||
--accent-dark-color: theme("colors.teal.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.teal.200");
|
||||
--gradient-via-color: theme("colors.teal.400");
|
||||
--gradient-to-color: theme("colors.teal.600");
|
||||
}
|
||||
|
||||
@mixin blue-theme {
|
||||
--accent-color: theme("colors.blue.500");
|
||||
--accent-light-color: theme("colors.blue.400");
|
||||
--accent-dark-color: theme("colors.blue.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.blue.200");
|
||||
--gradient-via-color: theme("colors.blue.400");
|
||||
--gradient-to-color: theme("colors.blue.600");
|
||||
}
|
||||
|
||||
@mixin indigo-theme {
|
||||
--accent-color: theme("colors.indigo.500");
|
||||
--accent-light-color: theme("colors.indigo.400");
|
||||
--accent-dark-color: theme("colors.indigo.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.indigo.200");
|
||||
--gradient-via-color: theme("colors.indigo.400");
|
||||
--gradient-to-color: theme("colors.indigo.600");
|
||||
}
|
||||
|
||||
@mixin purple-theme {
|
||||
--accent-color: theme("colors.purple.500");
|
||||
--accent-light-color: theme("colors.purple.400");
|
||||
--accent-dark-color: theme("colors.purple.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.purple.200");
|
||||
--gradient-via-color: theme("colors.purple.400");
|
||||
--gradient-to-color: theme("colors.purple.600");
|
||||
}
|
||||
|
||||
@mixin yellow-theme {
|
||||
--accent-color: theme("colors.yellow.500");
|
||||
--accent-light-color: theme("colors.yellow.400");
|
||||
--accent-dark-color: theme("colors.yellow.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.yellow.200");
|
||||
--gradient-via-color: theme("colors.yellow.400");
|
||||
--gradient-to-color: theme("colors.yellow.600");
|
||||
}
|
||||
|
||||
@mixin orange-theme {
|
||||
--accent-color: theme("colors.orange.500");
|
||||
--accent-light-color: theme("colors.orange.400");
|
||||
--accent-dark-color: theme("colors.orange.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.orange.200");
|
||||
--gradient-via-color: theme("colors.orange.400");
|
||||
--gradient-to-color: theme("colors.orange.600");
|
||||
}
|
||||
|
||||
@mixin red-theme {
|
||||
--accent-color: theme("colors.red.500");
|
||||
--accent-light-color: theme("colors.red.400");
|
||||
--accent-dark-color: theme("colors.red.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.red.200");
|
||||
--gradient-via-color: theme("colors.red.400");
|
||||
--gradient-to-color: theme("colors.red.600");
|
||||
}
|
||||
|
||||
@mixin pink-theme {
|
||||
--accent-color: theme("colors.pink.500");
|
||||
--accent-light-color: theme("colors.pink.400");
|
||||
--accent-dark-color: theme("colors.pink.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.pink.200");
|
||||
--gradient-via-color: theme("colors.pink.400");
|
||||
--gradient-to-color: theme("colors.pink.600");
|
||||
}
|
||||
|
||||
:root {
|
||||
@include base-theme;
|
||||
@include dark-theme;
|
||||
@include dark-editor-theme;
|
||||
@include green-theme;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
@include light-theme;
|
||||
@include light-editor-theme;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
@include dark-theme;
|
||||
@include dark-editor-theme;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root.black {
|
||||
@include black-theme;
|
||||
@include black-editor-theme;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root[data-accent="blue"] {
|
||||
@include blue-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="green"] {
|
||||
@include green-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="teal"] {
|
||||
@include teal-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="indigo"] {
|
||||
@include indigo-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="purple"] {
|
||||
@include purple-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="orange"] {
|
||||
@include orange-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="pink"] {
|
||||
@include pink-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="red"] {
|
||||
@include red-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="yellow"] {
|
||||
@include yellow-theme;
|
||||
}
|
||||
89
packages/hoppscotch-common/assets/themes/accent-themes.scss
Normal file
89
packages/hoppscotch-common/assets/themes/accent-themes.scss
Normal file
@@ -0,0 +1,89 @@
|
||||
@mixin green-theme {
|
||||
--accent-color: #10b981;
|
||||
--accent-light-color: #34d399;
|
||||
--accent-dark-color: #059669;
|
||||
--accent-contrast-color: #fff;
|
||||
--gradient-from-color: #a7f3d0;
|
||||
--gradient-via-color: #34d399;
|
||||
--gradient-to-color: #059669;
|
||||
}
|
||||
|
||||
@mixin teal-theme {
|
||||
--accent-color: #14b8a6;
|
||||
--accent-light-color: #2dd4bf;
|
||||
--accent-dark-color: #0d9488;
|
||||
--accent-contrast-color: #fff;
|
||||
--gradient-from-color: #99f6e4;
|
||||
--gradient-via-color: #2dd4bf;
|
||||
--gradient-to-color: #0d9488;
|
||||
}
|
||||
|
||||
@mixin blue-theme {
|
||||
--accent-color: #3b82f6;
|
||||
--accent-light-color: #60a5fa;
|
||||
--accent-dark-color: #2563eb;
|
||||
--accent-contrast-color: #fff;
|
||||
--gradient-from-color: #bfdbfe;
|
||||
--gradient-via-color: #60a5fa;
|
||||
--gradient-to-color: #2563eb;
|
||||
}
|
||||
|
||||
@mixin indigo-theme {
|
||||
--accent-color: #6366f1;
|
||||
--accent-light-color: #818cf8;
|
||||
--accent-dark-color: #4f46e5;
|
||||
--accent-contrast-color: #fff;
|
||||
--gradient-from-color: #c7d2fe;
|
||||
--gradient-via-color: #818cf8;
|
||||
--gradient-to-color: #4f46e5;
|
||||
}
|
||||
|
||||
@mixin purple-theme {
|
||||
--accent-color: #8b5cf6;
|
||||
--accent-light-color: #a78bfa;
|
||||
--accent-dark-color: #7c3aed;
|
||||
--accent-contrast-color: #fff;
|
||||
--gradient-from-color: #ddd6fe;
|
||||
--gradient-via-color: #a78bfa;
|
||||
--gradient-to-color: #7c3aed;
|
||||
}
|
||||
|
||||
@mixin yellow-theme {
|
||||
--accent-color: #f59e0b;
|
||||
--accent-light-color: #fbbf24;
|
||||
--accent-dark-color: #d97706;
|
||||
--accent-contrast-color: #fff;
|
||||
--gradient-from-color: #fde68a;
|
||||
--gradient-via-color: #fbbf24;
|
||||
--gradient-to-color: #d97706;
|
||||
}
|
||||
|
||||
@mixin orange-theme {
|
||||
--accent-color: #f97316;
|
||||
--accent-light-color: #fb923c;
|
||||
--accent-dark-color: #ea580c;
|
||||
--accent-contrast-color: #fff;
|
||||
--gradient-from-color: #fed7aa;
|
||||
--gradient-via-color: #fb923c;
|
||||
--gradient-to-color: #ea580c;
|
||||
}
|
||||
|
||||
@mixin red-theme {
|
||||
--accent-color: #ef4444;
|
||||
--accent-light-color: #f87171;
|
||||
--accent-dark-color: #dc2626;
|
||||
--accent-contrast-color: #fff;
|
||||
--gradient-from-color: #fecaca;
|
||||
--gradient-via-color: #f87171;
|
||||
--gradient-to-color: #dc2626;
|
||||
}
|
||||
|
||||
@mixin pink-theme {
|
||||
--accent-color: #ec4899;
|
||||
--accent-light-color: #f472b6;
|
||||
--accent-dark-color: #db2777;
|
||||
--accent-contrast-color: #fff;
|
||||
--gradient-from-color: #fbcfe8;
|
||||
--gradient-via-color: #f472b6;
|
||||
--gradient-to-color: #db2777;
|
||||
}
|
||||
81
packages/hoppscotch-common/assets/themes/base-themes.scss
Normal file
81
packages/hoppscotch-common/assets/themes/base-themes.scss
Normal file
@@ -0,0 +1,81 @@
|
||||
@mixin base-theme {
|
||||
--font-sans: "Inter Variable", sans-serif;
|
||||
--font-icon: "Material Symbols Rounded Variable";
|
||||
--font-mono: "Roboto Mono Variable", monospace;
|
||||
--font-size-body: 0.75rem;
|
||||
--font-size-tiny: 0.688rem;
|
||||
--line-height-body: 1rem;
|
||||
--upper-primary-sticky-fold: 4.125rem;
|
||||
--upper-secondary-sticky-fold: 6.188rem;
|
||||
--upper-tertiary-sticky-fold: 8.25rem;
|
||||
--upper-fourth-sticky-fold: 10.2rem;
|
||||
--upper-mobile-primary-sticky-fold: 6.625rem;
|
||||
--upper-mobile-secondary-sticky-fold: 8.688rem;
|
||||
--upper-mobile-sticky-fold: 10.75rem;
|
||||
--upper-mobile-tertiary-sticky-fold: 8.25rem;
|
||||
--lower-primary-sticky-fold: 3rem;
|
||||
--lower-secondary-sticky-fold: 5.063rem;
|
||||
--lower-tertiary-sticky-fold: 7.125rem;
|
||||
--lower-fourth-sticky-fold: 9.188rem;
|
||||
--sidebar-primary-sticky-fold: 2rem;
|
||||
}
|
||||
|
||||
@mixin dark-theme {
|
||||
--primary-color: #181818;
|
||||
--primary-light-color: #1c1c1e;
|
||||
--primary-dark-color: #262626;
|
||||
--primary-contrast-color: #171717;
|
||||
|
||||
--secondary-color: #a3a3a3;
|
||||
--secondary-light-color: #737373;
|
||||
--secondary-dark-color: #fafafa;
|
||||
|
||||
--divider-color: #262626;
|
||||
--divider-light-color: #1f1f1f;
|
||||
--divider-dark-color: #2d2d2d;
|
||||
|
||||
--error-color: #292524;
|
||||
--tooltip-color: #f5f5f5;
|
||||
--popover-color: #1b1b1b;
|
||||
--editor-theme: "merbivore_soft";
|
||||
}
|
||||
|
||||
@mixin light-theme {
|
||||
--primary-color: #ffffff;
|
||||
--primary-light-color: #f9fafb;
|
||||
--primary-dark-color: #f3f4f6;
|
||||
--primary-contrast-color: #fdfdfd;
|
||||
|
||||
--secondary-color: #6b7280;
|
||||
--secondary-light-color: #9ca3af;
|
||||
--secondary-dark-color: #111827;
|
||||
|
||||
--divider-color: #f3f4f6;
|
||||
--divider-light-color: #f3f4f6;
|
||||
--divider-dark-color: #d1d5db;
|
||||
|
||||
--error-color: #fef3c7;
|
||||
--tooltip-color: #262626;
|
||||
--popover-color: #ffffff;
|
||||
--editor-theme: "textmate";
|
||||
}
|
||||
|
||||
@mixin black-theme {
|
||||
--primary-color: #0f0f0f;
|
||||
--primary-light-color: #171717;
|
||||
--primary-dark-color: #181818;
|
||||
--primary-contrast-color: #0f0f0f;
|
||||
|
||||
--secondary-color: #a3a3a3;
|
||||
--secondary-light-color: #737373;
|
||||
--secondary-dark-color: #f5f5f5;
|
||||
|
||||
--divider-color: #1c1c1e;
|
||||
--divider-light-color: #181818;
|
||||
--divider-dark-color: #323232;
|
||||
|
||||
--error-color: #1c1917;
|
||||
--tooltip-color: #f5f5f5;
|
||||
--popover-color: #0f0f0f;
|
||||
--editor-theme: "twilight";
|
||||
}
|
||||
41
packages/hoppscotch-common/assets/themes/editor-themes.scss
Normal file
41
packages/hoppscotch-common/assets/themes/editor-themes.scss
Normal file
@@ -0,0 +1,41 @@
|
||||
@mixin dark-editor-theme {
|
||||
--editor-type-color: #a78bfa;
|
||||
--editor-name-color: #60a5fa;
|
||||
--editor-operator-color: #818cf8;
|
||||
--editor-invalid-color: #f87171;
|
||||
--editor-separator-color: #9ca3af;
|
||||
--editor-meta-color: #9ca3af;
|
||||
--editor-variable-color: #34d399;
|
||||
--editor-link-color: #22d3ee;
|
||||
--editor-process-color: #e879f9;
|
||||
--editor-constant-color: #a78bfa;
|
||||
--editor-keyword-color: #f472b6;
|
||||
}
|
||||
|
||||
@mixin light-editor-theme {
|
||||
--editor-type-color: #7c3aed;
|
||||
--editor-name-color: #dc2626;
|
||||
--editor-operator-color: #4f46e5;
|
||||
--editor-invalid-color: #dc2626;
|
||||
--editor-separator-color: #4b5563;
|
||||
--editor-meta-color: #4b5563;
|
||||
--editor-variable-color: #059669;
|
||||
--editor-link-color: #0891b2;
|
||||
--editor-process-color: #2563eb;
|
||||
--editor-constant-color: #c026d3;
|
||||
--editor-keyword-color: #db2777;
|
||||
}
|
||||
|
||||
@mixin black-editor-theme {
|
||||
--editor-type-color: #a78bfa;
|
||||
--editor-name-color: #e879f9;
|
||||
--editor-operator-color: #818cf8;
|
||||
--editor-invalid-color: #f87171;
|
||||
--editor-separator-color: #9ca3af;
|
||||
--editor-meta-color: #9ca3af;
|
||||
--editor-variable-color: #34d399;
|
||||
--editor-link-color: #22d3ee;
|
||||
--editor-process-color: #a78bfa;
|
||||
--editor-constant-color: #60a5fa;
|
||||
--editor-keyword-color: #f472b6;
|
||||
}
|
||||
64
packages/hoppscotch-common/assets/themes/themes.scss
Normal file
64
packages/hoppscotch-common/assets/themes/themes.scss
Normal file
@@ -0,0 +1,64 @@
|
||||
@import "./base-themes.scss";
|
||||
@import "./editor-themes.scss";
|
||||
@import "./accent-themes.scss";
|
||||
|
||||
:root {
|
||||
@include base-theme;
|
||||
@include dark-theme;
|
||||
@include green-theme;
|
||||
@include dark-editor-theme;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
@include light-theme;
|
||||
@include light-editor-theme;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
@include dark-theme;
|
||||
@include dark-editor-theme;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root.black {
|
||||
@include black-theme;
|
||||
@include black-editor-theme;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root[data-accent="blue"] {
|
||||
@include blue-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="green"] {
|
||||
@include green-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="teal"] {
|
||||
@include teal-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="indigo"] {
|
||||
@include indigo-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="purple"] {
|
||||
@include purple-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="orange"] {
|
||||
@include orange-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="pink"] {
|
||||
@include pink-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="red"] {
|
||||
@include red-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="yellow"] {
|
||||
@include yellow-theme;
|
||||
}
|
||||
@@ -112,6 +112,7 @@
|
||||
},
|
||||
"authorization": {
|
||||
"generate_token": "Generate Token",
|
||||
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init",
|
||||
"include_in_url": "Include in URL",
|
||||
"learn": "Learn how",
|
||||
"pass_key_by": "Pass by",
|
||||
@@ -124,6 +125,7 @@
|
||||
"created": "Collection created",
|
||||
"different_parent": "Cannot reorder collection with different parent",
|
||||
"edit": "Edit Collection",
|
||||
"import_or_create": "Import or create a collection",
|
||||
"invalid_name": "Please provide a name for the collection",
|
||||
"invalid_root_move": "Collection already in the root",
|
||||
"moved": "Moved Successfully",
|
||||
@@ -209,6 +211,7 @@
|
||||
"empty_variables": "No variables",
|
||||
"global": "Global",
|
||||
"global_variables": "Global variables",
|
||||
"import_or_create": "Import or create a environment",
|
||||
"invalid_name": "Please provide a name for the environment",
|
||||
"list": "Environment variables",
|
||||
"my_environments": "My Environments",
|
||||
@@ -249,7 +252,9 @@
|
||||
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
|
||||
"network_error": "There seems to be a network error. Please try again.",
|
||||
"network_fail": "Could not send request",
|
||||
"no_collections_to_export": "No collections to export. Please create a collection to get started.",
|
||||
"no_duration": "No duration",
|
||||
"no_environments_to_export": "No environments to export. Please create an environment to get started.",
|
||||
"no_results_found": "No matches found",
|
||||
"page_not_found": "This page could not be found",
|
||||
"proxy_error": "Proxy error",
|
||||
@@ -456,6 +461,7 @@
|
||||
"enter_curl": "Enter cURL command",
|
||||
"generate_code": "Generate code",
|
||||
"generated_code": "Generated code",
|
||||
"go_to_authorization_tab": "Go to Authorization",
|
||||
"header_list": "Header List",
|
||||
"invalid_name": "Please provide a name for the request",
|
||||
"method": "Method",
|
||||
@@ -743,9 +749,11 @@
|
||||
"disconnected_from": "Disconnected from {name}",
|
||||
"docs_generated": "Documentation generated",
|
||||
"download_started": "Download started",
|
||||
"download_failed": "Download failed",
|
||||
"enabled": "Enabled",
|
||||
"file_imported": "File imported",
|
||||
"finished_in": "Finished in {duration} ms",
|
||||
"hide": "Hide",
|
||||
"history_deleted": "History deleted",
|
||||
"linewrap": "Wrap lines",
|
||||
"loading": "Loading...",
|
||||
@@ -756,6 +764,7 @@
|
||||
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||
"published_message": "Published message: {message} to topic: {topic}",
|
||||
"reconnection_error": "Failed to reconnect",
|
||||
"show":"Show",
|
||||
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
||||
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
||||
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
||||
@@ -837,7 +846,7 @@
|
||||
"new": "New Team",
|
||||
"new_created": "New team created",
|
||||
"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_request_found": "Request not found.",
|
||||
"not_found": "Team not found. Contact your team owner.",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"choose_file": "選擇一個檔案",
|
||||
"clear": "清除",
|
||||
"clear_all": "全部清除",
|
||||
"clear_history": "Clear all History",
|
||||
"clear_history": "清除所有歷史記錄",
|
||||
"close": "關閉",
|
||||
"connect": "連線",
|
||||
"connecting": "正在連接",
|
||||
@@ -79,8 +79,8 @@
|
||||
"search": "搜尋",
|
||||
"share": "分享",
|
||||
"shortcuts": "快捷方式",
|
||||
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
|
||||
"social_links": "Social links",
|
||||
"social_description": "在社交媒體上追蹤我們即可在第一時間得知新聞、更新、以及新版本的消息。",
|
||||
"social_links": "社群連結",
|
||||
"spotlight": "聚光燈",
|
||||
"status": "狀態",
|
||||
"status_description": "檢查網站狀態",
|
||||
@@ -135,15 +135,15 @@
|
||||
"renamed": "集合已重新命名",
|
||||
"request_in_use": "請求正在使用中",
|
||||
"save_as": "另存為",
|
||||
"save_to_collection": "Save to Collection",
|
||||
"save_to_collection": "儲存到集合",
|
||||
"select": "選擇一個集合",
|
||||
"select_location": "選擇位置",
|
||||
"select_team": "選擇一個團隊",
|
||||
"team_collections": "團隊集合"
|
||||
},
|
||||
"confirm": {
|
||||
"close_unsaved_tab": "Are you sure you want to close this tab?",
|
||||
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
|
||||
"close_unsaved_tab": "您確定要關閉此分頁嗎?",
|
||||
"close_unsaved_tabs": "您確定要關閉所有分頁嗎?{count} 個未儲存的分頁將會遺失。",
|
||||
"exit_team": "您確定要離開此團隊嗎?",
|
||||
"logout": "您確定要登出嗎?",
|
||||
"remove_collection": "您確定要永久刪除該集合嗎?",
|
||||
@@ -158,9 +158,9 @@
|
||||
"sync": "您想從雲端恢復您的工作區嗎?這將丟棄您的本地進度。"
|
||||
},
|
||||
"context_menu": {
|
||||
"add_parameters": "Add to parameters",
|
||||
"open_request_in_new_tab": "Open request in new tab",
|
||||
"set_environment_variable": "Set as variable"
|
||||
"add_parameters": "新增至參數",
|
||||
"open_request_in_new_tab": "在新分頁開啟請求",
|
||||
"set_environment_variable": "設為變數"
|
||||
},
|
||||
"count": {
|
||||
"header": "請求標頭 {count}",
|
||||
@@ -204,31 +204,31 @@
|
||||
"create_new": "建立新環境",
|
||||
"created": "已建立環境",
|
||||
"deleted": "刪除環境",
|
||||
"duplicated": "Environment duplicated",
|
||||
"duplicated": "已複製環境",
|
||||
"edit": "編輯環境",
|
||||
"empty_variables": "No variables",
|
||||
"global": "Global",
|
||||
"global_variables": "Global variables",
|
||||
"empty_variables": "無變數",
|
||||
"global": "全域",
|
||||
"global_variables": "全域變數",
|
||||
"invalid_name": "請提供有效的環境名稱",
|
||||
"list": "Environment variables",
|
||||
"list": "環境變數",
|
||||
"my_environments": "我的環境",
|
||||
"name": "Name",
|
||||
"name": "名稱",
|
||||
"nested_overflow": "巢狀環境變數不得大於 10 層",
|
||||
"new": "建立環境",
|
||||
"no_active_environment": "No active environment",
|
||||
"no_active_environment": "無使用中的環境",
|
||||
"no_environment": "無環境",
|
||||
"no_environment_description": "未選取任何環境。請選擇要對以下變數進行的動作。",
|
||||
"quick_peek": "Environment Quick Peek",
|
||||
"replace_with_variable": "Replace with variable",
|
||||
"scope": "Scope",
|
||||
"quick_peek": "快速預覽環境",
|
||||
"replace_with_variable": "以變數替代",
|
||||
"scope": "範圍",
|
||||
"select": "選擇環境",
|
||||
"set": "Set environment",
|
||||
"set_as_environment": "Set as environment",
|
||||
"set": "設定環境",
|
||||
"set_as_environment": "設為環境",
|
||||
"team_environments": "團隊環境",
|
||||
"title": "環境",
|
||||
"updated": "更新環境",
|
||||
"value": "Value",
|
||||
"variable": "Variable",
|
||||
"value": "數值",
|
||||
"variable": "變數",
|
||||
"variable_list": "變數列表"
|
||||
},
|
||||
"error": {
|
||||
@@ -252,7 +252,7 @@
|
||||
"no_duration": "無持續時間",
|
||||
"no_results_found": "找不到結果",
|
||||
"page_not_found": "找不到此頁面",
|
||||
"proxy_error": "Proxy error",
|
||||
"proxy_error": "Proxy 錯誤",
|
||||
"script_fail": "無法執行預請求指令碼",
|
||||
"something_went_wrong": "發生了一些錯誤",
|
||||
"test_script_fail": "無法執行測試指令碼"
|
||||
@@ -278,13 +278,13 @@
|
||||
"renamed": "資料夾已重新命名"
|
||||
},
|
||||
"graphql": {
|
||||
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
|
||||
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
|
||||
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
|
||||
"connection_switch_confirm": "您要使用最新的 GraphQL 端點連線嗎?",
|
||||
"connection_switch_new_url": "切換至分頁將斷開使用中的 GraphQL 連線。新的連線網址為 ",
|
||||
"connection_switch_url": "您已連接至 GraphQL 端點。連線網址為 ",
|
||||
"mutations": "變體",
|
||||
"schema": "綱要",
|
||||
"subscriptions": "訂閱",
|
||||
"switch_connection": "Switch connection"
|
||||
"switch_connection": "切換連線"
|
||||
},
|
||||
"group": {
|
||||
"time": "時間",
|
||||
@@ -339,27 +339,27 @@
|
||||
"title": "匯入"
|
||||
},
|
||||
"inspections": {
|
||||
"description": "Inspect possible errors",
|
||||
"description": "檢查潛在錯誤",
|
||||
"environment": {
|
||||
"add_environment": "Add to Environment",
|
||||
"not_found": "Environment variable “{environment}” not found."
|
||||
"add_environment": "新增至環境",
|
||||
"not_found": "找不到環境變數 “{environment}”。"
|
||||
},
|
||||
"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": {
|
||||
"401_error": "Please check your authentication credentials.",
|
||||
"404_error": "Please check your request URL and method type.",
|
||||
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
|
||||
"default_error": "Please check your request.",
|
||||
"network_error": "Please check your network connection."
|
||||
"401_error": "請檢查您的授權認證。",
|
||||
"404_error": "請檢查您的請求網址和方式類型。",
|
||||
"cors_error": "請檢查您的跨來源資源共用設定。",
|
||||
"default_error": "請檢查您的請求。",
|
||||
"network_error": "請檢查您的網路連線。"
|
||||
},
|
||||
"title": "Inspector",
|
||||
"title": "檢查工具",
|
||||
"url": {
|
||||
"extension_not_installed": "Extension not installed.",
|
||||
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
|
||||
"extention_enable_action": "Enable Browser Extension",
|
||||
"extention_not_enabled": "Extension not enabled."
|
||||
"extension_not_installed": "未安裝擴充套件。",
|
||||
"extension_unknown_origin": "請確認您是否已將 API 端點的來源加入 Hoppscotch 擴充套件的清單。",
|
||||
"extention_enable_action": "啟用瀏覽器擴充套件",
|
||||
"extention_not_enabled": "未啟用擴充套件。"
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
@@ -472,7 +472,7 @@
|
||||
"payload": "負載",
|
||||
"query": "查詢",
|
||||
"raw_body": "原始請求本體",
|
||||
"rename": "Rename Request",
|
||||
"rename": "重新命名請求",
|
||||
"renamed": "請求已重新命名",
|
||||
"run": "執行",
|
||||
"save": "儲存",
|
||||
@@ -510,7 +510,7 @@
|
||||
"accent_color": "強調色",
|
||||
"account": "帳號",
|
||||
"account_deleted": "已刪除您的帳號",
|
||||
"account_description": "自定義您的帳號設定。",
|
||||
"account_description": "自訂您的帳號設定。",
|
||||
"account_email_description": "您的主要電子郵件地址。",
|
||||
"account_name_description": "這是您的顯示名稱。",
|
||||
"background": "背景",
|
||||
@@ -542,7 +542,7 @@
|
||||
"read_the": "閱讀",
|
||||
"reset_default": "重置為預設",
|
||||
"short_codes": "快捷碼",
|
||||
"short_codes_description": "我們為您打造的快捷碼。",
|
||||
"short_codes_description": "您建立的快捷碼。",
|
||||
"sidebar_on_left": "左側邊欄",
|
||||
"sync": "同步",
|
||||
"sync_collections": "集合",
|
||||
@@ -551,9 +551,9 @@
|
||||
"sync_history": "歷史",
|
||||
"system_mode": "系統",
|
||||
"telemetry": "遙測服務",
|
||||
"telemetry_helps_us": "遙測服務幫助我們進行個性化操作,為您提供最佳體驗。",
|
||||
"telemetry_helps_us": "遙測服務能夠幫助我們進行個人化操作,為您提供最佳體驗。",
|
||||
"theme": "主題",
|
||||
"theme_description": "自定義您的應用程式主題。",
|
||||
"theme_description": "自訂您的應用程式主題。",
|
||||
"use_experimental_url_bar": "使用帶有環境醒目標示的實驗性網址欄",
|
||||
"user": "使用者",
|
||||
"verified_email": "已確認電子郵件地址",
|
||||
@@ -592,26 +592,26 @@
|
||||
"title": "導航"
|
||||
},
|
||||
"others": {
|
||||
"prettify": "Prettify Editor's Content",
|
||||
"title": "Others"
|
||||
"prettify": "美化編輯器的內容",
|
||||
"title": "其他"
|
||||
},
|
||||
"request": {
|
||||
"copy_request_link": "複製請求連結",
|
||||
"delete_method": "選擇 DELETE 方法",
|
||||
"get_method": "選擇 GET 方法",
|
||||
"head_method": "選擇 HEAD 方法",
|
||||
"import_curl": "Import cURL",
|
||||
"import_curl": "匯入 cURL",
|
||||
"method": "方法",
|
||||
"next_method": "選擇下一個方法",
|
||||
"post_method": "選擇 POST 方法",
|
||||
"previous_method": "選擇上一個方法",
|
||||
"put_method": "選擇 PUT 方法",
|
||||
"rename": "Rename Request",
|
||||
"rename": "重新命名請求",
|
||||
"reset_request": "重置請求",
|
||||
"save_request": "Save Request",
|
||||
"save_request": "儲存請求",
|
||||
"save_to_collections": "儲存到集合",
|
||||
"send_request": "傳送請求",
|
||||
"show_code": "Generate code snippet",
|
||||
"show_code": "產生程式碼片段",
|
||||
"title": "請求"
|
||||
},
|
||||
"response": {
|
||||
@@ -642,82 +642,82 @@
|
||||
"url": "網址"
|
||||
},
|
||||
"spotlight": {
|
||||
"change_language": "Change Language",
|
||||
"change_language": "變更語言",
|
||||
"environments": {
|
||||
"delete": "Delete current environment",
|
||||
"duplicate": "Duplicate current environment",
|
||||
"duplicate_global": "Duplicate global environment",
|
||||
"edit": "Edit current environment",
|
||||
"edit_global": "Edit global environment",
|
||||
"new": "Create new environment",
|
||||
"new_variable": "Create a new environment variable",
|
||||
"title": "Environments"
|
||||
"delete": "刪除目前環境",
|
||||
"duplicate": "複製目前環境",
|
||||
"duplicate_global": "複製全域環境",
|
||||
"edit": "編輯目前環境",
|
||||
"edit_global": "編輯全域環境",
|
||||
"new": "建立新環境",
|
||||
"new_variable": "建立新環境變數",
|
||||
"title": "環境"
|
||||
},
|
||||
"general": {
|
||||
"chat": "Chat with support",
|
||||
"help_menu": "Help and support",
|
||||
"open_docs": "Read Documentation",
|
||||
"open_github": "Open GitHub repository",
|
||||
"open_keybindings": "Keyboard shortcuts",
|
||||
"social": "Social",
|
||||
"title": "General"
|
||||
"chat": "與客服對話",
|
||||
"help_menu": "幫助與支援",
|
||||
"open_docs": "閱讀說明文件",
|
||||
"open_github": "開啟 GitHub 儲存庫",
|
||||
"open_keybindings": "鍵盤快捷鍵",
|
||||
"social": "社交",
|
||||
"title": "一般"
|
||||
},
|
||||
"graphql": {
|
||||
"connect": "Connect to server",
|
||||
"disconnect": "Disconnect from server"
|
||||
"connect": "連接至伺服器",
|
||||
"disconnect": "斷開與伺服器的連線"
|
||||
},
|
||||
"miscellaneous": {
|
||||
"invite": "Invite your friends to Hoppscotch",
|
||||
"title": "Miscellaneous"
|
||||
"invite": "邀請您的朋友使用 Hoppscotch",
|
||||
"title": "雜項"
|
||||
},
|
||||
"request": {
|
||||
"save_as_new": "Save as new request",
|
||||
"select_method": "Select method",
|
||||
"switch_to": "Switch to",
|
||||
"tab_authorization": "Authorization tab",
|
||||
"tab_body": "Body tab",
|
||||
"tab_headers": "Headers tab",
|
||||
"tab_parameters": "Parameters tab",
|
||||
"tab_pre_request_script": "Pre-request script tab",
|
||||
"tab_query": "Query tab",
|
||||
"tab_tests": "Tests tab",
|
||||
"tab_variables": "Variables tab"
|
||||
"save_as_new": "儲存為新請求",
|
||||
"select_method": "選擇方法",
|
||||
"switch_to": "切換至",
|
||||
"tab_authorization": "授權分頁",
|
||||
"tab_body": "本體分頁",
|
||||
"tab_headers": "標頭分頁",
|
||||
"tab_parameters": "參數分頁",
|
||||
"tab_pre_request_script": "預請求腳本分頁",
|
||||
"tab_query": "查詢分頁",
|
||||
"tab_tests": "測試分頁",
|
||||
"tab_variables": "變數分頁"
|
||||
},
|
||||
"response": {
|
||||
"copy": "Copy response",
|
||||
"download": "Download response as file",
|
||||
"title": "Response"
|
||||
"copy": "複製回應",
|
||||
"download": "下載回應",
|
||||
"title": "回應"
|
||||
},
|
||||
"section": {
|
||||
"interceptor": "Interceptor",
|
||||
"interface": "Interface",
|
||||
"theme": "Theme",
|
||||
"user": "User"
|
||||
"interceptor": "攔截器",
|
||||
"interface": "介面",
|
||||
"theme": "主題",
|
||||
"user": "使用者"
|
||||
},
|
||||
"settings": {
|
||||
"change_interceptor": "Change Interceptor",
|
||||
"change_language": "Change Language",
|
||||
"change_interceptor": "變更攔截器",
|
||||
"change_language": "變更語言",
|
||||
"theme": {
|
||||
"black": "Black",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"system": "System preference"
|
||||
"black": "黑色",
|
||||
"dark": "暗色",
|
||||
"light": "亮色",
|
||||
"system": "跟隨系統"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"close_current": "Close current tab",
|
||||
"close_others": "Close all other tabs",
|
||||
"duplicate": "Duplicate current tab",
|
||||
"new_tab": "Open a new tab",
|
||||
"title": "Tabs"
|
||||
"close_current": "關閉目前分頁",
|
||||
"close_others": "關閉所有其他分頁",
|
||||
"duplicate": "複製目前分頁",
|
||||
"new_tab": "開啟新分頁",
|
||||
"title": "分頁"
|
||||
},
|
||||
"workspace": {
|
||||
"delete": "Delete current team",
|
||||
"edit": "Edit current team",
|
||||
"invite": "Invite people to team",
|
||||
"new": "Create new team",
|
||||
"switch_to_personal": "Switch to your personal workspace",
|
||||
"title": "Teams"
|
||||
"delete": "刪除目前團隊",
|
||||
"edit": "編輯目前團隊",
|
||||
"invite": "邀請他人加入團隊",
|
||||
"new": "建立新團隊",
|
||||
"switch_to_personal": "切換至您的個人工作區",
|
||||
"title": "團隊"
|
||||
}
|
||||
},
|
||||
"sse": {
|
||||
@@ -777,11 +777,11 @@
|
||||
"tab": {
|
||||
"authorization": "授權",
|
||||
"body": "請求本體",
|
||||
"close": "Close Tab",
|
||||
"close_others": "Close other Tabs",
|
||||
"close": "關閉分頁",
|
||||
"close_others": "關閉其他分頁",
|
||||
"collections": "集合",
|
||||
"documentation": "幫助文件",
|
||||
"duplicate": "Duplicate Tab",
|
||||
"duplicate": "複製分頁",
|
||||
"environments": "環境",
|
||||
"headers": "請求標頭",
|
||||
"history": "歷史記錄",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hoppscotch/common",
|
||||
"private": true,
|
||||
"version": "2023.8.1",
|
||||
"version": "2023.8.2",
|
||||
"scripts": {
|
||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||
"test": "vitest --run",
|
||||
@@ -133,12 +133,17 @@
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@vue/runtime-core": "^3.3.4",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.23",
|
||||
"prettier-plugin-tailwindcss": "^0.5.6",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"vite-plugin-fonts": "^0.6.0",
|
||||
"openapi-types": "^12.1.3",
|
||||
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||
"sass": "^1.66.0",
|
||||
@@ -154,9 +159,7 @@
|
||||
"vite-plugin-pages-sitemap": "^1.6.1",
|
||||
"vite-plugin-pwa": "^0.16.4",
|
||||
"vite-plugin-vue-layouts": "^0.8.0",
|
||||
"vite-plugin-windicss": "^1.9.1",
|
||||
"vitest": "^0.34.2",
|
||||
"vue-tsc": "^1.8.8",
|
||||
"windicss": "^3.5.6"
|
||||
"vue-tsc": "^1.8.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<div
|
||||
v-if="isLoadingInitialRoute"
|
||||
class="flex flex-col items-center justify-center min-h-screen"
|
||||
class="flex min-h-screen flex-col items-center justify-center"
|
||||
>
|
||||
<HoppSmartSpinner />
|
||||
</div>
|
||||
|
||||
@@ -189,7 +189,6 @@ declare module 'vue' {
|
||||
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
|
||||
SmartExpand: typeof import('./../../hoppscotch-ui/src/components/smart/Expand.vue')['default']
|
||||
SmartFileChip: typeof import('./../../hoppscotch-ui/src/components/smart/FileChip.vue')['default']
|
||||
SmartFontSizePicker: typeof import('./components/smart/FontSizePicker.vue')['default']
|
||||
SmartInput: typeof import('./../../hoppscotch-ui/src/components/smart/Input.vue')['default']
|
||||
SmartIntersection: typeof import('./../../hoppscotch-ui/src/components/smart/Intersection.vue')['default']
|
||||
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
|
||||
|
||||
@@ -2,53 +2,16 @@
|
||||
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
|
||||
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
||||
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
|
||||
|
||||
<HoppSmartConfirmModal
|
||||
:show="confirmRemove"
|
||||
:title="t('confirm.remove_team')"
|
||||
@hide-modal="confirmRemove = false"
|
||||
@resolve="deleteTeam()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
|
||||
const showShortcuts = ref(false)
|
||||
const showShare = ref(false)
|
||||
const showLogin = ref(false)
|
||||
|
||||
const confirmRemove = ref(false)
|
||||
|
||||
const teamID = ref<string | null>(null)
|
||||
|
||||
const deleteTeam = () => {
|
||||
if (!teamID.value) return
|
||||
pipe(
|
||||
backendDeleteTeam(teamID.value),
|
||||
TE.match(
|
||||
(err) => {
|
||||
// TODO: Better errors ? We know the possible errors now
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
console.error(err)
|
||||
},
|
||||
() => {
|
||||
invokeAction("workspace.switch.personal")
|
||||
toast.success(`${t("team.deleted")}`)
|
||||
}
|
||||
)
|
||||
)() // Tasks (and TEs) are lazy, so call the function returned
|
||||
}
|
||||
|
||||
defineActionHandler("flyouts.keybinds.toggle", () => {
|
||||
showShortcuts.value = !showShortcuts.value
|
||||
})
|
||||
@@ -60,9 +23,4 @@ defineActionHandler("modals.share.toggle", () => {
|
||||
defineActionHandler("modals.login.toggle", () => {
|
||||
showLogin.value = !showLogin.value
|
||||
})
|
||||
|
||||
defineActionHandler("modals.team.delete", ({ teamId }) => {
|
||||
teamID.value = teamId
|
||||
confirmRemove.value = true
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative flex items-center px-4 py-2 transition bg-error text-tiny group"
|
||||
class="group relative flex items-center bg-error px-4 py-2 text-tiny transition"
|
||||
role="alert"
|
||||
>
|
||||
<icon-lucide-info class="mr-2" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
ref="contextMenuRef"
|
||||
class="fixed bg-popover shadow-lg transform translate-y-8 border border-dividerDark p-2 rounded"
|
||||
class="fixed translate-y-8 transform rounded border border-dividerDark bg-popover p-2 shadow-lg"
|
||||
:style="`top: ${position.top}px; left: ${position.left}px; z-index: 1000;`"
|
||||
>
|
||||
<div v-if="contextMenuOptions" class="flex flex-col">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<p class="px-2 mb-4 text-secondaryLight">
|
||||
<p class="mb-4 px-2 text-secondaryLight">
|
||||
{{ t("app.developer_option_description") }}
|
||||
</p>
|
||||
<div class="flex flex-1">
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
@click="COLUMN_LAYOUT = !COLUMN_LAYOUT"
|
||||
/>
|
||||
<span
|
||||
class="transition transform"
|
||||
class="transform transition"
|
||||
:class="{
|
||||
'rotate-180': SIDEBAR_ON_LEFT,
|
||||
}"
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<header
|
||||
class="flex items-center justify-between flex-1 flex-shrink-0 px-2 py-2 space-x-2 overflow-x-auto overflow-y-hidden"
|
||||
class="flex flex-1 flex-shrink-0 items-center justify-between space-x-2 overflow-x-auto overflow-y-hidden px-2 py-2"
|
||||
>
|
||||
<div
|
||||
class="inline-flex items-center justify-start flex-1 space-x-2"
|
||||
class="inline-flex flex-1 items-center justify-start space-x-2"
|
||||
:style="{
|
||||
paddingTop: platform.ui?.appHeader?.paddingTop?.value,
|
||||
paddingLeft: platform.ui?.appHeader?.paddingLeft?.value,
|
||||
}"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
class="tracking-wide !font-bold !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark uppercase"
|
||||
class="!font-bold uppercase tracking-wide !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
:label="t('app.name')"
|
||||
to="/"
|
||||
/>
|
||||
</div>
|
||||
<div class="inline-flex items-center justify-center flex-1 space-x-2">
|
||||
<div class="inline-flex flex-1 items-center justify-center space-x-2">
|
||||
<button
|
||||
class="flex flex-1 items-center justify-between px-2 py-1 self-stretch bg-primaryDark transition text-secondaryLight cursor-text rounded border border-dividerDark max-w-60 hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary"
|
||||
class="flex max-w-[15rem] flex-1 cursor-text items-center justify-between self-stretch rounded border border-dividerDark bg-primaryDark px-2 py-1 text-secondaryLight transition hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary"
|
||||
@click="invokeAction('modals.search.toggle')"
|
||||
>
|
||||
<span class="inline-flex flex-1 items-center">
|
||||
<icon-lucide-search class="mr-2 svg-icons" />
|
||||
<icon-lucide-search class="svg-icons mr-2" />
|
||||
{{ t("app.search") }}
|
||||
</span>
|
||||
<span class="flex space-x-1">
|
||||
@@ -48,7 +48,7 @@
|
||||
@click="invokeAction('modals.support.toggle')"
|
||||
/>
|
||||
</div>
|
||||
<div class="inline-flex items-center justify-end flex-1 space-x-2">
|
||||
<div class="inline-flex flex-1 items-center justify-end space-x-2">
|
||||
<div
|
||||
v-if="currentUser === null"
|
||||
class="inline-flex items-center space-x-2"
|
||||
@@ -56,7 +56,7 @@
|
||||
<HoppButtonSecondary
|
||||
:icon="IconUploadCloud"
|
||||
:label="t('header.save_workspace')"
|
||||
class="hidden md:flex bg-green-500/15 py-1.75 border border-green-600/25 !text-green-500 hover:bg-green-400/10 focus-visible:bg-green-400/10 focus-visible:border-green-800/50 !focus-visible:text-green-600 hover:border-green-800/50 !hover:text-green-600"
|
||||
class="py-1.75 !focus-visible:text-green-600 !hover:text-green-600 hidden border border-green-600/25 bg-green-500/[.15] !text-green-500 hover:border-green-800/50 hover:bg-green-400/10 focus-visible:border-green-800/50 focus-visible:bg-green-400/10 md:flex"
|
||||
@click="invokeAction('modals.login.toggle')"
|
||||
/>
|
||||
<HoppButtonPrimary
|
||||
@@ -77,13 +77,13 @@
|
||||
@handle-click="handleTeamEdit()"
|
||||
/>
|
||||
<div
|
||||
class="flex border divide-x rounded bg-green-500/15 divide-green-600/25 border-green-600/25 focus-within:bg-green-400/10 focus-within:border-green-800/50 focus-within:divide-green-800/50 hover:bg-green-400/10 hover:border-green-800/50 hover:divide-green-800/50"
|
||||
class="flex divide-x divide-green-600/25 rounded border border-green-600/25 bg-green-500/[.15] focus-within:divide-green-800/50 focus-within:border-green-800/50 focus-within:bg-green-400/10 hover:divide-green-800/50 hover:border-green-800/50 hover:bg-green-400/10"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('team.invite_tooltip')"
|
||||
:icon="IconUserPlus"
|
||||
class="py-1.75 !text-green-500 !focus-visible:text-green-600 !hover:text-green-600"
|
||||
class="py-1.75 !focus-visible:text-green-600 !hover:text-green-600 !text-green-500"
|
||||
@click="handleInvite()"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
@@ -95,7 +95,7 @@
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('team.edit')"
|
||||
:icon="IconSettings"
|
||||
class="py-1.75 !text-green-500 !focus-visible:text-green-600 !hover:text-green-600"
|
||||
class="py-1.75 !focus-visible:text-green-600 !hover:text-green-600 !text-green-500"
|
||||
@click="handleTeamEdit()"
|
||||
/>
|
||||
</div>
|
||||
@@ -110,7 +110,7 @@
|
||||
:title="t('workspace.change')"
|
||||
:label="mdAndLarger ? workspaceName : ``"
|
||||
:icon="workspace.type === 'personal' ? IconUser : IconUsers"
|
||||
class="pr-8 select-wrapper rounded bg-blue-500/15 py-1.75 border border-blue-600/25 !text-blue-500 focus-visible:bg-blue-400/10 focus-visible:border-blue-800/50 !focus-visible:text-blue-600 hover:bg-blue-400/10 hover:border-blue-800/50 !hover:text-blue-600"
|
||||
class="select-wrapper !focus-visible:text-blue-600 !hover:text-blue-600 rounded border border-blue-600/25 bg-blue-500/[.15] py-[0.4375rem] pr-8 !text-blue-500 hover:border-blue-800/50 hover:bg-blue-400/10 focus-visible:border-blue-800/50 focus-visible:bg-blue-400/10"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
@@ -176,7 +176,7 @@
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<div class="flex flex-col px-2 text-tiny">
|
||||
<span class="inline-flex font-semibold truncate">
|
||||
<span class="inline-flex truncate font-semibold">
|
||||
{{
|
||||
currentUser.displayName ||
|
||||
t("profile.default_hopp_displayname")
|
||||
@@ -231,29 +231,39 @@
|
||||
@invite-team="inviteTeam(editingTeamName, editingTeamID)"
|
||||
@refetch-teams="refetchTeams"
|
||||
/>
|
||||
|
||||
<HoppSmartConfirmModal
|
||||
:show="confirmRemove"
|
||||
:title="t('confirm.remove_team')"
|
||||
@hide-modal="confirmRemove = false"
|
||||
@resolve="deleteTeam"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from "vue"
|
||||
import IconUser from "~icons/lucide/user"
|
||||
import IconUsers from "~icons/lucide/users"
|
||||
import IconSettings from "~icons/lucide/settings"
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
||||
import IconUserPlus from "~icons/lucide/user-plus"
|
||||
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
||||
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
||||
import { pwaDefferedPrompt, installPWA } from "@modules/pwa"
|
||||
import { platform } from "~/platform"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { WorkspaceService } from "~/services/workspace.service"
|
||||
import { useService } from "dioc/vue"
|
||||
import { installPWA, pwaDefferedPrompt } from "@modules/pwa"
|
||||
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
||||
import { computed, reactive, ref, watch } from "vue"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql"
|
||||
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
||||
import { platform } from "~/platform"
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
||||
import IconSettings from "~icons/lucide/settings"
|
||||
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
||||
import IconUser from "~icons/lucide/user"
|
||||
import IconUserPlus from "~icons/lucide/user-plus"
|
||||
import IconUsers from "~icons/lucide/users"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -278,6 +288,9 @@ const currentUser = useReadonlyStream(
|
||||
platform.auth.getProbableUser()
|
||||
)
|
||||
|
||||
const confirmRemove = ref(false)
|
||||
const teamID = ref<string | null>(null)
|
||||
|
||||
const selectedTeam = ref<GetMyTeamsQuery["myTeams"][number] | undefined>()
|
||||
|
||||
// TeamList-Adapter
|
||||
@@ -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
|
||||
const tippyActions = ref<any | null>(null)
|
||||
const profile = ref<any | null>(null)
|
||||
@@ -405,6 +436,12 @@ defineActionHandler(
|
||||
computed(() => !currentUser.value)
|
||||
)
|
||||
|
||||
defineActionHandler("modals.team.delete", ({ teamId }) => {
|
||||
if (selectedTeam.value?.myRole !== TeamMemberRole.Owner) return noPermission()
|
||||
teamID.value = teamId
|
||||
confirmRemove.value = true
|
||||
})
|
||||
|
||||
const noPermission = () => {
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-if="inspectionResults && inspectionResults.length > 0">
|
||||
<tippy interactive trigger="click" theme="popover">
|
||||
<div class="flex justify-center items-center flex-1 flex-col">
|
||||
<div class="flex flex-1 flex-col items-center justify-center">
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconAlertTriangle"
|
||||
@@ -10,12 +10,12 @@
|
||||
/>
|
||||
</div>
|
||||
<template #content="{ hide }">
|
||||
<div class="flex flex-col space-y-2 items-start flex-1">
|
||||
<div class="flex flex-1 flex-col items-start space-y-2">
|
||||
<div
|
||||
class="flex justify-between border rounded pl-2 border-divider bg-popover sticky top-0 self-stretch"
|
||||
class="sticky top-0 flex justify-between self-stretch rounded border border-divider bg-popover pl-2"
|
||||
>
|
||||
<span class="flex items-center flex-1">
|
||||
<icon-lucide-activity class="mr-2 svg-icons text-accent" />
|
||||
<span class="flex flex-1 items-center">
|
||||
<icon-lucide-activity class="svg-icons mr-2 text-accent" />
|
||||
<span class="font-bold">
|
||||
{{ t("inspections.title") }}
|
||||
</span>
|
||||
@@ -31,10 +31,10 @@
|
||||
<div
|
||||
v-for="(inspector, index) in inspectionResults"
|
||||
:key="index"
|
||||
class="flex self-stretch max-w-md w-full"
|
||||
class="flex w-full max-w-md self-stretch"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col flex-1 rounded border border-dashed border-dividerDark divide-y divide-dashed divide-dividerDark"
|
||||
class="flex flex-1 flex-col divide-y divide-dashed divide-dividerDark rounded border border-dashed border-dividerDark"
|
||||
>
|
||||
<span
|
||||
v-if="inspector.text.type === 'text'"
|
||||
@@ -44,13 +44,13 @@
|
||||
<HoppSmartLink
|
||||
blank
|
||||
:to="inspector.doc.link"
|
||||
class="text-accent hover:text-accentDark transition"
|
||||
class="text-accent transition hover:text-accentDark"
|
||||
>
|
||||
{{ inspector.doc.text }}
|
||||
<icon-lucide-arrow-up-right class="svg-icons" />
|
||||
</HoppSmartLink>
|
||||
</span>
|
||||
<span v-if="inspector.action" class="flex p-2 space-x-2">
|
||||
<span v-if="inspector.action" class="flex space-x-2 p-2">
|
||||
<HoppButtonSecondary
|
||||
:label="inspector.action.text"
|
||||
outline
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<h2 class="p-4 font-semibold font-bold text-secondaryDark">
|
||||
<h2 class="p-4 font-bold font-semibold text-secondaryDark">
|
||||
{{ t("layout.name") }}
|
||||
</h2>
|
||||
<HoppSmartItem
|
||||
@@ -27,7 +27,7 @@
|
||||
active
|
||||
@click="expandCollection"
|
||||
/>
|
||||
<h2 class="p-4 font-semibold font-bold text-secondaryDark">
|
||||
<h2 class="p-4 font-bold font-semibold text-secondaryDark">
|
||||
{{ t("support.title") }}
|
||||
</h2>
|
||||
<template
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
class="share-link"
|
||||
tabindex="0"
|
||||
>
|
||||
<component :is="platform.icon" class="w-6 h-6" />
|
||||
<component :is="platform.icon" class="h-6 w-6" />
|
||||
<span class="mt-3">
|
||||
{{ platform.name }}
|
||||
</span>
|
||||
</a>
|
||||
<button class="share-link" @click="copyAppLink">
|
||||
<component :is="copyIcon" class="w-6 h-6 text-xl" />
|
||||
<component :is="copyIcon" class="h-6 w-6 text-xl" />
|
||||
<span class="mt-3">
|
||||
{{ t("app.copy") }}
|
||||
</span>
|
||||
@@ -119,14 +119,14 @@ const hideModal = () => {
|
||||
.share-link {
|
||||
@apply border border-dividerLight;
|
||||
@apply rounded;
|
||||
@apply flex-col flex;
|
||||
@apply flex flex-col;
|
||||
@apply p-4;
|
||||
@apply items-center;
|
||||
@apply justify-center;
|
||||
@apply font-semibold;
|
||||
@apply hover: (bg-primaryLight text-secondaryDark);
|
||||
@apply focus: outline-none;
|
||||
@apply focus-visible: border-divider;
|
||||
@apply hover:bg-primaryLight hover:text-secondaryDark;
|
||||
@apply focus:outline-none;
|
||||
@apply focus-visible:border-divider;
|
||||
|
||||
svg {
|
||||
@apply opacity-80;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<HoppSmartSlideOver :show="show" :title="t('app.shortcuts')" @close="close()">
|
||||
<template #content>
|
||||
<div
|
||||
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
|
||||
class="sticky top-0 z-10 flex flex-shrink-0 flex-col overflow-x-auto bg-primary"
|
||||
>
|
||||
<HoppSmartInput
|
||||
v-model="filterText"
|
||||
@@ -17,7 +17,7 @@
|
||||
v-if="isEmpty(shortcutsResults)"
|
||||
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
||||
>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
</HoppSmartPlaceholder>
|
||||
|
||||
<details
|
||||
@@ -28,16 +28,16 @@
|
||||
open
|
||||
>
|
||||
<summary
|
||||
class="flex items-center flex-1 min-w-0 px-6 py-4 font-semibold transition cursor-pointer focus:outline-none text-secondaryLight hover:text-secondaryDark"
|
||||
class="flex min-w-0 flex-1 cursor-pointer items-center px-6 py-4 font-semibold text-secondaryLight transition hover:text-secondaryDark focus:outline-none"
|
||||
>
|
||||
<icon-lucide-chevron-right class="mr-2 indicator" />
|
||||
<icon-lucide-chevron-right class="indicator mr-2" />
|
||||
<span
|
||||
class="font-semibold truncate capitalize-first text-secondaryDark"
|
||||
class="capitalize-first truncate font-semibold text-secondaryDark"
|
||||
>
|
||||
{{ sectionTitle }}
|
||||
</span>
|
||||
</summary>
|
||||
<div class="flex flex-col px-6 pb-4 space-y-2">
|
||||
<div class="flex flex-col space-y-2 px-6 pb-4">
|
||||
<AppShortcutsEntry
|
||||
v-for="(shortcut, index) in sectionResults"
|
||||
:key="`shortcut-${index}`"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="flex items-center py-1">
|
||||
<span class="flex flex-1 mr-4">
|
||||
<span class="mr-4 flex flex-1">
|
||||
{{ shortcut.label }}
|
||||
</span>
|
||||
<kbd
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center text-secondaryLight">
|
||||
<div class="flex mb-4 space-x-2">
|
||||
<div class="mb-4 flex space-x-2">
|
||||
<div class="flex flex-col items-end space-y-4 text-right">
|
||||
<span class="flex items-center flex-1">
|
||||
<span class="flex flex-1 items-center">
|
||||
{{ t("shortcut.request.send_request") }}
|
||||
</span>
|
||||
<span class="flex items-center flex-1">
|
||||
<span class="flex flex-1 items-center">
|
||||
{{ t("shortcut.general.show_all") }}
|
||||
</span>
|
||||
<span class="flex items-center flex-1">
|
||||
<span class="flex flex-1 items-center">
|
||||
{{ t("shortcut.general.command_menu") }}
|
||||
</span>
|
||||
<span class="flex items-center flex-1">
|
||||
<span class="flex flex-1 items-center">
|
||||
{{ t("shortcut.general.help_menu") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<aside class="flex justify-between h-full md:flex-col">
|
||||
<nav class="flex flex-1 flex-nowrap md:flex-col md:flex-none bg-primary">
|
||||
<aside class="flex h-full justify-between md:flex-col">
|
||||
<nav class="flex flex-1 flex-nowrap bg-primary md:flex-none md:flex-col">
|
||||
<HoppSmartLink
|
||||
v-for="(navigation, index) in primaryNavigation"
|
||||
:key="`navigation-${index}`"
|
||||
@@ -73,25 +73,25 @@ const primaryNavigation = [
|
||||
.nav-link {
|
||||
@apply relative;
|
||||
@apply p-4;
|
||||
@apply flex flex-col flex-1;
|
||||
@apply flex flex-1 flex-col;
|
||||
@apply items-center;
|
||||
@apply justify-center;
|
||||
@apply hover: (bg-primaryDark text-secondaryDark);
|
||||
@apply focus-visible: text-secondaryDark;
|
||||
@apply hover:bg-primaryDark hover:text-secondaryDark;
|
||||
@apply focus-visible:text-secondaryDark;
|
||||
@apply after:absolute;
|
||||
@apply after:inset-x-0;
|
||||
@apply after:md: inset-x-auto;
|
||||
@apply after:md: inset-y-0;
|
||||
@apply after:md:inset-x-auto;
|
||||
@apply after:md:inset-y-0;
|
||||
@apply after:bottom-0;
|
||||
@apply after:md: bottom-auto;
|
||||
@apply after:md: left-0;
|
||||
@apply after:z-2;
|
||||
@apply after:md:bottom-auto;
|
||||
@apply after:md:left-0;
|
||||
@apply after:z-10;
|
||||
@apply after:h-0.5;
|
||||
@apply after:md: h-full;
|
||||
@apply after:md:h-full;
|
||||
@apply after:w-full;
|
||||
@apply after:md: w-0.5;
|
||||
@apply after:content-DEFAULT;
|
||||
@apply focus: after: bg-divider;
|
||||
@apply after:md:w-0.5;
|
||||
@apply after:content-[""];
|
||||
@apply focus:after:bg-divider;
|
||||
|
||||
.svg-icons {
|
||||
@apply opacity-75;
|
||||
@@ -105,7 +105,7 @@ const primaryNavigation = [
|
||||
&.router-link-active {
|
||||
@apply text-secondaryDark;
|
||||
@apply bg-primaryLight;
|
||||
@apply hover: text-secondaryDark;
|
||||
@apply hover:text-secondaryDark;
|
||||
@apply after:bg-accent;
|
||||
|
||||
.svg-icons {
|
||||
@@ -116,7 +116,7 @@ const primaryNavigation = [
|
||||
&.exact-active-link {
|
||||
@apply text-secondaryDark;
|
||||
@apply bg-primaryLight;
|
||||
@apply hover: text-secondaryDark;
|
||||
@apply hover:text-secondaryDark;
|
||||
@apply after:bg-accent;
|
||||
|
||||
.svg-icons {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<button
|
||||
ref="el"
|
||||
class="flex items-center flex-1 px-6 py-4 font-medium space-x-4 transition cursor-pointer relative search-entry focus:outline-none"
|
||||
class="search-entry relative flex flex-1 cursor-pointer items-center space-x-4 px-6 py-4 font-medium transition focus:outline-none"
|
||||
:class="{ 'active bg-primaryLight text-secondaryDark': active }"
|
||||
tabindex="-1"
|
||||
@click="emit('action')"
|
||||
@@ -9,7 +9,7 @@
|
||||
>
|
||||
<component
|
||||
:is="entry.icon"
|
||||
class="opacity-50 svg-icons"
|
||||
class="svg-icons opacity-50"
|
||||
:class="{ 'opacity-100': active }"
|
||||
/>
|
||||
<template
|
||||
@@ -112,9 +112,9 @@ watch(
|
||||
@apply after:left-0;
|
||||
@apply after:bottom-0;
|
||||
@apply after:bg-transparent;
|
||||
@apply after:z-2;
|
||||
@apply after:z-10;
|
||||
@apply after:w-0.5;
|
||||
@apply after:content-DEFAULT;
|
||||
@apply after:content-[''];
|
||||
|
||||
&.active {
|
||||
@apply after:bg-accentLight;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{{ historyEntry.request.url }}
|
||||
</span>
|
||||
<span
|
||||
class="font-semibold truncate text-tiny flex flex-shrink-0 border border-dividerDark rounded-md px-1"
|
||||
class="flex flex-shrink-0 truncate rounded-md border border-dividerDark px-1 text-tiny font-semibold"
|
||||
>
|
||||
{{ historyEntry.request.query.split("\n")[0] }}
|
||||
</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<span class="flex flex-1 space-x-2 items-center">
|
||||
<span class="flex flex-1 items-center space-x-2">
|
||||
<template v-for="(folder, index) in pathFolders" :key="index">
|
||||
<span class="block" :class="{ truncate: index !== 0 }">
|
||||
{{ folder.name }}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</span>
|
||||
<icon-lucide-chevron-right class="flex flex-shrink-0" />
|
||||
<span
|
||||
class="font-semibold truncate text-tiny flex flex-shrink-0 border border-dividerDark rounded-md px-1"
|
||||
class="flex flex-shrink-0 truncate rounded-md border border-dividerDark px-1 text-tiny font-semibold"
|
||||
:class="entryStatus.className"
|
||||
>
|
||||
{{ historyEntry.request.method }}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
</template>
|
||||
<span
|
||||
v-if="request"
|
||||
class="font-semibold truncate text-tiny flex flex-shrink-0 border border-dividerDark rounded-md px-1"
|
||||
:class="getMethodLabelColorClassOf(request)"
|
||||
class="flex flex-shrink-0 truncate rounded-md border border-dividerDark px-1 text-tiny font-semibold"
|
||||
:style="{ color: getMethodLabelColorClassOf(request) }"
|
||||
>
|
||||
{{ request.method.toUpperCase() }}
|
||||
</span>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
@close="emit('hide-modal')"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col border-b transition border-divider">
|
||||
<div class="flex flex-col border-b border-divider transition">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="command"
|
||||
@@ -16,14 +16,14 @@
|
||||
autocomplete="off"
|
||||
name="command"
|
||||
:placeholder="`${t('app.type_a_command_search')}`"
|
||||
class="flex flex-1 text-base bg-transparent text-secondaryDark px-6 py-5"
|
||||
class="flex flex-1 bg-transparent px-6 py-5 text-base text-secondaryDark"
|
||||
/>
|
||||
<HoppSmartSpinner v-if="searchSession?.loading" class="mr-6" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="searchSession && search.length > 0"
|
||||
class="flex flex-col flex-1 overflow-y-auto border-b border-divider divide-y divide-dividerLight"
|
||||
class="flex flex-1 flex-col divide-y divide-dividerLight overflow-y-auto border-b border-divider"
|
||||
>
|
||||
<div
|
||||
v-for="([sectionID, sectionResult], sectionIndex) in scoredResults"
|
||||
@@ -31,7 +31,7 @@
|
||||
class="flex flex-col"
|
||||
>
|
||||
<h5
|
||||
class="px-6 py-2 bg-primaryContrast z-10 text-secondaryLight sticky top-0"
|
||||
class="sticky top-0 z-10 bg-primaryContrast px-6 py-2 text-secondaryLight"
|
||||
>
|
||||
{{ sectionResult.title }}
|
||||
</h5>
|
||||
@@ -49,7 +49,7 @@
|
||||
:text="`${t('state.nothing_found')} ‟${search}”`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
</template>
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.clear')"
|
||||
@@ -59,7 +59,7 @@
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-shrink-0 text-tiny text-secondaryLight p-4 justify-between whitespace-nowrap overflow-auto <sm:hidden"
|
||||
class="flex flex-shrink-0 justify-between overflow-auto whitespace-nowrap p-4 text-tiny text-secondaryLight <sm:hidden"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<kbd class="shortcut-key">↑</kbd>
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
import { ref, watch } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
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 t = useI18n()
|
||||
@@ -60,11 +61,12 @@ const emit = defineEmits<{
|
||||
|
||||
const editingName = ref("")
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
watch(
|
||||
() => props.show,
|
||||
(show) => {
|
||||
if (show) {
|
||||
editingName.value = currentActiveTab.value.document.request.name
|
||||
editingName.value = tabs.currentActiveTab.value.document.request.name
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,16 +12,16 @@
|
||||
@dragleave="ordering = false"
|
||||
@dragend="resetDragState"
|
||||
></div>
|
||||
<div class="flex flex-col relative">
|
||||
<div class="relative flex flex-col">
|
||||
<div
|
||||
class="absolute bg-accent opacity-0 pointer-events-none inset-0 z-1 transition"
|
||||
class="z-1 pointer-events-none absolute inset-0 bg-accent opacity-0 transition"
|
||||
:class="{
|
||||
'opacity-25':
|
||||
dragging && notSameDestination && notSameParentDestination,
|
||||
}"
|
||||
></div>
|
||||
<div
|
||||
class="flex items-stretch group relative z-3 cursor-pointer pointer-events-auto"
|
||||
class="z-3 group pointer-events-auto relative flex cursor-pointer items-stretch"
|
||||
:draggable="!hasNoTeamAccess"
|
||||
@dragstart="dragStart"
|
||||
@drop="handelDrop($event)"
|
||||
@@ -36,11 +36,11 @@
|
||||
@contextmenu.prevent="options?.tippy.show()"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-center flex-1 min-w-0"
|
||||
class="flex min-w-0 flex-1 items-center justify-center"
|
||||
@click="emit('toggle-children')"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center px-4 pointer-events-none"
|
||||
class="pointer-events-none flex items-center justify-center px-4"
|
||||
>
|
||||
<HoppSmartSpinner v-if="isCollLoading" />
|
||||
<component
|
||||
@@ -51,7 +51,7 @@
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 transition pointer-events-none group-hover:text-secondaryDark"
|
||||
class="pointer-events-none flex min-w-0 flex-1 py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ collectionName }}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div v-if="step.name === 'FILE_IMPORT'" class="space-y-4">
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary text-dividerDark"
|
||||
class="mr-4 inline-flex flex-shrink-0 items-center justify-center rounded-full border-4 border-primary text-dividerDark"
|
||||
:class="{
|
||||
'!text-green-500': hasFile,
|
||||
}"
|
||||
@@ -38,14 +38,14 @@
|
||||
</span>
|
||||
</p>
|
||||
<p
|
||||
class="flex flex-col ml-10 border border-dashed rounded border-dividerDark"
|
||||
class="ml-10 flex flex-col rounded border border-dashed border-dividerDark"
|
||||
>
|
||||
<input
|
||||
id="inputChooseFileToImportFrom"
|
||||
ref="inputChooseFileToImportFrom"
|
||||
name="inputChooseFileToImportFrom"
|
||||
type="file"
|
||||
class="p-4 cursor-pointer transition file:transition file:cursor-pointer text-secondary hover:text-secondaryDark file:mr-2 file:py-2 file:px-4 file:rounded file:border-0 file:text-secondary hover:file:text-secondaryDark file:bg-primaryLight hover:file:bg-primaryDark"
|
||||
class="cursor-pointer p-4 text-secondary transition file:mr-2 file:cursor-pointer file:rounded file:border-0 file:bg-primaryLight file:px-4 file:py-2 file:text-secondary file:transition hover:text-secondaryDark hover:file:bg-primaryDark hover:file:text-secondaryDark"
|
||||
:accept="step.metadata.acceptedFileTypes"
|
||||
@change="onFileChange"
|
||||
/>
|
||||
@@ -54,7 +54,7 @@
|
||||
<div v-else-if="step.name === 'URL_IMPORT'" class="space-y-4">
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary text-dividerDark"
|
||||
class="mr-4 inline-flex flex-shrink-0 items-center justify-center rounded-full border-4 border-primary text-dividerDark"
|
||||
:class="{
|
||||
'!text-green-500': hasGist,
|
||||
}"
|
||||
@@ -65,7 +65,7 @@
|
||||
{{ t(`${step.metadata.caption}`) }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="flex flex-col ml-10">
|
||||
<p class="ml-10 flex flex-col">
|
||||
<input
|
||||
v-model="inputChooseGistToImportFrom"
|
||||
type="url"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
|
||||
class="sticky z-10 flex flex-1 justify-between border-b border-dividerLight bg-primary"
|
||||
:style="
|
||||
saveRequest
|
||||
? 'top: calc(var(--upper-primary-sticky-fold) - var(--line-height-body))'
|
||||
@@ -25,13 +25,13 @@
|
||||
<HoppButtonSecondary
|
||||
v-if="!saveRequest"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconArchive"
|
||||
:icon="IconImport"
|
||||
:title="t('modal.import_export')"
|
||||
@click="emit('display-modal-import-export')"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-1 flex-col">
|
||||
<HoppSmartTree :adapter="myAdapter">
|
||||
<template
|
||||
#content="{ node, toggleChildren, isOpen, highlightChildren }"
|
||||
@@ -248,7 +248,7 @@
|
||||
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
@@ -257,12 +257,27 @@
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="emit('display-modal-add')"
|
||||
/>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<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
|
||||
v-else-if="node.data.type === 'collections'"
|
||||
@@ -288,8 +303,7 @@
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.folder')}`"
|
||||
:text="t('empty.folder')"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartTree>
|
||||
</div>
|
||||
@@ -297,9 +311,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconArchive from "~icons/lucide/archive"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { computed, PropType, Ref, toRef } from "vue"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
@@ -312,7 +326,8 @@ import { useColorMode } from "@composables/theming"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { Picked } from "~/helpers/types/HoppPicked.js"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
|
||||
export type Collection = {
|
||||
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) => {
|
||||
return pipe(
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
@dragend="resetDragState"
|
||||
></div>
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
class="group flex items-stretch"
|
||||
:draggable="!hasNoTeamAccess"
|
||||
@drop="handelDrop"
|
||||
@dragstart="dragStart"
|
||||
@@ -23,12 +23,13 @@
|
||||
@contextmenu.prevent="options?.tippy.show()"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-center flex-1 min-w-0 cursor-pointer pointer-events-auto"
|
||||
class="pointer-events-auto flex min-w-0 flex-1 cursor-pointer items-center justify-center"
|
||||
@click="selectRequest()"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center w-16 px-2 truncate pointer-events-none"
|
||||
class="pointer-events-none flex w-16 items-center justify-center truncate px-2"
|
||||
:class="requestLabelColor"
|
||||
:style="{ color: requestLabelColor }"
|
||||
>
|
||||
<component
|
||||
:is="IconCheckCircle"
|
||||
@@ -37,12 +38,12 @@
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
/>
|
||||
<HoppSmartSpinner v-else-if="isRequestLoading" />
|
||||
<span v-else class="font-semibold truncate text-tiny">
|
||||
<span v-else class="truncate text-tiny font-semibold">
|
||||
{{ request.method }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="flex items-center flex-1 min-w-0 py-2 pr-2 pointer-events-none transition group-hover:text-secondaryDark"
|
||||
class="pointer-events-none flex min-w-0 flex-1 items-center py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ request.name }}
|
||||
@@ -50,15 +51,15 @@
|
||||
<span
|
||||
v-if="isActive"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
class="relative h-1.5 w-1.5 flex flex-shrink-0 mx-3"
|
||||
class="relative mx-3 flex h-1.5 w-1.5 flex-shrink-0"
|
||||
:title="`${t('collection.request_in_use')}`"
|
||||
>
|
||||
<span
|
||||
class="absolute inline-flex flex-shrink-0 w-full h-full bg-green-500 rounded-full opacity-75 animate-ping"
|
||||
class="absolute inline-flex h-full w-full flex-shrink-0 animate-ping rounded-full bg-green-500 opacity-75"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="relative inline-flex flex-shrink-0 rounded-full h-1.5 w-1.5 bg-green-500"
|
||||
class="relative inline-flex h-1.5 w-1.5 flex-shrink-0 rounded-full bg-green-500"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -82,12 +82,16 @@ import {
|
||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||
import { computedWithControl } from "@vueuse/core"
|
||||
import { platform } from "~/platform"
|
||||
import { currentActiveTab as activeRESTTab } from "~/helpers/rest/tab"
|
||||
import { currentActiveTab as activeGQLTab } from "~/helpers/graphql/tab"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const RESTTabs = useService(RESTTabService)
|
||||
const GQLTabs = useService(GQLTabService)
|
||||
|
||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
||||
|
||||
type CollectionType =
|
||||
@@ -123,13 +127,13 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const gqlRequestName = computedWithControl(
|
||||
() => activeGQLTab.value,
|
||||
() => activeGQLTab.value.document.request.name
|
||||
() => GQLTabs.currentActiveTab.value,
|
||||
() => GQLTabs.currentActiveTab.value.document.request.name
|
||||
)
|
||||
|
||||
const restRequestName = computedWithControl(
|
||||
() => activeRESTTab.value,
|
||||
() => activeRESTTab.value.document.request.name
|
||||
() => RESTTabs.currentActiveTab.value,
|
||||
() => RESTTabs.currentActiveTab.value.document.request.name
|
||||
)
|
||||
|
||||
const reqName = computed(() => {
|
||||
@@ -145,12 +149,14 @@ const reqName = computed(() => {
|
||||
const requestName = ref(reqName.value)
|
||||
|
||||
watch(
|
||||
() => [activeRESTTab.value, activeGQLTab.value],
|
||||
() => [RESTTabs.currentActiveTab.value, GQLTabs.currentActiveTab.value],
|
||||
() => {
|
||||
if (props.mode === "rest") {
|
||||
requestName.value = activeRESTTab.value?.document.request.name ?? ""
|
||||
requestName.value =
|
||||
RESTTabs.currentActiveTab.value?.document.request.name ?? ""
|
||||
} 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 =
|
||||
props.mode === "rest"
|
||||
? cloneDeep(activeRESTTab.value.document.request)
|
||||
: cloneDeep(activeGQLTab.value.document.request)
|
||||
? cloneDeep(RESTTabs.currentActiveTab.value.document.request)
|
||||
: cloneDeep(GQLTabs.currentActiveTab.value.document.request)
|
||||
|
||||
requestUpdated.name = requestName.value
|
||||
|
||||
@@ -224,7 +230,7 @@ const saveRequestAs = async () => {
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
activeRESTTab.value.document = {
|
||||
RESTTabs.currentActiveTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -251,7 +257,7 @@ const saveRequestAs = async () => {
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
activeRESTTab.value.document = {
|
||||
RESTTabs.currentActiveTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -279,7 +285,7 @@ const saveRequestAs = async () => {
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
activeRESTTab.value.document = {
|
||||
RESTTabs.currentActiveTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -439,7 +445,7 @@ const updateTeamCollectionOrFolder = (
|
||||
(result) => {
|
||||
const { createRequestInCollection } = result
|
||||
|
||||
activeRESTTab.value.document = {
|
||||
RESTTabs.currentActiveTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -460,7 +466,7 @@ const updateTeamCollectionOrFolder = (
|
||||
const requestSaved = () => {
|
||||
toast.success(`${t("request.added")}`)
|
||||
nextTick(() => {
|
||||
activeRESTTab.value.document.isDirty = false
|
||||
RESTTabs.currentActiveTab.value.document.isDirty = false
|
||||
})
|
||||
hideModal()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
|
||||
class="sticky z-10 flex flex-1 justify-between border-b border-dividerLight bg-primary"
|
||||
:style="
|
||||
saveRequest
|
||||
? 'top: calc(var(--upper-primary-sticky-fold) - var(--line-height-body))'
|
||||
@@ -15,12 +15,12 @@
|
||||
class="!rounded-none"
|
||||
:icon="IconPlus"
|
||||
:title="t('team.no_access')"
|
||||
:label="t('action.new')"
|
||||
:label="t('add.new')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
:icon="IconPlus"
|
||||
:label="t('action.new')"
|
||||
:label="t('add.new')"
|
||||
class="!rounded-none"
|
||||
@click="emit('display-modal-add')"
|
||||
/>
|
||||
@@ -39,7 +39,7 @@
|
||||
collectionsType.type === 'team-collections' &&
|
||||
collectionsType.selectedTeam === undefined
|
||||
"
|
||||
:icon="IconArchive"
|
||||
:icon="IconImport"
|
||||
:title="t('modal.import_export')"
|
||||
@click="emit('display-modal-import-export')"
|
||||
/>
|
||||
@@ -261,55 +261,68 @@
|
||||
/>
|
||||
</template>
|
||||
<template #emptyNode="{ node }">
|
||||
<div v-if="node === null">
|
||||
<div @drop="(e) => e.stopPropagation()">
|
||||
<HoppSmartPlaceholder
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-if="hasNoTeamAccess"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
disabled
|
||||
<HoppSmartPlaceholder
|
||||
v-if="node === null"
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
@drop.stop
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
:title="t('team.no_access')"
|
||||
:label="t('action.new')"
|
||||
:disabled="hasNoTeamAccess"
|
||||
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||
@click="
|
||||
hasNoTeamAccess ? null : emit('display-modal-import-export')
|
||||
"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
:icon="IconPlus"
|
||||
:label="t('action.new')"
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
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>
|
||||
<HoppSmartPlaceholder
|
||||
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
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
<div
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="
|
||||
node.data.type === 'collections' &&
|
||||
emit('add-folder', {
|
||||
path: node.id,
|
||||
folder: node.data.data.data,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
v-else-if="node.data.type === 'folders'"
|
||||
@drop="(e) => e.stopPropagation()"
|
||||
>
|
||||
<HoppSmartPlaceholder
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.folder')}`"
|
||||
:text="t('empty.folder')"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.folder')}`"
|
||||
:text="t('empty.folder')"
|
||||
@drop.stop
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartTree>
|
||||
</div>
|
||||
@@ -317,9 +330,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconArchive from "~icons/lucide/archive"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import { computed, PropType, Ref, toRef } from "vue"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
@@ -335,10 +348,12 @@ import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { Picked } from "~/helpers/types/HoppPicked.js"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { useService } from "dioc/vue"
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
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) => {
|
||||
return pipe(
|
||||
|
||||
@@ -36,11 +36,14 @@
|
||||
import { ref, watch } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
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 t = useI18n()
|
||||
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
folderPath?: string
|
||||
@@ -63,7 +66,7 @@ watch(
|
||||
() => props.show,
|
||||
(show) => {
|
||||
if (show) {
|
||||
editingName.value = currentActiveTab.value?.document.request.name
|
||||
editingName.value = tabs.currentActiveTab.value?.document.request.name
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
class="group flex items-stretch"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@@ -11,7 +11,7 @@
|
||||
@contextmenu.prevent="options.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center px-4 cursor-pointer"
|
||||
class="flex cursor-pointer items-center justify-center px-4"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<component
|
||||
@@ -21,7 +21,7 @@
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
class="flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
@@ -136,10 +136,10 @@
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-0.5 hover:bg-dividerDark hover:scale-x-125"
|
||||
class="ml-[1.375rem] flex w-0.5 transform cursor-nsResize bg-dividerLight transition hover:scale-x-125 hover:bg-dividerDark"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
<div class="flex flex-1 flex-col truncate">
|
||||
<CollectionsGraphqlFolder
|
||||
v-for="(folder, index) in collection.folders"
|
||||
:key="`folder-${String(index)}`"
|
||||
@@ -220,7 +220,8 @@ import {
|
||||
moveGraphqlRequest,
|
||||
} from "~/newstore/collections"
|
||||
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({
|
||||
picked: { type: Object, default: null },
|
||||
@@ -235,6 +236,8 @@ const colorMode = useColorMode()
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
// TODO: improve types plz
|
||||
const emit = defineEmits<{
|
||||
(e: "select", i: Picked | null): void
|
||||
@@ -295,7 +298,7 @@ const removeCollection = () => {
|
||||
emit("select", null)
|
||||
}
|
||||
|
||||
const possibleTabs = getTabsRefTo((tab) => {
|
||||
const possibleTabs = tabs.getTabsRefTo((tab) => {
|
||||
const ctx = tab.document.saveContext
|
||||
|
||||
if (!ctx) return false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
class="group flex items-stretch"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@@ -11,7 +11,7 @@
|
||||
@contextmenu.prevent="options.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center px-4 cursor-pointer"
|
||||
class="flex cursor-pointer items-center justify-center px-4"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<component
|
||||
@@ -21,7 +21,7 @@
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
class="flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
@@ -128,10 +128,10 @@
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-0.5 hover:bg-dividerDark hover:scale-x-125"
|
||||
class="ml-[1.375rem] flex w-0.5 transform cursor-nsResize bg-dividerLight transition hover:scale-x-125 hover:bg-dividerDark"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
<div class="flex flex-1 flex-col truncate">
|
||||
<!-- Referring to this component only (this is recursive) -->
|
||||
<Folder
|
||||
v-for="(subFolder, subFolderIndex) in folder.folders"
|
||||
@@ -203,12 +203,15 @@ import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { removeGraphqlFolder, moveGraphqlRequest } from "~/newstore/collections"
|
||||
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 t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
const props = defineProps({
|
||||
picked: { type: Object, default: null },
|
||||
// Whether the request is in a selectable mode (activates 'select' event)
|
||||
@@ -277,7 +280,7 @@ const removeFolder = () => {
|
||||
emit("select", { picked: null })
|
||||
}
|
||||
|
||||
const possibleTabs = getTabsRefTo((tab) => {
|
||||
const possibleTabs = tabs.getTabsRefTo((tab) => {
|
||||
const ctx = tab.document.saveContext
|
||||
|
||||
if (!ctx) return false
|
||||
|
||||
@@ -260,6 +260,13 @@ const importFromJSON = () => {
|
||||
|
||||
const exportJSON = () => {
|
||||
const dataToWrite = collectionJson.value
|
||||
|
||||
const parsedCollections = JSON.parse(dataToWrite)
|
||||
|
||||
if (!parsedCollections.length) {
|
||||
return toast.error(t("error.no_collections_to_export"))
|
||||
}
|
||||
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
class="group flex items-stretch"
|
||||
draggable="true"
|
||||
@dragstart="dragStart"
|
||||
@dragover.stop
|
||||
@@ -10,7 +10,7 @@
|
||||
@contextmenu.prevent="options.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center w-16 px-2 truncate cursor-pointer"
|
||||
class="flex w-16 cursor-pointer items-center justify-center truncate px-2"
|
||||
@click="selectRequest()"
|
||||
>
|
||||
<component
|
||||
@@ -20,7 +20,7 @@
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="flex items-center flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
class="flex min-w-0 flex-1 cursor-pointer items-center py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="selectRequest()"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
@@ -29,15 +29,15 @@
|
||||
<span
|
||||
v-if="isActive"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
class="relative h-1.5 w-1.5 flex flex-shrink-0 mx-3"
|
||||
class="relative mx-3 flex h-1.5 w-1.5 flex-shrink-0"
|
||||
:title="`${t('collection.request_in_use')}`"
|
||||
>
|
||||
<span
|
||||
class="absolute inline-flex flex-shrink-0 w-full h-full bg-green-500 rounded-full opacity-75 animate-ping"
|
||||
class="absolute inline-flex h-full w-full flex-shrink-0 animate-ping rounded-full bg-green-500 opacity-75"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="relative inline-flex flex-shrink-0 rounded-full h-1.5 w-1.5 bg-green-500"
|
||||
class="relative inline-flex h-1.5 w-1.5 flex-shrink-0 rounded-full bg-green-500"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
@@ -137,12 +137,8 @@ import { useToast } from "@composables/toast"
|
||||
import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { removeGraphqlRequest } from "~/newstore/collections"
|
||||
import {
|
||||
createNewTab,
|
||||
getTabRefWithSaveContext,
|
||||
currentTabID,
|
||||
currentActiveTab,
|
||||
} from "~/helpers/graphql/tab"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
|
||||
// Template refs
|
||||
const tippyActions = ref<any | null>(null)
|
||||
@@ -154,6 +150,8 @@ const deleteAction = ref<any | null>(null)
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
const props = defineProps({
|
||||
// Whether the object is selected (show the tick mark)
|
||||
picked: { type: Object, default: null },
|
||||
@@ -165,7 +163,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const isActive = computed(() => {
|
||||
const saveCtx = currentActiveTab.value?.document.saveContext
|
||||
const saveCtx = tabs.currentActiveTab.value?.document.saveContext
|
||||
|
||||
if (!saveCtx) return false
|
||||
|
||||
@@ -201,7 +199,7 @@ const selectRequest = () => {
|
||||
if (props.saveRequest) {
|
||||
pick()
|
||||
} else {
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: props.folderPath,
|
||||
requestIndex: props.requestIndex,
|
||||
@@ -209,11 +207,11 @@ const selectRequest = () => {
|
||||
|
||||
// Switch to that request if that request is open
|
||||
if (possibleTab) {
|
||||
currentTabID.value = possibleTab.value.id
|
||||
tabs.setActiveTab(possibleTab.value.id)
|
||||
return
|
||||
}
|
||||
|
||||
createNewTab({
|
||||
tabs.createNewTab({
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: props.folderPath,
|
||||
@@ -253,7 +251,7 @@ const removeRequest = () => {
|
||||
}
|
||||
|
||||
// Detach the request from any of the tabs
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: props.folderPath,
|
||||
requestIndex: props.requestIndex,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div :class="{ 'rounded border border-divider': saveRequest }">
|
||||
<div
|
||||
class="sticky z-10 flex flex-col flex-shrink-0 overflow-x-auto rounded-t bg-primary"
|
||||
class="sticky z-10 flex flex-shrink-0 flex-col overflow-x-auto rounded-t bg-primary"
|
||||
:style="
|
||||
saveRequest ? 'top: calc(-1 * var(--line-height-body))' : 'top: 0'
|
||||
"
|
||||
@@ -11,10 +11,10 @@
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
:placeholder="t('action.search')"
|
||||
class="py-2 pl-4 pr-2 bg-transparent !border-0"
|
||||
class="!border-0 bg-transparent py-2 pl-4 pr-2"
|
||||
/>
|
||||
<div
|
||||
class="flex justify-between flex-1 flex-shrink-0 border-y bg-primary border-dividerLight"
|
||||
class="flex flex-1 flex-shrink-0 justify-between border-y border-dividerLight bg-primary"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconPlus"
|
||||
@@ -34,7 +34,7 @@
|
||||
v-if="!saveRequest"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('modal.import_export')"
|
||||
:icon="IconArchive"
|
||||
:icon="IconImport"
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
</div>
|
||||
@@ -66,19 +66,34 @@
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<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
|
||||
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
|
||||
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<CollectionsGraphqlAdd
|
||||
@@ -140,12 +155,13 @@ import {
|
||||
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
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 { useReadonlyStream } from "@composables/stream"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
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({
|
||||
props: {
|
||||
@@ -158,14 +174,16 @@ export default defineComponent({
|
||||
const collections = useReadonlyStream(graphqlCollections$, [], "deep")
|
||||
const colorMode = useColorMode()
|
||||
const t = useI18n()
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
return {
|
||||
collections,
|
||||
colorMode,
|
||||
t,
|
||||
tabs,
|
||||
IconPlus,
|
||||
IconHelpCircle,
|
||||
IconArchive,
|
||||
IconImport,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -267,13 +285,13 @@ export default defineComponent({
|
||||
},
|
||||
onAddRequest({ name, path, index }) {
|
||||
const newRequest = {
|
||||
...currentActiveTab.value.document.request,
|
||||
...this.tabs.currentActiveTab.value.document.request,
|
||||
name,
|
||||
}
|
||||
|
||||
saveGraphqlRequestAs(path, newRequest)
|
||||
|
||||
createNewTab({
|
||||
this.tabs.createNewTab({
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: path,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
@dragend="draggingToRoot = false"
|
||||
>
|
||||
<div
|
||||
class="sticky z-10 flex flex-col flex-shrink-0 overflow-x-auto border-b bg-primary border-dividerLight"
|
||||
class="sticky z-10 flex flex-shrink-0 flex-col overflow-x-auto border-b border-dividerLight bg-primary"
|
||||
:class="{ 'rounded-t': saveRequest }"
|
||||
:style="
|
||||
saveRequest ? 'top: calc(-1 * var(--line-height-body))' : 'top: 0'
|
||||
@@ -86,12 +86,12 @@
|
||||
@display-modal-import-export="displayModalImportExport(true)"
|
||||
/>
|
||||
<div
|
||||
class="hidden bg-primaryDark flex-col flex-1 items-center py-15 justify-center px-4 text-secondaryLight"
|
||||
class="py-15 hidden flex-1 flex-col items-center justify-center bg-primaryDark px-4 text-secondaryLight"
|
||||
:class="{
|
||||
'!flex': draggingToRoot && currentReorderingStatus.type !== 'request',
|
||||
}"
|
||||
>
|
||||
<icon-lucide-list-end class="svg-icons !w-8 !h-8" />
|
||||
<icon-lucide-list-end class="svg-icons !h-8 !w-8" />
|
||||
</div>
|
||||
<CollectionsAdd
|
||||
:show="showModalAdd"
|
||||
@@ -219,12 +219,6 @@ import {
|
||||
import * as E from "fp-ts/Either"
|
||||
import { platform } from "~/platform"
|
||||
import { createCollectionGists } from "~/helpers/gist"
|
||||
import {
|
||||
createNewTab,
|
||||
currentActiveTab,
|
||||
currentTabID,
|
||||
getTabRefWithSaveContext,
|
||||
} from "~/helpers/rest/tab"
|
||||
import {
|
||||
getRequestsByPath,
|
||||
resolveSaveContextOnRequestReorder,
|
||||
@@ -239,9 +233,11 @@ import { currentReorderingStatus$ } from "~/newstore/reordering"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { WorkspaceService } from "~/services/workspace.service"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
const props = defineProps({
|
||||
saveRequest: {
|
||||
@@ -377,22 +373,26 @@ const updateSelectedTeam = (team: SelectedTeam) => {
|
||||
const workspace = workspaceService.currentWorkspace
|
||||
|
||||
// Used to switch collection type and team when user switch workspace in the global workspace switcher
|
||||
// Check if there is a teamID in the workspace, if yes, switch to team collection and select the team
|
||||
// If there is no teamID, switch to my environment
|
||||
// Check if there is a teamID in the workspace, if yes, switch to team collections and select the team
|
||||
// If there is no teamID, switch to my collections
|
||||
watch(
|
||||
() => {
|
||||
const space = workspace.value
|
||||
|
||||
if (space.type === "personal") return undefined
|
||||
else return space.teamID
|
||||
return space.type === "personal" ? undefined : space.teamID
|
||||
},
|
||||
(teamID) => {
|
||||
if (!teamID) {
|
||||
switchToMyCollections()
|
||||
} else if (teamID) {
|
||||
if (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 newRequest = {
|
||||
...cloneDeep(currentActiveTab.value.document.request),
|
||||
...cloneDeep(tabs.currentActiveTab.value.document.request),
|
||||
name: requestName,
|
||||
}
|
||||
|
||||
@@ -659,7 +659,7 @@ const onAddRequest = (requestName: string) => {
|
||||
if (!path) return
|
||||
const insertionIndex = saveRESTRequestAs(path, newRequest)
|
||||
|
||||
createNewTab({
|
||||
tabs.createNewTab({
|
||||
request: newRequest,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -708,7 +708,7 @@ const onAddRequest = (requestName: string) => {
|
||||
(result) => {
|
||||
const { createRequestInCollection } = result
|
||||
|
||||
createNewTab({
|
||||
tabs.createNewTab({
|
||||
request: newRequest,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -931,7 +931,7 @@ const updateEditingRequest = (newName: string) => {
|
||||
|
||||
if (folderPath === null || requestIndex === null) return
|
||||
|
||||
const possibleActiveTab = getTabRefWithSaveContext({
|
||||
const possibleActiveTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
requestIndex,
|
||||
folderPath,
|
||||
@@ -975,7 +975,7 @@ const updateEditingRequest = (newName: string) => {
|
||||
)
|
||||
)()
|
||||
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID,
|
||||
})
|
||||
@@ -1211,7 +1211,7 @@ const onRemoveRequest = () => {
|
||||
emit("select", null)
|
||||
}
|
||||
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath,
|
||||
requestIndex,
|
||||
@@ -1271,7 +1271,7 @@ const onRemoveRequest = () => {
|
||||
)()
|
||||
|
||||
// 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",
|
||||
requestID,
|
||||
})
|
||||
@@ -1304,14 +1304,14 @@ const selectRequest = (selectedRequest: {
|
||||
let possibleTab = null
|
||||
|
||||
if (collectionsType.value.type === "team-collections") {
|
||||
possibleTab = getTabRefWithSaveContext({
|
||||
possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
})
|
||||
if (possibleTab) {
|
||||
currentTabID.value = possibleTab.value.id
|
||||
tabs.setActiveTab(possibleTab.value.id)
|
||||
} else {
|
||||
createNewTab({
|
||||
tabs.createNewTab({
|
||||
request: cloneDeep(request),
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -1321,16 +1321,16 @@ const selectRequest = (selectedRequest: {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
possibleTab = getTabRefWithSaveContext({
|
||||
possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
requestIndex: parseInt(requestIndex),
|
||||
folderPath: folderPath!,
|
||||
})
|
||||
if (possibleTab) {
|
||||
currentTabID.value = possibleTab.value.id
|
||||
tabs.setActiveTab(possibleTab.value.id)
|
||||
} else {
|
||||
// If not, open the request in a new tab
|
||||
createNewTab({
|
||||
tabs.createNewTab({
|
||||
request: cloneDeep(request),
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
@@ -1373,7 +1373,7 @@ const dropRequest = (payload: {
|
||||
destinationCollectionIndex
|
||||
)
|
||||
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath,
|
||||
requestIndex: pathToLastIndex(requestIndex),
|
||||
@@ -1422,7 +1422,7 @@ const dropRequest = (payload: {
|
||||
1
|
||||
)
|
||||
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
})
|
||||
@@ -1938,6 +1938,12 @@ const exportJSONCollection = async () => {
|
||||
|
||||
await getJSONCollection()
|
||||
|
||||
const parsedCollections = JSON.parse(collectionJSON.value)
|
||||
|
||||
if (!parsedCollections.length) {
|
||||
return toast.error(t("error.no_collections_to_export"))
|
||||
}
|
||||
|
||||
initializeDownloadCollection(collectionJSON.value, null)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex space-y-4 flex-1 flex-col">
|
||||
<div class="flex items-center space-x-8 ml-2">
|
||||
<label for="name" class="font-semibold min-w-10">{{
|
||||
<div class="flex flex-1 flex-col space-y-4">
|
||||
<div class="ml-2 flex items-center space-x-8">
|
||||
<label for="name" class="min-w-10 font-semibold">{{
|
||||
t("environment.name")
|
||||
}}</label>
|
||||
<input
|
||||
@@ -17,8 +17,8 @@
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center space-x-8 ml-2">
|
||||
<label for="value" class="font-semibold min-w-10">{{
|
||||
<div class="ml-2 flex items-center space-x-8">
|
||||
<label for="value" class="min-w-10 font-semibold">{{
|
||||
t("environment.value")
|
||||
}}</label>
|
||||
<input
|
||||
@@ -28,17 +28,17 @@
|
||||
:placeholder="t('environment.value')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center space-x-8 ml-2">
|
||||
<label for="scope" class="font-semibold min-w-10">
|
||||
<div class="ml-2 flex items-center space-x-8">
|
||||
<label for="scope" class="min-w-10 font-semibold">
|
||||
{{ t("environment.scope") }}
|
||||
</label>
|
||||
<div
|
||||
class="relative flex flex-1 flex-col border border-divider rounded focus-visible:border-dividerDark"
|
||||
class="relative flex flex-1 flex-col rounded border border-divider focus-visible:border-dividerDark"
|
||||
>
|
||||
<EnvironmentsSelector v-model="scope" :is-scope-selector="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="replaceWithVariable" class="flex space-x-2 mt-3">
|
||||
<div v-if="replaceWithVariable" class="mt-3 flex space-x-2">
|
||||
<div class="min-w-18" />
|
||||
<HoppSmartCheckbox
|
||||
:on="replaceWithVariable"
|
||||
@@ -83,11 +83,14 @@ import {
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
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 toast = useToast()
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
position: { top: number; left: number }
|
||||
@@ -189,8 +192,8 @@ const addEnvironment = async () => {
|
||||
//replace the current tab endpoint with the variable name with << and >>
|
||||
const variableName = `<<${editingName.value}>>`
|
||||
//replace the currenttab endpoint containing the value in the text with variablename
|
||||
currentActiveTab.value.document.request.endpoint =
|
||||
currentActiveTab.value.document.request.endpoint.replace(
|
||||
tabs.currentActiveTab.value.document.request.endpoint =
|
||||
tabs.currentActiveTab.value.document.request.endpoint.replace(
|
||||
editingValue.value,
|
||||
variableName
|
||||
)
|
||||
|
||||
@@ -377,6 +377,13 @@ const importFromPostman = ({
|
||||
|
||||
const exportJSON = () => {
|
||||
const dataToWrite = environmentJson.value
|
||||
|
||||
const parsedCollections = JSON.parse(dataToWrite)
|
||||
|
||||
if (!parsedCollections.length) {
|
||||
return toast.error(t("error.no_environments_to_export"))
|
||||
}
|
||||
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
: `${t('environment.select')}`
|
||||
: ''
|
||||
"
|
||||
class="flex-1 !justify-start pr-8 rounded-none"
|
||||
class="flex-1 !justify-start rounded-none pr-8"
|
||||
/>
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
@@ -101,7 +101,7 @@
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-2"
|
||||
class="mb-2 inline-flex h-16 w-16 flex-col object-contain object-center"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
/>
|
||||
<span class="pb-2 text-center">
|
||||
@@ -148,7 +148,7 @@
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-2"
|
||||
class="mb-2 inline-flex h-16 w-16 flex-col object-contain object-center"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
/>
|
||||
<span class="pb-2 text-center">
|
||||
@@ -160,7 +160,7 @@
|
||||
v-if="!teamListLoading && teamAdapterError"
|
||||
class="flex flex-col items-center py-4"
|
||||
>
|
||||
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
||||
<icon-lucide-help-circle class="svg-icons mb-4" />
|
||||
{{ getErrorMessage(teamAdapterError) }}
|
||||
</div>
|
||||
</HoppSmartTab>
|
||||
@@ -190,7 +190,7 @@
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<div
|
||||
class="sticky top-0 font-semibold truncate flex items-center justify-between text-secondaryDark bg-primary border border-divider rounded pl-4"
|
||||
class="sticky top-0 flex items-center justify-between truncate rounded border border-divider bg-primary pl-4 font-semibold text-secondaryDark"
|
||||
>
|
||||
{{ t("environment.global_variables") }}
|
||||
<HoppButtonSecondary
|
||||
@@ -205,12 +205,12 @@
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="my-2 flex flex-col flex-1 space-y-2 pl-4 pr-2">
|
||||
<div class="my-2 flex flex-1 flex-col space-y-2 pl-4 pr-2">
|
||||
<div class="flex flex-1 space-x-4">
|
||||
<span class="w-1/4 min-w-32 truncate text-tiny font-semibold">
|
||||
<span class="min-w-32 w-1/4 truncate text-tiny font-semibold">
|
||||
{{ t("environment.name") }}
|
||||
</span>
|
||||
<span class="w-full min-w-32 truncate text-tiny font-semibold">
|
||||
<span class="min-w-32 w-full truncate text-tiny font-semibold">
|
||||
{{ t("environment.value") }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -219,10 +219,10 @@
|
||||
:key="index"
|
||||
class="flex flex-1 space-x-4"
|
||||
>
|
||||
<span class="text-secondaryLight w-1/4 min-w-32 truncate">
|
||||
<span class="min-w-32 w-1/4 truncate text-secondaryLight">
|
||||
{{ variable.key }}
|
||||
</span>
|
||||
<span class="text-secondaryLight w-full min-w-32 truncate">
|
||||
<span class="min-w-32 w-full truncate text-secondaryLight">
|
||||
{{ variable.value }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -231,7 +231,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sticky top-0 mt-2 font-semibold truncate flex items-center justify-between text-secondaryDark bg-primary border border-divider rounded pl-4"
|
||||
class="sticky top-0 mt-2 flex items-center justify-between truncate rounded border border-divider bg-primary pl-4 font-semibold text-secondaryDark"
|
||||
:class="{
|
||||
'bg-primaryLight': !selectedEnv.variables,
|
||||
}"
|
||||
@@ -252,16 +252,16 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedEnv.type === 'NO_ENV_SELECTED'"
|
||||
class="text-secondaryLight my-2 flex flex-col flex-1 pl-4"
|
||||
class="my-2 flex flex-1 flex-col pl-4 text-secondaryLight"
|
||||
>
|
||||
{{ t("environment.no_active_environment") }}
|
||||
</div>
|
||||
<div v-else class="my-2 flex flex-col flex-1 space-y-2 pl-4 pr-2">
|
||||
<div v-else class="my-2 flex flex-1 flex-col space-y-2 pl-4 pr-2">
|
||||
<div class="flex flex-1 space-x-4">
|
||||
<span class="w-1/4 min-w-32 truncate text-tiny font-semibold">
|
||||
<span class="min-w-32 w-1/4 truncate text-tiny font-semibold">
|
||||
{{ t("environment.name") }}
|
||||
</span>
|
||||
<span class="w-full min-w-32 truncate text-tiny font-semibold">
|
||||
<span class="min-w-32 w-full truncate text-tiny font-semibold">
|
||||
{{ t("environment.value") }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -270,10 +270,10 @@
|
||||
:key="index"
|
||||
class="flex flex-1 space-x-4"
|
||||
>
|
||||
<span class="text-secondaryLight w-1/4 min-w-32 truncate">
|
||||
<span class="min-w-32 w-1/4 truncate text-secondaryLight">
|
||||
{{ variable.key }}
|
||||
</span>
|
||||
<span class="text-secondaryLight w-full min-w-32 truncate">
|
||||
<span class="min-w-32 w-full truncate text-secondaryLight">
|
||||
{{ variable.value }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -478,7 +478,8 @@ watch(
|
||||
teamEnvListAdapter.changeTeamID(newVal.teamID)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const selectedEnv = computed(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
|
||||
class="sticky top-0 z-10 flex flex-shrink-0 flex-col overflow-x-auto bg-primary"
|
||||
>
|
||||
<WorkspaceCurrent :section="t('tab.environments')" />
|
||||
<EnvironmentsMyEnvironment
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
@submit="saveEnvironment"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-between flex-1">
|
||||
<div class="flex flex-1 items-center justify-between">
|
||||
<label for="variableList" class="p-4">
|
||||
{{ t("environment.variable_list") }}
|
||||
</label>
|
||||
@@ -37,11 +37,11 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="evnExpandError"
|
||||
class="w-full px-4 py-2 mb-2 overflow-auto font-mono text-red-400 whitespace-normal rounded bg-primaryLight"
|
||||
class="mb-2 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400"
|
||||
>
|
||||
{{ t("environment.nested_overflow") }}
|
||||
</div>
|
||||
<div class="border rounded divide-y divide-dividerLight border-divider">
|
||||
<div class="divide-y divide-dividerLight rounded border border-divider">
|
||||
<div
|
||||
v-for="({ id, env }, index) in vars"
|
||||
:key="`variable-${id}-${index}`"
|
||||
@@ -50,7 +50,7 @@
|
||||
<input
|
||||
v-model="env.key"
|
||||
v-focus
|
||||
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||
class="flex flex-1 bg-transparent px-4 py-2"
|
||||
:placeholder="`${t('count.variable', { count: index + 1 })}`"
|
||||
:name="'param' + index"
|
||||
/>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
class="group flex items-stretch"
|
||||
@contextmenu.prevent="options!.tippy.show()"
|
||||
>
|
||||
<span
|
||||
v-if="environmentIndex === 'Global'"
|
||||
class="flex items-center justify-center px-4 cursor-pointer"
|
||||
class="flex cursor-pointer items-center justify-center px-4"
|
||||
@click="emit('edit-environment')"
|
||||
>
|
||||
<icon-lucide-globe class="svg-icons" />
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="flex items-center justify-center px-4 cursor-pointer"
|
||||
class="flex cursor-pointer items-center justify-center px-4"
|
||||
@click="emit('edit-environment')"
|
||||
>
|
||||
<icon-lucide-layers class="svg-icons" />
|
||||
</span>
|
||||
<span
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
class="flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="emit('edit-environment')"
|
||||
>
|
||||
<span class="truncate">
|
||||
@@ -46,6 +46,7 @@
|
||||
role="menu"
|
||||
@keyup.e="edit!.$el.click()"
|
||||
@keyup.d="duplicate!.$el.click()"
|
||||
@keyup.j="exportAsJsonEl!.$el.click()"
|
||||
@keyup.delete="
|
||||
!(environmentIndex === 'Global')
|
||||
? deleteAction!.$el.click()
|
||||
@@ -77,6 +78,18 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="exportAsJsonEl"
|
||||
:icon="IconEdit"
|
||||
:label="`${t('export.as_json')}`"
|
||||
:shortcut="['J']"
|
||||
@click="
|
||||
() => {
|
||||
exportEnvironmentAsJSON()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="environmentIndex !== 'Global'"
|
||||
ref="deleteAction"
|
||||
@@ -121,6 +134,7 @@ import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -136,10 +150,18 @@ const emit = defineEmits<{
|
||||
|
||||
const confirmRemove = ref(false)
|
||||
|
||||
const exportEnvironmentAsJSON = () => {
|
||||
const { environment, environmentIndex } = props
|
||||
exportAsJSON(environment, environmentIndex)
|
||||
? toast.success(t("state.download_started"))
|
||||
: toast.error(t("state.download_failed"))
|
||||
}
|
||||
|
||||
const tippyActions = ref<TippyComponent | null>(null)
|
||||
const options = ref<TippyComponent | null>(null)
|
||||
const edit = ref<typeof HoppSmartItem>()
|
||||
const duplicate = ref<typeof HoppSmartItem>()
|
||||
const exportAsJsonEl = ref<typeof HoppSmartItem>()
|
||||
const deleteAction = ref<typeof HoppSmartItem>()
|
||||
|
||||
const removeEnvironment = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="sticky z-10 flex justify-between flex-1 flex-shrink-0 overflow-x-auto border-b top-upperPrimaryStickyFold border-dividerLight bg-primary"
|
||||
class="sticky top-upperPrimaryStickyFold z-10 flex flex-1 flex-shrink-0 justify-between overflow-x-auto border-b border-dividerLight bg-primary"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconPlus"
|
||||
@@ -19,7 +19,7 @@
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconArchive"
|
||||
:icon="IconImport"
|
||||
:title="t('modal.import_export')"
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
@@ -33,17 +33,32 @@
|
||||
@edit-environment="editEnvironment(index)"
|
||||
/>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="environments.length === 0"
|
||||
v-if="!environments.length"
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("environment.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<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>
|
||||
<EnvironmentsMyDetails
|
||||
:show="showModalDetails"
|
||||
@@ -66,8 +81,8 @@ import { environments$ } from "~/newstore/environments"
|
||||
import { useColorMode } from "~/composables/theming"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import IconArchive from "~icons/lucide/archive"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
@submit="saveEnvironment"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-between flex-1">
|
||||
<div class="flex flex-1 items-center justify-between">
|
||||
<label for="variableList" class="p-4">
|
||||
{{ t("environment.variable_list") }}
|
||||
</label>
|
||||
@@ -37,11 +37,11 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="evnExpandError"
|
||||
class="w-full px-4 py-2 mb-2 overflow-auto font-mono text-red-400 whitespace-normal rounded bg-primaryLight"
|
||||
class="mb-2 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400"
|
||||
>
|
||||
{{ t("environment.nested_overflow") }}
|
||||
</div>
|
||||
<div class="border rounded divide-y divide-dividerLight border-divider">
|
||||
<div class="divide-y divide-dividerLight rounded border border-divider">
|
||||
<div
|
||||
v-for="({ id, env }, index) in vars"
|
||||
:key="`variable-${id}-${index}`"
|
||||
@@ -50,7 +50,7 @@
|
||||
<input
|
||||
v-model="env.key"
|
||||
v-focus
|
||||
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||
class="flex flex-1 bg-transparent px-4 py-2"
|
||||
:class="isViewer && 'opacity-25'"
|
||||
:placeholder="`${t('count.variable', { count: index + 1 })}`"
|
||||
:name="'param' + index"
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
class="group flex items-stretch"
|
||||
@contextmenu.prevent="options!.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center px-4 cursor-pointer"
|
||||
class="flex cursor-pointer items-center justify-center px-4"
|
||||
@click="emit('edit-environment')"
|
||||
>
|
||||
<icon-lucide-layers class="svg-icons" />
|
||||
</span>
|
||||
<span
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
class="flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="emit('edit-environment')"
|
||||
>
|
||||
<span class="truncate">
|
||||
@@ -39,6 +39,7 @@
|
||||
role="menu"
|
||||
@keyup.e="edit!.$el.click()"
|
||||
@keyup.d="duplicate!.$el.click()"
|
||||
@keyup.j="exportAsJsonEl!.$el.click()"
|
||||
@keyup.delete="deleteAction!.$el.click()"
|
||||
@keyup.escape="options!.tippy().hide()"
|
||||
>
|
||||
@@ -54,6 +55,7 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
||||
<HoppSmartItem
|
||||
ref="duplicate"
|
||||
:icon="IconCopy"
|
||||
@@ -66,6 +68,18 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="exportAsJsonEl"
|
||||
:icon="IconEdit"
|
||||
:label="`${t('export.as_json')}`"
|
||||
:shortcut="['J']"
|
||||
@click="
|
||||
() => {
|
||||
exportEnvironmentAsJSON()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
@@ -109,6 +123,7 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -124,11 +139,17 @@ const emit = defineEmits<{
|
||||
|
||||
const confirmRemove = ref(false)
|
||||
|
||||
const exportEnvironmentAsJSON = () =>
|
||||
exportAsJSON(props.environment)
|
||||
? toast.success(t("state.download_started"))
|
||||
: toast.error(t("state.download_failed"))
|
||||
|
||||
const tippyActions = ref<TippyComponent | null>(null)
|
||||
const options = ref<TippyComponent | null>(null)
|
||||
const edit = ref<typeof HoppSmartItem>()
|
||||
const duplicate = ref<typeof HoppSmartItem>()
|
||||
const deleteAction = ref<typeof HoppSmartItem>()
|
||||
const exportAsJsonEl = ref<typeof HoppSmartItem>()
|
||||
|
||||
const removeEnvironment = () => {
|
||||
pipe(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="sticky z-10 flex justify-between flex-1 flex-shrink-0 overflow-x-auto border-b top-upperPrimaryStickyFold border-dividerLight bg-primary"
|
||||
class="sticky top-upperPrimaryStickyFold z-10 flex flex-1 flex-shrink-0 justify-between overflow-x-auto border-b border-dividerLight bg-primary"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-if="team === undefined || team.myRole === 'VIEWER'"
|
||||
@@ -31,40 +31,49 @@
|
||||
v-if="team !== undefined && team.myRole === 'VIEWER'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
disabled
|
||||
:icon="IconArchive"
|
||||
:icon="IconImport"
|
||||
:title="t('modal.import_export')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconArchive"
|
||||
:icon="IconImport"
|
||||
:title="t('modal.import_export')"
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="!loading && teamEnvironments.length === 0 && !adapterError"
|
||||
v-if="!loading && !teamEnvironments.length && !adapterError"
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-if="team === undefined || team.myRole === 'VIEWER'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
disabled
|
||||
filled
|
||||
:icon="IconPlus"
|
||||
:title="t('team.no_access')"
|
||||
:label="t('action.new')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("environment.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||
:disabled="isTeamViewer"
|
||||
@click="isTeamViewer ? null : displayModalImportExport(true)"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
outline
|
||||
:icon="IconPlus"
|
||||
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||
:disabled="isTeamViewer"
|
||||
@click="isTeamViewer ? null : displayModalAdd(true)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</HoppSmartPlaceholder>
|
||||
<div v-else-if="!loading">
|
||||
<EnvironmentsTeamsEnvironment
|
||||
@@ -85,7 +94,7 @@
|
||||
v-if="!loading && adapterError"
|
||||
class="flex flex-col items-center py-4"
|
||||
>
|
||||
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
||||
<icon-lucide-help-circle class="svg-icons mb-4" />
|
||||
{{ getErrorMessage(adapterError) }}
|
||||
</div>
|
||||
<EnvironmentsTeamsDetails
|
||||
@@ -108,14 +117,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { computed, ref } from "vue"
|
||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { useColorMode } from "~/composables/theming"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconArchive from "~icons/lucide/archive"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
|
||||
@@ -138,6 +147,8 @@ const action = ref<"new" | "edit">("edit")
|
||||
const editingEnvironment = ref<TeamEnvironment | null>(null)
|
||||
const editingVariableName = ref("")
|
||||
|
||||
const isTeamViewer = computed(() => props.team?.myRole === "VIEWER")
|
||||
|
||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||
action.value = "new"
|
||||
showModalDetails.value = shouldDisplay
|
||||
|
||||
@@ -47,9 +47,9 @@
|
||||
/>
|
||||
</form>
|
||||
<div v-if="mode === 'email-sent'" class="flex flex-col px-4">
|
||||
<div class="flex flex-col items-center justify-center max-w-md">
|
||||
<icon-lucide-inbox class="w-6 h-6 text-accent" />
|
||||
<h3 class="my-2 text-lg text-center">
|
||||
<div class="flex max-w-md flex-col items-center justify-center">
|
||||
<icon-lucide-inbox class="h-6 w-6 text-accent" />
|
||||
<h3 class="my-2 text-center text-lg">
|
||||
{{ t("auth.we_sent_magic_link") }}
|
||||
</h3>
|
||||
<p class="text-center">
|
||||
@@ -63,7 +63,7 @@
|
||||
<template #footer>
|
||||
<div
|
||||
v-if="mode === 'sign-in' && tosLink && privacyPolicyLink"
|
||||
class="text-secondaryLight text-tiny"
|
||||
class="text-tiny text-secondaryLight"
|
||||
>
|
||||
By signing in, you are agreeing to our
|
||||
<HoppSmartAnchor
|
||||
@@ -90,7 +90,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="mode === 'email-sent'"
|
||||
class="flex justify-between flex-1 text-secondaryLight"
|
||||
class="flex flex-1 justify-between text-secondaryLight"
|
||||
>
|
||||
<HoppSmartAnchor
|
||||
class="link"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between pl-4 border-y bg-primary border-dividerLight"
|
||||
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between border-y border-dividerLight bg-primary pl-4"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<label class="font-semibold truncate text-secondaryLight">
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("authorization.type") }}
|
||||
</label>
|
||||
<tippy
|
||||
@@ -15,7 +15,7 @@
|
||||
>
|
||||
<span class="select-wrapper">
|
||||
<HoppButtonSecondary
|
||||
class="pr-8 ml-2 rounded-none"
|
||||
class="ml-2 rounded-none pr-8"
|
||||
:label="authName"
|
||||
/>
|
||||
</span>
|
||||
@@ -171,7 +171,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sticky flex-shrink-0 h-full p-4 overflow-auto overflow-x-auto bg-primary top-upperTertiaryStickyFold min-w-46 max-w-1/3 z-9"
|
||||
class="z-9 sticky top-upperTertiaryStickyFold h-full min-w-46 max-w-1/3 flex-shrink-0 overflow-auto overflow-x-auto bg-primary p-4"
|
||||
>
|
||||
<div class="pb-2 text-secondaryLight">
|
||||
{{ t("helpers.authorization") }}
|
||||
|
||||
@@ -21,19 +21,19 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="gqlField.description"
|
||||
class="py-2 text-secondaryLight field-desc"
|
||||
class="field-desc py-2 text-secondaryLight"
|
||||
>
|
||||
{{ gqlField.description }}
|
||||
</div>
|
||||
<div
|
||||
v-if="gqlField.isDeprecated"
|
||||
class="inline-block px-2 py-1 my-1 text-black bg-yellow-200 rounded field-deprecated"
|
||||
class="field-deprecated my-1 inline-block rounded bg-yellow-200 px-2 py-1 text-black"
|
||||
>
|
||||
{{ t("state.deprecated") }}
|
||||
</div>
|
||||
<div v-if="fieldArgs.length > 0">
|
||||
<h5 class="my-2">Arguments:</h5>
|
||||
<div class="pl-4 border-l-2 border-divider">
|
||||
<div class="border-l-2 border-divider pl-4">
|
||||
<div v-for="(field, index) in fieldArgs" :key="`field-${index}`">
|
||||
<span>
|
||||
{{ field.name }}:
|
||||
@@ -44,7 +44,7 @@
|
||||
</span>
|
||||
<div
|
||||
v-if="field.description"
|
||||
class="py-2 text-secondaryLight field-desc"
|
||||
class="field-desc py-2 text-secondaryLight"
|
||||
>
|
||||
{{ field.description }}
|
||||
</div>
|
||||
@@ -84,7 +84,7 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.field-highlighted {
|
||||
@apply border-accent border-b-2;
|
||||
@apply border-b-2 border-accent;
|
||||
}
|
||||
|
||||
.field-title {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between pl-4 border-y bg-primary border-dividerLight"
|
||||
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between border-y border-dividerLight bg-primary pl-4"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ t("tab.headers") }}
|
||||
@@ -42,7 +42,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="bulkMode" ref="bulkEditor" class="flex flex-col flex-1"></div>
|
||||
<div v-if="bulkMode" ref="bulkEditor" class="flex flex-1 flex-col"></div>
|
||||
<div v-else>
|
||||
<draggable
|
||||
v-model="workingHeaders"
|
||||
@@ -56,7 +56,7 @@
|
||||
>
|
||||
<template #item="{ element: header, index }">
|
||||
<div
|
||||
class="flex border-b divide-x divide-dividerLight border-dividerLight draggable-content group"
|
||||
class="draggable-content group flex divide-x divide-dividerLight border-b border-dividerLight"
|
||||
>
|
||||
<span>
|
||||
<HoppButtonSecondary
|
||||
@@ -71,7 +71,7 @@
|
||||
:icon="IconGripVertical"
|
||||
class="cursor-auto text-primary hover:text-primary"
|
||||
:class="{
|
||||
'draggable-handle group-hover:text-secondaryLight !cursor-grab':
|
||||
'draggable-handle !cursor-grab group-hover:text-secondaryLight':
|
||||
index !== workingHeaders?.length - 1,
|
||||
}"
|
||||
tabindex="-1"
|
||||
@@ -91,7 +91,7 @@
|
||||
px-4
|
||||
truncate
|
||||
"
|
||||
class="flex-1 !flex"
|
||||
class="!flex flex-1"
|
||||
@input="
|
||||
updateHeader(index, {
|
||||
id: header.id,
|
||||
@@ -102,7 +102,7 @@
|
||||
"
|
||||
/>
|
||||
<input
|
||||
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||
class="flex flex-1 bg-transparent px-4 py-2"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
:name="`value ${String(index)}`"
|
||||
:value="header.value"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between pl-4 border-y bg-primary border-dividerLight"
|
||||
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between border-y border-dividerLight bg-primary pl-4"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ t("request.query") }}
|
||||
@@ -16,7 +16,7 @@
|
||||
:title="`${t('request.stop')}`"
|
||||
:label="`${t('request.stop')}`"
|
||||
:icon="IconStop"
|
||||
class="rounded-none !text-accent !hover:text-accentDark"
|
||||
class="!hover:text-accentDark rounded-none !text-accent"
|
||||
@click="unsubscribe()"
|
||||
/>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
:label="`${selectedOperation.name?.value ?? t('request.run')}`"
|
||||
:icon="IconPlay"
|
||||
:disabled="!selectedOperation"
|
||||
class="rounded-none !text-accent !hover:text-accentDark"
|
||||
class="!hover:text-accentDark rounded-none !text-accent"
|
||||
@click="runQuery(selectedOperation)"
|
||||
/>
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="queryEditor" class="flex flex-col flex-1"></div>
|
||||
<div ref="queryEditor" class="flex flex-1 flex-col"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="sticky top-0 z-10 flex flex-shrink-0 p-4 overflow-x-auto space-x-2 bg-primary"
|
||||
class="sticky top-0 z-10 flex flex-shrink-0 space-x-2 overflow-x-auto bg-primary p-4"
|
||||
>
|
||||
<div class="inline-flex flex-1 space-x-2">
|
||||
<input
|
||||
@@ -9,7 +9,7 @@
|
||||
type="url"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
class="w-full px-4 py-2 border rounded bg-primaryLight border-divider text-secondaryDark"
|
||||
class="w-full rounded border border-divider bg-primaryLight px-4 py-2 text-secondaryDark"
|
||||
:placeholder="`${t('request.url')}`"
|
||||
:disabled="connected"
|
||||
@keyup.enter="onConnectClick"
|
||||
@@ -64,7 +64,6 @@
|
||||
<script setup lang="ts">
|
||||
import { platform } from "~/platform"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { currentActiveTab } from "~/helpers/graphql/tab"
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { connection } from "~/helpers/graphql/connection"
|
||||
import { connect } from "~/helpers/graphql/connection"
|
||||
@@ -72,8 +71,10 @@ import { disconnect } from "~/helpers/graphql/connection"
|
||||
import { InterceptorService } from "~/services/interceptor.service"
|
||||
import { useService } from "dioc/vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
|
||||
const t = useI18n()
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
const interceptorService = useService(InterceptorService)
|
||||
|
||||
@@ -82,9 +83,9 @@ const connectionSwitchModal = ref(false)
|
||||
const connected = computed(() => connection.state === "CONNECTED")
|
||||
|
||||
const url = computed({
|
||||
get: () => currentActiveTab.value?.document.request.url ?? "",
|
||||
get: () => tabs.currentActiveTab.value?.document.request.url ?? "",
|
||||
set: (value) => {
|
||||
currentActiveTab.value!.document.request.url = value
|
||||
tabs.currentActiveTab.value!.document.request.url = value
|
||||
},
|
||||
})
|
||||
|
||||
@@ -97,7 +98,7 @@ const onConnectClick = () => {
|
||||
}
|
||||
|
||||
const gqlConnect = () => {
|
||||
connect(url.value, currentActiveTab.value?.document.request.headers)
|
||||
connect(url.value, tabs.currentActiveTab.value?.document.request.headers)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_REQUEST_RUN",
|
||||
@@ -114,7 +115,7 @@ const switchConnection = () => {
|
||||
const lastTwoUrls = ref<string[]>([])
|
||||
|
||||
watch(
|
||||
currentActiveTab,
|
||||
tabs.currentActiveTab,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
lastTwoUrls.value.push(newVal.document.request.url)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 h-full">
|
||||
<div class="flex h-full flex-1 flex-col">
|
||||
<HoppSmartTabs
|
||||
v-model="selectedOptionTab"
|
||||
styles="sticky top-0 bg-primary z-10 border-b-0"
|
||||
@@ -58,8 +58,7 @@ import { computed, ref, watch } from "vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { platform } from "~/platform"
|
||||
import { currentActiveTab } from "~/helpers/graphql/tab"
|
||||
import { computedWithControl } from "@vueuse/core"
|
||||
import { computedWithControl, useVModel } from "@vueuse/core"
|
||||
import {
|
||||
GQLResponseEvent,
|
||||
runGQLOperation,
|
||||
@@ -68,26 +67,39 @@ import {
|
||||
import { useService } from "dioc/vue"
|
||||
import { InterceptorService } from "~/services/interceptor.service"
|
||||
import { editGraphqlRequest } from "~/newstore/collections"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
|
||||
const VALID_GQL_OPERATIONS = [
|
||||
"query",
|
||||
"headers",
|
||||
"variables",
|
||||
"authorization",
|
||||
] as const
|
||||
|
||||
export type GQLOptionTabs = (typeof VALID_GQL_OPERATIONS)[number]
|
||||
|
||||
export type GQLOptionTabs = "query" | "headers" | "variables" | "authorization"
|
||||
const selectedOptionTab = ref<GQLOptionTabs>("query")
|
||||
const interceptorService = useService(InterceptorService)
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
// v-model integration with props and emit
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: HoppGQLRequest
|
||||
response?: GQLResponseEvent[] | null
|
||||
optionTab?: GQLOptionTabs
|
||||
tabId: string
|
||||
}>(),
|
||||
{
|
||||
response: null,
|
||||
optionTab: "query",
|
||||
}
|
||||
)
|
||||
const emit = defineEmits(["update:modelValue", "update:response"])
|
||||
const selectedOptionTab = useVModel(props, "optionTab", emit)
|
||||
|
||||
const request = ref(props.modelValue)
|
||||
|
||||
@@ -100,8 +112,8 @@ watch(
|
||||
)
|
||||
|
||||
const url = computedWithControl(
|
||||
() => currentActiveTab.value,
|
||||
() => currentActiveTab.value.document.request.url
|
||||
() => tabs.currentActiveTab.value,
|
||||
() => tabs.currentActiveTab.value.document.request.url
|
||||
)
|
||||
|
||||
const activeGQLHeadersCount = computed(
|
||||
@@ -136,6 +148,9 @@ const runQuery = async (
|
||||
const duration = Date.now() - startTime
|
||||
completePageProgress()
|
||||
toast.success(`${t("state.finished_in", { duration })}`)
|
||||
if (definition?.operation === "subscription" && request.value.auth) {
|
||||
toast.success(t("authorization.graphql_headers"))
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
// response.value = [`${e}`]
|
||||
@@ -182,17 +197,17 @@ const hideRequestModal = () => {
|
||||
}
|
||||
const saveRequest = () => {
|
||||
if (
|
||||
currentActiveTab.value.document.saveContext &&
|
||||
currentActiveTab.value.document.saveContext.originLocation ===
|
||||
tabs.currentActiveTab.value.document.saveContext &&
|
||||
tabs.currentActiveTab.value.document.saveContext.originLocation ===
|
||||
"user-collection"
|
||||
) {
|
||||
editGraphqlRequest(
|
||||
currentActiveTab.value.document.saveContext.folderPath,
|
||||
currentActiveTab.value.document.saveContext.requestIndex,
|
||||
currentActiveTab.value.document.request
|
||||
tabs.currentActiveTab.value.document.saveContext.folderPath,
|
||||
tabs.currentActiveTab.value.document.saveContext.requestIndex,
|
||||
tabs.currentActiveTab.value.document.request
|
||||
)
|
||||
|
||||
currentActiveTab.value.document.isDirty = false
|
||||
tabs.currentActiveTab.value.document.isDirty = false
|
||||
} else {
|
||||
showSaveRequestModal.value = true
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
<template #primary>
|
||||
<GraphqlRequestOptions
|
||||
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"
|
||||
/>
|
||||
</template>
|
||||
<template #secondary>
|
||||
<GraphqlResponse :response="tab.response" />
|
||||
<GraphqlResponse :response="tab.document.response" />
|
||||
</template>
|
||||
</AppPaneLayout>
|
||||
</template>
|
||||
@@ -18,14 +19,15 @@ import { useVModel } from "@vueuse/core"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { watch } from "vue"
|
||||
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
|
||||
|
||||
const props = defineProps<{ modelValue: HoppGQLTab }>()
|
||||
const props = defineProps<{ modelValue: HoppTab<HoppGQLDocument> }>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", val: HoppGQLTab): void
|
||||
(e: "update:modelValue", val: HoppTab<HoppGQLDocument>): void
|
||||
}>()
|
||||
|
||||
const tab = useVModel(props, "modelValue", emit)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 overflow-auto whitespace-nowrap">
|
||||
<div v-if="response?.length === 1" class="flex flex-col flex-1">
|
||||
<div class="flex flex-1 flex-col overflow-auto whitespace-nowrap">
|
||||
<div v-if="response?.length === 1" class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky top-0 z-10 flex items-center justify-between flex-shrink-0 pl-4 overflow-x-auto border-b bg-primary border-dividerLight"
|
||||
class="sticky top-0 z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4"
|
||||
>
|
||||
<label class="font-semibold truncate text-secondaryLight">
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("response.title") }}
|
||||
</label>
|
||||
<div class="flex items-center">
|
||||
@@ -33,11 +33,11 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="schemaEditor" class="flex flex-col flex-1"></div>
|
||||
<div ref="schemaEditor" class="flex flex-1 flex-col"></div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="response && response?.length > 1"
|
||||
class="flex flex-col flex-1"
|
||||
class="flex flex-1 flex-col"
|
||||
>
|
||||
<GraphqlSubscriptionLog :log="response" />
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
class="flex flex-1 p-4 py-2 bg-transparent"
|
||||
class="flex flex-1 bg-transparent p-4 py-2"
|
||||
/>
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
@@ -112,9 +112,9 @@
|
||||
<HoppSmartTab :id="'schema'" :icon="IconBox" :label="`${t('tab.schema')}`">
|
||||
<div
|
||||
v-if="schemaString"
|
||||
class="sticky top-0 z-10 flex items-center justify-between flex-shrink-0 pl-4 overflow-x-auto border-b bg-primary border-dividerLight"
|
||||
class="sticky top-0 z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4"
|
||||
>
|
||||
<label class="font-semibold truncate text-secondaryLight">
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("graphql.schema") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
@@ -149,7 +149,7 @@
|
||||
<div
|
||||
v-if="schemaString"
|
||||
ref="schemaEditor"
|
||||
class="flex flex-col flex-1"
|
||||
class="flex flex-1 flex-col"
|
||||
></div>
|
||||
<HoppSmartPlaceholder
|
||||
v-else
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div ref="container" class="flex flex-col flex-1">
|
||||
<div ref="container" class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky top-0 z-10 flex items-center justify-between flex-none pl-4 border-b bg-primary border-dividerLight"
|
||||
class="sticky top-0 z-10 flex flex-none items-center justify-between border-b border-dividerLight bg-primary pl-4"
|
||||
>
|
||||
<label for="log" class="py-2 font-semibold text-secondaryLight">
|
||||
{{ "Subscription Log" }}
|
||||
@@ -43,7 +43,7 @@
|
||||
class="overflow-y-auto border-b border-dividerLight"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col h-auto h-full border-r divide-y divide-dividerLight border-dividerLight"
|
||||
class="flex h-auto h-full flex-col divide-y divide-dividerLight border-r border-dividerLight"
|
||||
>
|
||||
<RealtimeLogEntry
|
||||
v-for="(entry, index) in log"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||
:title="tab.document.request.name"
|
||||
class="truncate px-2 flex items-center"
|
||||
class="flex items-center truncate px-2"
|
||||
@dblclick="emit('open-rename-modal')"
|
||||
@contextmenu.prevent="options?.tippy?.show()"
|
||||
@click.middle="emit('close-tab')"
|
||||
@@ -14,7 +14,7 @@
|
||||
theme="popover"
|
||||
:on-shown="() => tippyActions!.focus()"
|
||||
>
|
||||
<span class="leading-8 px-2 truncate">
|
||||
<span class="truncate px-2 leading-8">
|
||||
{{ tab.document.request.name }}
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
@@ -92,12 +92,13 @@ import IconXCircle from "~icons/lucide/x-circle"
|
||||
import IconXSquare from "~icons/lucide/x-square"
|
||||
import IconFileEdit from "~icons/lucide/file-edit"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import { HoppGQLTab } from "~/helpers/graphql/tab"
|
||||
import { HoppTab } from "~/services/tab"
|
||||
import { HoppGQLDocument } from "~/helpers/graphql/document"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
defineProps<{
|
||||
tab: HoppGQLTab
|
||||
tab: HoppTab<HoppGQLDocument>
|
||||
isRemovable: boolean
|
||||
}>()
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<span v-else-if="isEnum" class="text-accent">enum </span>
|
||||
{{ gqlType.name }}
|
||||
</div>
|
||||
<div v-if="gqlType.description" class="py-2 text-secondaryLight type-desc">
|
||||
<div v-if="gqlType.description" class="type-desc py-2 text-secondaryLight">
|
||||
{{ gqlType.description }}
|
||||
</div>
|
||||
<div v-if="interfaces.length > 0">
|
||||
@@ -18,7 +18,7 @@
|
||||
<GraphqlTypeLink
|
||||
:gql-type="gqlInterface"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
class="pl-4 border-l-2 border-divider"
|
||||
class="border-l-2 border-divider pl-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,7 +29,7 @@
|
||||
:key="`child-${index}`"
|
||||
:gql-type="child"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
class="pl-4 border-l-2 border-divider"
|
||||
class="border-l-2 border-divider pl-4"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="gqlType.getFields">
|
||||
@@ -37,7 +37,7 @@
|
||||
<GraphqlField
|
||||
v-for="(field, index) in gqlType.getFields()"
|
||||
:key="`field-${index}`"
|
||||
class="pl-4 border-l-2 border-divider"
|
||||
class="border-l-2 border-divider pl-4"
|
||||
:gql-field="field"
|
||||
:is-highlighted="isFieldHighlighted({ field })"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
@@ -48,7 +48,7 @@
|
||||
<div
|
||||
v-for="(value, index) in gqlType.getValues()"
|
||||
:key="`value-${index}`"
|
||||
class="pl-4 border-l-2 border-divider"
|
||||
class="border-l-2 border-divider pl-4"
|
||||
v-text="value.name"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between pl-4 border-y bg-primary border-dividerLight"
|
||||
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between border-y border-dividerLight bg-primary pl-4"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ t("request.variables") }}
|
||||
@@ -16,7 +16,7 @@
|
||||
:title="`${t('request.stop')}`"
|
||||
:label="`${t('request.stop')}`"
|
||||
:icon="IconStop"
|
||||
class="rounded-none !text-accent !hover:text-accentDark"
|
||||
class="!hover:text-accentDark rounded-none !text-accent"
|
||||
@click="unsubscribe()"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
@@ -30,7 +30,7 @@
|
||||
:label="`${selectedOperation.name?.value ?? t('request.run')}`"
|
||||
:icon="IconPlay"
|
||||
:disabled="!selectedOperation"
|
||||
class="rounded-none !text-accent !hover:text-accentDark"
|
||||
class="!hover:text-accentDark rounded-none !text-accent"
|
||||
@click="runQuery(selectedOperation)"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
@@ -67,7 +67,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="variableEditor" class="flex flex-col flex-1"></div>
|
||||
<div ref="variableEditor" class="flex flex-1 flex-col"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col group">
|
||||
<div class="group flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span
|
||||
v-tippy="{
|
||||
@@ -7,7 +7,7 @@
|
||||
delay: [500, 20],
|
||||
content: entry.updatedOn ? shortDateTime(entry.updatedOn) : null,
|
||||
}"
|
||||
class="flex flex-1 min-w-0 py-2 pl-4 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
class="flex min-w-0 flex-1 cursor-pointer py-2 pl-4 pr-2 transition group-hover:text-secondaryDark"
|
||||
data-testid="restore_history_entry"
|
||||
@click="useEntry"
|
||||
>
|
||||
@@ -36,7 +36,7 @@
|
||||
:title="!entry.star ? t('add.star') : t('remove.star')"
|
||||
:icon="entry.star ? IconStarOff : IconStar"
|
||||
color="yellow"
|
||||
:class="{ 'group-hover:inline-flex hidden': !entry.star }"
|
||||
:class="{ 'hidden group-hover:inline-flex': !entry.star }"
|
||||
data-testid="star_button"
|
||||
@click="emit('toggle-star')"
|
||||
/>
|
||||
@@ -45,7 +45,7 @@
|
||||
<span
|
||||
v-for="(line, index) in query"
|
||||
:key="`line-${index}`"
|
||||
class="px-4 font-mono truncate whitespace-pre cursor-pointer text-secondaryLight"
|
||||
class="cursor-pointer truncate whitespace-pre px-4 font-mono text-secondaryLight"
|
||||
data-testid="restore_history_entry"
|
||||
@click="useEntry"
|
||||
>{{ line }}</span
|
||||
@@ -67,9 +67,11 @@ import IconMaximize2 from "~icons/lucide/maximize-2"
|
||||
|
||||
import { useI18n } from "@composables/i18n"
|
||||
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 tabs = useService(GQLTabService)
|
||||
|
||||
const props = defineProps<{
|
||||
entry: GQLHistoryEntry
|
||||
@@ -93,7 +95,7 @@ const query = computed(() =>
|
||||
)
|
||||
|
||||
const useEntry = () => {
|
||||
createNewTab({
|
||||
tabs.createNewTab({
|
||||
request: makeGQLRequest({
|
||||
name: props.entry.request.name,
|
||||
url: props.entry.request.url,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto border-b bg-primary border-dividerLight"
|
||||
class="sticky top-0 z-10 flex flex-shrink-0 flex-col overflow-x-auto border-b border-dividerLight bg-primary"
|
||||
>
|
||||
<WorkspaceCurrent :section="t('tab.history')" />
|
||||
<div class="flex">
|
||||
@@ -9,7 +9,7 @@
|
||||
v-model="filterText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="flex flex-1 p-4 py-2 bg-transparent"
|
||||
class="flex flex-1 bg-transparent p-4 py-2"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
/>
|
||||
<div class="flex">
|
||||
@@ -69,13 +69,13 @@
|
||||
open
|
||||
>
|
||||
<summary
|
||||
class="flex items-center justify-between flex-1 min-w-0 transition cursor-pointer focus:outline-none text-secondaryLight text-tiny group"
|
||||
class="group flex min-w-0 flex-1 cursor-pointer items-center justify-between text-tiny text-secondaryLight transition focus:outline-none"
|
||||
>
|
||||
<span
|
||||
class="inline-flex items-center justify-center px-4 py-2 transition group-hover:text-secondary truncate"
|
||||
class="inline-flex items-center justify-center truncate px-4 py-2 transition group-hover:text-secondary"
|
||||
>
|
||||
<icon-lucide-chevron-right
|
||||
class="mr-2 indicator flex flex-shrink-0"
|
||||
class="indicator mr-2 flex flex-shrink-0"
|
||||
/>
|
||||
<span
|
||||
:class="[
|
||||
@@ -124,7 +124,7 @@
|
||||
:text="`${t('state.nothing_found')} ‟${filterText || filterSelection}”`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
</template>
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.clear')"
|
||||
@@ -176,8 +176,9 @@ import {
|
||||
|
||||
import HistoryRestCard from "./rest/Card.vue"
|
||||
import HistoryGraphqlCard from "./graphql/Card.vue"
|
||||
import { createNewTab } from "~/helpers/rest/tab"
|
||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
|
||||
type HistoryEntry = GQLHistoryEntry | RESTHistoryEntry
|
||||
|
||||
@@ -293,8 +294,9 @@ const clearHistory = () => {
|
||||
|
||||
// NOTE: For GQL, the HistoryGraphqlCard component already implements useEntry
|
||||
// (That is not a really good behaviour tho ¯\_(ツ)_/¯)
|
||||
const tabs = useService(RESTTabService)
|
||||
const useHistory = (entry: RESTHistoryEntry) => {
|
||||
createNewTab({
|
||||
tabs.createNewTab({
|
||||
request: entry.request,
|
||||
isDirty: false,
|
||||
})
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
class="group flex items-stretch"
|
||||
@contextmenu.prevent="options!.tippy.show()"
|
||||
>
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||
class="flex items-center justify-center w-16 px-2 truncate cursor-pointer"
|
||||
class="flex w-16 cursor-pointer items-center justify-center truncate px-2"
|
||||
:class="entryStatus.className"
|
||||
data-testid="restore_history_entry"
|
||||
:title="`${duration}`"
|
||||
@click="emit('use-entry')"
|
||||
>
|
||||
<span class="font-semibold truncate text-tiny">
|
||||
<span class="truncate text-tiny font-semibold">
|
||||
{{ entry.request.method }}
|
||||
</span>
|
||||
</span>
|
||||
@@ -21,7 +21,7 @@
|
||||
delay: [500, 20],
|
||||
content: entry.updatedOn ? shortDateTime(entry.updatedOn) : null,
|
||||
}"
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
class="flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
data-testid="restore_history_entry"
|
||||
@click="emit('use-entry')"
|
||||
>
|
||||
@@ -74,7 +74,7 @@
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="!entry.star ? t('add.star') : t('remove.star')"
|
||||
:class="{ 'group-hover:inline-flex hidden': !entry.star }"
|
||||
:class="{ 'hidden group-hover:inline-flex': !entry.star }"
|
||||
:icon="entry.star ? IconStarOff : IconStar"
|
||||
color="yellow"
|
||||
data-testid="star_button"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky z-10 flex items-center justify-between flex-shrink-0 pl-4 overflow-x-auto border-b bg-primary border-dividerLight top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold"
|
||||
class="sticky top-upperMobileSecondaryStickyFold z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4 sm:top-upperSecondaryStickyFold"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<label class="font-semibold truncate text-secondaryLight">
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("authorization.type") }}
|
||||
</label>
|
||||
<tippy
|
||||
@@ -15,7 +15,7 @@
|
||||
>
|
||||
<span class="select-wrapper">
|
||||
<HoppButtonSecondary
|
||||
class="pr-8 ml-2 rounded-none"
|
||||
class="ml-2 rounded-none pr-8"
|
||||
:label="authName"
|
||||
/>
|
||||
</span>
|
||||
@@ -149,7 +149,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sticky flex-shrink-0 h-full p-4 overflow-auto overflow-x-auto bg-primary top-upperTertiaryStickyFold min-w-46 max-w-1/3 z-9"
|
||||
class="z-9 sticky top-upperTertiaryStickyFold h-full min-w-46 max-w-1/3 flex-shrink-0 overflow-auto overflow-x-auto bg-primary p-4"
|
||||
>
|
||||
<div class="pb-2 text-secondaryLight">
|
||||
{{ t("helpers.authorization") }}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky z-10 flex items-center justify-between flex-shrink-0 pl-4 overflow-x-auto border-b bg-primary border-dividerLight top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold"
|
||||
class="sticky top-upperMobileSecondaryStickyFold z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4 sm:top-upperSecondaryStickyFold"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<label class="font-semibold truncate text-secondaryLight">
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("request.content_type") }}
|
||||
</label>
|
||||
<tippy
|
||||
@@ -16,13 +16,13 @@
|
||||
<span class="select-wrapper">
|
||||
<HoppButtonSecondary
|
||||
:label="body.contentType || t('state.none')"
|
||||
class="pr-8 ml-2 rounded-none"
|
||||
class="ml-2 rounded-none pr-8"
|
||||
/>
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col space-y-2 divide-y focus:outline-none divide-dividerLight"
|
||||
class="flex flex-col space-y-2 divide-y divide-dividerLight focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
@@ -46,8 +46,8 @@
|
||||
:key="`contentTypeItems-${contentTypeItemsIndex}`"
|
||||
class="flex flex-col text-left"
|
||||
>
|
||||
<div class="flex px-4 py-2 rounded">
|
||||
<span class="font-bold text-tiny text-secondaryLight">
|
||||
<div class="flex rounded px-4 py-2">
|
||||
<span class="text-tiny font-bold text-secondaryLight">
|
||||
{{ t(contentTypeItems.title) }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -59,7 +59,9 @@
|
||||
:key="`contentTypeItem-${contentTypeIndex}`"
|
||||
:label="contentTypeItem"
|
||||
:info-icon="
|
||||
contentTypeItem === body.contentType ? IconDone : null
|
||||
contentTypeItem === body.contentType
|
||||
? IconDone
|
||||
: undefined
|
||||
"
|
||||
:active-info-icon="contentTypeItem === body.contentType"
|
||||
@click="
|
||||
@@ -136,7 +138,7 @@ import IconDone from "~icons/lucide/check"
|
||||
import IconExternalLink from "~icons/lucide/external-link"
|
||||
import IconInfo from "~icons/lucide/info"
|
||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||
import { RequestOptionTabs } from "./RequestOptions.vue"
|
||||
import { RESTOptionTabs } from "./RequestOptions.vue"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const t = useI18n()
|
||||
@@ -147,7 +149,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "change-tab", value: RequestOptionTabs): void
|
||||
(e: "change-tab", value: RESTOptionTabs): void
|
||||
(e: "update:headers", value: HoppRESTHeader[]): 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)
|
||||
if (!isContentTypeAlreadyExist()) {
|
||||
// TODO: Fix this
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="sticky z-10 flex items-center justify-between flex-shrink-0 pl-4 overflow-x-auto border-b bg-primary border-dividerLight top-upperMobileStickyFold sm:top-upperMobileTertiaryStickyFold"
|
||||
class="sticky top-upperMobileStickyFold z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4 sm:top-upperMobileTertiaryStickyFold"
|
||||
>
|
||||
<label class="font-semibold truncate text-secondaryLight">
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("request.body") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
@@ -41,7 +41,7 @@
|
||||
>
|
||||
<template #item="{ element: { entry }, index }">
|
||||
<div
|
||||
class="flex border-b divide-x divide-dividerLight border-dividerLight draggable-content group"
|
||||
class="draggable-content group flex divide-x divide-dividerLight border-b border-dividerLight"
|
||||
>
|
||||
<span>
|
||||
<HoppButtonSecondary
|
||||
@@ -56,7 +56,7 @@
|
||||
:icon="IconGripVertical"
|
||||
class="cursor-auto text-primary hover:text-primary"
|
||||
:class="{
|
||||
'draggable-handle group-hover:text-secondaryLight !cursor-grab':
|
||||
'draggable-handle !cursor-grab group-hover:text-secondaryLight':
|
||||
index !== workingParams?.length - 1,
|
||||
}"
|
||||
tabindex="-1"
|
||||
@@ -75,7 +75,7 @@
|
||||
"
|
||||
/>
|
||||
<div v-if="entry.isFile" class="file-chips-container">
|
||||
<div class="space-x-2 file-chips-wrapper">
|
||||
<div class="file-chips-wrapper space-x-2">
|
||||
<HoppSmartFileChip
|
||||
v-for="(file, fileIndex) in entry.value"
|
||||
:key="`param-${index}-file-${fileIndex}`"
|
||||
@@ -104,7 +104,7 @@
|
||||
:name="`attachment${index}`"
|
||||
type="file"
|
||||
multiple
|
||||
class="p-1 transition cursor-pointer file:transition file:cursor-pointer text-secondaryLight hover:text-secondaryDark file:mr-2 file:py-1 file:px-4 file:rounded file:border-0 file:text-tiny text-tiny file:text-secondary hover:file:text-secondaryDark file:bg-primaryLight hover:file:bg-primaryDark"
|
||||
class="cursor-pointer p-1 text-tiny text-secondaryLight transition file:mr-2 file:cursor-pointer file:rounded file:border-0 file:bg-primaryLight file:px-4 file:py-1 file:text-tiny file:text-secondary file:transition hover:text-secondaryDark hover:file:bg-primaryDark hover:file:text-secondaryDark"
|
||||
@change="setRequestAttachment(index, entry, $event)"
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -28,12 +28,12 @@
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
<div class="flex flex-col space-y-2">
|
||||
<div class="sticky z-10 top-0 flex-shrink-0 overflow-x-auto">
|
||||
<div class="sticky top-0 z-10 flex-shrink-0 overflow-x-auto">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="flex w-full p-4 py-2 input !bg-primaryContrast"
|
||||
class="input flex w-full !bg-primaryContrast p-4 py-2"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
/>
|
||||
</div>
|
||||
@@ -61,7 +61,7 @@
|
||||
:text="`${t('state.nothing_found')} ‟${searchQuery}”`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
@@ -70,16 +70,16 @@
|
||||
</tippy>
|
||||
<div
|
||||
v-if="errorState"
|
||||
class="w-full px-4 py-2 mt-4 overflow-auto font-mono text-red-400 whitespace-normal rounded bg-primaryLight"
|
||||
class="mt-4 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400"
|
||||
>
|
||||
{{ t("error.something_went_wrong") }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="codegenType"
|
||||
class="mt-4 border rounded border-dividerLight"
|
||||
class="mt-4 rounded border border-dividerLight"
|
||||
>
|
||||
<div class="flex items-center justify-between pl-4">
|
||||
<label class="font-semibold truncate text-secondaryLight">
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("request.generated_code") }}
|
||||
</label>
|
||||
<div class="flex items-center">
|
||||
@@ -106,7 +106,7 @@
|
||||
</div>
|
||||
<div
|
||||
ref="generatedCode"
|
||||
class="border-t rounded-b border-dividerLight"
|
||||
class="rounded-b border-t border-dividerLight"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -157,9 +157,10 @@ import {
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
import cloneDeep from "lodash-es/cloneDeep"
|
||||
import { platform } from "~/platform"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { useService } from "dioc/vue"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -173,7 +174,8 @@ const emit = defineEmits<{
|
||||
|
||||
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 errorState = ref(false)
|
||||
|
||||
@@ -242,7 +244,7 @@ watch(
|
||||
() => props.show,
|
||||
(goingToShow) => {
|
||||
if (goingToShow) {
|
||||
request.value = cloneDeep(currentActiveTab.value.document.request)
|
||||
request.value = cloneDeep(tabs.currentActiveTab.value.document.request)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_REST_CODEGEN_OPENED",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user