Compare commits
109 Commits
release/20
...
refactor/w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73a5f4657c | ||
|
|
4ba53a8bf3 | ||
|
|
2374ceb808 | ||
|
|
17169e1c46 | ||
|
|
4c74d0f865 | ||
|
|
8b930a6d3d | ||
|
|
f4a37f19c9 | ||
|
|
86b17e2bd3 | ||
|
|
e0083aa70d | ||
|
|
25b0818016 | ||
|
|
648cc8f5bd | ||
|
|
6032cbb17b | ||
|
|
52bff8ee68 | ||
|
|
e342e53db1 | ||
|
|
141652ffb6 | ||
|
|
bb57b2248c | ||
|
|
5d8da5fe49 | ||
|
|
c8f0142b16 | ||
|
|
2f2273ee2c | ||
|
|
b239b6b4a6 | ||
|
|
bbac317b71 | ||
|
|
412daa4d17 | ||
|
|
0abdc63f0e | ||
|
|
9e8112a4e5 | ||
|
|
5aa57fce3f | ||
|
|
8b65090dfb | ||
|
|
7ca94a99b7 | ||
|
|
fe3adeeb17 | ||
|
|
3a195711a4 | ||
|
|
6cde6200ae | ||
|
|
e5e1260632 | ||
|
|
90c9f2a9b1 | ||
|
|
db9ba17529 | ||
|
|
197d253e8b | ||
|
|
cd92dfec47 | ||
|
|
8467417e7a | ||
|
|
f6067f14aa | ||
|
|
3fd85df84b | ||
|
|
01573cc51c | ||
|
|
b19486ea03 | ||
|
|
a729dfcacb | ||
|
|
62612e6c51 | ||
|
|
f87a4c81d8 | ||
|
|
116f2fd279 | ||
|
|
7e2deaac5b | ||
|
|
240b131e06 | ||
|
|
5b3986a53f | ||
|
|
84fc31e8a9 | ||
|
|
c0978c3b20 | ||
|
|
d70d5bdb16 | ||
|
|
c30ee5becc | ||
|
|
5a64cdb7bc | ||
|
|
89f2479845 | ||
|
|
46654853f0 | ||
|
|
af7e6b70cd | ||
|
|
7c52c6b79d | ||
|
|
fe01322bf7 | ||
|
|
0a0f441da1 | ||
|
|
d0c7c4a245 | ||
|
|
076006c4a6 | ||
|
|
6ed9c09f06 | ||
|
|
8483339005 | ||
|
|
cd23bb63c1 | ||
|
|
f4ea999d2d | ||
|
|
c43e4fcefd | ||
|
|
316dc8f759 | ||
|
|
89f7c2ce5e | ||
|
|
0d00826019 | ||
|
|
00285df348 | ||
|
|
b821f452cf | ||
|
|
faa0bf7714 | ||
|
|
3a176f6620 | ||
|
|
7549e456c8 | ||
|
|
68795a5017 | ||
|
|
b0c72fd295 | ||
|
|
63eca80ff6 | ||
|
|
30b6a67505 | ||
|
|
97899ec023 | ||
|
|
2c47a63ca0 | ||
|
|
a0e373a4f3 | ||
|
|
392b2fc48d | ||
|
|
c1a8a871d2 | ||
|
|
1abbdb0fe0 | ||
|
|
f0f504d10e | ||
|
|
f0dab55c99 | ||
|
|
d6a8e60239 | ||
|
|
89bcc58de6 | ||
|
|
ab7df212c2 | ||
|
|
29e25b0ead | ||
|
|
f8ac6dfeb1 | ||
|
|
7d2d335b37 | ||
|
|
76875db865 | ||
|
|
96e2d87b57 | ||
|
|
be353d9f72 | ||
|
|
38bc2c12c3 | ||
|
|
97644fa508 | ||
|
|
eb3446ae23 | ||
|
|
6c29961d09 | ||
|
|
ef1117d8cc | ||
|
|
5c4b651aee | ||
|
|
391e5a20f5 | ||
|
|
4b8f3bd8da | ||
|
|
94248076e6 | ||
|
|
eecc3db4e9 | ||
|
|
426e7594f4 | ||
|
|
934dc473f0 | ||
|
|
be57255bf7 | ||
|
|
f89561da54 | ||
|
|
c2c4e620c2 |
@@ -34,11 +34,11 @@
|
|||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"vue": "3.3.9"
|
"vue": "3.4.27"
|
||||||
},
|
},
|
||||||
"packageExtensions": {
|
"packageExtensions": {
|
||||||
"httpsnippet@3.0.1": {
|
"httpsnippet@3.0.1": {
|
||||||
"peerDependencies": {
|
"dependencies": {
|
||||||
"ajv": "6.12.3"
|
"ajv": "6.12.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:18.8.0 AS builder
|
FROM node:20.12.2 AS builder
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
"collection": "@nestjs/schematics",
|
"collection": "@nestjs/schematics",
|
||||||
"sourceRoot": "src",
|
"sourceRoot": "src",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"assets": [
|
"assets": [{ "include": "mailer/templates/**/*", "outDir": "dist" }],
|
||||||
"**/*.hbs"
|
|
||||||
],
|
|
||||||
"watchAssets": true
|
"watchAssets": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-backend",
|
"name": "hoppscotch-backend",
|
||||||
"version": "2024.3.1",
|
"version": "2024.3.3",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
"@nestjs/passport": "10.0.2",
|
"@nestjs/passport": "10.0.2",
|
||||||
"@nestjs/platform-express": "10.2.7",
|
"@nestjs/platform-express": "10.2.7",
|
||||||
"@nestjs/schedule": "4.0.1",
|
"@nestjs/schedule": "4.0.1",
|
||||||
|
"@nestjs/terminus": "10.2.3",
|
||||||
"@nestjs/throttler": "5.0.1",
|
"@nestjs/throttler": "5.0.1",
|
||||||
"@prisma/client": "5.8.1",
|
"@prisma/client": "5.8.1",
|
||||||
"argon2": "0.30.3",
|
"argon2": "0.30.3",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { loadInfraConfiguration } from './infra-config/helper';
|
|||||||
import { MailerModule } from './mailer/mailer.module';
|
import { MailerModule } from './mailer/mailer.module';
|
||||||
import { PosthogModule } from './posthog/posthog.module';
|
import { PosthogModule } from './posthog/posthog.module';
|
||||||
import { ScheduleModule } from '@nestjs/schedule';
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
|
import { HealthModule } from './health/health.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -100,6 +101,7 @@ import { ScheduleModule } from '@nestjs/schedule';
|
|||||||
InfraConfigModule,
|
InfraConfigModule,
|
||||||
PosthogModule,
|
PosthogModule,
|
||||||
ScheduleModule.forRoot(),
|
ScheduleModule.forRoot(),
|
||||||
|
HealthModule,
|
||||||
],
|
],
|
||||||
providers: [GQLComplexityPlugin],
|
providers: [GQLComplexityPlugin],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
|
|||||||
24
packages/hoppscotch-backend/src/health/health.controller.ts
Normal file
24
packages/hoppscotch-backend/src/health/health.controller.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
HealthCheck,
|
||||||
|
HealthCheckService,
|
||||||
|
PrismaHealthIndicator,
|
||||||
|
} from '@nestjs/terminus';
|
||||||
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
|
|
||||||
|
@Controller('health')
|
||||||
|
export class HealthController {
|
||||||
|
constructor(
|
||||||
|
private health: HealthCheckService,
|
||||||
|
private prismaHealth: PrismaHealthIndicator,
|
||||||
|
private prisma: PrismaService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@HealthCheck()
|
||||||
|
check() {
|
||||||
|
return this.health.check([
|
||||||
|
async () => this.prismaHealth.pingCheck('database', this.prisma),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
packages/hoppscotch-backend/src/health/health.module.ts
Normal file
10
packages/hoppscotch-backend/src/health/health.module.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { HealthController } from './health.controller';
|
||||||
|
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||||
|
import { TerminusModule } from '@nestjs/terminus';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule, TerminusModule],
|
||||||
|
controllers: [HealthController],
|
||||||
|
})
|
||||||
|
export class HealthModule {}
|
||||||
@@ -374,7 +374,8 @@
|
|||||||
"mutations": "Mutations",
|
"mutations": "Mutations",
|
||||||
"schema": "Schema",
|
"schema": "Schema",
|
||||||
"subscriptions": "Subscriptions",
|
"subscriptions": "Subscriptions",
|
||||||
"switch_connection": "Switch connection"
|
"switch_connection": "Switch connection",
|
||||||
|
"url_placeholder": "Enter a GraphQL endpoint URL"
|
||||||
},
|
},
|
||||||
"graphql_collections": {
|
"graphql_collections": {
|
||||||
"title": "GraphQL Collections"
|
"title": "GraphQL Collections"
|
||||||
@@ -598,6 +599,7 @@
|
|||||||
"title": "Request",
|
"title": "Request",
|
||||||
"type": "Request type",
|
"type": "Request type",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
|
"url_placeholder": "Enter a URL or paste a cURL command",
|
||||||
"variables": "Variables",
|
"variables": "Variables",
|
||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
@@ -1025,7 +1027,8 @@
|
|||||||
"personal": "Personal Workspace",
|
"personal": "Personal Workspace",
|
||||||
"other_workspaces": "My Workspaces",
|
"other_workspaces": "My Workspaces",
|
||||||
"team": "Workspace",
|
"team": "Workspace",
|
||||||
"title": "Workspaces"
|
"title": "Workspaces",
|
||||||
|
"no_workspace": "No Workspace"
|
||||||
},
|
},
|
||||||
"site_protection": {
|
"site_protection": {
|
||||||
"login_to_continue": "Login to continue",
|
"login_to_continue": "Login to continue",
|
||||||
|
|||||||
@@ -58,24 +58,6 @@
|
|||||||
"new": "Ajouter un nouveau",
|
"new": "Ajouter un nouveau",
|
||||||
"star": "Ajouter une étoile"
|
"star": "Ajouter une étoile"
|
||||||
},
|
},
|
||||||
"cookies": {
|
|
||||||
"modal": {
|
|
||||||
"new_domain_name": "Nouveau nom de domaine",
|
|
||||||
"set": "Définir un cookie",
|
|
||||||
"cookie_string": "Chaîne de caractères de cookie",
|
|
||||||
"enter_cookie_string": "Saisir la chaîne de caractères du cookie",
|
|
||||||
"cookie_name": "Nom",
|
|
||||||
"cookie_value": "Valeur",
|
|
||||||
"cookie_path": "Chemin d'accès",
|
|
||||||
"cookie_expires": "Expiration",
|
|
||||||
"managed_tab": "Gestion",
|
|
||||||
"raw_tab": "Brut",
|
|
||||||
"interceptor_no_support": "L'intercepteur que vous avez sélectionné ne prend pas en charge les cookies. Sélectionnez un autre intercepteur et réessayez.",
|
|
||||||
"empty_domains": "La liste des domaines est vide",
|
|
||||||
"empty_domain": "Le domaine est vide",
|
|
||||||
"no_cookies_in_domain": "Aucun cookie n'est défini pour ce domaine"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"app": {
|
"app": {
|
||||||
"chat_with_us": "Discuter avec nous",
|
"chat_with_us": "Discuter avec nous",
|
||||||
"contact_us": "Nous contacter",
|
"contact_us": "Nous contacter",
|
||||||
@@ -187,7 +169,7 @@
|
|||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"close_unsaved_tab": "Êtes-vous sûr de vouloir fermer cet onglet ?",
|
"close_unsaved_tab": "Êtes-vous sûr de vouloir fermer cet onglet ?",
|
||||||
"close_unsaved_tabs": "Êtes-vous sûr de vouloir fermer tous les onglets ? {Les onglets non enregistrés seront perdus.",
|
"close_unsaved_tabs": "Êtes-vous sûr de vouloir fermer tous les onglets ? {count} onglets non enregistrés seront perdus",
|
||||||
"exit_team": "Êtes-vous sûr de vouloir quitter cette équipe ?",
|
"exit_team": "Êtes-vous sûr de vouloir quitter cette équipe ?",
|
||||||
"logout": "Êtes-vous sûr de vouloir vous déconnecter?",
|
"logout": "Êtes-vous sûr de vouloir vous déconnecter?",
|
||||||
"remove_collection": "Voulez-vous vraiment supprimer définitivement cette collection ?",
|
"remove_collection": "Voulez-vous vraiment supprimer définitivement cette collection ?",
|
||||||
@@ -207,6 +189,24 @@
|
|||||||
"open_request_in_new_tab": "Ouvrir la demande dans un nouvel onglet",
|
"open_request_in_new_tab": "Ouvrir la demande dans un nouvel onglet",
|
||||||
"set_environment_variable": "Définir comme variable"
|
"set_environment_variable": "Définir comme variable"
|
||||||
},
|
},
|
||||||
|
"cookies": {
|
||||||
|
"modal": {
|
||||||
|
"new_domain_name": "Nouveau nom de domaine",
|
||||||
|
"set": "Définir un cookie",
|
||||||
|
"cookie_string": "Chaîne de caractères de cookie",
|
||||||
|
"enter_cookie_string": "Saisir la chaîne de caractères du cookie",
|
||||||
|
"cookie_name": "Nom",
|
||||||
|
"cookie_value": "Valeur",
|
||||||
|
"cookie_path": "Chemin d'accès",
|
||||||
|
"cookie_expires": "Expiration",
|
||||||
|
"managed_tab": "Gestion",
|
||||||
|
"raw_tab": "Brut",
|
||||||
|
"interceptor_no_support": "L'intercepteur que vous avez sélectionné ne prend pas en charge les cookies. Sélectionnez un autre intercepteur et réessayez.",
|
||||||
|
"empty_domains": "La liste des domaines est vide",
|
||||||
|
"empty_domain": "Le domaine est vide",
|
||||||
|
"no_cookies_in_domain": "Aucun cookie n'est défini pour ce domaine"
|
||||||
|
}
|
||||||
|
},
|
||||||
"count": {
|
"count": {
|
||||||
"header": "En-tête {count}",
|
"header": "En-tête {count}",
|
||||||
"message": "Message {compte}",
|
"message": "Message {compte}",
|
||||||
@@ -410,7 +410,7 @@
|
|||||||
"description": "Inspecter les erreurs possibles",
|
"description": "Inspecter les erreurs possibles",
|
||||||
"environment": {
|
"environment": {
|
||||||
"add_environment": "Ajouter à l'environnement",
|
"add_environment": "Ajouter à l'environnement",
|
||||||
"not_found": "La variable d'environnement “{environnement}“ n'a pas été trouvée."
|
"not_found": "La variable d'environnement “{environment}“ n'a pas été trouvée."
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"cookie": "Le navigateur ne permet pas à Hoppscotch de définir l'en-tête Cookie. Pendant que nous travaillons sur l'application de bureau Hoppscotch (bientôt disponible), veuillez utiliser l'en-tête d'autorisation à la place."
|
"cookie": "Le navigateur ne permet pas à Hoppscotch de définir l'en-tête Cookie. Pendant que nous travaillons sur l'application de bureau Hoppscotch (bientôt disponible), veuillez utiliser l'en-tête d'autorisation à la place."
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,12 +11,13 @@
|
|||||||
"connect": "Подключиться",
|
"connect": "Подключиться",
|
||||||
"connecting": "Соединение...",
|
"connecting": "Соединение...",
|
||||||
"copy": "Скопировать",
|
"copy": "Скопировать",
|
||||||
"create": "Create",
|
"create": "Создать",
|
||||||
"delete": "Удалить",
|
"delete": "Удалить",
|
||||||
"disconnect": "Отключиться",
|
"disconnect": "Отключиться",
|
||||||
"dismiss": "Скрыть",
|
"dismiss": "Скрыть",
|
||||||
"dont_save": "Не сохранять",
|
"dont_save": "Не сохранять",
|
||||||
"download_file": "Скачать файл",
|
"download_file": "Скачать файл",
|
||||||
|
"download_here": "Download here",
|
||||||
"drag_to_reorder": "Перетягивайте для сортировки",
|
"drag_to_reorder": "Перетягивайте для сортировки",
|
||||||
"duplicate": "Дублировать",
|
"duplicate": "Дублировать",
|
||||||
"edit": "Редактировать",
|
"edit": "Редактировать",
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
"go_back": "Вернуться",
|
"go_back": "Вернуться",
|
||||||
"go_forward": "Вперёд",
|
"go_forward": "Вперёд",
|
||||||
"group_by": "Сгруппировать по",
|
"group_by": "Сгруппировать по",
|
||||||
|
"hide_secret": "Hide secret",
|
||||||
"label": "Название",
|
"label": "Название",
|
||||||
"learn_more": "Узнать больше",
|
"learn_more": "Узнать больше",
|
||||||
"less": "Меньше",
|
"less": "Меньше",
|
||||||
@@ -33,7 +35,7 @@
|
|||||||
"open_workspace": "Открыть пространство",
|
"open_workspace": "Открыть пространство",
|
||||||
"paste": "Вставить",
|
"paste": "Вставить",
|
||||||
"prettify": "Форматировать",
|
"prettify": "Форматировать",
|
||||||
"properties": "Properties",
|
"properties": "Параметры",
|
||||||
"remove": "Удалить",
|
"remove": "Удалить",
|
||||||
"rename": "Переименовать",
|
"rename": "Переименовать",
|
||||||
"restore": "Восстановить",
|
"restore": "Восстановить",
|
||||||
@@ -42,13 +44,14 @@
|
|||||||
"scroll_to_top": "Вверх",
|
"scroll_to_top": "Вверх",
|
||||||
"search": "Поиск",
|
"search": "Поиск",
|
||||||
"send": "Отправить",
|
"send": "Отправить",
|
||||||
"share": "Share",
|
"share": "Поделиться",
|
||||||
|
"show_secret": "Show secret",
|
||||||
"start": "Начать",
|
"start": "Начать",
|
||||||
"starting": "Запускаю",
|
"starting": "Запускаю",
|
||||||
"stop": "Стоп",
|
"stop": "Стоп",
|
||||||
"to_close": "что бы закрыть",
|
"to_close": "закрыть",
|
||||||
"to_navigate": "для навигации",
|
"to_navigate": "для навигации",
|
||||||
"to_select": "выборать",
|
"to_select": "выбрать",
|
||||||
"turn_off": "Выключить",
|
"turn_off": "Выключить",
|
||||||
"turn_on": "Включить",
|
"turn_on": "Включить",
|
||||||
"undo": "Отменить",
|
"undo": "Отменить",
|
||||||
@@ -66,12 +69,12 @@
|
|||||||
"copy_interface_type": "Copy interface type",
|
"copy_interface_type": "Copy interface type",
|
||||||
"copy_user_id": "Копировать токен пользователя",
|
"copy_user_id": "Копировать токен пользователя",
|
||||||
"developer_option": "Настройки разработчика",
|
"developer_option": "Настройки разработчика",
|
||||||
"developer_option_description": "Инструмент разработчика помогает обслуживить и развивить Hoppscotch",
|
"developer_option_description": "Инструмент разработчика помогает обслуживать и развивать Hoppscotch",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"documentation": "Документация",
|
"documentation": "Документация",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
"help": "Справка, отзывы и документация",
|
"help": "Справка, отзывы и документация",
|
||||||
"home": "Дом",
|
"home": "На главную",
|
||||||
"invite": "Пригласить",
|
"invite": "Пригласить",
|
||||||
"invite_description": "В Hoppscotch мы разработали простой и интуитивно понятный интерфейс для создания и управления вашими API. Hoppscotch - это инструмент, который помогает создавать, тестировать, документировать и делиться своими API.",
|
"invite_description": "В Hoppscotch мы разработали простой и интуитивно понятный интерфейс для создания и управления вашими API. Hoppscotch - это инструмент, который помогает создавать, тестировать, документировать и делиться своими API.",
|
||||||
"invite_your_friends": "Пригласить своих друзей",
|
"invite_your_friends": "Пригласить своих друзей",
|
||||||
@@ -85,7 +88,7 @@
|
|||||||
"reload": "Перезагрузить",
|
"reload": "Перезагрузить",
|
||||||
"search": "Поиск",
|
"search": "Поиск",
|
||||||
"share": "Поделиться",
|
"share": "Поделиться",
|
||||||
"shortcuts": "Ярлыки",
|
"shortcuts": "Горячие клавиши",
|
||||||
"social_description": "Подписывайся на наши соц. сети и оставайся всегда в курсе последних новостей, обновлений и релизов.",
|
"social_description": "Подписывайся на наши соц. сети и оставайся всегда в курсе последних новостей, обновлений и релизов.",
|
||||||
"social_links": "Социальные сети",
|
"social_links": "Социальные сети",
|
||||||
"spotlight": "Прожектор",
|
"spotlight": "Прожектор",
|
||||||
@@ -96,17 +99,19 @@
|
|||||||
"type_a_command_search": "Введите команду или выполните поиск…",
|
"type_a_command_search": "Введите команду или выполните поиск…",
|
||||||
"we_use_cookies": "Мы используем куки",
|
"we_use_cookies": "Мы используем куки",
|
||||||
"whats_new": "Что нового?",
|
"whats_new": "Что нового?",
|
||||||
"wiki": "Вики"
|
"wiki": "Узнать больше"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"account_exists": "Учетная запись существует с разными учетными данными - войдите, чтобы связать обе учетные записи",
|
"account_exists": "Учетная запись существует с разными учетными данными - войдите, чтобы связать обе учетные записи",
|
||||||
"all_sign_in_options": "Все варианты входа",
|
"all_sign_in_options": "Все варианты входа",
|
||||||
|
"continue_with_auth_provider": "Continue with {provider}",
|
||||||
"continue_with_email": "Продолжить с электронной почтой",
|
"continue_with_email": "Продолжить с электронной почтой",
|
||||||
"continue_with_github": "Продолжить с GitHub",
|
"continue_with_github": "Продолжить с GitHub",
|
||||||
|
"continue_with_github_enterprise": "Continue with GitHub Enterprise",
|
||||||
"continue_with_google": "Продолжить с Google",
|
"continue_with_google": "Продолжить с Google",
|
||||||
"continue_with_microsoft": "Продолжить с Microsoft",
|
"continue_with_microsoft": "Продолжить с Microsoft",
|
||||||
"email": "Электронное письмо",
|
"email": "Электронное письмо",
|
||||||
"logged_out": "Вышли из",
|
"logged_out": "Успешно вышли. Будем скучать!",
|
||||||
"login": "Авторизоваться",
|
"login": "Авторизоваться",
|
||||||
"login_success": "Успешный вход в систему",
|
"login_success": "Успешный вход в систему",
|
||||||
"login_to_hoppscotch": "Войти в Hoppscotch",
|
"login_to_hoppscotch": "Войти в Hoppscotch",
|
||||||
@@ -121,7 +126,7 @@
|
|||||||
"generate_token": "Сгенерировать токен",
|
"generate_token": "Сгенерировать токен",
|
||||||
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init",
|
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init",
|
||||||
"include_in_url": "Добавить в URL",
|
"include_in_url": "Добавить в URL",
|
||||||
"inherited_from": "Inherited {auth} from parent collection {collection} ",
|
"inherited_from": "Унаследован тип аутентификации {auth} из родительской коллекции {collection}",
|
||||||
"learn": "Узнать больше",
|
"learn": "Узнать больше",
|
||||||
"oauth": {
|
"oauth": {
|
||||||
"redirect_auth_server_returned_error": "Auth Server returned an error state",
|
"redirect_auth_server_returned_error": "Auth Server returned an error state",
|
||||||
@@ -135,11 +140,32 @@
|
|||||||
"redirect_no_token_endpoint": "No Token Endpoint Defined",
|
"redirect_no_token_endpoint": "No Token Endpoint Defined",
|
||||||
"something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect",
|
"something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect",
|
||||||
"something_went_wrong_on_token_generation": "Something went wrong on token generation",
|
"something_went_wrong_on_token_generation": "Something went wrong on token generation",
|
||||||
"token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed"
|
"token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed",
|
||||||
|
"grant_type": "Grant Type",
|
||||||
|
"grant_type_auth_code": "Authorization Code",
|
||||||
|
"token_fetched_successfully": "Token fetched successfully",
|
||||||
|
"token_fetch_failed": "Failed to fetch token",
|
||||||
|
"validation_failed": "Validation Failed, please check the form fields",
|
||||||
|
"label_authorization_endpoint": "Authorization Endpoint",
|
||||||
|
"label_client_id": "Client ID",
|
||||||
|
"label_client_secret": "Client Secret",
|
||||||
|
"label_code_challenge": "Code Challenge",
|
||||||
|
"label_code_challenge_method": "Code Challenge Method",
|
||||||
|
"label_code_verifier": "Code Verifier",
|
||||||
|
"label_scopes": "Scopes",
|
||||||
|
"label_token_endpoint": "Token Endpoint",
|
||||||
|
"label_use_pkce": "Use PKCE",
|
||||||
|
"label_implicit": "Implicit",
|
||||||
|
"label_password": "Password",
|
||||||
|
"label_username": "Username",
|
||||||
|
"label_auth_code": "Authorization Code",
|
||||||
|
"label_client_credentials": "Client Credentials"
|
||||||
},
|
},
|
||||||
|
"pass_by_headers_label": "Headers",
|
||||||
|
"pass_by_query_params_label": "Query Parameters",
|
||||||
"pass_key_by": "Pass by",
|
"pass_key_by": "Pass by",
|
||||||
"password": "Пароль",
|
"password": "Пароль",
|
||||||
"save_to_inherit": "Please save this request in any collection to inherit the authorization",
|
"save_to_inherit": "Чтобы унаследовать аутентификации, нужно сохранить запрос в коллекции",
|
||||||
"token": "Токен",
|
"token": "Токен",
|
||||||
"type": "Метод авторизации",
|
"type": "Метод авторизации",
|
||||||
"username": "Имя пользователя"
|
"username": "Имя пользователя"
|
||||||
@@ -149,6 +175,7 @@
|
|||||||
"different_parent": "Нельзя сортировать коллекцию с разной родительской коллекцией",
|
"different_parent": "Нельзя сортировать коллекцию с разной родительской коллекцией",
|
||||||
"edit": "Редактировать коллекцию",
|
"edit": "Редактировать коллекцию",
|
||||||
"import_or_create": "Вы можете импортировать существующую или создать новую коллекцию",
|
"import_or_create": "Вы можете импортировать существующую или создать новую коллекцию",
|
||||||
|
"import_collection": "Импортировать коллекцию",
|
||||||
"invalid_name": "Укажите допустимое название коллекции",
|
"invalid_name": "Укажите допустимое название коллекции",
|
||||||
"invalid_root_move": "Коллекция уже в корне",
|
"invalid_root_move": "Коллекция уже в корне",
|
||||||
"moved": "Перемещено успешно",
|
"moved": "Перемещено успешно",
|
||||||
@@ -157,38 +184,36 @@
|
|||||||
"name_length_insufficient": "Имя коллекции должно иметь 3 или более символов",
|
"name_length_insufficient": "Имя коллекции должно иметь 3 или более символов",
|
||||||
"new": "Создать коллекцию",
|
"new": "Создать коллекцию",
|
||||||
"order_changed": "Порядок коллекции обновлён",
|
"order_changed": "Порядок коллекции обновлён",
|
||||||
"properties": "Collection Properties",
|
"properties": "Параметры коллекции",
|
||||||
"properties_updated": "Collection Properties Updated",
|
"properties_updated": "Параметры коллекции обновлены",
|
||||||
"renamed": "Коллекция переименована",
|
"renamed": "Коллекция переименована",
|
||||||
"request_in_use": "Запрос обрабатывается",
|
"request_in_use": "Запрос обрабатывается",
|
||||||
"save_as": "Сохранить как",
|
"save_as": "Сохранить как",
|
||||||
"save_to_collection": "Сохранить в коллекцию",
|
"save_to_collection": "Сохранить в коллекцию",
|
||||||
"select": "Выбрать коллекцию",
|
"select": "Выбрать коллекцию",
|
||||||
"select_location": "Выберите местоположение",
|
"select_location": "Выберите местоположение"
|
||||||
"select_team": "Выберите команду",
|
|
||||||
"team_collections": "Коллекции команд"
|
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"close_unsaved_tab": "Вы уверены что хотите закрыть эту вкладку?",
|
"close_unsaved_tab": "Вы уверены, что хотите закрыть эту вкладку?",
|
||||||
"close_unsaved_tabs": "Вы уверены что хотите закрыть все эти вкладки? Несохранённые данные {count} вкладок будут утеряны.",
|
"close_unsaved_tabs": "Вы уверены, что хотите закрыть все эти вкладки? Несохранённые данные {count} вкладок будут утеряны.",
|
||||||
"exit_team": "Вы точно хотите покинуть эту команду?",
|
"exit_team": "Вы точно хотите покинуть эту команду?",
|
||||||
"logout": "Вы действительно хотите выйти?",
|
"logout": "Вы действительно хотите выйти?",
|
||||||
"remove_collection": "Вы уверены, что хотите навсегда удалить эту коллекцию?",
|
"remove_collection": "Вы уверены, что хотите навсегда удалить эту коллекцию?",
|
||||||
"remove_environment": "Вы действительно хотите удалить эту среду без возможности восстановления?",
|
"remove_environment": "Вы действительно хотите удалить это окружение без возможности восстановления?",
|
||||||
"remove_folder": "Вы уверены, что хотите навсегда удалить эту папку?",
|
"remove_folder": "Вы уверены, что хотите навсегда удалить эту папку?",
|
||||||
"remove_history": "Вы уверены, что хотите навсегда удалить всю историю?",
|
"remove_history": "Вы уверены, что хотите навсегда удалить всю историю?",
|
||||||
"remove_request": "Вы уверены, что хотите навсегда удалить этот запрос?",
|
"remove_request": "Вы уверены, что хотите навсегда удалить этот запрос?",
|
||||||
"remove_shared_request": "Are you sure you want to permanently delete this shared request?",
|
"remove_shared_request": "Вы уверены, что хотите навсегда удалить этот запрос?",
|
||||||
"remove_team": "Вы уверены, что хотите удалить эту команду?",
|
"remove_team": "Вы уверены, что хотите удалить эту команду?",
|
||||||
"remove_telemetry": "Вы действительно хотите отказаться от телеметрии?",
|
"remove_telemetry": "Вы действительно хотите отказаться от телеметрии?",
|
||||||
"request_change": "Вы уверены что хотите сбросить текущий запрос, все не сохранённые данные будт утеряны?",
|
"request_change": "Вы уверены, что хотите сбросить текущий запрос, все не сохранённые данные будт утеряны?",
|
||||||
"save_unsaved_tab": "Вы хотите сохранить изменения в этой вкладке?",
|
"save_unsaved_tab": "Вы хотите сохранить изменения в этой вкладке?",
|
||||||
"sync": "Вы уверены, что хотите синхронизировать это рабочее пространство?"
|
"sync": "Вы уверены, что хотите синхронизировать это рабочее пространство?"
|
||||||
},
|
},
|
||||||
"context_menu": {
|
"context_menu": {
|
||||||
"add_parameters": "Add to parameters",
|
"add_parameters": "Добавить в список параметров",
|
||||||
"open_request_in_new_tab": "Open request in new tab",
|
"open_request_in_new_tab": "Открыть запрос в новом окне",
|
||||||
"set_environment_variable": "Set as variable"
|
"set_environment_variable": "Добавить значение в переменную"
|
||||||
},
|
},
|
||||||
"cookies": {
|
"cookies": {
|
||||||
"modal": {
|
"modal": {
|
||||||
@@ -227,24 +252,25 @@
|
|||||||
"collections": "Коллекции пустые",
|
"collections": "Коллекции пустые",
|
||||||
"documentation": "Подключите GraphQL endpoint, чтобы увидеть документацию.",
|
"documentation": "Подключите GraphQL endpoint, чтобы увидеть документацию.",
|
||||||
"endpoint": "Endpoint не может быть пустым",
|
"endpoint": "Endpoint не может быть пустым",
|
||||||
"environments": "Окружения пусты",
|
"environments": "Переменных окружения нет",
|
||||||
"folder": "Папка пуста",
|
"folder": "Папка пуста",
|
||||||
"headers": "У этого запроса нет заголовков",
|
"headers": "У этого запроса нет заголовков",
|
||||||
"history": "История пуста",
|
"history": "История пуста",
|
||||||
"invites": "Вы еще никого не приглашали",
|
"invites": "Вы еще никого не приглашали",
|
||||||
"members": "В этой команде еще нет участников",
|
"members": "В этой команде еще нет участников",
|
||||||
"parameters": "Этот запрос не имеет параметров",
|
"parameters": "Этот запрос не содержит параметров",
|
||||||
"pending_invites": "Пока что нет ожидающих заявок на вступление в команду",
|
"pending_invites": "Пока что нет ожидающих заявок на вступление в команду",
|
||||||
"profile": "Войдите, чтобы просмотреть свой профиль",
|
"profile": "Войдите, чтобы просмотреть свой профиль",
|
||||||
"protocols": "Протоколы пустые",
|
"protocols": "Протоколы пустые",
|
||||||
|
"request_variables": "Этот запрос не содержит никаких переменных",
|
||||||
|
"secret_environments": "Секреты хранятся только на этом устройстве и не синхронизируются с сервером",
|
||||||
"schema": "Подключиться к конечной точке GraphQL",
|
"schema": "Подключиться к конечной точке GraphQL",
|
||||||
"shared_requests": "Shared requests are empty",
|
"shared_requests": "Вы еще не делились запросами с другими",
|
||||||
"shared_requests_logout": "Login to view your shared requests or create a new one",
|
"shared_requests_logout": "Нужно войти, чтобы делиться запросами и управлять ими",
|
||||||
"subscription": "Нет подписок",
|
"subscription": "Нет подписок",
|
||||||
"team_name": "Название команды пусто",
|
"team_name": "Название команды пусто",
|
||||||
"teams": "Команды пустые",
|
"teams": "Команды пустые",
|
||||||
"tests": "Для этого запроса нет тестов",
|
"tests": "Для этого запроса нет тестов"
|
||||||
"shortcodes": "Нет коротких ссылок"
|
|
||||||
},
|
},
|
||||||
"environment": {
|
"environment": {
|
||||||
"add_to_global": "Добавить в глобальное окружение",
|
"add_to_global": "Добавить в глобальное окружение",
|
||||||
@@ -252,53 +278,57 @@
|
|||||||
"create_new": "Создать новое окружение",
|
"create_new": "Создать новое окружение",
|
||||||
"created": "Окружение создано",
|
"created": "Окружение создано",
|
||||||
"deleted": "Окружение удалено",
|
"deleted": "Окружение удалено",
|
||||||
"duplicated": "Environment duplicated",
|
"duplicated": "Окружение продублировано",
|
||||||
"edit": "Редактировать окружение",
|
"edit": "Редактировать окружение",
|
||||||
"empty_variables": "No variables",
|
"empty_variables": "Переменные еще не добавлены",
|
||||||
"global": "Global",
|
"global": "Global",
|
||||||
"global_variables": "Global variables",
|
"global_variables": "Глобальные переменные",
|
||||||
"import_or_create": "Импортировать или создать новое окружение",
|
"import_or_create": "Импортировать или создать новое окружение",
|
||||||
"invalid_name": "Укажите допустимое имя для окружения",
|
"invalid_name": "Укажите допустимое имя для окружения",
|
||||||
"list": "Переменные окружения",
|
"list": "Переменные окружения",
|
||||||
"my_environments": "Мои окружения",
|
"my_environments": "Мои окружения",
|
||||||
"name": "Name",
|
"name": "Имя",
|
||||||
"nested_overflow": "максимальный уровень вложения переменных окружения - 10",
|
"nested_overflow": "максимальный уровень вложения переменных окружения - 10",
|
||||||
"new": "Новая среда",
|
"new": "Новая среда",
|
||||||
"no_active_environment": "Нет активных окружений",
|
"no_active_environment": "Нет активных окружений",
|
||||||
"no_environment": "Нет окружения",
|
"no_environment": "Нет окружения",
|
||||||
"no_environment_description": "Не выбрано окружение, выберите что делать с переменными.",
|
"no_environment_description": "Не выбрано окружение, выберите что делать с переменными.",
|
||||||
"quick_peek": "Environment Quick Peek",
|
"quick_peek": "Быстрый просмотр переменных",
|
||||||
"replace_with_variable": "Replace with variable",
|
"replace_with_variable": "Replace with variable",
|
||||||
"scope": "Scope",
|
"scope": "Scope",
|
||||||
|
"secrets": "Секретные переменные",
|
||||||
|
"secret_value": "Секретное значение",
|
||||||
"select": "Выберите среду",
|
"select": "Выберите среду",
|
||||||
"set": "Set environment",
|
"set": "Выбрать окружение",
|
||||||
"set_as_environment": "Set as environment",
|
"set_as_environment": "Поместить значение в переменную",
|
||||||
"team_environments": "Окружения команды",
|
"team_environments": "Окружения команды",
|
||||||
"title": "Окружения",
|
"title": "Окружения",
|
||||||
"updated": "Окружение обновлено",
|
"updated": "Окружение обновлено",
|
||||||
"value": "Value",
|
"value": "Значение",
|
||||||
"variable": "Variable",
|
"variable": "Переменная",
|
||||||
|
"variables": "Переменные",
|
||||||
"variable_list": "Список переменных"
|
"variable_list": "Список переменных"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"authproviders_load_error": "Unable to load auth providers",
|
"authproviders_load_error": "Unable to load auth providers",
|
||||||
"browser_support_sse": "Похоже, в этом браузере нет поддержки событий, отправленных сервером.",
|
"browser_support_sse": "Похоже, в этом браузере нет поддержки событий, отправленных сервером.",
|
||||||
"check_console_details": "Подробности смотрите в журнале консоли.",
|
"check_console_details": "Подробности смотрите в журнале консоли.",
|
||||||
"check_how_to_add_origin": "Инструкция как добавить origin в настройки расширения",
|
"check_how_to_add_origin": "Инструкция как это сделать",
|
||||||
"curl_invalid_format": "cURL неправильно отформатирован",
|
"curl_invalid_format": "cURL неправильно отформатирован",
|
||||||
"danger_zone": "Опасная зона",
|
"danger_zone": "Опасная зона",
|
||||||
"delete_account": "Вы являетесь владельцем этой команды:",
|
"delete_account": "Вы являетесь владельцем этой команды:",
|
||||||
"delete_account_description": "Прежде чем удалить аккаунт вам необходимо либо назначить владельцом другого пользователя, либо удалить команды в которых вы являетесь владельцем.",
|
"delete_account_description": "Прежде чем удалить аккаунт вам необходимо либо назначить владельцом другого пользователя, либо удалить команды в которых вы являетесь владельцем.",
|
||||||
|
"empty_profile_name": "Имя пользователя не может быть пустым",
|
||||||
"empty_req_name": "Пустое имя запроса",
|
"empty_req_name": "Пустое имя запроса",
|
||||||
"f12_details": "(F12 для подробностей)",
|
"f12_details": "(F12 для подробностей)",
|
||||||
"gql_prettify_invalid_query": "Не удалось определить недопустимый запрос, устранить синтаксические ошибки запроса и повторить попытку.",
|
"gql_prettify_invalid_query": "Не удалось отформатировать, т.к. в запросе есть синтаксические ошибки. Устраните их и повторите попытку.",
|
||||||
"incomplete_config_urls": "Не заполнены URL конфигурации",
|
"incomplete_config_urls": "Не заполнены URL конфигурации",
|
||||||
"incorrect_email": "Не корректный Email",
|
"incorrect_email": "Не корректный Email",
|
||||||
"invalid_link": "Не корректная ссылка",
|
"invalid_link": "Не корректная ссылка",
|
||||||
"invalid_link_description": "Ссылка, по которой вы перешли, - недействительна, либо срок ее действия истек.",
|
"invalid_link_description": "Ссылка, по которой вы перешли, - недействительна, либо срок ее действия истек.",
|
||||||
"invalid_embed_link": "The embed does not exist or is invalid.",
|
"invalid_embed_link": "The embed does not exist or is invalid.",
|
||||||
"json_parsing_failed": "Не корректный JSON",
|
"json_parsing_failed": "Не корректный JSON",
|
||||||
"json_prettify_invalid_body": "Не удалось определить недопустимое тело, устранить синтаксические ошибки json и повторить попытку.",
|
"json_prettify_invalid_body": "Не удалось определить формат строки, устраните синтаксические ошибки и повторите попытку.",
|
||||||
"network_error": "Похоже, возникла проблема с соединением. Попробуйте еще раз.",
|
"network_error": "Похоже, возникла проблема с соединением. Попробуйте еще раз.",
|
||||||
"network_fail": "Не удалось отправить запрос",
|
"network_fail": "Не удалось отправить запрос",
|
||||||
"no_collections_to_export": "Нечего экспортировать. Для начала нужно создать коллекцию.",
|
"no_collections_to_export": "Нечего экспортировать. Для начала нужно создать коллекцию.",
|
||||||
@@ -306,8 +336,10 @@
|
|||||||
"no_environments_to_export": "Нечего экспортировать. Для начала нужно создать переменные окружения.",
|
"no_environments_to_export": "Нечего экспортировать. Для начала нужно создать переменные окружения.",
|
||||||
"no_results_found": "Совпадения не найдены",
|
"no_results_found": "Совпадения не найдены",
|
||||||
"page_not_found": "Эта страница не найдена",
|
"page_not_found": "Эта страница не найдена",
|
||||||
"please_install_extension": "Нужно установить специальное расширение и добавить этот домен как новый origin в настройках расширения.",
|
"please_install_extension": "Ничего страшного. Просто нужно установить специальное расширение в браузере.",
|
||||||
"proxy_error": "Proxy error",
|
"proxy_error": "Proxy error",
|
||||||
|
"reading_files": "Произошла ошибка при чтении файла или нескольких файлов",
|
||||||
|
"same_profile_name": "Задано имя пользователя такое же как и было",
|
||||||
"script_fail": "Не удалось выполнить сценарий предварительного запроса",
|
"script_fail": "Не удалось выполнить сценарий предварительного запроса",
|
||||||
"something_went_wrong": "Что-то пошло не так",
|
"something_went_wrong": "Что-то пошло не так",
|
||||||
"test_script_fail": "Не удалось выполнить тестирование запроса"
|
"test_script_fail": "Не удалось выполнить тестирование запроса"
|
||||||
@@ -315,13 +347,12 @@
|
|||||||
"export": {
|
"export": {
|
||||||
"as_json": "Экспорт как JSON",
|
"as_json": "Экспорт как JSON",
|
||||||
"create_secret_gist": "Создать секретный Gist",
|
"create_secret_gist": "Создать секретный Gist",
|
||||||
"create_secret_gist_tooltip_text": "Export as secret Gist",
|
"create_secret_gist_tooltip_text": "Экспортировать как секретный Gist",
|
||||||
"failed": "Something went wrong while exporting",
|
"failed": "Произошла ошибка во время экспорта",
|
||||||
"secret_gist_success": "Successfully exported as secret Gist",
|
"secret_gist_success": "Успешно экспортировано как секретный Gist",
|
||||||
"require_github": "Войдите через GitHub, чтобы создать секретную суть",
|
"require_github": "Войдите через GitHub, чтобы создать секретную суть",
|
||||||
"title": "Экспорт",
|
"title": "Экспорт",
|
||||||
"success": "Successfully exported",
|
"success": "Успешно экспортировано"
|
||||||
"gist_created": "Gist создан"
|
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
"all": "Все",
|
"all": "Все",
|
||||||
@@ -346,7 +377,7 @@
|
|||||||
"switch_connection": "Изменить соединение"
|
"switch_connection": "Изменить соединение"
|
||||||
},
|
},
|
||||||
"graphql_collections": {
|
"graphql_collections": {
|
||||||
"title": "GraphQL Collections"
|
"title": "Коллекции GraphQL"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"time": "Время",
|
"time": "Время",
|
||||||
@@ -359,8 +390,8 @@
|
|||||||
},
|
},
|
||||||
"helpers": {
|
"helpers": {
|
||||||
"authorization": "Заголовок авторизации будет автоматически сгенерирован при отправке запроса.",
|
"authorization": "Заголовок авторизации будет автоматически сгенерирован при отправке запроса.",
|
||||||
"collection_properties_authorization": " This authorization will be set for every request in this collection.",
|
"collection_properties_authorization": "Этот заголовок авторизации будет подставляться при каждом запросе в этой коллекции.",
|
||||||
"collection_properties_header": "This header will be set for every request in this collection.",
|
"collection_properties_header": "Этот заголовок будет подставляться при каждом запросе в этой коллекции.",
|
||||||
"generate_documentation_first": "Сначала создайте документацию",
|
"generate_documentation_first": "Сначала создайте документацию",
|
||||||
"network_fail": "Невозможно достичь конечной точки API. Проверьте подключение к сети и попробуйте еще раз.",
|
"network_fail": "Невозможно достичь конечной точки API. Проверьте подключение к сети и попробуйте еще раз.",
|
||||||
"offline": "Кажется, вы не в сети. Данные в этой рабочей области могут быть устаревшими.",
|
"offline": "Кажется, вы не в сети. Данные в этой рабочей области могут быть устаревшими.",
|
||||||
@@ -380,10 +411,12 @@
|
|||||||
"import": {
|
"import": {
|
||||||
"collections": "Импортировать коллекции",
|
"collections": "Импортировать коллекции",
|
||||||
"curl": "Импортировать из cURL",
|
"curl": "Импортировать из cURL",
|
||||||
"environments_from_gist": "Import From Gist",
|
"environments_from_gist": "Импортировать из Gist",
|
||||||
"environments_from_gist_description": "Import Hoppscotch Environments From Gist",
|
"environments_from_gist_description": "Импортировать переменные окружения Hoppscotch из Gist",
|
||||||
"failed": "Ошибка импорта",
|
"failed": "Ошибка импорта",
|
||||||
"from_file": "Import from File",
|
"file_size_limit_exceeded_warning_multiple_files": "Выбранные файлы превышают рекомендованный лимит в 10MB. Были импортированы только первые {files}",
|
||||||
|
"file_size_limit_exceeded_warning_single_file": "Размер выбранного в данный момент файла превышает рекомендуемый лимит в 10 МБ. Пожалуйста, выберите другой файл.",
|
||||||
|
"from_file": "Импортировать из одного или нескольких файлов",
|
||||||
"from_gist": "Импорт из Gist",
|
"from_gist": "Импорт из Gist",
|
||||||
"from_gist_description": "Импортировать через Gist URL",
|
"from_gist_description": "Импортировать через Gist URL",
|
||||||
"from_insomnia": "Импортировать с Insomnia",
|
"from_insomnia": "Импортировать с Insomnia",
|
||||||
@@ -398,9 +431,9 @@
|
|||||||
"from_postman_description": "Импортировать из коллекции Postman",
|
"from_postman_description": "Импортировать из коллекции Postman",
|
||||||
"from_url": "Импортировать из URL",
|
"from_url": "Импортировать из URL",
|
||||||
"gist_url": "Введите URL-адрес Gist",
|
"gist_url": "Введите URL-адрес Gist",
|
||||||
"gql_collections_from_gist_description": "Import GraphQL Collections From Gist",
|
"gql_collections_from_gist_description": "Импортировать GraphQL коллекцию из Gist",
|
||||||
"hoppscotch_environment": "Hoppscotch Environment",
|
"hoppscotch_environment": "Hoppscotch Environment",
|
||||||
"hoppscotch_environment_description": "Import Hoppscotch Environment JSON file",
|
"hoppscotch_environment_description": "Импортировать окружение Hoppscotch из JSON файла",
|
||||||
"import_from_url_invalid_fetch": "Не удалить получить данные по этому URL",
|
"import_from_url_invalid_fetch": "Не удалить получить данные по этому URL",
|
||||||
"import_from_url_invalid_file_format": "Ошибка при импорте коллекций",
|
"import_from_url_invalid_file_format": "Ошибка при импорте коллекций",
|
||||||
"import_from_url_invalid_type": "Неподдерживаемый тип. Поддерживаемые типы: 'hoppscotch', 'openapi', 'postman', 'insomnia'",
|
"import_from_url_invalid_type": "Неподдерживаемый тип. Поддерживаемые типы: 'hoppscotch', 'openapi', 'postman', 'insomnia'",
|
||||||
@@ -409,16 +442,19 @@
|
|||||||
"json_description": "Импортировать из коллекции Hoppscotch",
|
"json_description": "Импортировать из коллекции Hoppscotch",
|
||||||
"postman_environment": "Postman Environment",
|
"postman_environment": "Postman Environment",
|
||||||
"postman_environment_description": "Import Postman Environment from a JSON file",
|
"postman_environment_description": "Import Postman Environment from a JSON file",
|
||||||
|
"success": "Успешно импортировано",
|
||||||
"title": "Импортировать"
|
"title": "Импортировать"
|
||||||
},
|
},
|
||||||
"inspections": {
|
"inspections": {
|
||||||
"description": "Inspect possible errors",
|
"description": "Показать возможные ошибки",
|
||||||
"environment": {
|
"environment": {
|
||||||
"add_environment": "Add to Environment",
|
"add_environment": "Добавить переменную",
|
||||||
"not_found": "Environment variable “{environment}” not found."
|
"add_environment_value": "Заполнить значение",
|
||||||
|
"empty_value": "Значение переменной окружения '{variable}' пустое",
|
||||||
|
"not_found": "Переменная окружения “{environment}” не задана."
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
|
"cookie": "Из-за ограничений безопасности в веб версии нельзя задать Cookie параметры. Пожалуйста, используйте Hoppscotch Desktop приложение или используйте заголовок Authorization вместо этого."
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"401_error": "Please check your authentication credentials.",
|
"401_error": "Please check your authentication credentials.",
|
||||||
@@ -427,12 +463,12 @@
|
|||||||
"default_error": "Please check your request.",
|
"default_error": "Please check your request.",
|
||||||
"network_error": "Please check your network connection."
|
"network_error": "Please check your network connection."
|
||||||
},
|
},
|
||||||
"title": "Inspector",
|
"title": "Помощник",
|
||||||
"url": {
|
"url": {
|
||||||
"extension_not_installed": "Extension not installed.",
|
"extension_not_installed": "Расширение не установлено.",
|
||||||
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
|
"extension_unknown_origin": "Убедитесь, что текущий домен добавлен в список доверенных ресурсов в расширении браузера",
|
||||||
"extention_enable_action": "Enable Browser Extension",
|
"extention_enable_action": "Подключить расширение",
|
||||||
"extention_not_enabled": "Extension not enabled."
|
"extention_not_enabled": "Расширение в браузере не подключено."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
@@ -445,11 +481,11 @@
|
|||||||
"modal": {
|
"modal": {
|
||||||
"close_unsaved_tab": "У вас есть не сохранённые изменения",
|
"close_unsaved_tab": "У вас есть не сохранённые изменения",
|
||||||
"collections": "Коллекции",
|
"collections": "Коллекции",
|
||||||
"confirm": "Подтверждать",
|
"confirm": "Подтвердите действие",
|
||||||
"customize_request": "Customize Request",
|
"customize_request": "Customize Request",
|
||||||
"edit_request": "Изменить запрос",
|
"edit_request": "Изменить запрос",
|
||||||
"import_export": "Импорт Экспорт",
|
"import_export": "Импорт Экспорт",
|
||||||
"share_request": "Share Request"
|
"share_request": "Поделиться запросом"
|
||||||
},
|
},
|
||||||
"mqtt": {
|
"mqtt": {
|
||||||
"already_subscribed": "Вы уже подписаны на этот топик",
|
"already_subscribed": "Вы уже подписаны на этот топик",
|
||||||
@@ -485,7 +521,7 @@
|
|||||||
"doc": "Документы",
|
"doc": "Документы",
|
||||||
"graphql": "GraphQL",
|
"graphql": "GraphQL",
|
||||||
"profile": "Профиль",
|
"profile": "Профиль",
|
||||||
"realtime": "В реальном времени",
|
"realtime": "Realtime",
|
||||||
"rest": "REST",
|
"rest": "REST",
|
||||||
"settings": "Настройки"
|
"settings": "Настройки"
|
||||||
},
|
},
|
||||||
@@ -507,8 +543,8 @@
|
|||||||
"roles": "Роли",
|
"roles": "Роли",
|
||||||
"roles_description": "Роли позволяют настраивать доступ конкретным людям к публичным коллекциям.",
|
"roles_description": "Роли позволяют настраивать доступ конкретным людям к публичным коллекциям.",
|
||||||
"updated": "Профиль обновлен",
|
"updated": "Профиль обновлен",
|
||||||
"viewer": "Зритель",
|
"viewer": "Читатель",
|
||||||
"viewer_description": "Зрительно могут только просматривать и использовать запросы."
|
"viewer_description": "Могут только просматривать и использовать запросы."
|
||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"star": "Удалить звезду"
|
"star": "Удалить звезду"
|
||||||
@@ -530,11 +566,11 @@
|
|||||||
"enter_curl": "Введите сюда команду cURL",
|
"enter_curl": "Введите сюда команду cURL",
|
||||||
"generate_code": "Сгенерировать код",
|
"generate_code": "Сгенерировать код",
|
||||||
"generated_code": "Сгенерированный код",
|
"generated_code": "Сгенерированный код",
|
||||||
"go_to_authorization_tab": "Go to Authorization",
|
"go_to_authorization_tab": "Перейти на вкладку авторизации",
|
||||||
"go_to_body_tab": "Go to Body tab",
|
"go_to_body_tab": "Перейти на вкладку тела запроса",
|
||||||
"header_list": "Список заголовков",
|
"header_list": "Список заголовков",
|
||||||
"invalid_name": "Укажите имя для запроса",
|
"invalid_name": "Укажите имя для запроса",
|
||||||
"method": "Методика",
|
"method": "Метод",
|
||||||
"moved": "Запрос перемещён",
|
"moved": "Запрос перемещён",
|
||||||
"name": "Имя запроса",
|
"name": "Имя запроса",
|
||||||
"new": "Новый запрос",
|
"new": "Новый запрос",
|
||||||
@@ -548,22 +584,22 @@
|
|||||||
"payload": "Полезная нагрузка",
|
"payload": "Полезная нагрузка",
|
||||||
"query": "Запрос",
|
"query": "Запрос",
|
||||||
"raw_body": "Необработанное тело запроса",
|
"raw_body": "Необработанное тело запроса",
|
||||||
"rename": "Переименость запрос",
|
"rename": "Переименовать запрос",
|
||||||
"renamed": "Запрос переименован",
|
"renamed": "Запрос переименован",
|
||||||
|
"request_variables": "Переменные запроса",
|
||||||
"run": "Запустить",
|
"run": "Запустить",
|
||||||
"save": "Сохранить",
|
"save": "Сохранить",
|
||||||
"save_as": "Сохранить как",
|
"save_as": "Сохранить как",
|
||||||
"saved": "Запрос сохранен",
|
"saved": "Запрос сохранен",
|
||||||
"share": "Делиться",
|
"share": "Поделиться",
|
||||||
"share_description": "Поделиться Hoppscotch с друзьями",
|
"share_description": "Поделиться Hoppscotch с друзьями",
|
||||||
"share_request": "Share Request",
|
"share_request": "Поделиться запросом",
|
||||||
"stop": "Stop",
|
"stop": "Стоп",
|
||||||
"title": "Запрос",
|
"title": "Запрос",
|
||||||
"type": "Тип запроса",
|
"type": "Тип запроса",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"variables": "Переменные",
|
"variables": "Переменные",
|
||||||
"view_my_links": "Посмотреть мои ссылки",
|
"view_my_links": "Посмотреть мои ссылки"
|
||||||
"copy_link": "Копировать ссылку"
|
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Аудио",
|
"audio": "Аудио",
|
||||||
@@ -586,7 +622,7 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"accent_color": "Основной цвет",
|
"accent_color": "Основной цвет",
|
||||||
"account": "Счет",
|
"account": "Аккаунт",
|
||||||
"account_deleted": "Ваш аккаунт был удалён",
|
"account_deleted": "Ваш аккаунт был удалён",
|
||||||
"account_description": "Настройте параметры своей учетной записи.",
|
"account_description": "Настройте параметры своей учетной записи.",
|
||||||
"account_email_description": "Ваш основной адрес электронной почты.",
|
"account_email_description": "Ваш основной адрес электронной почты.",
|
||||||
@@ -639,29 +675,29 @@
|
|||||||
"verify_email": "Подтвердить Email"
|
"verify_email": "Подтвердить Email"
|
||||||
},
|
},
|
||||||
"shared_requests": {
|
"shared_requests": {
|
||||||
"button": "Button",
|
"button": "Кнопка",
|
||||||
"button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.",
|
"button_info": "Создать кнопку 'Run in Hoppscotch' на свой сайт, блог или README.",
|
||||||
"copy_html": "Copy HTML",
|
"copy_html": "Копировать HTML код",
|
||||||
"copy_link": "Copy Link",
|
"copy_link": "Копировать ссылку",
|
||||||
"copy_markdown": "Copy Markdown",
|
"copy_markdown": "Копировать Markdown",
|
||||||
"creating_widget": "Creating widget",
|
"creating_widget": "Создание виджет",
|
||||||
"customize": "Customize",
|
"customize": "Настроить",
|
||||||
"deleted": "Shared request deleted",
|
"deleted": "Запрос удален",
|
||||||
"description": "Select a widget, you can change and customize this later",
|
"description": "Выберите вид как вы поделитесь запросом, позже вы сможете дополнительно его настроить",
|
||||||
"embed": "Embed",
|
"embed": "Встраиваемое окно",
|
||||||
"embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.",
|
"embed_info": "Добавьте небольшую площадку 'Hoppscotch API Playground' на свой веб-сайт, блог или документацию.",
|
||||||
"link": "Link",
|
"link": "Ссылка",
|
||||||
"link_info": "Create a shareable link to share with anyone on the internet with view access.",
|
"link_info": "Создайте общедоступную ссылку, которой можно поделиться с любым пользователем, имеющим доступ к просмотру.",
|
||||||
"modified": "Shared request modified",
|
"modified": "Запрос изменен",
|
||||||
"not_found": "Shared request not found",
|
"not_found": "Такой ссылке не нашлось",
|
||||||
"open_new_tab": "Open in new tab",
|
"open_new_tab": "Открыть в новом окне",
|
||||||
"preview": "Preview",
|
"preview": "Preview",
|
||||||
"run_in_hoppscotch": "Run in Hoppscotch",
|
"run_in_hoppscotch": "Run in Hoppscotch",
|
||||||
"theme": {
|
"theme": {
|
||||||
"dark": "Dark",
|
"dark": "Темная",
|
||||||
"light": "Light",
|
"light": "Светлая",
|
||||||
"system": "System",
|
"system": "Системная",
|
||||||
"title": "Theme"
|
"title": "Тема"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"shortcut": {
|
"shortcut": {
|
||||||
@@ -669,7 +705,7 @@
|
|||||||
"close_current_menu": "Закрыть текущее меню",
|
"close_current_menu": "Закрыть текущее меню",
|
||||||
"command_menu": "Меню поиска и команд",
|
"command_menu": "Меню поиска и команд",
|
||||||
"help_menu": "Меню помощи",
|
"help_menu": "Меню помощи",
|
||||||
"show_all": "Горячие клавиши",
|
"show_all": "Список горячих клавиш",
|
||||||
"title": "Общий"
|
"title": "Общий"
|
||||||
},
|
},
|
||||||
"miscellaneous": {
|
"miscellaneous": {
|
||||||
@@ -696,20 +732,19 @@
|
|||||||
"get_method": "Выберите метод GET",
|
"get_method": "Выберите метод GET",
|
||||||
"head_method": "Выберите метод HEAD",
|
"head_method": "Выберите метод HEAD",
|
||||||
"import_curl": "Импортировать из cURL",
|
"import_curl": "Импортировать из cURL",
|
||||||
"method": "Методика",
|
"method": "Метод",
|
||||||
"next_method": "Выберите следующий метод",
|
"next_method": "Выберите следующий метод",
|
||||||
"post_method": "Выберите метод POST",
|
"post_method": "Выберите метод POST",
|
||||||
"previous_method": "Выбрать предыдущий метод",
|
"previous_method": "Выбрать предыдущий метод",
|
||||||
"put_method": "Выберите метод PUT",
|
"put_method": "Выберите метод PUT",
|
||||||
"rename": "Переименовать запрос",
|
"rename": "Переименовать запрос",
|
||||||
"reset_request": "Сбросить запрос",
|
"reset_request": "Сбросить запрос",
|
||||||
"save_request": "Сохарнить запрос",
|
"save_request": "Сохранить запрос",
|
||||||
"save_to_collections": "Сохранить в коллекции",
|
"save_to_collections": "Сохранить в коллекции",
|
||||||
"send_request": "Послать запрос",
|
"send_request": "Послать запрос",
|
||||||
"share_request": "Share Request",
|
"share_request": "Поделиться запросом",
|
||||||
"show_code": "Generate code snippet",
|
"show_code": "Сгенерировать фрагмент кода из запроса",
|
||||||
"title": "Запрос",
|
"title": "Запрос"
|
||||||
"copy_request_link": "Копировать ссылку на запрос"
|
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"copy": "Копировать запрос в буфер обмена",
|
"copy": "Копировать запрос в буфер обмена",
|
||||||
@@ -717,11 +752,11 @@
|
|||||||
"title": "Запрос"
|
"title": "Запрос"
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"black": "Черный режим",
|
"black": "Переключить на черный режим",
|
||||||
"dark": "Тёмный режим",
|
"dark": "Переключить на тёмный режим",
|
||||||
"light": "Светлый режим",
|
"light": "Переключить на светлый режим",
|
||||||
"system": "Определяется системой",
|
"system": "Переключить на тему, исходя из настроек системы",
|
||||||
"title": "Тема"
|
"title": "Внешний вид"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"show": {
|
"show": {
|
||||||
@@ -730,6 +765,11 @@
|
|||||||
"more": "Показать больше",
|
"more": "Показать больше",
|
||||||
"sidebar": "Показать боковую панель"
|
"sidebar": "Показать боковую панель"
|
||||||
},
|
},
|
||||||
|
"site_protection": {
|
||||||
|
"error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status",
|
||||||
|
"login_to_continue": "Login to continue",
|
||||||
|
"login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance."
|
||||||
|
},
|
||||||
"socketio": {
|
"socketio": {
|
||||||
"communication": "Коммуникация",
|
"communication": "Коммуникация",
|
||||||
"connection_not_authorized": "Это SocketIO соединение не использует какую-либо авторизацию.",
|
"connection_not_authorized": "Это SocketIO соединение не использует какую-либо авторизацию.",
|
||||||
@@ -739,82 +779,89 @@
|
|||||||
"url": "URL"
|
"url": "URL"
|
||||||
},
|
},
|
||||||
"spotlight": {
|
"spotlight": {
|
||||||
"change_language": "Change Language",
|
"change_language": "Изменить язык",
|
||||||
"environments": {
|
"environments": {
|
||||||
"delete": "Delete current environment",
|
"delete": "Удалить текущее окружение",
|
||||||
"duplicate": "Duplicate current environment",
|
"duplicate": "Дублировать текущее окружение",
|
||||||
"duplicate_global": "Duplicate global environment",
|
"duplicate_global": "Дублировать глобальное окружение",
|
||||||
"edit": "Edit current environment",
|
"edit": "Редактировать текущее окружение",
|
||||||
"edit_global": "Edit global environment",
|
"edit_global": "Редактировать глобальное окружение",
|
||||||
"new": "Create new environment",
|
"new": "Создать новое окружение",
|
||||||
"new_variable": "Create a new environment variable",
|
"new_variable": "Создать новую переменную окружения",
|
||||||
"title": "Environments"
|
"title": "Окружение"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"chat": "Chat with support",
|
"chat": "Чат с поддержкой",
|
||||||
"help_menu": "Help and support",
|
"help_menu": "Помощь",
|
||||||
"open_docs": "Read Documentation",
|
"open_docs": "Почитать документацию",
|
||||||
"open_github": "Open GitHub repository",
|
"open_github": "Открыть GitHub репозиторий",
|
||||||
"open_keybindings": "Keyboard shortcuts",
|
"open_keybindings": "Горячие клавиши",
|
||||||
"social": "Social",
|
"social": "Соц. сети",
|
||||||
"title": "General"
|
"title": "Общее"
|
||||||
},
|
},
|
||||||
"graphql": {
|
"graphql": {
|
||||||
"connect": "Connect to server",
|
"connect": "Подключиться к серверу",
|
||||||
"disconnect": "Disconnect from server"
|
"disconnect": "Отключиться от сервера"
|
||||||
},
|
},
|
||||||
"miscellaneous": {
|
"miscellaneous": {
|
||||||
"invite": "Invite your friends to Hoppscotch",
|
"invite": "Пригласить друзей в Hoppscotch",
|
||||||
"title": "Miscellaneous"
|
"title": "Другое"
|
||||||
|
},
|
||||||
|
"phrases": {
|
||||||
|
"create_environment": "Создать окружение",
|
||||||
|
"create_workspace": "Создать пространство",
|
||||||
|
"import_collections": "Импортировать коллекцию",
|
||||||
|
"share_request": "Поделиться запросом",
|
||||||
|
"try": "Попробовать"
|
||||||
},
|
},
|
||||||
"request": {
|
"request": {
|
||||||
"save_as_new": "Save as new request",
|
"save_as_new": "Сохранить как новый запрос",
|
||||||
"select_method": "Select method",
|
"select_method": "Выбрать метод",
|
||||||
"switch_to": "Switch to",
|
"switch_to": "Переключиться",
|
||||||
"tab_authorization": "Authorization tab",
|
"tab_authorization": "На вкладку авторизации",
|
||||||
"tab_body": "Body tab",
|
"tab_body": "На вкладку тела запроса",
|
||||||
"tab_headers": "Headers tab",
|
"tab_headers": "На вкладку заголовков",
|
||||||
"tab_parameters": "Parameters tab",
|
"tab_parameters": "На вкладку параметров",
|
||||||
"tab_pre_request_script": "Pre-request script tab",
|
"tab_pre_request_script": "На вкладку пред-скрипта запроса",
|
||||||
"tab_query": "Query tab",
|
"tab_query": "На вкладку запроса",
|
||||||
"tab_tests": "Tests tab",
|
"tab_tests": "На вкладку тестов",
|
||||||
"tab_variables": "Variables tab"
|
"tab_variables": "На вкладку переменных запроса"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"copy": "Copy response",
|
"copy": "Копировать содержимое ответа",
|
||||||
"download": "Download response as file",
|
"download": "Сказать содержимое ответа как файл",
|
||||||
"title": "Response"
|
"title": "Ответ запроса"
|
||||||
},
|
},
|
||||||
"section": {
|
"section": {
|
||||||
"interceptor": "Interceptor",
|
"interceptor": "Перехватчик",
|
||||||
"interface": "Interface",
|
"interface": "Интерфейс",
|
||||||
"theme": "Theme",
|
"theme": "Внешний вид",
|
||||||
"user": "User"
|
"user": "Пользователь"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"change_interceptor": "Change Interceptor",
|
"change_interceptor": "Изменить перехватчик",
|
||||||
"change_language": "Change Language",
|
"change_language": "Изменить язык",
|
||||||
"theme": {
|
"theme": {
|
||||||
"black": "Black",
|
"black": "Черная",
|
||||||
"dark": "Dark",
|
"dark": "Темная",
|
||||||
"light": "Light",
|
"light": "Светлая",
|
||||||
"system": "System preference"
|
"system": "Как задано в системе"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tab": {
|
"tab": {
|
||||||
"close_current": "Close current tab",
|
"close_current": "Закрыть текущую вкладку",
|
||||||
"close_others": "Close all other tabs",
|
"close_others": "Закрыть все вкладки",
|
||||||
"duplicate": "Duplicate current tab",
|
"duplicate": "Продублировать текущую вкладку",
|
||||||
"new_tab": "Open a new tab",
|
"new_tab": "Открыть в новой вкладке",
|
||||||
"title": "Tabs"
|
"title": "Вкладки"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"delete": "Delete current team",
|
"delete": "Удалить текущую команду",
|
||||||
"edit": "Edit current team",
|
"edit": "Редактировать текущую команду",
|
||||||
"invite": "Invite people to team",
|
"invite": "Пригласить людей в команду",
|
||||||
"new": "Create new team",
|
"new": "Создать новую команду",
|
||||||
"switch_to_personal": "Switch to your personal workspace",
|
"switch_to_personal": "Переключить на персональное пространство",
|
||||||
"title": "Teams"
|
"title": "Команды"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sse": {
|
"sse": {
|
||||||
@@ -824,7 +871,7 @@
|
|||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"bulk_mode": "Множественное редактирование",
|
"bulk_mode": "Множественное редактирование",
|
||||||
"bulk_mode_placeholder": "Каждый параметр должен начинаться с новой строки\nКлючи и значения разедляются двоеточием\nИспользуйте # для комментария",
|
"bulk_mode_placeholder": "Каждый параметр должен начинаться с новой строки\nКлючи и значения разделяются двоеточием\nИспользуйте # для комментария",
|
||||||
"cleared": "Очищено",
|
"cleared": "Очищено",
|
||||||
"connected": "Связаны",
|
"connected": "Связаны",
|
||||||
"connected_to": "Подключено к {name}",
|
"connected_to": "Подключено к {name}",
|
||||||
@@ -843,20 +890,20 @@
|
|||||||
"download_failed": "Download failed",
|
"download_failed": "Download failed",
|
||||||
"download_started": "Скачивание началось",
|
"download_started": "Скачивание началось",
|
||||||
"enabled": "Включено",
|
"enabled": "Включено",
|
||||||
"file_imported": "Файл импортирован",
|
"file_imported": "Файл успешно импортирован",
|
||||||
"finished_in": "Завершено через {duration} мс",
|
"finished_in": "Завершено через {duration} мс",
|
||||||
"hide": "Hide",
|
"hide": "Скрыть",
|
||||||
"history_deleted": "История удалена",
|
"history_deleted": "История удалена",
|
||||||
"linewrap": "Обернуть линии",
|
"linewrap": "Обернуть линии",
|
||||||
"loading": "Загрузка...",
|
"loading": "Загрузка...",
|
||||||
"message_received": "Сообщение: {message} получено по топику: {topic}",
|
"message_received": "Сообщение: {message} получено по топику: {topic}",
|
||||||
"mqtt_subscription_failed": "Что-то пошло не так, при попытке подписаться на топик: {topic}",
|
"mqtt_subscription_failed": "Что-то пошло не так, при попытке подписаться на топик: {topic}",
|
||||||
"none": "Никто",
|
"none": "Не задан",
|
||||||
"nothing_found": "Ничего не найдено для",
|
"nothing_found": "Ничего не найдено для",
|
||||||
"published_error": "Что-то пошло не так при попытке опубликовать сообщение в топик {topic}: {message}",
|
"published_error": "Что-то пошло не так при попытке опубликовать сообщение в топик {topic}: {message}",
|
||||||
"published_message": "Опубликовано сообщение: {message} в топик: {topic}",
|
"published_message": "Опубликовано сообщение: {message} в топик: {topic}",
|
||||||
"reconnection_error": "Не удалось переподключиться",
|
"reconnection_error": "Не удалось переподключиться",
|
||||||
"show": "Show",
|
"show": "Показать",
|
||||||
"subscribed_failed": "Не удалось подписаться на топик: {topic}",
|
"subscribed_failed": "Не удалось подписаться на топик: {topic}",
|
||||||
"subscribed_success": "Успешно подписался на топик: {topic}",
|
"subscribed_success": "Успешно подписался на топик: {topic}",
|
||||||
"unsubscribed_failed": "Не удалось отписаться от топика: {topic}",
|
"unsubscribed_failed": "Не удалось отписаться от топика: {topic}",
|
||||||
@@ -871,7 +918,6 @@
|
|||||||
"forum": "Задавайте вопросы и получайте ответы",
|
"forum": "Задавайте вопросы и получайте ответы",
|
||||||
"github": "Подпишитесь на нас на Github",
|
"github": "Подпишитесь на нас на Github",
|
||||||
"shortcuts": "Просматривайте приложение быстрее",
|
"shortcuts": "Просматривайте приложение быстрее",
|
||||||
"team": "Свяжитесь с командой",
|
|
||||||
"title": "Служба поддержки",
|
"title": "Служба поддержки",
|
||||||
"twitter": "Следуйте за нами на Twitter"
|
"twitter": "Следуйте за нами на Twitter"
|
||||||
},
|
},
|
||||||
@@ -882,7 +928,7 @@
|
|||||||
"close_others": "Закрыть остальные вкладки",
|
"close_others": "Закрыть остальные вкладки",
|
||||||
"collections": "Коллекции",
|
"collections": "Коллекции",
|
||||||
"documentation": "Документация",
|
"documentation": "Документация",
|
||||||
"duplicate": "Duplicate Tab",
|
"duplicate": "Дублировать вкладку",
|
||||||
"environments": "Окружения",
|
"environments": "Окружения",
|
||||||
"headers": "Заголовки",
|
"headers": "Заголовки",
|
||||||
"history": "История",
|
"history": "История",
|
||||||
@@ -892,7 +938,8 @@
|
|||||||
"queries": "Запросы",
|
"queries": "Запросы",
|
||||||
"query": "Запрос",
|
"query": "Запрос",
|
||||||
"schema": "Схема",
|
"schema": "Схема",
|
||||||
"shared_requests": "Shared Requests",
|
"shared_requests": "Запросы в общем доступе",
|
||||||
|
"share_tab_request": "Поделиться запросом",
|
||||||
"socketio": "Socket.IO",
|
"socketio": "Socket.IO",
|
||||||
"sse": "SSE",
|
"sse": "SSE",
|
||||||
"tests": "Тесты",
|
"tests": "Тесты",
|
||||||
@@ -921,7 +968,6 @@
|
|||||||
"invite_tooltip": "Пригласить людей в Ваше рабочее пространство",
|
"invite_tooltip": "Пригласить людей в Ваше рабочее пространство",
|
||||||
"invited_to_team": "{owner} приглашает Вас присоединиться к команде {team}",
|
"invited_to_team": "{owner} приглашает Вас присоединиться к команде {team}",
|
||||||
"join": "Приглашение принято",
|
"join": "Приглашение принято",
|
||||||
"join_beta": "Присоединяйтесь к бета-программе, чтобы получить доступ к командам.",
|
|
||||||
"join_team": "Присоединиться к {team}",
|
"join_team": "Присоединиться к {team}",
|
||||||
"joined_team": "Вы присоединились к команде {team}",
|
"joined_team": "Вы присоединились к команде {team}",
|
||||||
"joined_team_description": "Теперь Вы участник этой команды",
|
"joined_team_description": "Теперь Вы участник этой команды",
|
||||||
@@ -950,6 +996,7 @@
|
|||||||
"permissions": "Разрешения",
|
"permissions": "Разрешения",
|
||||||
"same_target_destination": "Таже цель и конечная точка",
|
"same_target_destination": "Таже цель и конечная точка",
|
||||||
"saved": "Команда сохранена",
|
"saved": "Команда сохранена",
|
||||||
|
"search_title": "Team Requests",
|
||||||
"select_a_team": "Выбрать команду",
|
"select_a_team": "Выбрать команду",
|
||||||
"success_invites": "Принятые приглашения",
|
"success_invites": "Принятые приглашения",
|
||||||
"title": "Команды",
|
"title": "Команды",
|
||||||
@@ -981,16 +1028,8 @@
|
|||||||
"workspace": {
|
"workspace": {
|
||||||
"change": "Изменить пространство",
|
"change": "Изменить пространство",
|
||||||
"personal": "Моё пространство",
|
"personal": "Моё пространство",
|
||||||
|
"other_workspaces": "Пространства",
|
||||||
"team": "Пространство команды",
|
"team": "Пространство команды",
|
||||||
"title": "Рабочие пространства"
|
"title": "Рабочие пространства"
|
||||||
},
|
|
||||||
"shortcodes": {
|
|
||||||
"actions": "Действия",
|
|
||||||
"created_on": "Создано",
|
|
||||||
"deleted": "Удалёна",
|
|
||||||
"method": "Метод",
|
|
||||||
"not_found": "Короткая ссылка не найдена",
|
|
||||||
"short_code": "Короткая ссылка",
|
|
||||||
"url": "URL"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,9 +68,9 @@
|
|||||||
"developer_option": "Developer options",
|
"developer_option": "Developer options",
|
||||||
"developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.",
|
"developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"documentation": "Dökümanlar",
|
"documentation": "Dokümanlar",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
"help": "Yardım, geri bildirim ve dökümanlar",
|
"help": "Yardım, geri bildirim ve dokümanlar",
|
||||||
"home": "Ana sayfa",
|
"home": "Ana sayfa",
|
||||||
"invite": "Davet et",
|
"invite": "Davet et",
|
||||||
"invite_description": "Hoppscotch'ta API'lerinizi oluşturmak ve yönetmek için basit ve sezgisel bir arayüz tasarladık. Hoppscotch, API'lerinizi oluşturmanıza, test etmenize, belgelemenize ve paylaşmanıza yardımcı olan bir araçtır.",
|
"invite_description": "Hoppscotch'ta API'lerinizi oluşturmak ve yönetmek için basit ve sezgisel bir arayüz tasarladık. Hoppscotch, API'lerinizi oluşturmanıza, test etmenize, belgelemenize ve paylaşmanıza yardımcı olan bir araçtır.",
|
||||||
@@ -225,7 +225,7 @@
|
|||||||
"body": "Bu isteğin bir gövdesi yok",
|
"body": "Bu isteğin bir gövdesi yok",
|
||||||
"collection": "Koleksiyon boş",
|
"collection": "Koleksiyon boş",
|
||||||
"collections": "Koleksiyonlar boş",
|
"collections": "Koleksiyonlar boş",
|
||||||
"documentation": "Dökümanları görmek için GraphQL uç noktasını bağlayın",
|
"documentation": "Dokümanları görmek için GraphQL uç noktasını bağlayın",
|
||||||
"endpoint": "Uç nokta boş olamaz",
|
"endpoint": "Uç nokta boş olamaz",
|
||||||
"environments": "Ortamlar boş",
|
"environments": "Ortamlar boş",
|
||||||
"folder": "Klasör boş",
|
"folder": "Klasör boş",
|
||||||
@@ -735,7 +735,7 @@
|
|||||||
"url": "Bağlantı"
|
"url": "Bağlantı"
|
||||||
},
|
},
|
||||||
"spotlight": {
|
"spotlight": {
|
||||||
"change_language": "Change Language",
|
"change_language": "Dil Değiştir",
|
||||||
"environments": {
|
"environments": {
|
||||||
"delete": "Delete current environment",
|
"delete": "Delete current environment",
|
||||||
"duplicate": "Duplicate current environment",
|
"duplicate": "Duplicate current environment",
|
||||||
@@ -744,7 +744,7 @@
|
|||||||
"edit_global": "Edit global environment",
|
"edit_global": "Edit global environment",
|
||||||
"new": "Create new environment",
|
"new": "Create new environment",
|
||||||
"new_variable": "Create a new environment variable",
|
"new_variable": "Create a new environment variable",
|
||||||
"title": "Environments"
|
"title": "Ortamlar"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"chat": "Chat with support",
|
"chat": "Chat with support",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/common",
|
"name": "@hoppscotch/common",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2024.3.1",
|
"version": "2024.3.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
"test": "vitest --run",
|
"test": "vitest --run",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"@hoppscotch/codemirror-lang-graphql": "workspace:^",
|
"@hoppscotch/codemirror-lang-graphql": "workspace:^",
|
||||||
"@hoppscotch/data": "workspace:^",
|
"@hoppscotch/data": "workspace:^",
|
||||||
"@hoppscotch/js-sandbox": "workspace:^",
|
"@hoppscotch/js-sandbox": "workspace:^",
|
||||||
"@hoppscotch/ui": "0.1.0",
|
"@hoppscotch/ui": "0.1.4",
|
||||||
"@hoppscotch/vue-toasted": "0.1.0",
|
"@hoppscotch/vue-toasted": "0.1.0",
|
||||||
"@lezer/highlight": "1.2.0",
|
"@lezer/highlight": "1.2.0",
|
||||||
"@unhead/vue": "1.8.8",
|
"@unhead/vue": "1.8.8",
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
"axios": "1.6.2",
|
"axios": "1.6.2",
|
||||||
"buffer": "6.0.3",
|
"buffer": "6.0.3",
|
||||||
"cookie-es": "1.0.0",
|
"cookie-es": "1.0.0",
|
||||||
"dioc": "1.0.1",
|
"dioc": "3.0.1",
|
||||||
"esprima": "4.0.1",
|
"esprima": "4.0.1",
|
||||||
"events": "3.3.0",
|
"events": "3.3.0",
|
||||||
"fp-ts": "2.16.1",
|
"fp-ts": "2.16.1",
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"util": "0.12.5",
|
"util": "0.12.5",
|
||||||
"uuid": "9.0.1",
|
"uuid": "9.0.1",
|
||||||
"verzod": "0.2.2",
|
"verzod": "0.2.2",
|
||||||
"vue": "3.3.9",
|
"vue": "3.4.27",
|
||||||
"vue-i18n": "9.8.0",
|
"vue-i18n": "9.8.0",
|
||||||
"vue-pdf-embed": "1.2.1",
|
"vue-pdf-embed": "1.2.1",
|
||||||
"vue-router": "4.2.5",
|
"vue-router": "4.2.5",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ declare module 'vue' {
|
|||||||
AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default']
|
AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default']
|
||||||
AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default']
|
AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default']
|
||||||
AppSpotlightEntryRESTTeamRequestEntry: typeof import('./components/app/spotlight/entry/RESTTeamRequestEntry.vue')['default']
|
AppSpotlightEntryRESTTeamRequestEntry: typeof import('./components/app/spotlight/entry/RESTTeamRequestEntry.vue')['default']
|
||||||
|
AppSpotlightSearch: typeof import('./components/app/SpotlightSearch.vue')['default']
|
||||||
AppSupport: typeof import('./components/app/Support.vue')['default']
|
AppSupport: typeof import('./components/app/Support.vue')['default']
|
||||||
Collections: typeof import('./components/collections/index.vue')['default']
|
Collections: typeof import('./components/collections/index.vue')['default']
|
||||||
CollectionsAdd: typeof import('./components/collections/Add.vue')['default']
|
CollectionsAdd: typeof import('./components/collections/Add.vue')['default']
|
||||||
@@ -180,6 +181,10 @@ declare module 'vue' {
|
|||||||
LensesRenderersVideoLensRenderer: typeof import('./components/lenses/renderers/VideoLensRenderer.vue')['default']
|
LensesRenderersVideoLensRenderer: typeof import('./components/lenses/renderers/VideoLensRenderer.vue')['default']
|
||||||
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
|
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
|
||||||
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
|
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
|
||||||
|
NewCollections: typeof import('./components/new-collections/index.vue')['default']
|
||||||
|
NewCollectionsRest: typeof import('./components/new-collections/rest/index.vue')['default']
|
||||||
|
NewCollectionsRestCollection: typeof import('./components/new-collections/rest/Collection.vue')['default']
|
||||||
|
NewCollectionsRestRequest: typeof import('./components/new-collections/rest/Request.vue')['default']
|
||||||
ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default']
|
ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default']
|
||||||
RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default']
|
RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default']
|
||||||
RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default']
|
RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default']
|
||||||
@@ -211,6 +216,8 @@ declare module 'vue' {
|
|||||||
TeamsTeam: typeof import('./components/teams/Team.vue')['default']
|
TeamsTeam: typeof import('./components/teams/Team.vue')['default']
|
||||||
Tippy: typeof import('vue-tippy')['Tippy']
|
Tippy: typeof import('vue-tippy')['Tippy']
|
||||||
WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default']
|
WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default']
|
||||||
|
WorkspacePersonalWorkspaceSelector: typeof import('./components/workspace/PersonalWorkspaceSelector.vue')['default']
|
||||||
WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default']
|
WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default']
|
||||||
|
WorkspaceTestWorkspaceSelector: typeof import('./components/workspace/TestWorkspaceSelector.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,208 +2,222 @@
|
|||||||
<div>
|
<div>
|
||||||
<header
|
<header
|
||||||
ref="headerRef"
|
ref="headerRef"
|
||||||
class="grid grid-cols-5 grid-rows-1 gap-2 overflow-x-auto overflow-y-hidden p-2"
|
class="flex flex-1 flex-shrink-0 items-center justify-between space-x-2 overflow-x-auto overflow-y-hidden px-2 py-2"
|
||||||
@mousedown.prevent="platform.ui?.appHeader?.onHeaderAreaClick?.()"
|
@mousedown.prevent="platform.ui?.appHeader?.onHeaderAreaClick?.()"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="col-span-2 flex items-center justify-between space-x-2"
|
class="inline-flex flex-1 items-center justify-start space-x-2"
|
||||||
:style="{
|
:style="{
|
||||||
paddingTop: platform.ui?.appHeader?.paddingTop?.value,
|
paddingTop: platform.ui?.appHeader?.paddingTop?.value,
|
||||||
paddingLeft: platform.ui?.appHeader?.paddingLeft?.value,
|
paddingLeft: platform.ui?.appHeader?.paddingLeft?.value,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<HoppButtonSecondary
|
||||||
|
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 flex-1 items-center justify-center space-x-2">
|
||||||
|
<button
|
||||||
|
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="svg-icons mr-2" />
|
||||||
|
{{ t("app.search") }}
|
||||||
|
</span>
|
||||||
|
<span class="flex space-x-1">
|
||||||
|
<kbd class="shortcut-key">{{ getPlatformSpecialKey() }}</kbd>
|
||||||
|
<kbd class="shortcut-key">K</kbd>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-if="showInstallButton"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('header.install_pwa')"
|
||||||
|
:icon="IconDownload"
|
||||||
|
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||||
|
@click="installPWA()"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
|
:title="`${
|
||||||
|
mdAndLarger ? t('support.title') : t('app.options')
|
||||||
|
} <kbd>?</kbd>`"
|
||||||
|
:icon="IconLifeBuoy"
|
||||||
|
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||||
|
@click="invokeAction('modals.support.toggle')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
class="!font-bold uppercase tracking-wide !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark"
|
:icon="IconUploadCloud"
|
||||||
:label="t('app.name')"
|
:label="t('header.save_workspace')"
|
||||||
to="/"
|
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
|
||||||
|
:label="t('header.login')"
|
||||||
|
@click="invokeAction('modals.login.toggle')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-else class="inline-flex items-center space-x-2">
|
||||||
<div class="col-span-1 flex items-center justify-between space-x-2">
|
<TeamsMemberStack
|
||||||
<AppSpotlightSearch />
|
v-if="
|
||||||
</div>
|
workspace.type === 'team' &&
|
||||||
<div class="col-span-2 flex items-center justify-between space-x-2">
|
selectedTeam &&
|
||||||
<div class="flex">
|
selectedTeam.teamMembers.length > 1
|
||||||
<HoppButtonSecondary
|
"
|
||||||
v-if="showInstallButton"
|
:team-members="selectedTeam.teamMembers"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
show-count
|
||||||
:title="t('header.install_pwa')"
|
class="mx-2"
|
||||||
:icon="IconDownload"
|
@handle-click="handleTeamEdit()"
|
||||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
|
||||||
@click="installPWA()"
|
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
|
||||||
:title="`${
|
|
||||||
mdAndLarger ? t('support.title') : t('app.options')
|
|
||||||
} <kbd>?</kbd>`"
|
|
||||||
:icon="IconLifeBuoy"
|
|
||||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
|
||||||
@click="invokeAction('modals.support.toggle')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
<div
|
<div
|
||||||
v-if="currentUser === null"
|
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"
|
||||||
class="inline-flex items-center space-x-2"
|
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:icon="IconUploadCloud"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:label="t('header.save_workspace')"
|
:title="t('team.invite_tooltip')"
|
||||||
class="!focus-visible:text-emerald-600 !hover:text-emerald-600 hidden h-8 border border-emerald-600/25 bg-emerald-500/10 !text-emerald-500 hover:border-emerald-600/20 hover:bg-emerald-600/20 focus-visible:border-emerald-600/20 focus-visible:bg-emerald-600/20 md:flex"
|
:icon="IconUserPlus"
|
||||||
@click="invokeAction('modals.login.toggle')"
|
class="py-1.75 !focus-visible:text-green-600 !hover:text-green-600 !text-green-500"
|
||||||
|
@click="handleInvite()"
|
||||||
/>
|
/>
|
||||||
<HoppButtonPrimary
|
<HoppButtonSecondary
|
||||||
:label="t('header.login')"
|
|
||||||
class="h-8"
|
|
||||||
@click="invokeAction('modals.login.toggle')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else class="inline-flex items-center space-x-2">
|
|
||||||
<TeamsMemberStack
|
|
||||||
v-if="
|
v-if="
|
||||||
workspace.type === 'team' &&
|
workspace.type === 'team' &&
|
||||||
selectedTeam &&
|
selectedTeam &&
|
||||||
selectedTeam.teamMembers.length > 1
|
selectedTeam?.myRole === 'OWNER'
|
||||||
"
|
"
|
||||||
:team-members="selectedTeam.teamMembers"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
show-count
|
:title="t('team.edit')"
|
||||||
class="mx-2"
|
:icon="IconSettings"
|
||||||
@handle-click="handleTeamEdit()"
|
class="py-1.75 !focus-visible:text-green-600 !hover:text-green-600 !text-green-500"
|
||||||
|
@click="handleTeamEdit()"
|
||||||
/>
|
/>
|
||||||
<div
|
</div>
|
||||||
class="flex h-8 divide-x divide-emerald-600/25 rounded border border-emerald-600/25 bg-emerald-500/10 focus-within:divide-emerald-600/20 focus-within:border-emerald-600/20 focus-within:bg-emerald-600/20 hover:divide-emerald-600/20 hover:border-emerald-600/20 hover:bg-emerald-600/20"
|
<tippy
|
||||||
>
|
interactive
|
||||||
<HoppButtonSecondary
|
trigger="click"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
theme="popover"
|
||||||
:title="t('team.invite_tooltip')"
|
:on-shown="() => accountActions.focus()"
|
||||||
:icon="IconUserPlus"
|
>
|
||||||
class="!focus-visible:text-emerald-600 !hover:text-emerald-600 !text-emerald-500"
|
<HoppButtonSecondary
|
||||||
@click="handleInvite()"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
/>
|
:title="t('workspace.change')"
|
||||||
<HoppButtonSecondary
|
:label="mdAndLarger ? activeWorkspaceName : ``"
|
||||||
v-if="
|
:icon="activeWorkspaceIcon"
|
||||||
workspace.type === 'team' &&
|
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"
|
||||||
selectedTeam &&
|
/>
|
||||||
selectedTeam?.myRole === 'OWNER'
|
<template #content="{ hide }">
|
||||||
"
|
<div
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
ref="accountActions"
|
||||||
:title="t('team.edit')"
|
class="flex flex-col focus:outline-none"
|
||||||
:icon="IconSettings"
|
tabindex="0"
|
||||||
class="!focus-visible:text-emerald-600 !hover:text-emerald-600 !text-emerald-500"
|
@keyup.escape="hide()"
|
||||||
@click="handleTeamEdit()"
|
@click="hide()"
|
||||||
/>
|
>
|
||||||
</div>
|
<WorkspaceSelector />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
<span class="px-2">
|
||||||
<tippy
|
<tippy
|
||||||
interactive
|
interactive
|
||||||
trigger="click"
|
trigger="click"
|
||||||
theme="popover"
|
theme="popover"
|
||||||
:on-shown="() => accountActions.focus()"
|
:on-shown="() => tippyActions.focus()"
|
||||||
>
|
>
|
||||||
<HoppSmartSelectWrapper
|
<HoppSmartPicture
|
||||||
class="!text-blue-500 !focus-visible:text-blue-600 !hover:text-blue-600"
|
v-if="currentUser.photoURL"
|
||||||
>
|
v-tippy="{
|
||||||
<HoppButtonSecondary
|
theme: 'tooltip',
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
}"
|
||||||
:title="t('workspace.change')"
|
:url="currentUser.photoURL"
|
||||||
:label="mdAndLarger ? workspaceName : ``"
|
:alt="
|
||||||
:icon="workspace.type === 'personal' ? IconUser : IconUsers"
|
currentUser.displayName ||
|
||||||
class="!focus-visible:text-blue-600 !hover:text-blue-600 h-8 rounded border border-blue-600/25 bg-blue-500/10 pr-8 !text-blue-500 hover:border-blue-600/20 hover:bg-blue-600/20 focus-visible:border-blue-600/20 focus-visible:bg-blue-600/20"
|
t('profile.default_hopp_displayname')
|
||||||
/>
|
"
|
||||||
</HoppSmartSelectWrapper>
|
:title="
|
||||||
|
currentUser.displayName ||
|
||||||
|
currentUser.email ||
|
||||||
|
t('profile.default_hopp_displayname')
|
||||||
|
"
|
||||||
|
indicator
|
||||||
|
:indicator-styles="
|
||||||
|
network.isOnline ? 'bg-green-500' : 'bg-red-500'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartPicture
|
||||||
|
v-else
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="
|
||||||
|
currentUser.displayName ||
|
||||||
|
currentUser.email ||
|
||||||
|
t('profile.default_hopp_displayname')
|
||||||
|
"
|
||||||
|
:initial="currentUser.displayName || currentUser.email"
|
||||||
|
indicator
|
||||||
|
:indicator-styles="
|
||||||
|
network.isOnline ? 'bg-green-500' : 'bg-red-500'
|
||||||
|
"
|
||||||
|
/>
|
||||||
<template #content="{ hide }">
|
<template #content="{ hide }">
|
||||||
<div
|
<div
|
||||||
ref="accountActions"
|
ref="tippyActions"
|
||||||
class="flex flex-col focus:outline-none"
|
class="flex flex-col focus:outline-none"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
@keyup.p="profile.$el.click()"
|
||||||
|
@keyup.s="settings.$el.click()"
|
||||||
|
@keyup.l="logout.$el.click()"
|
||||||
@keyup.escape="hide()"
|
@keyup.escape="hide()"
|
||||||
@click="hide()"
|
|
||||||
>
|
>
|
||||||
<WorkspaceSelector />
|
<div class="flex flex-col px-2 text-tiny">
|
||||||
|
<span class="inline-flex truncate font-semibold">
|
||||||
|
{{
|
||||||
|
currentUser.displayName ||
|
||||||
|
t("profile.default_hopp_displayname")
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span class="inline-flex truncate text-secondaryLight">
|
||||||
|
{{ currentUser.email }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="profile"
|
||||||
|
to="/profile"
|
||||||
|
:icon="IconUser"
|
||||||
|
:label="t('navigation.profile')"
|
||||||
|
:shortcut="['P']"
|
||||||
|
@click="hide()"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="settings"
|
||||||
|
to="/settings"
|
||||||
|
:icon="IconSettings"
|
||||||
|
:label="t('navigation.settings')"
|
||||||
|
:shortcut="['S']"
|
||||||
|
@click="hide()"
|
||||||
|
/>
|
||||||
|
<FirebaseLogout
|
||||||
|
ref="logout"
|
||||||
|
:shortcut="['L']"
|
||||||
|
@confirm-logout="hide()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</tippy>
|
</tippy>
|
||||||
<span class="px-2">
|
</span>
|
||||||
<tippy
|
|
||||||
interactive
|
|
||||||
trigger="click"
|
|
||||||
theme="popover"
|
|
||||||
:on-shown="() => tippyActions.focus()"
|
|
||||||
>
|
|
||||||
<HoppSmartPicture
|
|
||||||
v-tippy="{
|
|
||||||
theme: 'tooltip',
|
|
||||||
}"
|
|
||||||
:name="currentUser.uid"
|
|
||||||
:title="
|
|
||||||
currentUser.displayName ||
|
|
||||||
currentUser.email ||
|
|
||||||
t('profile.default_hopp_displayname')
|
|
||||||
"
|
|
||||||
indicator
|
|
||||||
:indicator-styles="
|
|
||||||
network.isOnline ? 'bg-green-500' : 'bg-red-500'
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<template #content="{ hide }">
|
|
||||||
<div
|
|
||||||
ref="tippyActions"
|
|
||||||
class="flex flex-col focus:outline-none"
|
|
||||||
tabindex="0"
|
|
||||||
@keyup.p="profile.$el.click()"
|
|
||||||
@keyup.s="settings.$el.click()"
|
|
||||||
@keyup.l="logout.$el.click()"
|
|
||||||
@keyup.escape="hide()"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col px-2">
|
|
||||||
<span class="inline-flex truncate font-semibold">
|
|
||||||
{{
|
|
||||||
currentUser.displayName ||
|
|
||||||
t("profile.default_hopp_displayname")
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="inline-flex truncate text-secondaryLight text-tiny"
|
|
||||||
>
|
|
||||||
{{ currentUser.email }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="profile"
|
|
||||||
to="/profile"
|
|
||||||
:icon="IconUser"
|
|
||||||
:label="t('navigation.profile')"
|
|
||||||
:shortcut="['P']"
|
|
||||||
@click="hide()"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="settings"
|
|
||||||
to="/settings"
|
|
||||||
:icon="IconSettings"
|
|
||||||
:label="t('navigation.settings')"
|
|
||||||
:shortcut="['S']"
|
|
||||||
@click="hide()"
|
|
||||||
/>
|
|
||||||
<FirebaseLogout
|
|
||||||
ref="logout"
|
|
||||||
:shortcut="['L']"
|
|
||||||
@confirm-logout="hide()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</tippy>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<AppBanner
|
<AppBanner v-if="bannerContent" :banner="bannerContent" />
|
||||||
v-if="bannerContent"
|
|
||||||
:banner="bannerContent"
|
|
||||||
@dismiss="dismissOfflineBanner"
|
|
||||||
/>
|
|
||||||
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
|
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
|
||||||
<TeamsInvite
|
<TeamsInvite
|
||||||
v-if="workspace.type === 'team' && workspace.teamID"
|
v-if="workspace.type === 'team' && workspace.teamID"
|
||||||
@@ -219,6 +233,7 @@
|
|||||||
@invite-team="inviteTeam(editingTeamName, editingTeamID)"
|
@invite-team="inviteTeam(editingTeamName, editingTeamID)"
|
||||||
@refetch-teams="refetchTeams"
|
@refetch-teams="refetchTeams"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
:show="confirmRemove"
|
:show="confirmRemove"
|
||||||
:title="t('confirm.remove_team')"
|
:title="t('confirm.remove_team')"
|
||||||
@@ -232,29 +247,31 @@
|
|||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { installPWA, pwaDefferedPrompt } from "@modules/pwa"
|
import { installPWA, pwaDefferedPrompt } from "@modules/pwa"
|
||||||
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import * as TE from "fp-ts/TaskEither"
|
||||||
|
import { pipe } from "fp-ts/function"
|
||||||
import { computed, reactive, ref, watch } from "vue"
|
import { computed, reactive, ref, watch } from "vue"
|
||||||
|
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql"
|
||||||
|
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
||||||
|
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import {
|
||||||
|
BANNER_PRIORITY_HIGH,
|
||||||
|
BannerContent,
|
||||||
|
BannerService,
|
||||||
|
} from "~/services/banner.service"
|
||||||
|
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||||
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
import IconDownload from "~icons/lucide/download"
|
import IconDownload from "~icons/lucide/download"
|
||||||
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
||||||
import IconSettings from "~icons/lucide/settings"
|
import IconSettings from "~icons/lucide/settings"
|
||||||
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
||||||
import IconUser from "~icons/lucide/user"
|
import IconUser from "~icons/lucide/user"
|
||||||
import IconUserPlus from "~icons/lucide/user-plus"
|
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"
|
|
||||||
import {
|
|
||||||
BannerService,
|
|
||||||
BannerContent,
|
|
||||||
BANNER_PRIORITY_HIGH,
|
|
||||||
} from "~/services/banner.service"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -277,11 +294,10 @@ const bannerContent = computed(() => banner.content.value?.content)
|
|||||||
let bannerID: number | null = null
|
let bannerID: number | null = null
|
||||||
|
|
||||||
const offlineBanner: BannerContent = {
|
const offlineBanner: BannerContent = {
|
||||||
type: "warning",
|
type: "info",
|
||||||
text: (t) => t("helpers.offline"),
|
text: (t) => t("helpers.offline"),
|
||||||
alternateText: (t) => t("helpers.offline_short"),
|
alternateText: (t) => t("helpers.offline_short"),
|
||||||
score: BANNER_PRIORITY_HIGH,
|
score: BANNER_PRIORITY_HIGH,
|
||||||
dismissible: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const network = reactive(useNetwork())
|
const network = reactive(useNetwork())
|
||||||
@@ -298,8 +314,6 @@ watch(isOnline, () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const dismissOfflineBanner = () => banner.removeBanner(bannerID!)
|
|
||||||
|
|
||||||
const currentUser = useReadonlyStream(
|
const currentUser = useReadonlyStream(
|
||||||
platform.auth.getProbableUserStream(),
|
platform.auth.getProbableUserStream(),
|
||||||
platform.auth.getProbableUser()
|
platform.auth.getProbableUser()
|
||||||
@@ -317,12 +331,6 @@ const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
|||||||
|
|
||||||
const workspace = workspaceService.currentWorkspace
|
const workspace = workspaceService.currentWorkspace
|
||||||
|
|
||||||
const workspaceName = computed(() => {
|
|
||||||
return workspace.value.type === "personal"
|
|
||||||
? t("workspace.personal")
|
|
||||||
: workspace.value.teamName
|
|
||||||
})
|
|
||||||
|
|
||||||
const refetchTeams = () => {
|
const refetchTeams = () => {
|
||||||
teamListAdapter.fetchList()
|
teamListAdapter.fetchList()
|
||||||
}
|
}
|
||||||
@@ -357,6 +365,23 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const newWorkspaceService = useService(NewWorkspaceService)
|
||||||
|
|
||||||
|
const activeWorkspaceName = computed(() => {
|
||||||
|
const activeWorkspaceHandleRef =
|
||||||
|
newWorkspaceService.activeWorkspaceHandle.value?.get()
|
||||||
|
|
||||||
|
if (activeWorkspaceHandleRef?.value.type === "ok") {
|
||||||
|
return activeWorkspaceHandleRef.value.data.name
|
||||||
|
}
|
||||||
|
|
||||||
|
return t("workspace.no_workspace")
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeWorkspaceIcon = computed(() => {
|
||||||
|
return newWorkspaceService.activeWorkspaceDecor.value?.value.headerCurrentIcon
|
||||||
|
})
|
||||||
|
|
||||||
const showModalInvite = ref(false)
|
const showModalInvite = ref(false)
|
||||||
const showModalEdit = ref(false)
|
const showModalEdit = ref(false)
|
||||||
|
|
||||||
@@ -380,6 +405,8 @@ const inviteTeam = (team: { name: string }, teamID: string) => {
|
|||||||
|
|
||||||
// Show the workspace selected team invite modal if the user is an owner of the team else show the default invite modal
|
// Show the workspace selected team invite modal if the user is an owner of the team else show the default invite modal
|
||||||
const handleInvite = () => {
|
const handleInvite = () => {
|
||||||
|
if (!currentUser.value) return invokeAction("modals.login.toggle")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
workspace.value.type === "team" &&
|
workspace.value.type === "team" &&
|
||||||
workspace.value.teamID &&
|
workspace.value.teamID &&
|
||||||
|
|||||||
@@ -19,52 +19,50 @@ import { UrlSource } from "~/helpers/import-export/import/import-sources/UrlSour
|
|||||||
import IconFile from "~icons/lucide/file"
|
import IconFile from "~icons/lucide/file"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hoppRESTImporter,
|
|
||||||
hoppInsomniaImporter,
|
hoppInsomniaImporter,
|
||||||
hoppPostmanImporter,
|
|
||||||
toTeamsImporter,
|
|
||||||
hoppOpenAPIImporter,
|
hoppOpenAPIImporter,
|
||||||
|
hoppPostmanImporter,
|
||||||
|
hoppRESTImporter,
|
||||||
|
toTeamsImporter,
|
||||||
} from "~/helpers/import-export/import/importers"
|
} from "~/helpers/import-export/import/importers"
|
||||||
|
|
||||||
import { defineStep } from "~/composables/step-components"
|
import { defineStep } from "~/composables/step-components"
|
||||||
|
|
||||||
|
import MyCollectionImport from "~/components/importExport/ImportExportSteps/MyCollectionImport.vue"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { appendRESTCollections, restCollections$ } from "~/newstore/collections"
|
|
||||||
import MyCollectionImport from "~/components/importExport/ImportExportSteps/MyCollectionImport.vue"
|
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
|
|
||||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
|
||||||
import IconOpenAPI from "~icons/lucide/file"
|
|
||||||
import IconPostman from "~icons/hopp/postman"
|
|
||||||
import IconInsomnia from "~icons/hopp/insomnia"
|
import IconInsomnia from "~icons/hopp/insomnia"
|
||||||
|
import IconPostman from "~icons/hopp/postman"
|
||||||
|
import IconOpenAPI from "~icons/lucide/file"
|
||||||
|
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||||
import IconGithub from "~icons/lucide/github"
|
import IconGithub from "~icons/lucide/github"
|
||||||
import IconLink from "~icons/lucide/link"
|
import IconLink from "~icons/lucide/link"
|
||||||
|
|
||||||
import IconUser from "~icons/lucide/user"
|
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
import IconUser from "~icons/lucide/user"
|
||||||
|
|
||||||
import { getTeamCollectionJSON } from "~/helpers/backend/helpers"
|
import { getTeamCollectionJSON } from "~/helpers/backend/helpers"
|
||||||
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
import { initializeDownloadFile } from "~/helpers/import-export/export"
|
||||||
import { gistExporter } from "~/helpers/import-export/export/gist"
|
import { gistExporter } from "~/helpers/import-export/export/gist"
|
||||||
import { myCollectionsExporter } from "~/helpers/import-export/export/myCollections"
|
|
||||||
import { teamCollectionsExporter } from "~/helpers/import-export/export/teamCollections"
|
import { teamCollectionsExporter } from "~/helpers/import-export/export/teamCollections"
|
||||||
|
|
||||||
import { GistSource } from "~/helpers/import-export/import/import-sources/GistSource"
|
import { useService } from "dioc/vue"
|
||||||
import { ImporterOrExporter } from "~/components/importExport/types"
|
import { ImporterOrExporter } from "~/components/importExport/types"
|
||||||
|
import { GistSource } from "~/helpers/import-export/import/import-sources/GistSource"
|
||||||
|
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||||
|
import { TeamWorkspace } from "~/services/workspace.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
|
||||||
|
|
||||||
type CollectionType =
|
type CollectionType =
|
||||||
| {
|
| {
|
||||||
type: "team-collections"
|
type: "team-collections"
|
||||||
selectedTeam: SelectedTeam
|
selectedTeam: TeamWorkspace
|
||||||
}
|
}
|
||||||
| { type: "my-collections" }
|
| { type: "my-collections" }
|
||||||
|
|
||||||
@@ -84,17 +82,45 @@ const currentUser = useReadonlyStream(
|
|||||||
platform.auth.getCurrentUser()
|
platform.auth.getCurrentUser()
|
||||||
)
|
)
|
||||||
|
|
||||||
const myCollections = useReadonlyStream(restCollections$, [])
|
const workspaceService = useService(NewWorkspaceService)
|
||||||
|
|
||||||
|
const activeWorkspaceHandle = workspaceService.activeWorkspaceHandle
|
||||||
|
|
||||||
const showImportFailedError = () => {
|
const showImportFailedError = () => {
|
||||||
toast.error(t("import.failed"))
|
toast.error(t("import.failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleImportToStore = async (collections: HoppCollection[]) => {
|
const handleImportToStore = async (collections: HoppCollection[]) => {
|
||||||
const importResult =
|
if (props.collectionsType.type === "my-collections") {
|
||||||
props.collectionsType.type === "my-collections"
|
if (!activeWorkspaceHandle.value) {
|
||||||
? await importToPersonalWorkspace(collections)
|
return E.left("INVALID_WORKSPACE_HANDLE")
|
||||||
: await importToTeamsWorkspace(collections)
|
}
|
||||||
|
|
||||||
|
const collectionHandleResult = await workspaceService.importRESTCollections(
|
||||||
|
activeWorkspaceHandle.value,
|
||||||
|
collections
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(collectionHandleResult)) {
|
||||||
|
// INVALID_WORKSPACE_HANDLE
|
||||||
|
return toast.error(t("import.failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultHandle = collectionHandleResult.right
|
||||||
|
|
||||||
|
const requestHandleRef = resultHandle.get()
|
||||||
|
|
||||||
|
if (requestHandleRef.value.type === "invalid") {
|
||||||
|
// WORKSPACE_INVALIDATED
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(t("state.file_imported"))
|
||||||
|
emit("hide-modal")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const importResult = await importToTeamsWorkspace(collections)
|
||||||
|
|
||||||
if (E.isRight(importResult)) {
|
if (E.isRight(importResult)) {
|
||||||
toast.success(t("state.file_imported"))
|
toast.success(t("state.file_imported"))
|
||||||
@@ -104,13 +130,6 @@ const handleImportToStore = async (collections: HoppCollection[]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const importToPersonalWorkspace = (collections: HoppCollection[]) => {
|
|
||||||
appendRESTCollections(collections)
|
|
||||||
return E.right({
|
|
||||||
success: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function translateToTeamCollectionFormat(x: HoppCollection) {
|
function translateToTeamCollectionFormat(x: HoppCollection) {
|
||||||
const folders: HoppCollection[] = (x.folders ?? []).map(
|
const folders: HoppCollection[] = (x.folders ?? []).map(
|
||||||
translateToTeamCollectionFormat
|
translateToTeamCollectionFormat
|
||||||
@@ -390,28 +409,36 @@ const HoppMyCollectionsExporter: ImporterOrExporter = {
|
|||||||
applicableTo: ["personal-workspace"],
|
applicableTo: ["personal-workspace"],
|
||||||
isLoading: isHoppMyCollectionExporterInProgress,
|
isLoading: isHoppMyCollectionExporterInProgress,
|
||||||
},
|
},
|
||||||
action: () => {
|
action: async () => {
|
||||||
if (!myCollections.value.length) {
|
if (!activeWorkspaceHandle.value) {
|
||||||
return toast.error(t("error.no_collections_to_export"))
|
return toast.error("error.something_went_wrong")
|
||||||
}
|
}
|
||||||
|
|
||||||
isHoppMyCollectionExporterInProgress.value = true
|
isHoppMyCollectionExporterInProgress.value = true
|
||||||
|
|
||||||
const message = initializeDownloadCollection(
|
const result = await workspaceService.exportRESTCollections(
|
||||||
myCollectionsExporter(myCollections.value),
|
activeWorkspaceHandle.value
|
||||||
"Collections"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (E.isRight(message)) {
|
// INVALID_COLLECTION_HANDLE | NO_COLLECTIONS_TO_EXPORT
|
||||||
toast.success(t(message.right))
|
if (E.isLeft(result)) {
|
||||||
|
isHoppMyCollectionExporterInProgress.value = false
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
if (result.left.error === "NO_COLLECTIONS_TO_EXPORT") {
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
exporter: "json",
|
}
|
||||||
platform: "rest",
|
|
||||||
})
|
return toast.error(t("error.something_went_wrong"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toast.success(t("state.download_started"))
|
||||||
|
|
||||||
|
platform.analytics?.logEvent({
|
||||||
|
type: "HOPP_EXPORT_COLLECTION",
|
||||||
|
exporter: "json",
|
||||||
|
platform: "rest",
|
||||||
|
})
|
||||||
|
|
||||||
isHoppMyCollectionExporterInProgress.value = false
|
isHoppMyCollectionExporterInProgress.value = false
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -433,7 +460,7 @@ const HoppTeamCollectionsExporter: ImporterOrExporter = {
|
|||||||
props.collectionsType.selectedTeam
|
props.collectionsType.selectedTeam
|
||||||
) {
|
) {
|
||||||
const res = await teamCollectionsExporter(
|
const res = await teamCollectionsExporter(
|
||||||
props.collectionsType.selectedTeam.id
|
props.collectionsType.selectedTeam.teamID
|
||||||
)
|
)
|
||||||
|
|
||||||
if (E.isRight(res)) {
|
if (E.isRight(res)) {
|
||||||
@@ -445,10 +472,7 @@ const HoppTeamCollectionsExporter: ImporterOrExporter = {
|
|||||||
return toast.error(t("error.no_collections_to_export"))
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeDownloadCollection(
|
initializeDownloadFile(exportCollectionsToJSON, "team-collections")
|
||||||
exportCollectionsToJSON,
|
|
||||||
"team-collections"
|
|
||||||
)
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
type: "HOPP_EXPORT_COLLECTION",
|
||||||
@@ -487,7 +511,7 @@ const HoppGistCollectionsExporter: ImporterOrExporter = {
|
|||||||
const collectionJSON = await getCollectionJSON()
|
const collectionJSON = await getCollectionJSON()
|
||||||
const accessToken = currentUser.value?.accessToken
|
const accessToken = currentUser.value?.accessToken
|
||||||
|
|
||||||
if (!accessToken) {
|
if (!accessToken || E.isLeft(collectionJSON)) {
|
||||||
toast.error(t("error.something_went_wrong"))
|
toast.error(t("error.something_went_wrong"))
|
||||||
isHoppGistCollectionExporterInProgress.value = false
|
isHoppGistCollectionExporterInProgress.value = false
|
||||||
return
|
return
|
||||||
@@ -569,8 +593,8 @@ const hasTeamWriteAccess = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
collectionsType.selectedTeam.myRole === "EDITOR" ||
|
collectionsType.selectedTeam.role === "EDITOR" ||
|
||||||
collectionsType.selectedTeam.myRole === "OWNER"
|
collectionsType.selectedTeam.role === "OWNER"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -578,26 +602,49 @@ const selectedTeamID = computed(() => {
|
|||||||
const { collectionsType } = props
|
const { collectionsType } = props
|
||||||
|
|
||||||
return collectionsType.type === "team-collections"
|
return collectionsType.type === "team-collections"
|
||||||
? collectionsType.selectedTeam?.id
|
? collectionsType.selectedTeam?.teamID
|
||||||
: undefined
|
: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
const getCollectionJSON = async () => {
|
const getCollectionJSON = async () => {
|
||||||
|
// TODO: Implement `getRESTCollectionJSONView` for team workspace
|
||||||
if (
|
if (
|
||||||
props.collectionsType.type === "team-collections" &&
|
props.collectionsType.type === "team-collections" &&
|
||||||
props.collectionsType.selectedTeam?.id
|
props.collectionsType.selectedTeam?.teamID
|
||||||
) {
|
) {
|
||||||
const res = await getTeamCollectionJSON(
|
const res = await getTeamCollectionJSON(
|
||||||
props.collectionsType.selectedTeam?.id
|
props.collectionsType.selectedTeam?.teamID
|
||||||
)
|
)
|
||||||
|
|
||||||
return E.isRight(res)
|
return E.isRight(res)
|
||||||
? E.right(res.right.exportCollectionsToJSON)
|
? E.right(res.right.exportCollectionsToJSON)
|
||||||
: E.left(res.left)
|
: E.left(res.left.error.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.collectionsType.type === "my-collections") {
|
if (props.collectionsType.type === "my-collections") {
|
||||||
return E.right(JSON.stringify(myCollections.value, null, 2))
|
if (!activeWorkspaceHandle.value) {
|
||||||
|
return E.left("INVALID_WORKSPACE_HANDLE")
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionJSONHandleResult =
|
||||||
|
await workspaceService.getRESTCollectionJSONView(
|
||||||
|
activeWorkspaceHandle.value
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(collectionJSONHandleResult)) {
|
||||||
|
return E.left(collectionJSONHandleResult.left.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionJSONHandle = collectionJSONHandleResult.right
|
||||||
|
|
||||||
|
const collectionJSONHandleRef = collectionJSONHandle.get()
|
||||||
|
|
||||||
|
if (collectionJSONHandleRef.value.type === "invalid") {
|
||||||
|
// WORKSPACE_INVALIDATED
|
||||||
|
return E.left("WORKSPACE_INVALIDATED")
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(collectionJSONHandleRef.value.data.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
return E.left("INVALID_SELECTED_TEAM_OR_INVALID_COLLECTION_TYPE")
|
return E.left("INVALID_SELECTED_TEAM_OR_INVALID_COLLECTION_TYPE")
|
||||||
|
|||||||
@@ -100,11 +100,15 @@ const props = withDefaults(
|
|||||||
editingProperties: EditingProperties | null
|
editingProperties: EditingProperties | null
|
||||||
source: "REST" | "GraphQL"
|
source: "REST" | "GraphQL"
|
||||||
modelValue: string
|
modelValue: string
|
||||||
|
// TODO: Purpose of this prop is to maintain backwards compatibility
|
||||||
|
// To be removed after porting all usages of this component
|
||||||
|
emitWithFullCollection: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
show: false,
|
show: false,
|
||||||
loadingState: false,
|
loadingState: false,
|
||||||
editingProperties: null,
|
editingProperties: null,
|
||||||
|
emitWithFullCollection: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -20,19 +20,25 @@
|
|||||||
<label class="p-4">
|
<label class="p-4">
|
||||||
{{ t("collection.select_location") }}
|
{{ t("collection.select_location") }}
|
||||||
</label>
|
</label>
|
||||||
<CollectionsGraphql
|
<!-- <CollectionsGraphql
|
||||||
v-if="mode === 'graphql'"
|
v-if="mode === 'graphql'"
|
||||||
:picked="picked"
|
:picked="picked"
|
||||||
:save-request="true"
|
:save-request="true"
|
||||||
@select="onSelect"
|
@select="onSelect"
|
||||||
/>
|
/> -->
|
||||||
<Collections
|
<!-- <Collections
|
||||||
v-else
|
v-else
|
||||||
:picked="picked"
|
:picked="picked"
|
||||||
:save-request="true"
|
:save-request="true"
|
||||||
@select="onSelect"
|
@select="onSelect"
|
||||||
@update-team="updateTeam"
|
@update-team="updateTeam"
|
||||||
@update-collection-type="updateCollectionType"
|
@update-collection-type="updateCollectionType"
|
||||||
|
/> -->
|
||||||
|
<NewCollections
|
||||||
|
:picked="picked"
|
||||||
|
:save-request="true"
|
||||||
|
platform="rest"
|
||||||
|
@select="onSelect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -56,51 +62,40 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, reactive, ref, watch } from "vue"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { useToast } from "@composables/toast"
|
||||||
import {
|
import {
|
||||||
HoppGQLRequest,
|
HoppGQLRequest,
|
||||||
HoppRESTRequest,
|
HoppRESTRequest,
|
||||||
isHoppRESTRequest,
|
isHoppRESTRequest,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { pipe } from "fp-ts/function"
|
|
||||||
import * as TE from "fp-ts/TaskEither"
|
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
import {
|
|
||||||
createRequestInCollection,
|
|
||||||
updateTeamRequest,
|
|
||||||
} from "~/helpers/backend/mutations/TeamRequest"
|
|
||||||
import { Picked } from "~/helpers/types/HoppPicked"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
import { useToast } from "@composables/toast"
|
|
||||||
import {
|
|
||||||
cascadeParentCollectionForHeaderAuth,
|
|
||||||
editGraphqlRequest,
|
|
||||||
editRESTRequest,
|
|
||||||
saveGraphqlRequestAs,
|
|
||||||
saveRESTRequestAs,
|
|
||||||
} from "~/newstore/collections"
|
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
|
||||||
import { computedWithControl } from "@vueuse/core"
|
import { computedWithControl } from "@vueuse/core"
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import * as E from "fp-ts/Either"
|
||||||
|
import { cloneDeep } from "lodash-es"
|
||||||
|
import { computed, nextTick, reactive, ref, watch } from "vue"
|
||||||
|
|
||||||
|
import { Picked } from "~/helpers/types/HoppPicked"
|
||||||
|
import { cascadeParentCollectionForHeaderAuth } from "~/newstore/collections"
|
||||||
|
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||||
import { GQLTabService } from "~/services/tab/graphql"
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const RESTTabs = useService(RESTTabService)
|
const RESTTabs = useService(RESTTabService)
|
||||||
const GQLTabs = useService(GQLTabService)
|
const GQLTabs = useService(GQLTabService)
|
||||||
|
const workspaceService = useService(NewWorkspaceService)
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
// type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
||||||
|
|
||||||
type CollectionType =
|
// type CollectionType =
|
||||||
| {
|
// | {
|
||||||
type: "team-collections"
|
// type: "team-collections"
|
||||||
selectedTeam: SelectedTeam
|
// selectedTeam: SelectedTeam
|
||||||
}
|
// }
|
||||||
| { type: "my-collections"; selectedTeam: undefined }
|
// | { type: "my-collections"; selectedTeam: undefined }
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -168,10 +163,10 @@ const requestData = reactive({
|
|||||||
requestIndex: undefined as number | undefined,
|
requestIndex: undefined as number | undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const collectionsType = ref<CollectionType>({
|
// const collectionsType = ref<CollectionType>({
|
||||||
type: "my-collections",
|
// type: "my-collections",
|
||||||
selectedTeam: undefined,
|
// selectedTeam: undefined,
|
||||||
})
|
// })
|
||||||
|
|
||||||
const picked = ref<Picked | null>(null)
|
const picked = ref<Picked | null>(null)
|
||||||
|
|
||||||
@@ -192,13 +187,14 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const updateTeam = (newTeam: SelectedTeam) => {
|
// TODO: To be removed
|
||||||
collectionsType.value.selectedTeam = newTeam
|
// const updateTeam = (newTeam: SelectedTeam) => {
|
||||||
}
|
// collectionsType.value.selectedTeam = newTeam
|
||||||
|
// }
|
||||||
|
|
||||||
const updateCollectionType = (type: CollectionType["type"]) => {
|
// const updateCollectionType = (type: CollectionType["type"]) => {
|
||||||
collectionsType.value.type = type
|
// collectionsType.value.type = type
|
||||||
}
|
// }
|
||||||
|
|
||||||
const onSelect = (pickedVal: Picked | null) => {
|
const onSelect = (pickedVal: Picked | null) => {
|
||||||
picked.value = pickedVal
|
picked.value = pickedVal
|
||||||
@@ -214,104 +210,109 @@ const saveRequestAs = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestUpdated =
|
const updatedRequest =
|
||||||
props.mode === "rest"
|
props.mode === "rest"
|
||||||
? cloneDeep(RESTTabs.currentActiveTab.value.document.request)
|
? cloneDeep(RESTTabs.currentActiveTab.value.document.request)
|
||||||
: cloneDeep(GQLTabs.currentActiveTab.value.document.request)
|
: cloneDeep(GQLTabs.currentActiveTab.value.document.request)
|
||||||
|
|
||||||
requestUpdated.name = requestName.value
|
updatedRequest.name = requestName.value
|
||||||
|
|
||||||
if (picked.value.pickedType === "my-collection") {
|
if (!workspaceService.activeWorkspaceHandle.value) {
|
||||||
if (!isHoppRESTRequest(requestUpdated))
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
picked.value.pickedType === "my-collection" ||
|
||||||
|
picked.value.pickedType === "my-folder"
|
||||||
|
) {
|
||||||
|
if (!isHoppRESTRequest(updatedRequest))
|
||||||
throw new Error("requestUpdated is not a REST Request")
|
throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
const insertionIndex = saveRESTRequestAs(
|
const collectionPathIndex =
|
||||||
`${picked.value.collectionIndex}`,
|
picked.value.pickedType === "my-collection"
|
||||||
requestUpdated
|
? picked.value.collectionIndex.toString()
|
||||||
|
: picked.value.folderPath
|
||||||
|
|
||||||
|
const collectionHandleResult = await workspaceService.getCollectionHandle(
|
||||||
|
workspaceService.activeWorkspaceHandle.value,
|
||||||
|
collectionPathIndex
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(collectionHandleResult)) {
|
||||||
|
// INVALID_WORKSPACE_HANDLE | INVALID_COLLECTION_ID | INVALID_PATH
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionHandle = collectionHandleResult.right
|
||||||
|
|
||||||
|
const requestHandleResult = await workspaceService.createRESTRequest(
|
||||||
|
collectionHandle,
|
||||||
|
updatedRequest
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(requestHandleResult)) {
|
||||||
|
// WORKSPACE_INVALIDATED | INVALID_COLLECTION_HANDLE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestHandle = requestHandleResult.right
|
||||||
|
|
||||||
|
const requestHandleRef = requestHandle.get()
|
||||||
|
|
||||||
|
if (requestHandleRef.value.type === "invalid") {
|
||||||
|
// WORKSPACE_INVALIDATED | INVALID_COLLECTION_HANDLE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
RESTTabs.currentActiveTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: requestUpdated,
|
request: updatedRequest,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
originLocation: "user-collection",
|
originLocation: "workspace-user-collection",
|
||||||
folderPath: `${picked.value.collectionIndex}`,
|
requestHandle,
|
||||||
requestIndex: insertionIndex,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
|
||||||
`${picked.value.collectionIndex}`,
|
|
||||||
"rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
RESTTabs.currentActiveTab.value.document.inheritedProperties = {
|
|
||||||
auth,
|
|
||||||
headers,
|
|
||||||
}
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
requestSaved()
|
|
||||||
} else if (picked.value.pickedType === "my-folder") {
|
|
||||||
if (!isHoppRESTRequest(requestUpdated))
|
|
||||||
throw new Error("requestUpdated is not a REST Request")
|
|
||||||
|
|
||||||
const insertionIndex = saveRESTRequestAs(
|
|
||||||
picked.value.folderPath,
|
|
||||||
requestUpdated
|
|
||||||
)
|
|
||||||
|
|
||||||
RESTTabs.currentActiveTab.value.document = {
|
|
||||||
request: requestUpdated,
|
|
||||||
isDirty: false,
|
|
||||||
saveContext: {
|
|
||||||
originLocation: "user-collection",
|
|
||||||
folderPath: picked.value.folderPath,
|
|
||||||
requestIndex: insertionIndex,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
|
||||||
picked.value.folderPath,
|
|
||||||
"rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
RESTTabs.currentActiveTab.value.document.inheritedProperties = {
|
|
||||||
auth,
|
|
||||||
headers,
|
|
||||||
}
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
requestSaved()
|
requestSaved()
|
||||||
} else if (picked.value.pickedType === "my-request") {
|
} else if (picked.value.pickedType === "my-request") {
|
||||||
if (!isHoppRESTRequest(requestUpdated))
|
if (!isHoppRESTRequest(updatedRequest))
|
||||||
throw new Error("requestUpdated is not a REST Request")
|
throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
editRESTRequest(
|
const requestHandleResult = await workspaceService.getRequestHandle(
|
||||||
picked.value.folderPath,
|
workspaceService.activeWorkspaceHandle.value,
|
||||||
picked.value.requestIndex,
|
`${picked.value.folderPath}/${picked.value.requestIndex.toString()}`
|
||||||
requestUpdated
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(requestHandleResult)) {
|
||||||
|
// INVALID_COLLECTION_HANDLE | INVALID_REQUEST_ID | REQUEST_NOT_FOUND
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestHandle = requestHandleResult.right
|
||||||
|
|
||||||
|
const requestHandleRef = requestHandle.get()
|
||||||
|
|
||||||
|
if (requestHandleRef.value.type === "invalid") {
|
||||||
|
// WORKSPACE_INVALIDATED
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRequestResult = await workspaceService.updateRESTRequest(
|
||||||
|
requestHandle,
|
||||||
|
updatedRequest
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(updateRequestResult)) {
|
||||||
|
// WORKSPACE_INVALIDATED | INVALID_REQUEST_HANDLE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
RESTTabs.currentActiveTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: requestUpdated,
|
request: updatedRequest,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
originLocation: "user-collection",
|
originLocation: "workspace-user-collection",
|
||||||
folderPath: picked.value.folderPath,
|
requestHandle,
|
||||||
requestIndex: picked.value.requestIndex,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,152 +326,147 @@ const saveRequestAs = async () => {
|
|||||||
headers,
|
headers,
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: false,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
requestSaved()
|
|
||||||
} else if (picked.value.pickedType === "teams-collection") {
|
|
||||||
if (!isHoppRESTRequest(requestUpdated))
|
|
||||||
throw new Error("requestUpdated is not a REST Request")
|
|
||||||
|
|
||||||
updateTeamCollectionOrFolder(picked.value.collectionID, requestUpdated)
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
} else if (picked.value.pickedType === "teams-folder") {
|
|
||||||
if (!isHoppRESTRequest(requestUpdated))
|
|
||||||
throw new Error("requestUpdated is not a REST Request")
|
|
||||||
|
|
||||||
updateTeamCollectionOrFolder(picked.value.folderID, requestUpdated)
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
} else if (picked.value.pickedType === "teams-request") {
|
|
||||||
if (!isHoppRESTRequest(requestUpdated))
|
|
||||||
throw new Error("requestUpdated is not a REST Request")
|
|
||||||
|
|
||||||
if (
|
|
||||||
collectionsType.value.type !== "team-collections" ||
|
|
||||||
!collectionsType.value.selectedTeam
|
|
||||||
)
|
|
||||||
throw new Error("Collections Type mismatch")
|
|
||||||
|
|
||||||
modalLoadingState.value = true
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
request: JSON.stringify(requestUpdated),
|
|
||||||
title: requestUpdated.name,
|
|
||||||
}
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: false,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
|
|
||||||
pipe(
|
|
||||||
updateTeamRequest(picked.value.requestID, data),
|
|
||||||
TE.match(
|
|
||||||
(err: GQLError<string>) => {
|
|
||||||
toast.error(`${getErrorMessage(err)}`)
|
|
||||||
modalLoadingState.value = false
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
modalLoadingState.value = false
|
|
||||||
requestSaved()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)()
|
|
||||||
} else if (picked.value.pickedType === "gql-my-request") {
|
|
||||||
// TODO: Check for GQL request ?
|
|
||||||
editGraphqlRequest(
|
|
||||||
picked.value.folderPath,
|
|
||||||
picked.value.requestIndex,
|
|
||||||
requestUpdated as HoppGQLRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: false,
|
|
||||||
platform: "gql",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
|
|
||||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
|
||||||
picked.value.folderPath,
|
|
||||||
"graphql"
|
|
||||||
)
|
|
||||||
|
|
||||||
GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
|
||||||
auth,
|
|
||||||
headers,
|
|
||||||
}
|
|
||||||
|
|
||||||
requestSaved()
|
|
||||||
} else if (picked.value.pickedType === "gql-my-folder") {
|
|
||||||
// TODO: Check for GQL request ?
|
|
||||||
saveGraphqlRequestAs(
|
|
||||||
picked.value.folderPath,
|
|
||||||
requestUpdated as HoppGQLRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "gql",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
|
|
||||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
|
||||||
picked.value.folderPath,
|
|
||||||
"graphql"
|
|
||||||
)
|
|
||||||
|
|
||||||
GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
|
||||||
auth,
|
|
||||||
headers,
|
|
||||||
}
|
|
||||||
|
|
||||||
requestSaved()
|
|
||||||
} else if (picked.value.pickedType === "gql-my-collection") {
|
|
||||||
// TODO: Check for GQL request ?
|
|
||||||
saveGraphqlRequestAs(
|
|
||||||
`${picked.value.collectionIndex}`,
|
|
||||||
requestUpdated as HoppGQLRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "gql",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
|
|
||||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
|
||||||
`${picked.value.collectionIndex}`,
|
|
||||||
"graphql"
|
|
||||||
)
|
|
||||||
|
|
||||||
GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
|
||||||
auth,
|
|
||||||
headers,
|
|
||||||
}
|
|
||||||
|
|
||||||
requestSaved()
|
requestSaved()
|
||||||
}
|
}
|
||||||
|
// TODO: To be removed
|
||||||
|
// else if (picked.value.pickedType === "teams-collection") {
|
||||||
|
// if (!isHoppRESTRequest(updatedRequest))
|
||||||
|
// throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
|
// updateTeamCollectionOrFolder(picked.value.collectionID, updatedRequest)
|
||||||
|
|
||||||
|
// platform.analytics?.logEvent({
|
||||||
|
// type: "HOPP_SAVE_REQUEST",
|
||||||
|
// createdNow: true,
|
||||||
|
// platform: "rest",
|
||||||
|
// workspaceType: "team",
|
||||||
|
// })
|
||||||
|
// } else if (picked.value.pickedType === "teams-folder") {
|
||||||
|
// if (!isHoppRESTRequest(updatedRequest))
|
||||||
|
// throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
|
// updateTeamCollectionOrFolder(picked.value.folderID, updatedRequest)
|
||||||
|
|
||||||
|
// platform.analytics?.logEvent({
|
||||||
|
// type: "HOPP_SAVE_REQUEST",
|
||||||
|
// createdNow: true,
|
||||||
|
// platform: "rest",
|
||||||
|
// workspaceType: "team",
|
||||||
|
// })
|
||||||
|
// } else if (picked.value.pickedType === "teams-request") {
|
||||||
|
// if (!isHoppRESTRequest(updatedRequest))
|
||||||
|
// throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
|
// if (
|
||||||
|
// collectionsType.value.type !== "team-collections" ||
|
||||||
|
// !collectionsType.value.selectedTeam
|
||||||
|
// )
|
||||||
|
// throw new Error("Collections Type mismatch")
|
||||||
|
|
||||||
|
// modalLoadingState.value = true
|
||||||
|
|
||||||
|
// const data = {
|
||||||
|
// request: JSON.stringify(updatedRequest),
|
||||||
|
// title: updatedRequest.name,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// platform.analytics?.logEvent({
|
||||||
|
// type: "HOPP_SAVE_REQUEST",
|
||||||
|
// createdNow: false,
|
||||||
|
// platform: "rest",
|
||||||
|
// workspaceType: "team",
|
||||||
|
// })
|
||||||
|
|
||||||
|
// pipe(
|
||||||
|
// updateTeamRequest(picked.value.requestID, data),
|
||||||
|
// TE.match(
|
||||||
|
// (err: GQLError<string>) => {
|
||||||
|
// toast.error(`${getErrorMessage(err)}`)
|
||||||
|
// modalLoadingState.value = false
|
||||||
|
// },
|
||||||
|
// () => {
|
||||||
|
// modalLoadingState.value = false
|
||||||
|
// requestSaved()
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// )()
|
||||||
|
// } else if (picked.value.pickedType === "gql-my-request") {
|
||||||
|
// // TODO: Check for GQL request ?
|
||||||
|
// editGraphqlRequest(
|
||||||
|
// picked.value.folderPath,
|
||||||
|
// picked.value.requestIndex,
|
||||||
|
// updatedRequest as HoppGQLRequest
|
||||||
|
// )
|
||||||
|
|
||||||
|
// platform.analytics?.logEvent({
|
||||||
|
// type: "HOPP_SAVE_REQUEST",
|
||||||
|
// createdNow: false,
|
||||||
|
// platform: "gql",
|
||||||
|
// workspaceType: "team",
|
||||||
|
// })
|
||||||
|
|
||||||
|
// const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||||
|
// picked.value.folderPath,
|
||||||
|
// "graphql"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||||
|
// auth,
|
||||||
|
// headers,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// requestSaved()
|
||||||
|
// } else if (picked.value.pickedType === "gql-my-folder") {
|
||||||
|
// // TODO: Check for GQL request ?
|
||||||
|
// saveGraphqlRequestAs(
|
||||||
|
// picked.value.folderPath,
|
||||||
|
// updatedRequest as HoppGQLRequest
|
||||||
|
// )
|
||||||
|
|
||||||
|
// platform.analytics?.logEvent({
|
||||||
|
// type: "HOPP_SAVE_REQUEST",
|
||||||
|
// createdNow: true,
|
||||||
|
// platform: "gql",
|
||||||
|
// workspaceType: "team",
|
||||||
|
// })
|
||||||
|
|
||||||
|
// const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||||
|
// picked.value.folderPath,
|
||||||
|
// "graphql"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||||
|
// auth,
|
||||||
|
// headers,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// requestSaved()
|
||||||
|
// } else if (picked.value.pickedType === "gql-my-collection") {
|
||||||
|
// // TODO: Check for GQL request ?
|
||||||
|
// saveGraphqlRequestAs(
|
||||||
|
// `${picked.value.collectionIndex}`,
|
||||||
|
// updatedRequest as HoppGQLRequest
|
||||||
|
// )
|
||||||
|
|
||||||
|
// platform.analytics?.logEvent({
|
||||||
|
// type: "HOPP_SAVE_REQUEST",
|
||||||
|
// createdNow: true,
|
||||||
|
// platform: "gql",
|
||||||
|
// workspaceType: "team",
|
||||||
|
// })
|
||||||
|
|
||||||
|
// const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||||
|
// `${picked.value.collectionIndex}`,
|
||||||
|
// "graphql"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||||
|
// auth,
|
||||||
|
// headers,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// requestSaved()
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -478,50 +474,50 @@ const saveRequestAs = async () => {
|
|||||||
* @param collectionID - ID of the collection or folder
|
* @param collectionID - ID of the collection or folder
|
||||||
* @param requestUpdated - Updated request
|
* @param requestUpdated - Updated request
|
||||||
*/
|
*/
|
||||||
const updateTeamCollectionOrFolder = (
|
// const updateTeamCollectionOrFolder = (
|
||||||
collectionID: string,
|
// collectionID: string,
|
||||||
requestUpdated: HoppRESTRequest
|
// requestUpdated: HoppRESTRequest
|
||||||
) => {
|
// ) => {
|
||||||
if (
|
// if (
|
||||||
collectionsType.value.type !== "team-collections" ||
|
// collectionsType.value.type !== "team-collections" ||
|
||||||
!collectionsType.value.selectedTeam
|
// !collectionsType.value.selectedTeam
|
||||||
)
|
// )
|
||||||
throw new Error("Collections Type mismatch")
|
// throw new Error("Collections Type mismatch")
|
||||||
|
|
||||||
modalLoadingState.value = true
|
// modalLoadingState.value = true
|
||||||
|
|
||||||
const data = {
|
// const data = {
|
||||||
title: requestUpdated.name,
|
// title: requestUpdated.name,
|
||||||
request: JSON.stringify(requestUpdated),
|
// request: JSON.stringify(requestUpdated),
|
||||||
teamID: collectionsType.value.selectedTeam.id,
|
// teamID: collectionsType.value.selectedTeam.id,
|
||||||
}
|
// }
|
||||||
pipe(
|
// pipe(
|
||||||
createRequestInCollection(collectionID, data),
|
// createRequestInCollection(collectionID, data),
|
||||||
TE.match(
|
// TE.match(
|
||||||
(err: GQLError<string>) => {
|
// (err: GQLError<string>) => {
|
||||||
toast.error(`${getErrorMessage(err)}`)
|
// toast.error(`${getErrorMessage(err)}`)
|
||||||
modalLoadingState.value = false
|
// modalLoadingState.value = false
|
||||||
},
|
// },
|
||||||
(result) => {
|
// (result) => {
|
||||||
const { createRequestInCollection } = result
|
// const { createRequestInCollection } = result
|
||||||
|
|
||||||
RESTTabs.currentActiveTab.value.document = {
|
// RESTTabs.currentActiveTab.value.document = {
|
||||||
request: requestUpdated,
|
// request: requestUpdated,
|
||||||
isDirty: false,
|
// isDirty: false,
|
||||||
saveContext: {
|
// saveContext: {
|
||||||
originLocation: "team-collection",
|
// originLocation: "team-collection",
|
||||||
requestID: createRequestInCollection.id,
|
// requestID: createRequestInCollection.id,
|
||||||
collectionID: createRequestInCollection.collection.id,
|
// collectionID: createRequestInCollection.collection.id,
|
||||||
teamID: createRequestInCollection.collection.team.id,
|
// teamID: createRequestInCollection.collection.team.id,
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
|
|
||||||
modalLoadingState.value = false
|
// modalLoadingState.value = false
|
||||||
requestSaved()
|
// requestSaved()
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
)()
|
// )()
|
||||||
}
|
// }
|
||||||
|
|
||||||
const requestSaved = () => {
|
const requestSaved = () => {
|
||||||
toast.success(`${t("request.added")}`)
|
toast.success(`${t("request.added")}`)
|
||||||
@@ -536,24 +532,24 @@ const hideModal = () => {
|
|||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
|
|
||||||
const getErrorMessage = (err: GQLError<string>) => {
|
// const getErrorMessage = (err: GQLError<string>) => {
|
||||||
console.error(err)
|
// console.error(err)
|
||||||
if (err.type === "network_error") {
|
// if (err.type === "network_error") {
|
||||||
return t("error.network_error")
|
// return t("error.network_error")
|
||||||
}
|
// }
|
||||||
switch (err.error) {
|
// switch (err.error) {
|
||||||
case "team_coll/short_title":
|
// case "team_coll/short_title":
|
||||||
return t("collection.name_length_insufficient")
|
// return t("collection.name_length_insufficient")
|
||||||
case "team/invalid_coll_id":
|
// case "team/invalid_coll_id":
|
||||||
return t("team.invalid_id")
|
// return t("team.invalid_id")
|
||||||
case "team/not_required_role":
|
// case "team/not_required_role":
|
||||||
return t("profile.no_permission")
|
// return t("profile.no_permission")
|
||||||
case "team_req/not_required_role":
|
// case "team_req/not_required_role":
|
||||||
return t("profile.no_permission")
|
// return t("profile.no_permission")
|
||||||
case "Forbidden resource":
|
// case "Forbidden resource":
|
||||||
return t("profile.no_permission")
|
// return t("profile.no_permission")
|
||||||
default:
|
// default:
|
||||||
return t("error.something_went_wrong")
|
// return t("error.something_went_wrong")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -387,7 +387,6 @@ import IconPlus from "~icons/lucide/plus"
|
|||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
import IconImport from "~icons/lucide/folder-down"
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import { computed, PropType, Ref, toRef } from "vue"
|
import { computed, PropType, Ref, toRef } from "vue"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
||||||
@@ -400,17 +399,16 @@ import * as O from "fp-ts/Option"
|
|||||||
import { Picked } from "~/helpers/types/HoppPicked.js"
|
import { Picked } from "~/helpers/types/HoppPicked.js"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
|
import { TeamWorkspace } from "~/services/workspace.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const tabs = useService(RESTTabService)
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
|
||||||
|
|
||||||
type CollectionType =
|
type CollectionType =
|
||||||
| {
|
| {
|
||||||
type: "team-collections"
|
type: "team-collections"
|
||||||
selectedTeam: SelectedTeam
|
selectedTeam: TeamWorkspace
|
||||||
}
|
}
|
||||||
| { type: "my-collections"; selectedTeam: undefined }
|
| { type: "my-collections"; selectedTeam: undefined }
|
||||||
|
|
||||||
@@ -614,7 +612,7 @@ const hasNoTeamAccess = computed(
|
|||||||
() =>
|
() =>
|
||||||
props.collectionsType.type === "team-collections" &&
|
props.collectionsType.type === "team-collections" &&
|
||||||
(props.collectionsType.selectedTeam === undefined ||
|
(props.collectionsType.selectedTeam === undefined ||
|
||||||
props.collectionsType.selectedTeam.myRole === "VIEWER")
|
props.collectionsType.selectedTeam.role === "VIEWER")
|
||||||
)
|
)
|
||||||
|
|
||||||
const isSelected = ({
|
const isSelected = ({
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { GistSource } from "~/helpers/import-export/import/import-sources/GistSo
|
|||||||
|
|
||||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||||
import IconUser from "~icons/lucide/user"
|
import IconUser from "~icons/lucide/user"
|
||||||
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
import { initializeDownloadFile } from "~/helpers/import-export/export"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
@@ -133,12 +133,12 @@ const GqlCollectionsHoppExporter: ImporterOrExporter = {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
applicableTo: ["personal-workspace", "team-workspace"],
|
applicableTo: ["personal-workspace", "team-workspace"],
|
||||||
},
|
},
|
||||||
action: () => {
|
action: async () => {
|
||||||
if (!gqlCollections.value.length) {
|
if (!gqlCollections.value.length) {
|
||||||
return toast.error(t("error.no_collections_to_export"))
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = initializeDownloadCollection(
|
const message = await initializeDownloadFile(
|
||||||
gqlCollectionsExporter(gqlCollections.value),
|
gqlCollectionsExporter(gqlCollections.value),
|
||||||
"GQLCollections"
|
"GQLCollections"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ const toast = useToast()
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
// Whether to activate the ability to pick items (activates 'select' events)
|
// Whether to activate the ability to pick items (activates 'select' events)
|
||||||
saveRequest: boolean
|
saveRequest: boolean
|
||||||
picked: Picked
|
picked: Picked | null
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const collections = useReadonlyStream(graphqlCollections$, [], "deep")
|
const collections = useReadonlyStream(graphqlCollections$, [], "deep")
|
||||||
|
|||||||
@@ -178,7 +178,6 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { Picked } from "~/helpers/types/HoppPicked"
|
import { Picked } from "~/helpers/types/HoppPicked"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import {
|
import {
|
||||||
@@ -245,7 +244,7 @@ import {
|
|||||||
} from "~/helpers/collection/collection"
|
} from "~/helpers/collection/collection"
|
||||||
import { currentReorderingStatus$ } from "~/newstore/reordering"
|
import { currentReorderingStatus$ } from "~/newstore/reordering"
|
||||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { TeamWorkspace, WorkspaceService } from "~/services/workspace.service"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
@@ -274,16 +273,14 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: "select", payload: Picked | null): void
|
(event: "select", payload: Picked | null): void
|
||||||
(event: "update-team", team: SelectedTeam): void
|
(event: "update-team", team: TeamWorkspace): void
|
||||||
(event: "update-collection-type", type: CollectionType["type"]): void
|
(event: "update-collection-type", type: CollectionType["type"]): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
|
||||||
|
|
||||||
type CollectionType =
|
type CollectionType =
|
||||||
| {
|
| {
|
||||||
type: "team-collections"
|
type: "team-collections"
|
||||||
selectedTeam: SelectedTeam
|
selectedTeam: TeamWorkspace
|
||||||
}
|
}
|
||||||
| { type: "my-collections"; selectedTeam: undefined }
|
| { type: "my-collections"; selectedTeam: undefined }
|
||||||
|
|
||||||
@@ -330,9 +327,7 @@ const requestMoveLoading = ref<string[]>([])
|
|||||||
// TeamList-Adapter
|
// TeamList-Adapter
|
||||||
const workspaceService = useService(WorkspaceService)
|
const workspaceService = useService(WorkspaceService)
|
||||||
const teamListAdapter = workspaceService.acquireTeamListAdapter(null)
|
const teamListAdapter = workspaceService.acquireTeamListAdapter(null)
|
||||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
|
||||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
const teamListFetched = ref(false)
|
|
||||||
|
|
||||||
// Team Collection Adapter
|
// Team Collection Adapter
|
||||||
const teamCollectionAdapter = new TeamCollectionAdapter(null)
|
const teamCollectionAdapter = new TeamCollectionAdapter(null)
|
||||||
@@ -378,7 +373,7 @@ watch(
|
|||||||
filterTexts,
|
filterTexts,
|
||||||
(newFilterText) => {
|
(newFilterText) => {
|
||||||
if (collectionsType.value.type === "team-collections") {
|
if (collectionsType.value.type === "team-collections") {
|
||||||
const selectedTeamID = collectionsType.value.selectedTeam?.id
|
const selectedTeamID = collectionsType.value.selectedTeam?.teamID
|
||||||
|
|
||||||
selectedTeamID &&
|
selectedTeamID &&
|
||||||
debouncedSearch(newFilterText, selectedTeamID)?.catch(() => {})
|
debouncedSearch(newFilterText, selectedTeamID)?.catch(() => {})
|
||||||
@@ -435,28 +430,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
|
||||||
() => myTeams.value,
|
|
||||||
(newTeams) => {
|
|
||||||
if (newTeams && !teamListFetched.value) {
|
|
||||||
teamListFetched.value = true
|
|
||||||
if (REMEMBERED_TEAM_ID.value && currentUser.value) {
|
|
||||||
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
|
||||||
if (team) updateSelectedTeam(team)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => collectionsType.value.selectedTeam,
|
|
||||||
(newTeam) => {
|
|
||||||
if (newTeam) {
|
|
||||||
teamCollectionAdapter.changeTeamID(newTeam.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const switchToMyCollections = () => {
|
const switchToMyCollections = () => {
|
||||||
collectionsType.value.type = "my-collections"
|
collectionsType.value.type = "my-collections"
|
||||||
collectionsType.value.selectedTeam = undefined
|
collectionsType.value.selectedTeam = undefined
|
||||||
@@ -488,11 +461,12 @@ const expandTeamCollection = (collectionID: string) => {
|
|||||||
teamCollectionAdapter.expandCollection(collectionID)
|
teamCollectionAdapter.expandCollection(collectionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateSelectedTeam = (team: SelectedTeam) => {
|
const updateSelectedTeam = (team: TeamWorkspace) => {
|
||||||
if (team) {
|
if (team) {
|
||||||
collectionsType.value.type = "team-collections"
|
collectionsType.value.type = "team-collections"
|
||||||
|
teamCollectionAdapter.changeTeamID(team.teamID)
|
||||||
collectionsType.value.selectedTeam = team
|
collectionsType.value.selectedTeam = team
|
||||||
REMEMBERED_TEAM_ID.value = team.id
|
REMEMBERED_TEAM_ID.value = team.teamID
|
||||||
emit("update-team", team)
|
emit("update-team", team)
|
||||||
emit("update-collection-type", "team-collections")
|
emit("update-collection-type", "team-collections")
|
||||||
}
|
}
|
||||||
@@ -501,23 +475,14 @@ const updateSelectedTeam = (team: SelectedTeam) => {
|
|||||||
const workspace = workspaceService.currentWorkspace
|
const workspace = workspaceService.currentWorkspace
|
||||||
|
|
||||||
// Used to switch collection type and team when user switch workspace in the global workspace switcher
|
// Used to switch collection type and team when user switch workspace in the global workspace switcher
|
||||||
// Check if there is a teamID in the workspace, if yes, switch to team collections and select the team
|
|
||||||
// If there is no teamID, switch to my collections
|
|
||||||
watch(
|
watch(
|
||||||
() => {
|
workspace,
|
||||||
const space = workspace.value
|
(newWorkspace) => {
|
||||||
return space.type === "personal" ? undefined : space.teamID
|
if (newWorkspace.type === "personal") {
|
||||||
},
|
switchToMyCollections()
|
||||||
(teamID) => {
|
} else if (newWorkspace.type === "team") {
|
||||||
if (teamID) {
|
updateSelectedTeam(newWorkspace)
|
||||||
const team = myTeams.value?.find((t) => t.id === teamID)
|
|
||||||
if (team) {
|
|
||||||
updateSelectedTeam(team)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return switchToMyCollections()
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
@@ -545,7 +510,7 @@ const hasTeamWriteAccess = computed(() => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const role = collectionsType.value.selectedTeam?.myRole
|
const role = collectionsType.value.selectedTeam?.role
|
||||||
return role === "OWNER" || role === "EDITOR"
|
return role === "OWNER" || role === "EDITOR"
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -760,7 +725,7 @@ const addNewRootCollection = (name: string) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
pipe(
|
pipe(
|
||||||
createNewRootCollection(name, collectionsType.value.selectedTeam.id),
|
createNewRootCollection(name, collectionsType.value.selectedTeam.teamID),
|
||||||
TE.match(
|
TE.match(
|
||||||
(err: GQLError<string>) => {
|
(err: GQLError<string>) => {
|
||||||
toast.error(`${getErrorMessage(err)}`)
|
toast.error(`${getErrorMessage(err)}`)
|
||||||
@@ -831,7 +796,7 @@ const onAddRequest = (requestName: string) => {
|
|||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
request: JSON.stringify(newRequest),
|
request: JSON.stringify(newRequest),
|
||||||
teamID: collectionsType.value.selectedTeam.id,
|
teamID: collectionsType.value.selectedTeam.teamID,
|
||||||
title: requestName,
|
title: requestName,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1158,7 +1123,7 @@ const duplicateRequest = (payload: {
|
|||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
request: JSON.stringify(newRequest),
|
request: JSON.stringify(newRequest),
|
||||||
teamID: collectionsType.value.selectedTeam.id,
|
teamID: collectionsType.value.selectedTeam.teamID,
|
||||||
title: `${request.name} - ${t("action.duplicate")}`,
|
title: `${request.name} - ${t("action.duplicate")}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import IconFolderPlus from "~icons/lucide/folder-plus"
|
|||||||
import IconPostman from "~icons/hopp/postman"
|
import IconPostman from "~icons/hopp/postman"
|
||||||
import IconInsomnia from "~icons/hopp/insomnia"
|
import IconInsomnia from "~icons/hopp/insomnia"
|
||||||
import IconUser from "~icons/lucide/user"
|
import IconUser from "~icons/lucide/user"
|
||||||
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
import { initializeDownloadFile } from "~/helpers/import-export/export"
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
import { environmentsExporter } from "~/helpers/import-export/export/environments"
|
import { environmentsExporter } from "~/helpers/import-export/export/environments"
|
||||||
@@ -230,12 +230,12 @@ const HoppEnvironmentsExport: ImporterOrExporter = {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
applicableTo: ["personal-workspace", "team-workspace"],
|
applicableTo: ["personal-workspace", "team-workspace"],
|
||||||
},
|
},
|
||||||
action: () => {
|
action: async () => {
|
||||||
if (!environmentJson.value.length) {
|
if (!environmentJson.value.length) {
|
||||||
return toast.error(t("error.no_environments_to_export"))
|
return toast.error(t("error.no_environments_to_export"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = initializeDownloadCollection(
|
const message = await initializeDownloadFile(
|
||||||
environmentsExporter(environmentJson.value),
|
environmentsExporter(environmentJson.value),
|
||||||
"Environments"
|
"Environments"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -364,6 +364,7 @@ const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
|||||||
teamID: team.id,
|
teamID: team.id,
|
||||||
teamName: team.name,
|
teamName: team.name,
|
||||||
type: "team",
|
type: "team",
|
||||||
|
role: team.myRole,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
watch(
|
watch(
|
||||||
|
|||||||
@@ -46,41 +46,38 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from "vue"
|
|
||||||
import { isEqual } from "lodash-es"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
import { useReadonlyStream, useStream } from "@composables/stream"
|
import { useReadonlyStream, useStream } from "@composables/stream"
|
||||||
|
import { Environment } from "@hoppscotch/data"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import * as TE from "fp-ts/TaskEither"
|
||||||
|
import { pipe } from "fp-ts/function"
|
||||||
|
import { isEqual } from "lodash-es"
|
||||||
|
import { computed, ref, watch } from "vue"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
|
import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
||||||
|
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
||||||
import {
|
import {
|
||||||
|
deleteEnvironment,
|
||||||
getSelectedEnvironmentIndex,
|
getSelectedEnvironmentIndex,
|
||||||
globalEnv$,
|
globalEnv$,
|
||||||
selectedEnvironmentIndex$,
|
selectedEnvironmentIndex$,
|
||||||
setSelectedEnvironmentIndex,
|
setSelectedEnvironmentIndex,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
import { pipe } from "fp-ts/function"
|
import { platform } from "~/platform"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import { TeamWorkspace, WorkspaceService } from "~/services/workspace.service"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
|
||||||
import { deleteEnvironment } from "~/newstore/environments"
|
|
||||||
import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
|
||||||
import { useToast } from "~/composables/toast"
|
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { Environment } from "@hoppscotch/data"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
type EnvironmentType = "my-environments" | "team-environments"
|
type EnvironmentType = "my-environments" | "team-environments"
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
|
||||||
|
|
||||||
type EnvironmentsChooseType = {
|
type EnvironmentsChooseType = {
|
||||||
type: EnvironmentType
|
type: EnvironmentType
|
||||||
selectedTeam: SelectedTeam
|
selectedTeam: TeamWorkspace | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const environmentType = ref<EnvironmentsChooseType>({
|
const environmentType = ref<EnvironmentsChooseType>({
|
||||||
@@ -102,11 +99,7 @@ const currentUser = useReadonlyStream(
|
|||||||
platform.auth.getCurrentUser()
|
platform.auth.getCurrentUser()
|
||||||
)
|
)
|
||||||
|
|
||||||
// TeamList-Adapter
|
|
||||||
const workspaceService = useService(WorkspaceService)
|
const workspaceService = useService(WorkspaceService)
|
||||||
const teamListAdapter = workspaceService.acquireTeamListAdapter(null)
|
|
||||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
|
||||||
const teamListFetched = ref(false)
|
|
||||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
|
|
||||||
const adapter = new TeamEnvironmentAdapter(undefined)
|
const adapter = new TeamEnvironmentAdapter(undefined)
|
||||||
@@ -118,29 +111,17 @@ const loading = computed(
|
|||||||
() => adapterLoading.value && teamEnvironmentList.value.length === 0
|
() => adapterLoading.value && teamEnvironmentList.value.length === 0
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
|
||||||
() => myTeams.value,
|
|
||||||
(newTeams) => {
|
|
||||||
if (newTeams && !teamListFetched.value) {
|
|
||||||
teamListFetched.value = true
|
|
||||||
if (REMEMBERED_TEAM_ID.value && currentUser.value) {
|
|
||||||
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
|
||||||
if (team) updateSelectedTeam(team)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const switchToMyEnvironments = () => {
|
const switchToMyEnvironments = () => {
|
||||||
environmentType.value.selectedTeam = undefined
|
environmentType.value.selectedTeam = undefined
|
||||||
updateEnvironmentType("my-environments")
|
updateEnvironmentType("my-environments")
|
||||||
adapter.changeTeamID(undefined)
|
adapter.changeTeamID(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateSelectedTeam = (newSelectedTeam: SelectedTeam | undefined) => {
|
const updateSelectedTeam = (newSelectedTeam: TeamWorkspace | undefined) => {
|
||||||
if (newSelectedTeam) {
|
if (newSelectedTeam) {
|
||||||
|
adapter.changeTeamID(newSelectedTeam.teamID)
|
||||||
environmentType.value.selectedTeam = newSelectedTeam
|
environmentType.value.selectedTeam = newSelectedTeam
|
||||||
REMEMBERED_TEAM_ID.value = newSelectedTeam.id
|
REMEMBERED_TEAM_ID.value = newSelectedTeam.teamID
|
||||||
updateEnvironmentType("team-environments")
|
updateEnvironmentType("team-environments")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,15 +129,6 @@ const updateEnvironmentType = (newEnvironmentType: EnvironmentType) => {
|
|||||||
environmentType.value.type = newEnvironmentType
|
environmentType.value.type = newEnvironmentType
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
|
||||||
() => environmentType.value.selectedTeam,
|
|
||||||
(newTeam) => {
|
|
||||||
if (newTeam) {
|
|
||||||
adapter.changeTeamID(newTeam.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const workspace = workspaceService.currentWorkspace
|
const workspace = workspaceService.currentWorkspace
|
||||||
|
|
||||||
// Switch to my environments if workspace is personal and to team environments if workspace is team
|
// Switch to my environments if workspace is personal and to team environments if workspace is team
|
||||||
@@ -170,8 +142,7 @@ watch(workspace, (newWorkspace) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (newWorkspace.type === "team") {
|
} else if (newWorkspace.type === "team") {
|
||||||
const team = myTeams.value?.find((t) => t.id === newWorkspace.teamID)
|
updateSelectedTeam(newWorkspace)
|
||||||
updateSelectedTeam(team)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -54,9 +54,7 @@
|
|||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
:label="tab.label"
|
:label="tab.label"
|
||||||
>
|
>
|
||||||
<div
|
<div class="divide-y divide-dividerLight">
|
||||||
class="divide-y divide-dividerLight rounded border border-divider"
|
|
||||||
>
|
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="tab.variables.length === 0"
|
v-if="tab.variables.length === 0"
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
|
|||||||
@@ -56,9 +56,7 @@
|
|||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
:label="tab.label"
|
:label="tab.label"
|
||||||
>
|
>
|
||||||
<div
|
<div class="divide-y divide-dividerLight">
|
||||||
class="divide-y divide-dividerLight rounded border border-divider"
|
|
||||||
>
|
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="tab.variables.length === 0"
|
v-if="tab.variables.length === 0"
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
class="sticky top-upperPrimaryStickyFold z-10 flex flex-1 flex-shrink-0 justify-between overflow-x-auto border-b 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
|
<HoppButtonSecondary
|
||||||
v-if="team === undefined || team.myRole === 'VIEWER'"
|
v-if="team === undefined || team.role === 'VIEWER'"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
disabled
|
disabled
|
||||||
class="!rounded-none"
|
class="!rounded-none"
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
:icon="IconHelpCircle"
|
:icon="IconHelpCircle"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="team !== undefined && team.myRole === 'VIEWER'"
|
v-if="team !== undefined && team.role === 'VIEWER'"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
disabled
|
disabled
|
||||||
:icon="IconImport"
|
:icon="IconImport"
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
)"
|
)"
|
||||||
:key="`environment-${index}`"
|
:key="`environment-${index}`"
|
||||||
:environment="environment"
|
:environment="environment"
|
||||||
:is-viewer="team?.myRole === 'VIEWER'"
|
:is-viewer="team?.role === 'VIEWER'"
|
||||||
@edit-environment="editEnvironment(environment)"
|
@edit-environment="editEnvironment(environment)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,16 +103,16 @@
|
|||||||
:show="showModalDetails"
|
:show="showModalDetails"
|
||||||
:action="action"
|
:action="action"
|
||||||
:editing-environment="editingEnvironment"
|
:editing-environment="editingEnvironment"
|
||||||
:editing-team-id="team?.id"
|
:editing-team-id="team?.teamID"
|
||||||
:editing-variable-name="editingVariableName"
|
:editing-variable-name="editingVariableName"
|
||||||
:is-secret-option-selected="secretOptionSelected"
|
:is-secret-option-selected="secretOptionSelected"
|
||||||
:is-viewer="team?.myRole === 'VIEWER'"
|
:is-viewer="team?.role === 'VIEWER'"
|
||||||
@hide-modal="displayModalEdit(false)"
|
@hide-modal="displayModalEdit(false)"
|
||||||
/>
|
/>
|
||||||
<EnvironmentsImportExport
|
<EnvironmentsImportExport
|
||||||
v-if="showModalImportExport"
|
v-if="showModalImportExport"
|
||||||
:team-environments="teamEnvironments"
|
:team-environments="teamEnvironments"
|
||||||
:team-id="team?.id"
|
:team-id="team?.teamID"
|
||||||
environment-type="TEAM_ENV"
|
environment-type="TEAM_ENV"
|
||||||
@hide-modal="displayModalImportExport(false)"
|
@hide-modal="displayModalImportExport(false)"
|
||||||
/>
|
/>
|
||||||
@@ -129,16 +129,14 @@ import IconPlus from "~icons/lucide/plus"
|
|||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
import IconImport from "~icons/lucide/folder-down"
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { TeamWorkspace } from "~/services/workspace.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
team: SelectedTeam
|
team: TeamWorkspace | undefined
|
||||||
teamEnvironments: TeamEnvironment[]
|
teamEnvironments: TeamEnvironment[]
|
||||||
adapterError: GQLError<string> | null
|
adapterError: GQLError<string> | null
|
||||||
loading: boolean
|
loading: boolean
|
||||||
@@ -151,7 +149,7 @@ const editingEnvironment = ref<TeamEnvironment | null>(null)
|
|||||||
const editingVariableName = ref("")
|
const editingVariableName = ref("")
|
||||||
const secretOptionSelected = ref(false)
|
const secretOptionSelected = ref(false)
|
||||||
|
|
||||||
const isTeamViewer = computed(() => props.team?.myRole === "VIEWER")
|
const isTeamViewer = computed(() => props.team?.role === "VIEWER")
|
||||||
|
|
||||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||||
action.value = "new"
|
action.value = "new"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
class="w-full rounded border border-divider bg-primaryLight px-4 py-2 text-secondaryDark"
|
class="w-full rounded border border-divider bg-primaryLight px-4 py-2 text-secondaryDark"
|
||||||
:placeholder="`${t('request.url')}`"
|
:placeholder="`${t('graphql.url_placeholder')}`"
|
||||||
:disabled="connected"
|
:disabled="connected"
|
||||||
@keyup.enter="onConnectClick"
|
@keyup.enter="onConnectClick"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
>
|
>
|
||||||
<SmartEnvInput
|
<SmartEnvInput
|
||||||
v-model="tab.document.request.endpoint"
|
v-model="tab.document.request.endpoint"
|
||||||
:placeholder="`${t('request.url')}`"
|
:placeholder="`${t('request.url_placeholder')}`"
|
||||||
:auto-complete-source="userHistories"
|
:auto-complete-source="userHistories"
|
||||||
:auto-complete-env="true"
|
:auto-complete-env="true"
|
||||||
:inspection-results="tabResults"
|
:inspection-results="tabResults"
|
||||||
@@ -236,16 +236,28 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { useSetting } from "@composables/settings"
|
import { useSetting } from "@composables/settings"
|
||||||
import { useReadonlyStream, useStreamSubscriber } from "@composables/stream"
|
import { useReadonlyStream, useStreamSubscriber } from "@composables/stream"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { Ref, computed, ref, onUnmounted } from "vue"
|
import { Ref, computed, onUnmounted, ref } from "vue"
|
||||||
|
import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
||||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
import { runMutation } from "~/helpers/backend/GQLClient"
|
import { runMutation } from "~/helpers/backend/GQLClient"
|
||||||
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
|
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
|
||||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
import { getMethodLabelColor } from "~/helpers/rest/labelColoring"
|
||||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
import { editRESTRequest } from "~/newstore/collections"
|
import { RESTHistoryEntry, restHistory$ } from "~/newstore/history"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
import { InspectionService } from "~/services/inspection"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
import IconChevronDown from "~icons/lucide/chevron-down"
|
import IconChevronDown from "~icons/lucide/chevron-down"
|
||||||
import IconCode2 from "~icons/lucide/code-2"
|
import IconCode2 from "~icons/lucide/code-2"
|
||||||
import IconFileCode from "~icons/lucide/file-code"
|
import IconFileCode from "~icons/lucide/file-code"
|
||||||
@@ -253,21 +265,10 @@ import IconFolderPlus from "~icons/lucide/folder-plus"
|
|||||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||||
import IconSave from "~icons/lucide/save"
|
import IconSave from "~icons/lucide/save"
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
|
||||||
import { RESTHistoryEntry, restHistory$ } from "~/newstore/history"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { InspectionService } from "~/services/inspection"
|
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
|
||||||
import { HoppTab } from "~/services/tab"
|
|
||||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
|
||||||
import { getMethodLabelColor } from "~/helpers/rest/labelColoring"
|
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const interceptorService = useService(InterceptorService)
|
const interceptorService = useService(InterceptorService)
|
||||||
|
const newWorkspaceService = useService(NewWorkspaceService)
|
||||||
|
|
||||||
const methods = [
|
const methods = [
|
||||||
"GET",
|
"GET",
|
||||||
@@ -506,34 +507,61 @@ const cycleDownMethod = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveRequest = () => {
|
const saveRequest = async () => {
|
||||||
const saveCtx = tab.value.document.saveContext
|
const { saveContext } = tab.value.document
|
||||||
|
|
||||||
if (!saveCtx) {
|
if (!saveContext) {
|
||||||
showSaveRequestModal.value = true
|
showSaveRequestModal.value = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (saveCtx.originLocation === "user-collection") {
|
|
||||||
const req = tab.value.document.request
|
|
||||||
|
|
||||||
try {
|
if (saveContext.originLocation === "workspace-user-collection") {
|
||||||
editRESTRequest(saveCtx.folderPath, saveCtx.requestIndex, req)
|
const updatedRequest = tab.value.document.request
|
||||||
|
|
||||||
tab.value.document.isDirty = false
|
if (
|
||||||
|
!newWorkspaceService.activeWorkspaceHandle.value ||
|
||||||
platform.analytics?.logEvent({
|
!saveContext.requestHandle
|
||||||
type: "HOPP_SAVE_REQUEST",
|
) {
|
||||||
platform: "rest",
|
return
|
||||||
createdNow: false,
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
toast.success(`${t("request.saved")}`)
|
|
||||||
} catch (e) {
|
|
||||||
tab.value.document.saveContext = undefined
|
|
||||||
saveRequest()
|
|
||||||
}
|
}
|
||||||
} else if (saveCtx.originLocation === "team-collection") {
|
|
||||||
|
const { requestHandle } = saveContext
|
||||||
|
|
||||||
|
const requestHandleRef = requestHandle.get()
|
||||||
|
|
||||||
|
if (!requestHandleRef.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestHandleRef.value.type === "invalid") {
|
||||||
|
showSaveRequestModal.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRequestResult = await newWorkspaceService.updateRESTRequest(
|
||||||
|
requestHandle,
|
||||||
|
updatedRequest
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(updateRequestResult)) {
|
||||||
|
// INVALID_REQUEST_HANDLE
|
||||||
|
showSaveRequestModal.value = true
|
||||||
|
|
||||||
|
if (!tab.value.document.isDirty) {
|
||||||
|
tab.value.document.isDirty = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.value.document.isDirty = false
|
||||||
|
|
||||||
|
tab.value.document.saveContext = {
|
||||||
|
...saveContext,
|
||||||
|
requestHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(`${t("request.saved")}`)
|
||||||
|
} else if (saveContext.originLocation === "team-collection") {
|
||||||
const req = tab.value.document.request
|
const req = tab.value.document.request
|
||||||
|
|
||||||
// TODO: handle error case (NOTE: overwriteRequestTeams is async)
|
// TODO: handle error case (NOTE: overwriteRequestTeams is async)
|
||||||
@@ -546,7 +574,7 @@ const saveRequest = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
runMutation(UpdateRequestDocument, {
|
runMutation(UpdateRequestDocument, {
|
||||||
requestID: saveCtx.requestID,
|
requestID: saveContext.requestID,
|
||||||
data: {
|
data: {
|
||||||
title: req.name,
|
title: req.name,
|
||||||
request: JSON.stringify(req),
|
request: JSON.stringify(req),
|
||||||
|
|||||||
@@ -17,8 +17,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { watch } from "vue"
|
import { watch } from "vue"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep, isEqual } from "lodash-es"
|
||||||
import { isEqualHoppRESTRequest } from "@hoppscotch/data"
|
|
||||||
import { HoppTab } from "~/services/tab"
|
import { HoppTab } from "~/services/tab"
|
||||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
@@ -32,15 +31,42 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const tab = useVModel(props, "modelValue", emit)
|
const tab = useVModel(props, "modelValue", emit)
|
||||||
|
|
||||||
// TODO: Come up with a better dirty check
|
|
||||||
let oldRequest = cloneDeep(tab.value.document.request)
|
let oldRequest = cloneDeep(tab.value.document.request)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => tab.value.document.request,
|
() => tab.value.document.request,
|
||||||
(updatedValue) => {
|
(updatedValue) => {
|
||||||
|
// Request from the collection tree
|
||||||
if (
|
if (
|
||||||
!tab.value.document.isDirty &&
|
tab.value.document.saveContext?.originLocation ===
|
||||||
!isEqualHoppRESTRequest(oldRequest, updatedValue)
|
"workspace-user-collection"
|
||||||
) {
|
) {
|
||||||
|
const requestHandleRef =
|
||||||
|
tab.value.document.saveContext.requestHandle?.get()
|
||||||
|
|
||||||
|
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!tab.value.document.isDirty &&
|
||||||
|
!isEqual(oldRequest, requestHandleRef?.value.data.request)
|
||||||
|
) {
|
||||||
|
tab.value.document.isDirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
tab.value.document.isDirty &&
|
||||||
|
isEqual(oldRequest, requestHandleRef?.value.data.request)
|
||||||
|
) {
|
||||||
|
tab.value.document.isDirty = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsaved request
|
||||||
|
if (!tab.value.document.isDirty && !isEqual(oldRequest, updatedValue)) {
|
||||||
tab.value.document.isDirty = true
|
tab.value.document.isDirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
:icon="IconFolder"
|
:icon="IconFolder"
|
||||||
:label="`${t('tab.collections')}`"
|
:label="`${t('tab.collections')}`"
|
||||||
>
|
>
|
||||||
<Collections />
|
<!-- <Collections /> -->
|
||||||
|
<NewCollections :platform="'rest'" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
:id="'env'"
|
:id="'env'"
|
||||||
@@ -37,12 +38,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import IconClock from "~icons/lucide/clock"
|
|
||||||
import IconLayers from "~icons/lucide/layers"
|
|
||||||
import IconFolder from "~icons/lucide/folder"
|
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
|
||||||
import { ref } from "vue"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { ref } from "vue"
|
||||||
|
|
||||||
|
import IconClock from "~icons/lucide/clock"
|
||||||
|
import IconFolder from "~icons/lucide/folder"
|
||||||
|
import IconLayers from "~icons/lucide/layers"
|
||||||
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
|||||||
@@ -106,15 +106,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
import IconFileEdit from "~icons/lucide/file-edit"
|
||||||
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
import IconXCircle from "~icons/lucide/x-circle"
|
import IconXCircle from "~icons/lucide/x-circle"
|
||||||
import IconXSquare from "~icons/lucide/x-square"
|
import IconXSquare from "~icons/lucide/x-square"
|
||||||
import IconFileEdit from "~icons/lucide/file-edit"
|
|
||||||
import IconCopy from "~icons/lucide/copy"
|
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
|
||||||
import { HoppTab } from "~/services/tab"
|
|
||||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="!activeWorkspaceHandle">No Workspace Selected.</div>
|
||||||
|
|
||||||
|
<NewCollectionsRest
|
||||||
|
v-else-if="platform === 'rest'"
|
||||||
|
:picked="picked"
|
||||||
|
:save-request="saveRequest"
|
||||||
|
:workspace-handle="activeWorkspaceHandle"
|
||||||
|
@select="(payload) => emit('select', payload)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
|
||||||
|
import { Picked } from "~/helpers/types/HoppPicked"
|
||||||
|
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
picked?: Picked | null
|
||||||
|
platform: "rest" | "gql"
|
||||||
|
saveRequest?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "select", payload: Picked | null): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const workspaceService = useService(NewWorkspaceService)
|
||||||
|
|
||||||
|
const activeWorkspaceHandle = workspaceService.activeWorkspaceHandle
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,450 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div
|
||||||
|
class="h-1 w-full transition"
|
||||||
|
:class="[
|
||||||
|
{
|
||||||
|
'bg-accentDark': isReorderable,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@drop="orderUpdateCollectionEvent"
|
||||||
|
@dragover.prevent="ordering = true"
|
||||||
|
@dragleave="ordering = false"
|
||||||
|
@dragend="resetDragState"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div class="relative flex flex-col">
|
||||||
|
<div
|
||||||
|
class="z-[1] pointer-events-none absolute inset-0 bg-accent opacity-0 transition"
|
||||||
|
:class="{
|
||||||
|
'opacity-25':
|
||||||
|
dragging && notSameDestination && notSameParentDestination,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="z-[3] group pointer-events-auto relative flex cursor-pointer items-stretch"
|
||||||
|
:draggable="true"
|
||||||
|
@dragstart="dragStart"
|
||||||
|
@dragover="handleDragOver($event)"
|
||||||
|
@dragleave="resetDragState"
|
||||||
|
@dragend="
|
||||||
|
() => {
|
||||||
|
resetDragState()
|
||||||
|
dropItemID = ''
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@drop="handleDrop($event)"
|
||||||
|
@contextmenu.prevent="options?.tippy?.show()"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex min-w-0 flex-1 items-center justify-center"
|
||||||
|
@click="emit('toggle-children')"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="pointer-events-none flex items-center justify-center px-4"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="collectionIcon"
|
||||||
|
class="svg-icons"
|
||||||
|
:class="{ 'text-accent': isSelected }"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
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 }">
|
||||||
|
{{ collectionView.name }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:icon="IconFilePlus"
|
||||||
|
:title="t('request.new')"
|
||||||
|
class="hidden group-hover:inline-flex"
|
||||||
|
@click="addRequest"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:icon="IconFolderPlus"
|
||||||
|
:title="t('folder.new')"
|
||||||
|
class="hidden group-hover:inline-flex"
|
||||||
|
@click="addChildCollection"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<tippy
|
||||||
|
ref="options"
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
:on-shown="() => tippyActions!.focus()"
|
||||||
|
>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('action.more')"
|
||||||
|
:icon="IconMoreVertical"
|
||||||
|
/>
|
||||||
|
<template #content="{ hide }">
|
||||||
|
<div
|
||||||
|
ref="tippyActions"
|
||||||
|
class="flex flex-col focus:outline-none"
|
||||||
|
tabindex="0"
|
||||||
|
@keyup.r="requestAction?.$el.click()"
|
||||||
|
@keyup.n="folderAction?.$el.click()"
|
||||||
|
@keyup.e="edit?.$el.click()"
|
||||||
|
@keyup.delete="deleteAction?.$el.click()"
|
||||||
|
@keyup.x="exportAction?.$el.click()"
|
||||||
|
@keyup.p="propertiesAction?.$el.click()"
|
||||||
|
@keyup.escape="hide()"
|
||||||
|
>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="requestAction"
|
||||||
|
:icon="IconFilePlus"
|
||||||
|
:label="t('request.new')"
|
||||||
|
:shortcut="['R']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
addRequest()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="folderAction"
|
||||||
|
:icon="IconFolderPlus"
|
||||||
|
:label="t('folder.new')"
|
||||||
|
:shortcut="['N']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
addChildCollection()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="edit"
|
||||||
|
:icon="IconEdit"
|
||||||
|
:label="t('action.edit')"
|
||||||
|
:shortcut="['E']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
editCollection()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="exportAction"
|
||||||
|
:icon="IconDownload"
|
||||||
|
:label="t('export.title')"
|
||||||
|
:shortcut="['X']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('export-collection', collectionView.collectionID)
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="deleteAction"
|
||||||
|
:icon="IconTrash2"
|
||||||
|
:label="t('action.delete')"
|
||||||
|
:shortcut="['⌫']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
removeCollection()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="propertiesAction"
|
||||||
|
:icon="IconSettings2"
|
||||||
|
:label="t('action.properties')"
|
||||||
|
:shortcut="['P']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit(
|
||||||
|
'edit-collection-properties',
|
||||||
|
collectionView.collectionID
|
||||||
|
)
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="collectionView.isLastItem"
|
||||||
|
class="w-full transition"
|
||||||
|
:class="[
|
||||||
|
{
|
||||||
|
'bg-accentDark': isLastItemReorderable,
|
||||||
|
'h-1 ': collectionView.isLastItem,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@drop="updateLastItemOrder"
|
||||||
|
@dragover.prevent="orderingLastItem = true"
|
||||||
|
@dragleave="orderingLastItem = false"
|
||||||
|
@dragend="resetDragState"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from "vue"
|
||||||
|
import { TippyComponent } from "vue-tippy"
|
||||||
|
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
import {
|
||||||
|
currentReorderingStatus$,
|
||||||
|
changeCurrentReorderStatus,
|
||||||
|
} from "~/newstore/reordering"
|
||||||
|
import { RESTCollectionViewCollection } from "~/services/new-workspace/view"
|
||||||
|
|
||||||
|
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||||
|
import IconDownload from "~icons/lucide/download"
|
||||||
|
import IconEdit from "~icons/lucide/edit"
|
||||||
|
import IconFilePlus from "~icons/lucide/file-plus"
|
||||||
|
import IconFolder from "~icons/lucide/folder"
|
||||||
|
import IconFolderOpen from "~icons/lucide/folder-open"
|
||||||
|
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||||
|
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||||
|
import IconSettings2 from "~icons/lucide/settings-2"
|
||||||
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
collectionView: RESTCollectionViewCollection
|
||||||
|
isOpen: boolean
|
||||||
|
isSelected?: boolean | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "add-child-collection", parentCollectionIndexPath: string): void
|
||||||
|
(event: "add-request", parentCollectionIndexPath: string): void
|
||||||
|
(event: "dragging", payload: boolean): void
|
||||||
|
(event: "drop-event", payload: DataTransfer): void
|
||||||
|
(event: "drag-event", payload: DataTransfer): void
|
||||||
|
(
|
||||||
|
event: "edit-child-collection",
|
||||||
|
payload: { collectionIndexPath: string; collectionName: string }
|
||||||
|
): void
|
||||||
|
(event: "edit-collection-properties", collectionIndexPath: string): void
|
||||||
|
(
|
||||||
|
event: "edit-root-collection",
|
||||||
|
payload: { collectionIndexPath: string; collectionName: string }
|
||||||
|
): void
|
||||||
|
(event: "export-collection", collectionIndexPath: string): void
|
||||||
|
(event: "remove-child-collection", collectionIndexPath: string): void
|
||||||
|
(event: "remove-root-collection", collectionIndexPath: string): void
|
||||||
|
(event: "toggle-children"): void
|
||||||
|
(event: "update-collection-order", payload: DataTransfer): void
|
||||||
|
(event: "update-last-collection-order", payload: DataTransfer): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
|
const requestAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const folderAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const edit = ref<HTMLButtonElement | null>(null)
|
||||||
|
const deleteAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const exportAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const propertiesAction = ref<TippyComponent | null>(null)
|
||||||
|
const options = ref<TippyComponent | null>(null)
|
||||||
|
|
||||||
|
const dragging = ref(false)
|
||||||
|
const ordering = ref(false)
|
||||||
|
const orderingLastItem = ref(false)
|
||||||
|
const dropItemID = ref("")
|
||||||
|
|
||||||
|
const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
|
||||||
|
type: "collection",
|
||||||
|
id: "",
|
||||||
|
parentID: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Used to determine if the collection is being dragged to a different destination
|
||||||
|
// This is used to make the highlight effect work
|
||||||
|
watch(
|
||||||
|
() => dragging.value,
|
||||||
|
(val) => {
|
||||||
|
if (val && notSameDestination.value && notSameParentDestination.value) {
|
||||||
|
emit("dragging", true)
|
||||||
|
} else {
|
||||||
|
emit("dragging", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const collectionIcon = computed(() => {
|
||||||
|
if (props.isSelected) {
|
||||||
|
return IconCheckCircle
|
||||||
|
}
|
||||||
|
return props.isOpen ? IconFolderOpen : IconFolder
|
||||||
|
})
|
||||||
|
|
||||||
|
const notSameParentDestination = computed(() => {
|
||||||
|
return (
|
||||||
|
currentReorderingStatus.value.parentID !== props.collectionView.collectionID
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isRequestDragging = computed(() => {
|
||||||
|
return currentReorderingStatus.value.type === "request"
|
||||||
|
})
|
||||||
|
|
||||||
|
const isSameParent = computed(() => {
|
||||||
|
return (
|
||||||
|
currentReorderingStatus.value.parentID ===
|
||||||
|
props.collectionView.parentCollectionID
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isReorderable = computed(() => {
|
||||||
|
return (
|
||||||
|
ordering.value &&
|
||||||
|
notSameDestination.value &&
|
||||||
|
!isRequestDragging.value &&
|
||||||
|
isSameParent.value
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const isLastItemReorderable = computed(() => {
|
||||||
|
return (
|
||||||
|
orderingLastItem.value &&
|
||||||
|
notSameDestination.value &&
|
||||||
|
!isRequestDragging.value &&
|
||||||
|
isSameParent.value
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const addChildCollection = () => {
|
||||||
|
emit("add-child-collection", props.collectionView.collectionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addRequest = () => {
|
||||||
|
emit("add-request", props.collectionView.collectionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
const editCollection = () => {
|
||||||
|
const { collectionID: collectionIndexPath, name: collectionName } =
|
||||||
|
props.collectionView
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
collectionIndexPath,
|
||||||
|
collectionName,
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionIndexPath.split("/").length > 1
|
||||||
|
? emit("edit-child-collection", data)
|
||||||
|
: emit("edit-root-collection", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dragStart = ({ dataTransfer }: DragEvent) => {
|
||||||
|
if (dataTransfer) {
|
||||||
|
emit("drag-event", dataTransfer)
|
||||||
|
|
||||||
|
dropItemID.value = dataTransfer.getData("collectionIndex")
|
||||||
|
dragging.value = !dragging.value
|
||||||
|
|
||||||
|
changeCurrentReorderStatus({
|
||||||
|
type: "collection",
|
||||||
|
id: props.collectionView.collectionID,
|
||||||
|
parentID: props.collectionView.parentCollectionID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger the re-ordering event when a collection is dragged over another collection's top section
|
||||||
|
const handleDragOver = (e: DragEvent) => {
|
||||||
|
dragging.value = true
|
||||||
|
if (
|
||||||
|
e.offsetY < 10 &&
|
||||||
|
notSameDestination.value &&
|
||||||
|
!isRequestDragging.value &&
|
||||||
|
isSameParent.value
|
||||||
|
) {
|
||||||
|
ordering.value = true
|
||||||
|
dragging.value = false
|
||||||
|
orderingLastItem.value = false
|
||||||
|
} else if (
|
||||||
|
e.offsetY > 18 &&
|
||||||
|
notSameDestination.value &&
|
||||||
|
!isRequestDragging.value &&
|
||||||
|
isSameParent.value &&
|
||||||
|
props.collectionView.isLastItem
|
||||||
|
) {
|
||||||
|
orderingLastItem.value = true
|
||||||
|
dragging.value = false
|
||||||
|
ordering.value = false
|
||||||
|
} else {
|
||||||
|
ordering.value = false
|
||||||
|
orderingLastItem.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDrop = (e: DragEvent) => {
|
||||||
|
if (ordering.value) {
|
||||||
|
orderUpdateCollectionEvent(e)
|
||||||
|
} else if (orderingLastItem.value) {
|
||||||
|
updateLastItemOrder(e)
|
||||||
|
} else {
|
||||||
|
notSameParentDestination.value ? dropEvent(e) : e.stopPropagation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropEvent = (e: DragEvent) => {
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.stopPropagation()
|
||||||
|
emit("drop-event", e.dataTransfer)
|
||||||
|
resetDragState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderUpdateCollectionEvent = (e: DragEvent) => {
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.stopPropagation()
|
||||||
|
emit("update-collection-order", e.dataTransfer)
|
||||||
|
resetDragState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateLastItemOrder = (e: DragEvent) => {
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.stopPropagation()
|
||||||
|
emit("update-last-collection-order", e.dataTransfer)
|
||||||
|
resetDragState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notSameDestination = computed(() => {
|
||||||
|
return dropItemID.value !== props.collectionView.collectionID
|
||||||
|
})
|
||||||
|
|
||||||
|
const removeCollection = () => {
|
||||||
|
const { collectionID } = props.collectionView
|
||||||
|
|
||||||
|
collectionID.split("/").length > 1
|
||||||
|
? emit("remove-child-collection", collectionID)
|
||||||
|
: emit("remove-root-collection", collectionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetDragState = () => {
|
||||||
|
dragging.value = false
|
||||||
|
ordering.value = false
|
||||||
|
orderingLastItem.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div
|
||||||
|
class="h-1 w-full transition"
|
||||||
|
:class="[
|
||||||
|
{
|
||||||
|
'bg-accentDark': isReorderable,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@drop="updateRequestOrder"
|
||||||
|
@dragover.prevent="ordering = true"
|
||||||
|
@dragleave="resetDragState"
|
||||||
|
@dragend="resetDragState"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="group flex items-stretch"
|
||||||
|
:draggable="true"
|
||||||
|
@dragstart="dragStart"
|
||||||
|
@dragover="handleDragOver($event)"
|
||||||
|
@dragleave="resetDragState"
|
||||||
|
@dragend="resetDragState"
|
||||||
|
@drop="handleDrop"
|
||||||
|
@contextmenu.prevent="options?.tippy?.show()"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="pointer-events-auto flex min-w-0 flex-1 cursor-pointer items-center justify-center"
|
||||||
|
@click="selectRequest"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="pointer-events-none flex w-16 items-center justify-center truncate px-2"
|
||||||
|
:class="requestLabelColor"
|
||||||
|
:style="{ color: requestLabelColor }"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="IconCheckCircle"
|
||||||
|
v-if="isSelected"
|
||||||
|
class="svg-icons"
|
||||||
|
:class="{ 'text-accent': isSelected }"
|
||||||
|
/>
|
||||||
|
<span v-else class="truncate text-tiny font-semibold">
|
||||||
|
{{ requestView.request.method }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
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 }">
|
||||||
|
{{ requestView.request.name }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="props.isActive"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
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 h-full w-full flex-shrink-0 animate-ping rounded-full bg-green-500 opacity-75"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="relative inline-flex h-1.5 w-1.5 flex-shrink-0 rounded-full bg-green-500"
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:icon="IconRotateCCW"
|
||||||
|
:title="t('action.restore')"
|
||||||
|
class="hidden group-hover:inline-flex"
|
||||||
|
@click="selectRequest"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
<tippy
|
||||||
|
ref="options"
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
:on-shown="() => tippyActions?.focus()"
|
||||||
|
>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('action.more')"
|
||||||
|
:icon="IconMoreVertical"
|
||||||
|
/>
|
||||||
|
<template #content="{ hide }">
|
||||||
|
<div
|
||||||
|
ref="tippyActions"
|
||||||
|
class="flex flex-col focus:outline-none"
|
||||||
|
tabindex="0"
|
||||||
|
@keyup.e="edit?.$el.click()"
|
||||||
|
@keyup.d="duplicate?.$el.click()"
|
||||||
|
@keyup.delete="deleteAction?.$el.click()"
|
||||||
|
@keyup.s="shareAction?.$el.click()"
|
||||||
|
@keyup.escape="hide()"
|
||||||
|
>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="edit"
|
||||||
|
:icon="IconEdit"
|
||||||
|
:label="t('action.edit')"
|
||||||
|
:shortcut="['E']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('edit-request', {
|
||||||
|
requestIndexPath: requestView.requestID,
|
||||||
|
requestName: requestView.request.name,
|
||||||
|
})
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="duplicate"
|
||||||
|
:icon="IconCopy"
|
||||||
|
:label="t('action.duplicate')"
|
||||||
|
:shortcut="['D']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('duplicate-request', requestView.requestID)
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="deleteAction"
|
||||||
|
:icon="IconTrash2"
|
||||||
|
:label="t('action.delete')"
|
||||||
|
:shortcut="['⌫']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('remove-request', requestView.requestID)
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="shareAction"
|
||||||
|
:icon="IconShare2"
|
||||||
|
:label="t('action.share')"
|
||||||
|
:shortcut="['S']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('share-request', requestView.request)
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-full transition"
|
||||||
|
:class="[
|
||||||
|
{
|
||||||
|
'bg-accentDark': isLastItemReorderable,
|
||||||
|
'h-1 ': props.requestView.isLastItem,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@drop="handleDrop"
|
||||||
|
@dragover.prevent="orderingLastItem = true"
|
||||||
|
@dragleave="resetDragState"
|
||||||
|
@dragend="resetDragState"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { computed, ref } from "vue"
|
||||||
|
import { TippyComponent } from "vue-tippy"
|
||||||
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
|
||||||
|
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
||||||
|
import {
|
||||||
|
currentReorderingStatus$,
|
||||||
|
changeCurrentReorderStatus,
|
||||||
|
} from "~/newstore/reordering"
|
||||||
|
import { RESTCollectionViewRequest } from "~/services/new-workspace/view"
|
||||||
|
|
||||||
|
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||||
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
import IconEdit from "~icons/lucide/edit"
|
||||||
|
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||||
|
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||||
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
|
||||||
|
type: "collection",
|
||||||
|
id: "",
|
||||||
|
parentID: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
isActive: boolean
|
||||||
|
requestView: RESTCollectionViewRequest
|
||||||
|
isSelected: boolean | null | undefined
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "duplicate-request", requestIndexPath: string): void
|
||||||
|
(
|
||||||
|
event: "edit-request",
|
||||||
|
payload: {
|
||||||
|
requestIndexPath: string
|
||||||
|
requestName: string
|
||||||
|
}
|
||||||
|
): void
|
||||||
|
(event: "remove-request", requestIndexPath: string): void
|
||||||
|
(event: "select-request", requestIndexPath: string): void
|
||||||
|
(event: "share-request", request: HoppRESTRequest): void
|
||||||
|
(event: "drag-request", payload: DataTransfer): void
|
||||||
|
(event: "update-request-order", payload: DataTransfer): void
|
||||||
|
(event: "update-last-request-order", payload: DataTransfer): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
|
const edit = ref<HTMLButtonElement | null>(null)
|
||||||
|
const deleteAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const options = ref<TippyComponent | null>(null)
|
||||||
|
const duplicate = ref<HTMLButtonElement | null>(null)
|
||||||
|
const shareAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
|
||||||
|
const dragging = ref(false)
|
||||||
|
const ordering = ref(false)
|
||||||
|
const orderingLastItem = ref(false)
|
||||||
|
|
||||||
|
const isCollectionDragging = computed(() => {
|
||||||
|
return currentReorderingStatus.value.type === "collection"
|
||||||
|
})
|
||||||
|
|
||||||
|
const isLastItemReorderable = computed(() => {
|
||||||
|
return (
|
||||||
|
orderingLastItem.value && isSameParent.value && !isCollectionDragging.value
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isReorderable = computed(() => {
|
||||||
|
return (
|
||||||
|
ordering.value &&
|
||||||
|
!isCollectionDragging.value &&
|
||||||
|
isSameParent.value &&
|
||||||
|
!isSameRequest.value
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isSameParent = computed(() => {
|
||||||
|
return (
|
||||||
|
currentReorderingStatus.value.parentID === props.requestView.collectionID
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isSameRequest = computed(() => {
|
||||||
|
return currentReorderingStatus.value.id === props.requestView.requestID
|
||||||
|
})
|
||||||
|
|
||||||
|
const requestLabelColor = computed(() =>
|
||||||
|
getMethodLabelColorClassOf(props.requestView.request)
|
||||||
|
)
|
||||||
|
|
||||||
|
const dragStart = ({ dataTransfer }: DragEvent) => {
|
||||||
|
if (dataTransfer) {
|
||||||
|
emit("drag-request", dataTransfer)
|
||||||
|
dragging.value = !dragging.value
|
||||||
|
|
||||||
|
changeCurrentReorderStatus({
|
||||||
|
type: "request",
|
||||||
|
id: props.requestView.requestID,
|
||||||
|
parentID: props.requestView.collectionID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDrop = (e: DragEvent) => {
|
||||||
|
if (ordering.value) {
|
||||||
|
updateRequestOrder(e)
|
||||||
|
} else if (orderingLastItem.value) {
|
||||||
|
updateLastItemOrder(e)
|
||||||
|
} else {
|
||||||
|
updateRequestOrder(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger the re-ordering event when a request is dragged over another request's top section
|
||||||
|
const handleDragOver = (e: DragEvent) => {
|
||||||
|
dragging.value = true
|
||||||
|
if (e.offsetY < 10) {
|
||||||
|
ordering.value = true
|
||||||
|
dragging.value = false
|
||||||
|
orderingLastItem.value = false
|
||||||
|
} else if (e.offsetY > 18) {
|
||||||
|
orderingLastItem.value = true
|
||||||
|
dragging.value = false
|
||||||
|
ordering.value = false
|
||||||
|
} else {
|
||||||
|
ordering.value = false
|
||||||
|
orderingLastItem.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetDragState = () => {
|
||||||
|
dragging.value = false
|
||||||
|
ordering.value = false
|
||||||
|
orderingLastItem.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectRequest = () => emit("select-request", props.requestView.requestID)
|
||||||
|
|
||||||
|
const updateRequestOrder = (e: DragEvent) => {
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.stopPropagation()
|
||||||
|
resetDragState()
|
||||||
|
emit("update-request-order", e.dataTransfer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateLastItemOrder = (e: DragEvent) => {
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.stopPropagation()
|
||||||
|
resetDragState()
|
||||||
|
emit("update-last-request-order", e.dataTransfer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -37,13 +37,17 @@ import { TeamNameCodec } from "~/helpers/backend/types/TeamName"
|
|||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
|
switchWorkspaceAfterCreation?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -52,8 +56,12 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const editingName = ref<string | null>(null)
|
const editingName = ref<string | null>(null)
|
||||||
|
|
||||||
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
|
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const workspaceService = useService(WorkspaceService)
|
||||||
|
|
||||||
const addNewTeam = async () => {
|
const addNewTeam = async () => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
await pipe(
|
await pipe(
|
||||||
@@ -76,8 +84,19 @@ const addNewTeam = async () => {
|
|||||||
// Handle GQL errors (use err obj)
|
// Handle GQL errors (use err obj)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => {
|
(team) => {
|
||||||
toast.success(`${t("team.new_created")}`)
|
toast.success(`${t("team.new_created")}`)
|
||||||
|
|
||||||
|
if (props.switchWorkspaceAfterCreation) {
|
||||||
|
REMEMBERED_TEAM_ID.value = team.id
|
||||||
|
workspaceService.changeWorkspace({
|
||||||
|
teamID: team.id,
|
||||||
|
teamName: team.name,
|
||||||
|
type: "team",
|
||||||
|
role: team.myRole,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
hideModal()
|
hideModal()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
class="flex items-center overflow-x-auto whitespace-nowrap border-b border-dividerLight px-4 py-2 text-tiny text-secondaryLight"
|
class="flex items-center overflow-x-auto whitespace-nowrap border-b border-dividerLight px-4 py-2 text-tiny text-secondaryLight"
|
||||||
>
|
>
|
||||||
<span class="truncate">
|
<span class="truncate">
|
||||||
{{ currentWorkspace }}
|
{{ workspaceName ?? t("workspace.no_workspace") }}
|
||||||
</span>
|
</span>
|
||||||
<icon-lucide-chevron-right v-if="section" class="mx-2" />
|
<icon-lucide-chevron-right v-if="section" class="mx-2" />
|
||||||
{{ section }}
|
{{ section }}
|
||||||
@@ -14,29 +14,24 @@
|
|||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
section?: string
|
section?: string
|
||||||
isOnlyPersonal?: boolean
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const workspaceService = useService(WorkspaceService)
|
const workspaceService = useService(NewWorkspaceService)
|
||||||
const workspace = workspaceService.currentWorkspace
|
const activeWorkspaceHandle = workspaceService.activeWorkspaceHandle
|
||||||
|
|
||||||
const currentWorkspace = computed(() => {
|
const workspaceName = computed(() => {
|
||||||
if (props.isOnlyPersonal || workspace.value.type === "personal") {
|
const activeWorkspaceHandleRef = activeWorkspaceHandle.value?.get()
|
||||||
return t("workspace.personal")
|
|
||||||
}
|
|
||||||
return teamWorkspaceName.value
|
|
||||||
})
|
|
||||||
|
|
||||||
const teamWorkspaceName = computed(() => {
|
if (activeWorkspaceHandleRef?.value.type === "ok") {
|
||||||
if (workspace.value.type === "team" && workspace.value.teamName) {
|
return activeWorkspaceHandleRef.value.data.name
|
||||||
return workspace.value.teamName
|
|
||||||
}
|
}
|
||||||
return `${t("workspace.team")}`
|
|
||||||
|
return undefined
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<HoppSmartItem
|
||||||
|
:label="'Personal Workspace'"
|
||||||
|
:info-icon="
|
||||||
|
activeWorkspaceInfo?.provider ===
|
||||||
|
personalWorkspaceProviderService.providerID &&
|
||||||
|
activeWorkspaceInfo.workspaceID === 'personal'
|
||||||
|
? IconCheck
|
||||||
|
: undefined
|
||||||
|
"
|
||||||
|
:active-info-icon="
|
||||||
|
activeWorkspaceInfo?.provider ===
|
||||||
|
personalWorkspaceProviderService.providerID &&
|
||||||
|
activeWorkspaceInfo.workspaceID === 'personal'
|
||||||
|
"
|
||||||
|
@click="selectWorkspace"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { PersonalWorkspaceProviderService } from "~/services/new-workspace/providers/personal.workspace"
|
||||||
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
|
||||||
|
const workspaceService = useService(NewWorkspaceService)
|
||||||
|
const personalWorkspaceProviderService = useService(
|
||||||
|
PersonalWorkspaceProviderService
|
||||||
|
)
|
||||||
|
|
||||||
|
const activeWorkspaceInfo = computed(() => {
|
||||||
|
const activeWorkspaceHandleRef =
|
||||||
|
workspaceService.activeWorkspaceHandle.value?.get()
|
||||||
|
|
||||||
|
if (activeWorkspaceHandleRef?.value.type === "ok") {
|
||||||
|
return {
|
||||||
|
provider: activeWorkspaceHandleRef.value.data.providerID,
|
||||||
|
workspaceID: activeWorkspaceHandleRef.value.data.workspaceID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
function selectWorkspace() {
|
||||||
|
workspaceService.activeWorkspaceHandle.value =
|
||||||
|
personalWorkspaceProviderService.getPersonalWorkspaceHandle()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,190 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="rootEl">
|
<div ref="rootEl">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-col">
|
<div
|
||||||
<HoppSmartItem
|
v-for="(selectorComponent, index) in workspaceSelectorComponents"
|
||||||
:label="t('workspace.personal')"
|
:key="index"
|
||||||
:icon="IconUser"
|
class="flex flex-col"
|
||||||
:info-icon="workspace.type === 'personal' ? IconDone : undefined"
|
>
|
||||||
:active-info-icon="workspace.type === 'personal'"
|
<component :is="selectorComponent" />
|
||||||
@click="switchToPersonalWorkspace"
|
|
||||||
/>
|
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="loading" class="flex flex-col items-center justify-center p-4">
|
|
||||||
<HoppSmartSpinner class="mb-4" />
|
|
||||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
|
||||||
</div>
|
|
||||||
<HoppSmartPlaceholder
|
|
||||||
v-if="!loading && myTeams.length === 0"
|
|
||||||
:src="`/images/states/${colorMode.value}/add_group.svg`"
|
|
||||||
:alt="`${t('empty.teams')}`"
|
|
||||||
:text="`${t('empty.teams')}`"
|
|
||||||
>
|
|
||||||
<template #body>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
:label="t('team.create_new')"
|
|
||||||
filled
|
|
||||||
outline
|
|
||||||
:icon="IconPlus"
|
|
||||||
@click="displayModalAdd(true)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
<div v-else-if="!loading" class="flex flex-col">
|
|
||||||
<div
|
|
||||||
class="sticky top-0 z-10 mb-2 flex items-center justify-between bg-popover py-2 pl-2"
|
|
||||||
>
|
|
||||||
<div class="flex items-center px-2 font-semibold text-secondaryLight">
|
|
||||||
{{ t("workspace.other_workspaces") }}
|
|
||||||
</div>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:icon="IconPlus"
|
|
||||||
:title="`${t('team.create_new')}`"
|
|
||||||
outline
|
|
||||||
filled
|
|
||||||
class="ml-8 rounded !p-0.75"
|
|
||||||
@click="displayModalAdd(true)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<HoppSmartItem
|
|
||||||
v-for="(team, index) in myTeams"
|
|
||||||
:key="`team-${String(index)}`"
|
|
||||||
:icon="IconUsers"
|
|
||||||
:label="team.name"
|
|
||||||
:info-icon="isActiveWorkspace(team.id) ? IconDone : undefined"
|
|
||||||
:active-info-icon="isActiveWorkspace(team.id)"
|
|
||||||
@click="switchToTeamWorkspace(team)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="!loading && teamListAdapterError"
|
|
||||||
class="flex flex-col items-center py-4"
|
|
||||||
>
|
|
||||||
<icon-lucide-help-circle class="svg-icons mb-4" />
|
|
||||||
{{ t("error.something_went_wrong") }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<TeamsAdd :show="showModalAdd" @hide-modal="displayModalAdd(false)" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from "vue"
|
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
import IconUser from "~icons/lucide/user"
|
|
||||||
import IconUsers from "~icons/lucide/users"
|
|
||||||
import IconPlus from "~icons/lucide/plus"
|
|
||||||
import { useColorMode } from "@composables/theming"
|
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
import IconDone from "~icons/lucide/check"
|
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { useElementVisibility, useIntervalFn } from "@vueuse/core"
|
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||||
|
import { TestWorkspaceProviderService } from "~/services/new-workspace/providers/test.workspace"
|
||||||
|
|
||||||
const t = useI18n()
|
useService(TestWorkspaceProviderService)
|
||||||
const colorMode = useColorMode()
|
|
||||||
|
|
||||||
const showModalAdd = ref(false)
|
const newWorkspaceService = useService(NewWorkspaceService)
|
||||||
|
const workspaceSelectorComponents =
|
||||||
|
newWorkspaceService.workspaceSelectorComponents
|
||||||
|
|
||||||
const currentUser = useReadonlyStream(
|
// TODO: Handle the updates to these actions
|
||||||
platform.auth.getProbableUserStream(),
|
// defineActionHandler("modals.team.new", () => {
|
||||||
platform.auth.getProbableUser()
|
// displayModalAdd(true)
|
||||||
)
|
// })
|
||||||
|
//
|
||||||
const workspaceService = useService(WorkspaceService)
|
// defineActionHandler("workspace.switch.personal", switchToPersonalWorkspace)
|
||||||
const teamListadapter = workspaceService.acquireTeamListAdapter(null)
|
// defineActionHandler("workspace.switch", ({ teamId }) => {
|
||||||
const myTeams = useReadonlyStream(teamListadapter.teamList$, [])
|
// const team = myTeams.value.find((t) => t.id === teamId)
|
||||||
const isTeamListLoading = useReadonlyStream(teamListadapter.loading$, false)
|
// if (team) switchToTeamWorkspace(team)
|
||||||
const teamListAdapterError = useReadonlyStream(teamListadapter.error$, null)
|
// })
|
||||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
|
||||||
const teamListFetched = ref(false)
|
|
||||||
|
|
||||||
const rootEl = ref<HTMLElement>()
|
|
||||||
const elVisible = useElementVisibility(rootEl)
|
|
||||||
|
|
||||||
const { pause: pauseListPoll, resume: resumeListPoll } = useIntervalFn(() => {
|
|
||||||
if (teamListadapter.isInitialized) {
|
|
||||||
teamListadapter.fetchList()
|
|
||||||
}
|
|
||||||
}, 10000)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
elVisible,
|
|
||||||
() => {
|
|
||||||
if (elVisible.value) {
|
|
||||||
teamListadapter.fetchList()
|
|
||||||
|
|
||||||
resumeListPoll()
|
|
||||||
} else {
|
|
||||||
pauseListPoll()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(myTeams, (teams) => {
|
|
||||||
if (teams && !teamListFetched.value) {
|
|
||||||
teamListFetched.value = true
|
|
||||||
if (REMEMBERED_TEAM_ID.value && currentUser.value) {
|
|
||||||
const team = teams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
|
||||||
if (team) switchToTeamWorkspace(team)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const loading = computed(
|
|
||||||
() => isTeamListLoading.value && myTeams.value.length === 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const workspace = workspaceService.currentWorkspace
|
|
||||||
|
|
||||||
const isActiveWorkspace = computed(() => (id: string) => {
|
|
||||||
if (workspace.value.type === "personal") return false
|
|
||||||
return workspace.value.teamID === id
|
|
||||||
})
|
|
||||||
|
|
||||||
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
|
||||||
REMEMBERED_TEAM_ID.value = team.id
|
|
||||||
workspaceService.changeWorkspace({
|
|
||||||
teamID: team.id,
|
|
||||||
teamName: team.name,
|
|
||||||
type: "team",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const switchToPersonalWorkspace = () => {
|
|
||||||
REMEMBERED_TEAM_ID.value = undefined
|
|
||||||
workspaceService.changeWorkspace({
|
|
||||||
type: "personal",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => currentUser.value,
|
|
||||||
(user) => {
|
|
||||||
if (!user) {
|
|
||||||
switchToPersonalWorkspace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
|
||||||
showModalAdd.value = shouldDisplay
|
|
||||||
teamListadapter.fetchList()
|
|
||||||
}
|
|
||||||
|
|
||||||
defineActionHandler("modals.team.new", () => {
|
|
||||||
displayModalAdd(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
defineActionHandler("workspace.switch.personal", switchToPersonalWorkspace)
|
|
||||||
defineActionHandler("workspace.switch", ({ teamId }) => {
|
|
||||||
const team = myTeams.value.find((t) => t.id === teamId)
|
|
||||||
if (team) switchToTeamWorkspace(team)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<HoppSmartItem
|
||||||
|
v-for="candidate in candidates"
|
||||||
|
:key="candidate.id"
|
||||||
|
:label="candidate.name"
|
||||||
|
:info-icon="
|
||||||
|
activeWorkspaceInfo?.provider ===
|
||||||
|
testWorkspaceProviderService.providerID &&
|
||||||
|
activeWorkspaceInfo.workspaceID === candidate.id
|
||||||
|
? IconCheck
|
||||||
|
: undefined
|
||||||
|
"
|
||||||
|
:active-info-icon="
|
||||||
|
activeWorkspaceInfo?.provider ===
|
||||||
|
testWorkspaceProviderService.providerID &&
|
||||||
|
activeWorkspaceInfo.workspaceID === candidate.id
|
||||||
|
"
|
||||||
|
@click="selectWorkspace(candidate.id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||||
|
import { TestWorkspaceProviderService } from "~/services/new-workspace/providers/test.workspace"
|
||||||
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
|
||||||
|
const workspaceService = useService(NewWorkspaceService)
|
||||||
|
const testWorkspaceProviderService = useService(TestWorkspaceProviderService)
|
||||||
|
|
||||||
|
const candidates = testWorkspaceProviderService.getWorkspaceCandidates()
|
||||||
|
|
||||||
|
const activeWorkspaceInfo = computed(() => {
|
||||||
|
const activeWorkspaceHandle = workspaceService.activeWorkspaceHandle.value
|
||||||
|
const activeWorkspaceHandleRef = activeWorkspaceHandle?.get()
|
||||||
|
|
||||||
|
if (activeWorkspaceHandleRef?.value.type === "ok") {
|
||||||
|
return {
|
||||||
|
provider: activeWorkspaceHandleRef.value.data.providerID,
|
||||||
|
workspaceID: activeWorkspaceHandleRef.value.data.workspaceID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
async function selectWorkspace(workspaceID: string) {
|
||||||
|
const result =
|
||||||
|
await testWorkspaceProviderService.getWorkspaceHandle(workspaceID)
|
||||||
|
|
||||||
|
// TODO: Re-evaluate this ?
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
console.error(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceService.activeWorkspaceHandle.value = result.right
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { HoppCollection } from "@hoppscotch/data"
|
||||||
|
import { ChildrenResult, SmartTreeAdapter } from "@hoppscotch/ui"
|
||||||
|
import { Ref, computed, ref } from "vue"
|
||||||
|
import { navigateToFolderWithIndexPath } from "~/newstore/collections"
|
||||||
|
import { RESTCollectionViewItem } from "~/services/new-workspace/view"
|
||||||
|
|
||||||
|
export class WorkspaceRESTSearchCollectionTreeAdapter
|
||||||
|
implements SmartTreeAdapter<RESTCollectionViewItem>
|
||||||
|
{
|
||||||
|
constructor(public data: Ref<HoppCollection[]>) {}
|
||||||
|
|
||||||
|
getChildren(
|
||||||
|
nodeID: string | null
|
||||||
|
): Ref<ChildrenResult<RESTCollectionViewItem>> {
|
||||||
|
const result = ref<ChildrenResult<RESTCollectionViewItem>>({
|
||||||
|
status: "loading",
|
||||||
|
})
|
||||||
|
|
||||||
|
return computed(() => {
|
||||||
|
if (nodeID === null) {
|
||||||
|
result.value = {
|
||||||
|
status: "loaded",
|
||||||
|
data: this.data.value.map((item, index) => ({
|
||||||
|
id: index.toString(),
|
||||||
|
|
||||||
|
data: <RESTCollectionViewItem>{
|
||||||
|
type: "collection",
|
||||||
|
value: {
|
||||||
|
collectionID: index.toString(),
|
||||||
|
isLastItem: index === this.data.value.length - 1,
|
||||||
|
name: item.name,
|
||||||
|
parentCollectionID: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const indexPath = nodeID.split("/").map((x) => parseInt(x))
|
||||||
|
|
||||||
|
const item = navigateToFolderWithIndexPath(this.data.value, indexPath)
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
const collections = item.folders.map(
|
||||||
|
(childCollection, childCollectionID) => {
|
||||||
|
return {
|
||||||
|
id: `${nodeID}/${childCollectionID}`,
|
||||||
|
data: <RESTCollectionViewItem>{
|
||||||
|
type: "collection",
|
||||||
|
value: {
|
||||||
|
isLastItem:
|
||||||
|
item.folders?.length > 1
|
||||||
|
? childCollectionID === item.folders.length - 1
|
||||||
|
: false,
|
||||||
|
collectionID: `${nodeID}/${childCollectionID}`,
|
||||||
|
name: childCollection.name,
|
||||||
|
parentCollectionID: nodeID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const requests = item.requests.map((request, requestID) => {
|
||||||
|
return {
|
||||||
|
id: `${nodeID}/${requestID}`,
|
||||||
|
data: <RESTCollectionViewItem>{
|
||||||
|
type: "request",
|
||||||
|
value: {
|
||||||
|
isLastItem:
|
||||||
|
item.requests?.length > 1
|
||||||
|
? requestID === item.requests.length - 1
|
||||||
|
: false,
|
||||||
|
parentCollectionID: nodeID,
|
||||||
|
collectionID: nodeID,
|
||||||
|
requestID: `${nodeID}/${requestID}`,
|
||||||
|
request,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
result.value = {
|
||||||
|
status: "loaded",
|
||||||
|
data: [...collections, ...requests],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import {
|
||||||
|
ChildrenResult,
|
||||||
|
SmartTreeAdapter,
|
||||||
|
} from "@hoppscotch/ui/dist/src/helpers/treeAdapter"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { Ref, ref, watchEffect } from "vue"
|
||||||
|
import { NewWorkspaceService } from "~/services/new-workspace"
|
||||||
|
import { Handle } from "~/services/new-workspace/handle"
|
||||||
|
import { RESTCollectionViewItem } from "~/services/new-workspace/view"
|
||||||
|
import { Workspace } from "~/services/new-workspace/workspace"
|
||||||
|
|
||||||
|
export class WorkspaceRESTCollectionTreeAdapter
|
||||||
|
implements SmartTreeAdapter<RESTCollectionViewItem>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private workspaceHandle: Handle<Workspace>,
|
||||||
|
private workspaceService: NewWorkspaceService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public getChildren(
|
||||||
|
nodeID: string | null,
|
||||||
|
nodeType?: string
|
||||||
|
): Ref<ChildrenResult<RESTCollectionViewItem>> {
|
||||||
|
const workspaceHandleRef = this.workspaceHandle.get()
|
||||||
|
|
||||||
|
if (workspaceHandleRef.value.type !== "ok") {
|
||||||
|
throw new Error("Cannot issue children with invalid workspace handle")
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = ref<ChildrenResult<RESTCollectionViewItem>>({
|
||||||
|
status: "loading",
|
||||||
|
})
|
||||||
|
|
||||||
|
if (nodeID !== null) {
|
||||||
|
;(async () => {
|
||||||
|
if (nodeType === "request") {
|
||||||
|
result.value = {
|
||||||
|
status: "loaded",
|
||||||
|
data: [],
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionHandleResult =
|
||||||
|
await this.workspaceService.getCollectionHandle(
|
||||||
|
this.workspaceHandle,
|
||||||
|
nodeID
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Better error handling
|
||||||
|
if (E.isLeft(collectionHandleResult)) {
|
||||||
|
throw new Error(JSON.stringify(collectionHandleResult.left.error))
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionHandle = collectionHandleResult.right
|
||||||
|
|
||||||
|
const collectionChildrenResult =
|
||||||
|
await this.workspaceService.getRESTCollectionChildrenView(
|
||||||
|
collectionHandle
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Better error handling
|
||||||
|
if (E.isLeft(collectionChildrenResult)) {
|
||||||
|
throw new Error(JSON.stringify(collectionChildrenResult.left.error))
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionChildrenViewHandle =
|
||||||
|
collectionChildrenResult.right.get()
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (collectionChildrenViewHandle.value.type !== "ok") return
|
||||||
|
|
||||||
|
if (collectionChildrenViewHandle.value.data.loading.value) {
|
||||||
|
result.value = {
|
||||||
|
status: "loading",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.value = {
|
||||||
|
status: "loaded",
|
||||||
|
data: collectionChildrenViewHandle.value.data.content.value.map(
|
||||||
|
(item) => ({
|
||||||
|
id:
|
||||||
|
item.type === "request"
|
||||||
|
? item.value.requestID
|
||||||
|
: item.value.collectionID,
|
||||||
|
|
||||||
|
data: item,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
} else {
|
||||||
|
;(async () => {
|
||||||
|
const viewResult =
|
||||||
|
await this.workspaceService.getRESTRootCollectionView(
|
||||||
|
this.workspaceHandle
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Better error handling
|
||||||
|
if (E.isLeft(viewResult)) {
|
||||||
|
throw new Error(JSON.stringify(viewResult.left.error))
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewHandle = viewResult.right.get()
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (viewHandle.value.type !== "ok") return
|
||||||
|
|
||||||
|
if (viewHandle.value.data.loading.value) {
|
||||||
|
result.value = {
|
||||||
|
status: "loading",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.value = {
|
||||||
|
status: "loaded",
|
||||||
|
data: viewHandle.value.data.collections.value.map((coll) => ({
|
||||||
|
id: coll.collectionID,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
type: "collection",
|
||||||
|
value: coll,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
import { HoppCollection } from "@hoppscotch/data"
|
import { HoppCollection } from "@hoppscotch/data"
|
||||||
import { getAffectedIndexes } from "./affectedIndex"
|
|
||||||
import { GetSingleRequestDocument } from "../backend/graphql"
|
|
||||||
import { runGQLQuery } from "../backend/GQLClient"
|
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
|
|
||||||
import { getService } from "~/modules/dioc"
|
import { getService } from "~/modules/dioc"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
|
||||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
|
||||||
import { GQLTabService } from "~/services/tab/graphql"
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { runGQLQuery } from "../backend/GQLClient"
|
||||||
|
import { GetSingleRequestDocument } from "../backend/graphql"
|
||||||
|
import { HoppGQLSaveContext } from "../graphql/document"
|
||||||
|
import { HoppRESTSaveContext } from "../rest/document"
|
||||||
|
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||||
|
import { getAffectedIndexes } from "./affectedIndex"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve save context on reorder
|
* Resolve save context on reorder
|
||||||
@@ -18,15 +21,12 @@ import { GQLTabService } from "~/services/tab/graphql"
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function resolveSaveContextOnCollectionReorder(
|
export function resolveSaveContextOnCollectionReorder(payload: {
|
||||||
payload: {
|
lastIndex: number
|
||||||
lastIndex: number
|
newIndex: number
|
||||||
newIndex: number
|
folderPath: string
|
||||||
folderPath: string
|
length?: number // better way to do this? now it could be undefined
|
||||||
length?: number // better way to do this? now it could be undefined
|
}) {
|
||||||
},
|
|
||||||
type: "remove" | "drop" = "remove"
|
|
||||||
) {
|
|
||||||
const { lastIndex, folderPath, length } = payload
|
const { lastIndex, folderPath, length } = payload
|
||||||
let { newIndex } = payload
|
let { newIndex } = payload
|
||||||
|
|
||||||
@@ -41,12 +41,6 @@ export function resolveSaveContextOnCollectionReorder(
|
|||||||
if (newIndex === -1) {
|
if (newIndex === -1) {
|
||||||
// if (newIndex === -1) remove it from the map because it will be deleted
|
// if (newIndex === -1) remove it from the map because it will be deleted
|
||||||
affectedIndexes.delete(lastIndex)
|
affectedIndexes.delete(lastIndex)
|
||||||
// when collection deleted opended requests from that collection be affected
|
|
||||||
if (type === "remove") {
|
|
||||||
resetSaveContextForAffectedRequests(
|
|
||||||
folderPath ? `${folderPath}/${lastIndex}` : lastIndex.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add folder path as prefix to the affected indexes
|
// add folder path as prefix to the affected indexes
|
||||||
@@ -62,10 +56,27 @@ export function resolveSaveContextOnCollectionReorder(
|
|||||||
const tabService = getService(RESTTabService)
|
const tabService = getService(RESTTabService)
|
||||||
|
|
||||||
const tabs = tabService.getTabsRefTo((tab) => {
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return (
|
if (tab.document.saveContext?.originLocation === "user-collection") {
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
return affectedPaths.has(tab.document.saveContext.folderPath)
|
||||||
affectedPaths.has(tab.document.saveContext.folderPath)
|
}
|
||||||
)
|
|
||||||
|
if (
|
||||||
|
tab.document.saveContext?.originLocation !== "workspace-user-collection"
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestHandleRef = tab.document.saveContext.requestHandle?.get()
|
||||||
|
|
||||||
|
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const { requestID } = requestHandleRef.value.data
|
||||||
|
|
||||||
|
const collectionID = requestID.split("/").slice(0, -1).join("/")
|
||||||
|
|
||||||
|
return affectedPaths.has(collectionID)
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
@@ -75,6 +86,34 @@ export function resolveSaveContextOnCollectionReorder(
|
|||||||
)!
|
)!
|
||||||
tab.value.document.saveContext.folderPath = newPath
|
tab.value.document.saveContext.folderPath = newPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
tab.value.document.saveContext?.originLocation !==
|
||||||
|
"workspace-user-collection"
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestHandleRef = tab.value.document.saveContext.requestHandle?.get()
|
||||||
|
|
||||||
|
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const { requestID } = requestHandleRef.value.data
|
||||||
|
|
||||||
|
const collectionID = requestID.split("/").slice(0, -1).join("/")
|
||||||
|
|
||||||
|
const newCollectionID = affectedPaths.get(collectionID)
|
||||||
|
const newRequestID = `${newCollectionID}/${
|
||||||
|
requestID.split("/").slice(-1)[0]
|
||||||
|
}`
|
||||||
|
|
||||||
|
requestHandleRef.value.data = {
|
||||||
|
...requestHandleRef.value.data,
|
||||||
|
collectionID: newCollectionID!,
|
||||||
|
requestID: newRequestID,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,25 +125,63 @@ export function resolveSaveContextOnCollectionReorder(
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function updateSaveContextForAffectedRequests(
|
export function updateSaveContextForAffectedRequests(
|
||||||
oldFolderPath: string,
|
draggedCollectionIndex: string,
|
||||||
newFolderPath: string
|
destinationCollectionIndex: string
|
||||||
) {
|
) {
|
||||||
const tabService = getService(RESTTabService)
|
const tabService = getService(RESTTabService)
|
||||||
const tabs = tabService.getTabsRefTo((tab) => {
|
|
||||||
return (
|
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
|
||||||
tab.document.saveContext.folderPath.startsWith(oldFolderPath)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const tab of tabs) {
|
const activeTabs = tabService.getActiveTabs()
|
||||||
if (tab.value.document.saveContext?.originLocation === "user-collection") {
|
|
||||||
tab.value.document.saveContext = {
|
for (const tab of activeTabs.value) {
|
||||||
...tab.value.document.saveContext,
|
if (tab.document.saveContext?.originLocation === "user-collection") {
|
||||||
folderPath: tab.value.document.saveContext.folderPath.replace(
|
const { folderPath } = tab.document.saveContext
|
||||||
oldFolderPath,
|
|
||||||
newFolderPath
|
if (folderPath.startsWith(draggedCollectionIndex)) {
|
||||||
),
|
const newFolderPath = folderPath.replace(
|
||||||
|
draggedCollectionIndex,
|
||||||
|
destinationCollectionIndex
|
||||||
|
)
|
||||||
|
|
||||||
|
tab.document.saveContext = {
|
||||||
|
...tab.document.saveContext,
|
||||||
|
folderPath: newFolderPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
tab.document.saveContext?.originLocation === "workspace-user-collection"
|
||||||
|
) {
|
||||||
|
const requestHandleRef = tab.document.saveContext.requestHandle?.get()
|
||||||
|
|
||||||
|
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const { requestID } = requestHandleRef.value.data
|
||||||
|
|
||||||
|
const collectionID = requestID.split("/").slice(0, -1).join("/")
|
||||||
|
const requestIndex = requestID.split("/").slice(-1)[0]
|
||||||
|
|
||||||
|
if (collectionID.startsWith(draggedCollectionIndex)) {
|
||||||
|
const newCollectionID = collectionID.replace(
|
||||||
|
draggedCollectionIndex,
|
||||||
|
destinationCollectionIndex
|
||||||
|
)
|
||||||
|
const newRequestID = `${newCollectionID}/${requestIndex}`
|
||||||
|
|
||||||
|
tab.document.saveContext = {
|
||||||
|
...tab.document.saveContext,
|
||||||
|
requestID: newRequestID,
|
||||||
|
}
|
||||||
|
|
||||||
|
requestHandleRef.value.data = {
|
||||||
|
...requestHandleRef.value.data,
|
||||||
|
collectionID: newCollectionID,
|
||||||
|
requestID: newRequestID,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,6 +243,33 @@ function removeDuplicatesAndKeepLast(arr: HoppInheritedProperty["headers"]) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSaveContextCollectionID(
|
||||||
|
saveContext: HoppRESTSaveContext | HoppGQLSaveContext | undefined
|
||||||
|
): string | undefined {
|
||||||
|
if (!saveContext) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { originLocation } = saveContext
|
||||||
|
|
||||||
|
if (originLocation === "team-collection") {
|
||||||
|
return saveContext.collectionID
|
||||||
|
}
|
||||||
|
|
||||||
|
if (originLocation === "user-collection") {
|
||||||
|
return saveContext.folderPath
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestHandleRef = saveContext.requestHandle?.get()
|
||||||
|
|
||||||
|
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove `collectionID` and obtain it from `requestID`
|
||||||
|
return requestHandleRef.value.data.collectionID
|
||||||
|
}
|
||||||
|
|
||||||
export function updateInheritedPropertiesForAffectedRequests(
|
export function updateInheritedPropertiesForAffectedRequests(
|
||||||
path: string,
|
path: string,
|
||||||
inheritedProperties: HoppInheritedProperty,
|
inheritedProperties: HoppInheritedProperty,
|
||||||
@@ -177,22 +281,17 @@ export function updateInheritedPropertiesForAffectedRequests(
|
|||||||
const effectedTabs = tabService.getTabsRefTo((tab) => {
|
const effectedTabs = tabService.getTabsRefTo((tab) => {
|
||||||
const saveContext = tab.document.saveContext
|
const saveContext = tab.document.saveContext
|
||||||
|
|
||||||
const saveContextPath =
|
const collectionID = getSaveContextCollectionID(saveContext)
|
||||||
saveContext?.originLocation === "team-collection"
|
return collectionID?.startsWith(path) ?? false
|
||||||
? saveContext.collectionID
|
|
||||||
: saveContext?.folderPath
|
|
||||||
|
|
||||||
return saveContextPath?.startsWith(path) ?? false
|
|
||||||
})
|
})
|
||||||
|
|
||||||
effectedTabs.map((tab) => {
|
effectedTabs.map((tab) => {
|
||||||
const inheritedParentID =
|
const inheritedParentID =
|
||||||
tab.value.document.inheritedProperties?.auth.parentID
|
tab.value.document.inheritedProperties?.auth.parentID
|
||||||
|
|
||||||
const contextPath =
|
const contextPath = getSaveContextCollectionID(
|
||||||
tab.value.document.saveContext?.originLocation === "team-collection"
|
tab.value.document.saveContext
|
||||||
? tab.value.document.saveContext.collectionID
|
)
|
||||||
: tab.value.document.saveContext?.folderPath
|
|
||||||
|
|
||||||
const effectedPath = folderPathCloseToSaveContext(
|
const effectedPath = folderPathCloseToSaveContext(
|
||||||
inheritedParentID,
|
inheritedParentID,
|
||||||
@@ -227,21 +326,6 @@ export function updateInheritedPropertiesForAffectedRequests(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetSaveContextForAffectedRequests(folderPath: string) {
|
|
||||||
const tabService = getService(RESTTabService)
|
|
||||||
const tabs = tabService.getTabsRefTo((tab) => {
|
|
||||||
return (
|
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
|
||||||
tab.document.saveContext.folderPath.startsWith(folderPath)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const tab of tabs) {
|
|
||||||
tab.value.document.saveContext = null
|
|
||||||
tab.value.document.isDirty = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset save context to null if requests are deleted from the team collection or its folder
|
* Reset save context to null if requests are deleted from the team collection or its folder
|
||||||
* only runs when collection or folder is deleted
|
* only runs when collection or folder is deleted
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import {
|
|||||||
HoppGQLRequest,
|
HoppGQLRequest,
|
||||||
HoppRESTRequest,
|
HoppRESTRequest,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { getAffectedIndexes } from "./affectedIndex"
|
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
|
||||||
import { getService } from "~/modules/dioc"
|
import { getService } from "~/modules/dioc"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { getAffectedIndexes } from "./affectedIndex"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve save context on reorder
|
* Resolve save context on reorder
|
||||||
@@ -29,30 +30,76 @@ export function resolveSaveContextOnRequestReorder(payload: {
|
|||||||
if (newIndex > lastIndex) newIndex-- // there is a issue when going down? better way to resolve this?
|
if (newIndex > lastIndex) newIndex-- // there is a issue when going down? better way to resolve this?
|
||||||
if (lastIndex === newIndex) return
|
if (lastIndex === newIndex) return
|
||||||
|
|
||||||
const affectedIndexes = getAffectedIndexes(
|
const affectedIndices = getAffectedIndexes(
|
||||||
lastIndex,
|
lastIndex,
|
||||||
newIndex === -1 ? length! : newIndex
|
newIndex === -1 ? length! : newIndex
|
||||||
)
|
)
|
||||||
|
|
||||||
// if (newIndex === -1) remove it from the map because it will be deleted
|
// if (newIndex === -1) remove it from the map because it will be deleted
|
||||||
if (newIndex === -1) affectedIndexes.delete(lastIndex)
|
if (newIndex === -1) affectedIndices.delete(lastIndex)
|
||||||
|
|
||||||
const tabService = getService(RESTTabService)
|
const tabService = getService(RESTTabService)
|
||||||
const tabs = tabService.getTabsRefTo((tab) => {
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
return (
|
if (tab.document.saveContext?.originLocation === "user-collection") {
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
return (
|
||||||
tab.document.saveContext.folderPath === folderPath &&
|
tab.document.saveContext.folderPath === folderPath &&
|
||||||
affectedIndexes.has(tab.document.saveContext.requestIndex)
|
affectedIndices.has(tab.document.saveContext.requestIndex)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
tab.document.saveContext?.originLocation !== "workspace-user-collection"
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestHandleRef = tab.document.saveContext.requestHandle?.get()
|
||||||
|
|
||||||
|
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const { requestID } = requestHandleRef.value.data
|
||||||
|
const collectionID = requestID.split("/").slice(0, -1).join("/")
|
||||||
|
const requestIndex = parseInt(requestID.split("/").slice(-1)[0])
|
||||||
|
|
||||||
|
return collectionID === folderPath && affectedIndices.has(requestIndex)
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
if (tab.value.document.saveContext?.originLocation === "user-collection") {
|
if (tab.value.document.saveContext?.originLocation === "user-collection") {
|
||||||
const newIndex = affectedIndexes.get(
|
const newIndex = affectedIndices.get(
|
||||||
tab.value.document.saveContext?.requestIndex
|
tab.value.document.saveContext?.requestIndex
|
||||||
)!
|
)!
|
||||||
tab.value.document.saveContext.requestIndex = newIndex
|
tab.value.document.saveContext.requestIndex = newIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
tab.value.document.saveContext?.originLocation !==
|
||||||
|
"workspace-user-collection"
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestHandleRef = tab.value.document.saveContext.requestHandle?.get()
|
||||||
|
|
||||||
|
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { requestID } = requestHandleRef.value.data
|
||||||
|
|
||||||
|
const requestIDArr = requestID.split("/")
|
||||||
|
const requestIndex = affectedIndices.get(
|
||||||
|
parseInt(requestIDArr[requestIDArr.length - 1])
|
||||||
|
)!
|
||||||
|
|
||||||
|
requestIDArr[requestIDArr.length - 1] = requestIndex.toString()
|
||||||
|
|
||||||
|
requestHandleRef.value.data.requestID = requestIDArr.join("/")
|
||||||
|
requestHandleRef.value.data.collectionID = requestIDArr
|
||||||
|
.slice(0, -1)
|
||||||
|
.join("/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +114,7 @@ export function getRequestsByPath(
|
|||||||
|
|
||||||
if (pathArray.length === 1) {
|
if (pathArray.length === 1) {
|
||||||
const latestVersionedRequests = currentCollection.requests.filter(
|
const latestVersionedRequests = currentCollection.requests.filter(
|
||||||
(req): req is HoppRESTRequest => req.v === "3"
|
(req): req is HoppRESTRequest => req.v === "4"
|
||||||
)
|
)
|
||||||
|
|
||||||
return latestVersionedRequests
|
return latestVersionedRequests
|
||||||
@@ -78,7 +125,7 @@ export function getRequestsByPath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const latestVersionedRequests = currentCollection.requests.filter(
|
const latestVersionedRequests = currentCollection.requests.filter(
|
||||||
(req): req is HoppRESTRequest => req.v === "3"
|
(req): req is HoppRESTRequest => req.v === "4"
|
||||||
)
|
)
|
||||||
|
|
||||||
return latestVersionedRequests
|
return latestVersionedRequests
|
||||||
|
|||||||
@@ -1,32 +1,31 @@
|
|||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
|
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a downloadable file from a collection and prompts the user to download it.
|
* Create a downloadable file from a collection and prompts the user to download it.
|
||||||
* @param collectionJSON - JSON string of the collection
|
* @param collectionJSON - JSON string of the collection
|
||||||
* @param name - Name of the collection set as the file name
|
* @param name - Name of the collection set as the file name
|
||||||
*/
|
*/
|
||||||
export const initializeDownloadCollection = (
|
export const initializeDownloadFile = async (
|
||||||
collectionJSON: string,
|
collectionJSON: string,
|
||||||
name: string | null
|
name: string | null
|
||||||
) => {
|
) => {
|
||||||
const file = new Blob([collectionJSON], { type: "application/json" })
|
const result = await platform.io.saveFileWithDialog({
|
||||||
const a = document.createElement("a")
|
data: collectionJSON,
|
||||||
const url = URL.createObjectURL(file)
|
contentType: "application/json",
|
||||||
a.href = url
|
suggestedFilename: `${name ?? "collection"}.json`,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "Hoppscotch Collection/Environment JSON file",
|
||||||
|
extensions: ["json"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
if (name) {
|
if (result.type === "unknown" || result.type === "saved") {
|
||||||
a.download = `${name}.json`
|
return E.right("state.download_started")
|
||||||
} else {
|
|
||||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.appendChild(a)
|
return E.left("state.download_failed")
|
||||||
a.click()
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(a)
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
return E.right("state.download_started")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { HoppCollection } from "@hoppscotch/data"
|
|
||||||
|
|
||||||
export const myCollectionsExporter = (myCollections: HoppCollection[]) => {
|
|
||||||
return JSON.stringify(myCollections, null, 2)
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,37 @@ import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
|||||||
import { HoppTestResult } from "../types/HoppTestResult"
|
import { HoppTestResult } from "../types/HoppTestResult"
|
||||||
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||||
|
import { Handle } from "~/services/new-workspace/handle"
|
||||||
|
import { WorkspaceRequest } from "~/services/new-workspace/workspace"
|
||||||
|
|
||||||
export type HoppRESTSaveContext =
|
export type HoppRESTSaveContext =
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* The origin source of the request
|
||||||
|
*/
|
||||||
|
// TODO: Make this `user-collection` after porting all usages
|
||||||
|
// Future TODO: Keep separate types for the IDs (specific to persistence) & `requestHandle` (only existing at runtime)
|
||||||
|
originLocation: "workspace-user-collection"
|
||||||
|
/**
|
||||||
|
* ID of the workspace
|
||||||
|
*/
|
||||||
|
workspaceID?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID of the provider
|
||||||
|
*/
|
||||||
|
providerID?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to the request in the collection tree
|
||||||
|
*/
|
||||||
|
requestID?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle to the request open in the tab
|
||||||
|
*/
|
||||||
|
requestHandle?: Handle<WorkspaceRequest>
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
/**
|
/**
|
||||||
* The origin source of the request
|
* The origin source of the request
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export default class TeamListAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
|
this.teamList$.next([])
|
||||||
this.isDispose = true
|
this.isDispose = true
|
||||||
clearTimeout(this.timeoutHandle as any)
|
clearTimeout(this.timeoutHandle as any)
|
||||||
this.timeoutHandle = null
|
this.timeoutHandle = null
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export class TeamSearchService extends Service {
|
|||||||
expandingCollections: Ref<string[]> = ref([])
|
expandingCollections: Ref<string[]> = ref([])
|
||||||
expandedCollections: Ref<string[]> = ref([])
|
expandedCollections: Ref<string[]> = ref([])
|
||||||
|
|
||||||
// FUTURE-TODO: ideally this should return the search results / formatted results instead of directly manipulating the result set
|
// TODO: ideally this should return the search results / formatted results instead of directly manipulating the result set
|
||||||
// eg: do the spotlight formatting in the spotlight searcher and not here
|
// eg: do the spotlight formatting in the spotlight searcher and not here
|
||||||
searchTeams = async (query: string, teamID: string) => {
|
searchTeams = async (query: string, teamID: string) => {
|
||||||
if (!query.length) {
|
if (!query.length) {
|
||||||
|
|||||||
@@ -17,3 +17,24 @@ export type HoppInheritedProperty = {
|
|||||||
inheritedHeader: HoppRESTHeader | GQLHeader
|
inheritedHeader: HoppRESTHeader | GQLHeader
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ModifiedAuth<T, AuthType> = {
|
||||||
|
[K in keyof T]: K extends "inheritedAuth" ? Extract<T[K], AuthType> : T[K]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModifiedHeaders<T, HeaderType> = {
|
||||||
|
[K in keyof T]: K extends "inheritedHeader" ? Extract<T[K], HeaderType> : T[K]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModifiedHoppInheritedProperty<AuthType, HeaderType> = {
|
||||||
|
auth: ModifiedAuth<HoppInheritedProperty["auth"], AuthType>
|
||||||
|
headers: ModifiedHeaders<
|
||||||
|
HoppInheritedProperty["headers"][number],
|
||||||
|
HeaderType
|
||||||
|
>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HoppInheritedRESTProperty = ModifiedHoppInheritedProperty<
|
||||||
|
HoppRESTAuth,
|
||||||
|
HoppRESTHeader
|
||||||
|
>
|
||||||
|
|||||||
@@ -20,28 +20,29 @@ export type Picked =
|
|||||||
pickedType: "my-collection"
|
pickedType: "my-collection"
|
||||||
collectionIndex: number
|
collectionIndex: number
|
||||||
}
|
}
|
||||||
| {
|
// TODO: Enable this when rest of the implementation is in place
|
||||||
pickedType: "teams-request"
|
// | {
|
||||||
requestID: string
|
// pickedType: "teams-request"
|
||||||
}
|
// requestID: string
|
||||||
| {
|
// }
|
||||||
pickedType: "teams-folder"
|
// | {
|
||||||
folderID: string
|
// pickedType: "teams-folder"
|
||||||
}
|
// folderID: string
|
||||||
| {
|
// }
|
||||||
pickedType: "teams-collection"
|
// | {
|
||||||
collectionID: string
|
// pickedType: "teams-collection"
|
||||||
}
|
// collectionID: string
|
||||||
| {
|
// }
|
||||||
pickedType: "gql-my-request"
|
// | {
|
||||||
folderPath: string
|
// pickedType: "gql-my-request"
|
||||||
requestIndex: number
|
// folderPath: string
|
||||||
}
|
// requestIndex: number
|
||||||
| {
|
// }
|
||||||
pickedType: "gql-my-folder"
|
// | {
|
||||||
folderPath: string
|
// pickedType: "gql-my-folder"
|
||||||
}
|
// folderPath: string
|
||||||
| {
|
// }
|
||||||
pickedType: "gql-my-collection"
|
// | {
|
||||||
collectionIndex: number
|
// pickedType: "gql-my-collection"
|
||||||
}
|
// collectionIndex: number
|
||||||
|
// }
|
||||||
|
|||||||
17
packages/hoppscotch-common/src/helpers/utils/lazy.ts
Normal file
17
packages/hoppscotch-common/src/helpers/utils/lazy.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Create a function that will only run the given function once and caches the result.
|
||||||
|
*/
|
||||||
|
export function lazy<T>(fn: () => T): () => T {
|
||||||
|
let funcRan = false
|
||||||
|
let result: T | null = null
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (!funcRan) {
|
||||||
|
result = fn()
|
||||||
|
funcRan = true
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return result!
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,36 +3,40 @@ import { createApp } from "vue"
|
|||||||
import { initializeApp } from "./helpers/app"
|
import { initializeApp } from "./helpers/app"
|
||||||
import { initBackendGQLClient } from "./helpers/backend/GQLClient"
|
import { initBackendGQLClient } from "./helpers/backend/GQLClient"
|
||||||
import { performMigrations } from "./helpers/migrations"
|
import { performMigrations } from "./helpers/migrations"
|
||||||
|
import { getService } from "./modules/dioc"
|
||||||
import { PlatformDef, setPlatformDef } from "./platform"
|
import { PlatformDef, setPlatformDef } from "./platform"
|
||||||
|
import { PersonalWorkspaceProviderService } from "./services/new-workspace/providers/personal.workspace"
|
||||||
|
import { TestWorkspaceProviderService } from "./services/new-workspace/providers/test.workspace"
|
||||||
|
|
||||||
|
import { PersistenceService } from "./services/persistence"
|
||||||
|
|
||||||
|
import "nprogress/nprogress.css"
|
||||||
|
import "../assets/scss/styles.scss"
|
||||||
import "../assets/scss/tailwind.scss"
|
import "../assets/scss/tailwind.scss"
|
||||||
import "../assets/themes/themes.scss"
|
import "../assets/themes/themes.scss"
|
||||||
import "../assets/scss/styles.scss"
|
|
||||||
import "nprogress/nprogress.css"
|
|
||||||
|
|
||||||
import "unfonts.css"
|
|
||||||
|
|
||||||
import App from "./App.vue"
|
import App from "./App.vue"
|
||||||
import { getService } from "./modules/dioc"
|
|
||||||
import { PersistenceService } from "./services/persistence"
|
|
||||||
|
|
||||||
export function createHoppApp(el: string | Element, platformDef: PlatformDef) {
|
export function createHoppApp(el: string | Element, platformDef: PlatformDef) {
|
||||||
setPlatformDef(platformDef)
|
setPlatformDef(platformDef)
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
// Some basic work that needs to be done before module inits even
|
|
||||||
initBackendGQLClient()
|
|
||||||
initializeApp()
|
|
||||||
|
|
||||||
HOPP_MODULES.forEach((mod) => mod.onVueAppInit?.(app))
|
HOPP_MODULES.forEach((mod) => mod.onVueAppInit?.(app))
|
||||||
platformDef.addedHoppModules?.forEach((mod) => mod.onVueAppInit?.(app))
|
platformDef.addedHoppModules?.forEach((mod) => mod.onVueAppInit?.(app))
|
||||||
|
|
||||||
|
// Some basic work that needs to be done before module inits even
|
||||||
|
initBackendGQLClient()
|
||||||
|
initializeApp()
|
||||||
// TODO: Explore possibilities of moving this invocation to the service constructor
|
// TODO: Explore possibilities of moving this invocation to the service constructor
|
||||||
// `toast` was coming up as `null` in the previous attempts
|
// `toast` was coming up as `null` in the previous attempts
|
||||||
getService(PersistenceService).setupLocalPersistence()
|
getService(PersistenceService).setupLocalPersistence()
|
||||||
performMigrations()
|
performMigrations()
|
||||||
|
|
||||||
|
// TODO: Remove this
|
||||||
|
getService(TestWorkspaceProviderService)
|
||||||
|
getService(PersonalWorkspaceProviderService)
|
||||||
|
|
||||||
app.mount(el)
|
app.mount(el)
|
||||||
|
|
||||||
console.info(
|
console.info(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { HoppModule } from "."
|
import { HoppModule } from "."
|
||||||
import { Container, Service } from "dioc"
|
import { Container, ServiceClassInstance } from "dioc"
|
||||||
import { diocPlugin } from "dioc/vue"
|
import { diocPlugin } from "dioc/vue"
|
||||||
import { DebugService } from "~/services/debug.service"
|
import { DebugService } from "~/services/debug.service"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
@@ -22,7 +22,7 @@ if (import.meta.env.DEV) {
|
|||||||
* services. Please use `useService` if within components or try to convert your
|
* services. Please use `useService` if within components or try to convert your
|
||||||
* legacy subsystem into a service if possible.
|
* legacy subsystem into a service if possible.
|
||||||
*/
|
*/
|
||||||
export function getService<T extends typeof Service<any> & { ID: string }>(
|
export function getService<T extends ServiceClassInstance<any>>(
|
||||||
service: T
|
service: T
|
||||||
): InstanceType<T> {
|
): InstanceType<T> {
|
||||||
return serviceContainer.bind(service)
|
return serviceContainer.bind(service)
|
||||||
@@ -30,11 +30,10 @@ export function getService<T extends typeof Service<any> & { ID: string }>(
|
|||||||
|
|
||||||
export default <HoppModule>{
|
export default <HoppModule>{
|
||||||
onVueAppInit(app) {
|
onVueAppInit(app) {
|
||||||
// TODO: look into this
|
|
||||||
// @ts-expect-error Something weird with Vue versions
|
|
||||||
app.use(diocPlugin, {
|
app.use(diocPlugin, {
|
||||||
container: serviceContainer,
|
container: serviceContainer,
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const service of platform.addedServices ?? []) {
|
for (const service of platform.addedServices ?? []) {
|
||||||
serviceContainer.bind(service)
|
serviceContainer.bind(service)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
)
|
)
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We get the index path to the folder itself,
|
// We get the index path to the folder itself,
|
||||||
// we have to find the folder containing the target folder,
|
// we have to find the folder containing the target folder,
|
||||||
// so we pop the last path index
|
// so we pop the last path index
|
||||||
@@ -690,17 +691,11 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
|
|
||||||
// if the destination is null, we are moving to the end of the list
|
// if the destination is null, we are moving to the end of the list
|
||||||
if (destinationRequestIndex === null) {
|
if (destinationRequestIndex === null) {
|
||||||
// move to the end of the list
|
// TODO: Verify if this can be safely removed
|
||||||
targetLocation.requests.push(
|
targetLocation.requests.push(
|
||||||
targetLocation.requests.splice(requestIndex, 1)[0]
|
targetLocation.requests.splice(requestIndex, 1)[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
resolveSaveContextOnRequestReorder({
|
|
||||||
lastIndex: requestIndex,
|
|
||||||
newIndex: targetLocation.requests.length,
|
|
||||||
folderPath: destinationCollectionPath,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: newState,
|
state: newState,
|
||||||
}
|
}
|
||||||
@@ -708,12 +703,6 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
|
|
||||||
reorderItems(targetLocation.requests, requestIndex, destinationRequestIndex)
|
reorderItems(targetLocation.requests, requestIndex, destinationRequestIndex)
|
||||||
|
|
||||||
resolveSaveContextOnRequestReorder({
|
|
||||||
lastIndex: requestIndex,
|
|
||||||
newIndex: destinationRequestIndex,
|
|
||||||
folderPath: destinationCollectionPath,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: newState,
|
state: newState,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { pluck, distinctUntilChanged } from "rxjs/operators"
|
|
||||||
import { cloneDeep, defaultsDeep, has } from "lodash-es"
|
import { cloneDeep, defaultsDeep, has } from "lodash-es"
|
||||||
import { Observable } from "rxjs"
|
import { Observable } from "rxjs"
|
||||||
|
import { distinctUntilChanged, pluck } from "rxjs/operators"
|
||||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
import { nextTick } from "vue"
|
||||||
|
import { platform } from "~/platform"
|
||||||
import type { KeysMatching } from "~/types/ts-utils"
|
import type { KeysMatching } from "~/types/ts-utils"
|
||||||
|
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||||
|
|
||||||
export const HoppBgColors = ["system", "light", "dark", "black"] as const
|
export const HoppBgColors = ["system", "light", "dark", "black"] as const
|
||||||
|
|
||||||
@@ -69,51 +70,63 @@ export type SettingsDef = {
|
|||||||
HAS_OPENED_SPOTLIGHT: boolean
|
HAS_OPENED_SPOTLIGHT: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDefaultSettings = (): SettingsDef => ({
|
export const getDefaultSettings = (): SettingsDef => {
|
||||||
syncCollections: true,
|
const defaultSettings: SettingsDef = {
|
||||||
syncHistory: true,
|
syncCollections: true,
|
||||||
syncEnvironments: true,
|
syncHistory: true,
|
||||||
|
syncEnvironments: true,
|
||||||
|
|
||||||
WRAP_LINES: {
|
WRAP_LINES: {
|
||||||
httpRequestBody: true,
|
httpRequestBody: true,
|
||||||
httpResponseBody: true,
|
httpResponseBody: true,
|
||||||
httpHeaders: true,
|
httpHeaders: true,
|
||||||
httpParams: true,
|
httpParams: true,
|
||||||
httpUrlEncoded: true,
|
httpUrlEncoded: true,
|
||||||
httpPreRequest: true,
|
httpPreRequest: true,
|
||||||
httpTest: true,
|
httpTest: true,
|
||||||
httpRequestVariables: true,
|
httpRequestVariables: true,
|
||||||
graphqlQuery: true,
|
graphqlQuery: true,
|
||||||
graphqlResponseBody: true,
|
graphqlResponseBody: true,
|
||||||
graphqlHeaders: false,
|
graphqlHeaders: false,
|
||||||
graphqlVariables: false,
|
graphqlVariables: false,
|
||||||
graphqlSchema: true,
|
graphqlSchema: true,
|
||||||
importCurl: true,
|
importCurl: true,
|
||||||
codeGen: true,
|
codeGen: true,
|
||||||
cookie: true,
|
cookie: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
CURRENT_INTERCEPTOR_ID: "browser", // TODO: Allow the platform definition to take this place
|
CURRENT_INTERCEPTOR_ID: "",
|
||||||
|
|
||||||
// TODO: Interceptor related settings should move under the interceptor systems
|
// TODO: Interceptor related settings should move under the interceptor systems
|
||||||
PROXY_URL: "https://proxy.hoppscotch.io/",
|
PROXY_URL: "https://proxy.hoppscotch.io/",
|
||||||
URL_EXCLUDES: {
|
URL_EXCLUDES: {
|
||||||
auth: true,
|
auth: true,
|
||||||
httpUser: true,
|
httpUser: true,
|
||||||
httpPassword: true,
|
httpPassword: true,
|
||||||
bearerToken: true,
|
bearerToken: true,
|
||||||
oauth2Token: true,
|
oauth2Token: true,
|
||||||
},
|
},
|
||||||
THEME_COLOR: "indigo",
|
THEME_COLOR: "indigo",
|
||||||
BG_COLOR: "system",
|
BG_COLOR: "system",
|
||||||
TELEMETRY_ENABLED: true,
|
TELEMETRY_ENABLED: true,
|
||||||
EXPAND_NAVIGATION: false,
|
EXPAND_NAVIGATION: false,
|
||||||
SIDEBAR: true,
|
SIDEBAR: true,
|
||||||
SIDEBAR_ON_LEFT: false,
|
SIDEBAR_ON_LEFT: false,
|
||||||
COLUMN_LAYOUT: true,
|
COLUMN_LAYOUT: true,
|
||||||
|
|
||||||
HAS_OPENED_SPOTLIGHT: false,
|
HAS_OPENED_SPOTLIGHT: false,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Wait for platform to initialize before setting CURRENT_INTERCEPTOR_ID
|
||||||
|
nextTick(() => {
|
||||||
|
applySetting(
|
||||||
|
"CURRENT_INTERCEPTOR_ID",
|
||||||
|
platform?.interceptors.default || "browser"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return defaultSettings
|
||||||
|
}
|
||||||
|
|
||||||
type ApplySettingPayload = {
|
type ApplySettingPayload = {
|
||||||
[K in keyof SettingsDef]: {
|
[K in keyof SettingsDef]: {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<span
|
<span
|
||||||
v-if="tab.document.isDirty"
|
v-if="getTabDirtyStatus(tab)"
|
||||||
class="flex w-4 items-center justify-center text-secondary group-hover:hidden"
|
class="flex w-4 items-center justify-center text-secondary group-hover:hidden"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -95,24 +95,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted } from "vue"
|
|
||||||
import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
|
||||||
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
|
||||||
import { useRoute } from "vue-router"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
||||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService } from "~/services/inspection"
|
|
||||||
import { HeaderInspectorService } from "~/services/inspection/inspectors/header.inspector"
|
|
||||||
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
|
||||||
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { onMounted, ref } from "vue"
|
||||||
import { HoppTab } from "~/services/tab"
|
import { useRoute } from "vue-router"
|
||||||
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
||||||
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
import { InspectionService } from "~/services/inspection"
|
||||||
|
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
||||||
|
import { HeaderInspectorService } from "~/services/inspection/inspectors/header.inspector"
|
||||||
|
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
const savingRequest = ref(false)
|
const savingRequest = ref(false)
|
||||||
const confirmingCloseForTabID = ref<string | null>(null)
|
const confirmingCloseForTabID = ref<string | null>(null)
|
||||||
@@ -193,7 +193,7 @@ const inspectionService = useService(InspectionService)
|
|||||||
const removeTab = (tabID: string) => {
|
const removeTab = (tabID: string) => {
|
||||||
const tabState = tabs.getTabRef(tabID).value
|
const tabState = tabs.getTabRef(tabID).value
|
||||||
|
|
||||||
if (tabState.document.isDirty) {
|
if (getTabDirtyStatus(tabState)) {
|
||||||
confirmingCloseForTabID.value = tabID
|
confirmingCloseForTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
tabs.closeTab(tabState.id)
|
tabs.closeTab(tabState.id)
|
||||||
@@ -202,8 +202,10 @@ const removeTab = (tabID: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const closeOtherTabsAction = (tabID: string) => {
|
const closeOtherTabsAction = (tabID: string) => {
|
||||||
const isTabDirty = tabs.getTabRef(tabID).value?.document.isDirty
|
|
||||||
const dirtyTabCount = tabs.getDirtyTabsCount()
|
const dirtyTabCount = tabs.getDirtyTabsCount()
|
||||||
|
|
||||||
|
const isTabDirty = getTabDirtyStatus(tabs.getTabRef(tabID).value)
|
||||||
|
|
||||||
// If current tab is dirty, so we need to subtract 1 from the dirty tab count
|
// If current tab is dirty, so we need to subtract 1 from the dirty tab count
|
||||||
const balanceDirtyTabCount = isTabDirty ? dirtyTabCount - 1 : dirtyTabCount
|
const balanceDirtyTabCount = isTabDirty ? dirtyTabCount - 1 : dirtyTabCount
|
||||||
|
|
||||||
@@ -268,15 +270,24 @@ const onCloseConfirmSaveTab = () => {
|
|||||||
* Called when the user confirms they want to save the tab
|
* Called when the user confirms they want to save the tab
|
||||||
*/
|
*/
|
||||||
const onResolveConfirmSaveTab = () => {
|
const onResolveConfirmSaveTab = () => {
|
||||||
if (tabs.currentActiveTab.value.document.saveContext) {
|
const { saveContext } = tabs.currentActiveTab.value.document
|
||||||
invokeAction("request.save")
|
|
||||||
|
|
||||||
if (confirmingCloseForTabID.value) {
|
// There're two cases where the save request under a collection modal should open
|
||||||
tabs.closeTab(confirmingCloseForTabID.value)
|
// 1. Attempting to save a request that is not under a collection (When the save context is not available)
|
||||||
confirmingCloseForTabID.value = null
|
// 2. Deleting a request from the collection tree and attempting to save it while closing the respective tab (When the request handle is invalid)
|
||||||
}
|
if (
|
||||||
} else {
|
!saveContext ||
|
||||||
savingRequest.value = true
|
(saveContext.originLocation === "workspace-user-collection" &&
|
||||||
|
saveContext.requestHandle?.get().value.type === "invalid")
|
||||||
|
) {
|
||||||
|
return (savingRequest.value = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeAction("request.save")
|
||||||
|
|
||||||
|
if (confirmingCloseForTabID.value) {
|
||||||
|
tabs.closeTab(confirmingCloseForTabID.value)
|
||||||
|
confirmingCloseForTabID.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,6 +315,17 @@ const shareTabRequest = (tabID: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTabDirtyStatus = (tab: HoppTab<HoppRESTDocument>) => {
|
||||||
|
if (tab.document.isDirty) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
tab.document.saveContext?.originLocation === "workspace-user-collection" &&
|
||||||
|
tab.document.saveContext.requestHandle?.get().value.type === "invalid"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
defineActionHandler("contextmenu.open", ({ position, text }) => {
|
defineActionHandler("contextmenu.open", ({ position, text }) => {
|
||||||
if (text) {
|
if (text) {
|
||||||
contextMenu.value = {
|
contextMenu.value = {
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ import { AnalyticsPlatformDef } from "./analytics"
|
|||||||
import { InterceptorsPlatformDef } from "./interceptors"
|
import { InterceptorsPlatformDef } from "./interceptors"
|
||||||
import { HoppModule } from "~/modules"
|
import { HoppModule } from "~/modules"
|
||||||
import { InspectorsPlatformDef } from "./inspectors"
|
import { InspectorsPlatformDef } from "./inspectors"
|
||||||
import { Service } from "dioc"
|
import { ServiceClassInstance } from "dioc"
|
||||||
import { IOPlatformDef } from "./io"
|
import { IOPlatformDef } from "./io"
|
||||||
import { SpotlightPlatformDef } from "./spotlight"
|
import { SpotlightPlatformDef } from "./spotlight"
|
||||||
|
import { Ref } from "vue"
|
||||||
|
|
||||||
export type PlatformDef = {
|
export type PlatformDef = {
|
||||||
ui?: UIPlatformDef
|
ui?: UIPlatformDef
|
||||||
addedHoppModules?: HoppModule[]
|
addedHoppModules?: HoppModule[]
|
||||||
addedServices?: Array<typeof Service<unknown> & { ID: string }>
|
addedServices?: Array<ServiceClassInstance<unknown>>
|
||||||
auth: AuthPlatformDef
|
auth: AuthPlatformDef
|
||||||
analytics?: AnalyticsPlatformDef
|
analytics?: AnalyticsPlatformDef
|
||||||
io: IOPlatformDef
|
io: IOPlatformDef
|
||||||
@@ -45,6 +46,11 @@ export type PlatformDef = {
|
|||||||
* If a value is not given, then the value is assumed to be true
|
* If a value is not given, then the value is assumed to be true
|
||||||
*/
|
*/
|
||||||
promptAsUsingCookies?: boolean
|
promptAsUsingCookies?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show the A/B testing workspace switcher click login flow or not
|
||||||
|
*/
|
||||||
|
workspaceSwitcherLogin?: Ref<boolean>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Service } from "dioc"
|
import { Container, ServiceClassInstance } from "dioc"
|
||||||
import { Inspector } from "~/services/inspection"
|
import { Inspector } from "~/services/inspection"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8,8 +8,9 @@ export type PlatformInspectorsDef = {
|
|||||||
// We are keeping this as the only mode for now
|
// We are keeping this as the only mode for now
|
||||||
// So that if we choose to add other modes, we can do without breaking
|
// So that if we choose to add other modes, we can do without breaking
|
||||||
type: "service"
|
type: "service"
|
||||||
service: typeof Service<unknown> & { ID: string } & {
|
// TODO: I don't think this type is effective, we have to come up with a better impl
|
||||||
new (): Service & Inspector
|
service: ServiceClassInstance<unknown> & {
|
||||||
|
new (c: Container): Inspector
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Service } from "dioc"
|
import { Container, ServiceClassInstance } from "dioc"
|
||||||
import { Interceptor } from "~/services/interceptor.service"
|
import { Interceptor } from "~/services/interceptor.service"
|
||||||
|
|
||||||
export type PlatformInterceptorDef =
|
export type PlatformInterceptorDef =
|
||||||
| { type: "standalone"; interceptor: Interceptor }
|
| { type: "standalone"; interceptor: Interceptor }
|
||||||
| {
|
| {
|
||||||
type: "service"
|
type: "service"
|
||||||
service: typeof Service<unknown> & { ID: string } & {
|
// TODO: I don't think this type is effective, we have to come up with a better impl
|
||||||
new (): Service & Interceptor
|
service: ServiceClassInstance<unknown> & {
|
||||||
|
new (c: Container): Interceptor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Service } from "dioc"
|
import { Container, ServiceClassInstance } from "dioc"
|
||||||
import { SpotlightSearcher } from "~/services/spotlight"
|
import { SpotlightSearcher } from "~/services/spotlight"
|
||||||
|
|
||||||
export type SpotlightPlatformDef = {
|
export type SpotlightPlatformDef = {
|
||||||
additionalSearchers?: Array<
|
additionalSearchers?: Array<
|
||||||
typeof Service<unknown> & { ID: string } & {
|
ServiceClassInstance<unknown> & {
|
||||||
new (): Service & SpotlightSearcher
|
new (c: Container): SpotlightSearcher
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ export class ExtensionInspectorService extends Service implements Inspector {
|
|||||||
|
|
||||||
private readonly inspection = this.bind(InspectionService)
|
private readonly inspection = this.bind(InspectionService)
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.inspection.registerInspector(this)
|
this.inspection.registerInspector(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,9 +133,7 @@ export class ExtensionInterceptorService
|
|||||||
|
|
||||||
public selectable = { type: "selectable" as const }
|
public selectable = { type: "selectable" as const }
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.listenForExtensionStatus()
|
this.listenForExtensionStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,15 +13,17 @@ export const browserIODef: IOPlatformDef = {
|
|||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
|
||||||
a.href = url
|
a.href = url
|
||||||
a.download = pipe(
|
a.download =
|
||||||
url,
|
opts.suggestedFilename ??
|
||||||
S.split("/"),
|
pipe(
|
||||||
RNEA.last,
|
url,
|
||||||
S.split("#"),
|
S.split("/"),
|
||||||
RNEA.head,
|
RNEA.last,
|
||||||
S.split("?"),
|
S.split("#"),
|
||||||
RNEA.head
|
RNEA.head,
|
||||||
)
|
S.split("?"),
|
||||||
|
RNEA.head
|
||||||
|
)
|
||||||
|
|
||||||
document.body.appendChild(a)
|
document.body.appendChild(a)
|
||||||
a.click()
|
a.click()
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ export class EnvironmentMenuService extends Service implements ContextMenu {
|
|||||||
|
|
||||||
private readonly contextMenu = this.bind(ContextMenuService)
|
private readonly contextMenu = this.bind(ContextMenuService)
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.contextMenu.registerMenu(this)
|
this.contextMenu.registerMenu(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ export class ParameterMenuService extends Service implements ContextMenu {
|
|||||||
|
|
||||||
private readonly contextMenu = this.bind(ContextMenuService)
|
private readonly contextMenu = this.bind(ContextMenuService)
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.contextMenu.registerMenu(this)
|
this.contextMenu.registerMenu(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,7 @@ export class URLMenuService extends Service implements ContextMenu {
|
|||||||
private readonly contextMenu = this.bind(ContextMenuService)
|
private readonly contextMenu = this.bind(ContextMenuService)
|
||||||
private readonly restTab = this.bind(RESTTabService)
|
private readonly restTab = this.bind(RESTTabService)
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.contextMenu.registerMenu(this)
|
this.contextMenu.registerMenu(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ export class CookieJarService extends Service {
|
|||||||
*/
|
*/
|
||||||
public cookieJar = ref(new Map<string, string[]>())
|
public cookieJar = ref(new Map<string, string[]>())
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
public parseSetCookieString(setCookieString: string) {
|
public parseSetCookieString(setCookieString: string) {
|
||||||
return setCookieParse(setCookieString)
|
return setCookieParse(setCookieString)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ import { Service } from "dioc"
|
|||||||
export class DebugService extends Service {
|
export class DebugService extends Service {
|
||||||
public static readonly ID = "DEBUG_SERVICE"
|
public static readonly ID = "DEBUG_SERVICE"
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
console.log("DebugService is initialized...")
|
console.log("DebugService is initialized...")
|
||||||
|
|
||||||
const container = this.getContainer()
|
const container = this.getContainer()
|
||||||
|
|||||||
@@ -107,9 +107,7 @@ export class InspectionService extends Service {
|
|||||||
|
|
||||||
private readonly restTab = this.bind(RESTTabService)
|
private readonly restTab = this.bind(RESTTabService)
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.initializeListeners()
|
this.initializeListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +121,11 @@ export class InspectionService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initializeListeners() {
|
private initializeListeners() {
|
||||||
|
console.log(
|
||||||
|
`Current active tab from inspection service is `,
|
||||||
|
this.restTab.currentActiveTab.value
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [this.inspectors.entries(), this.restTab.currentActiveTab.value.id],
|
() => [this.inspectors.entries(), this.restTab.currentActiveTab.value.id],
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -53,9 +53,7 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||||||
}
|
}
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.inspection.registerInspector(this)
|
this.inspection.registerInspector(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ export class HeaderInspectorService extends Service implements Inspector {
|
|||||||
private readonly inspection = this.bind(InspectionService)
|
private readonly inspection = this.bind(InspectionService)
|
||||||
private readonly interceptorService = this.bind(InterceptorService)
|
private readonly interceptorService = this.bind(InterceptorService)
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.inspection.registerInspector(this)
|
this.inspection.registerInspector(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ export class ResponseInspectorService extends Service implements Inspector {
|
|||||||
|
|
||||||
private readonly inspection = this.bind(InspectionService)
|
private readonly inspection = this.bind(InspectionService)
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.inspection.registerInspector(this)
|
this.inspection.registerInspector(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,9 +178,7 @@ export class InterceptorService extends Service {
|
|||||||
return this.interceptors.get(this.currentInterceptorID.value)
|
return this.interceptors.get(this.currentInterceptorID.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
// If the current interceptor is unselectable, select the first selectable one, else null
|
// If the current interceptor is unselectable, select the first selectable one, else null
|
||||||
watch([() => this.interceptors, this.currentInterceptorID], () => {
|
watch([() => this.interceptors, this.currentInterceptorID], () => {
|
||||||
if (!this.currentInterceptorID.value) return
|
if (!this.currentInterceptorID.value) return
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Ref, WritableComputedRef } from "vue"
|
||||||
|
|
||||||
|
export type Handle<T, InvalidateReason = unknown> = {
|
||||||
|
get: () => HandleRef<T, InvalidateReason>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HandleRef<T, InvalidateReason = unknown> = Ref<
|
||||||
|
HandleState<T, InvalidateReason>
|
||||||
|
>
|
||||||
|
|
||||||
|
export type HandleState<T, InvalidateReason> =
|
||||||
|
| { type: "ok"; data: T }
|
||||||
|
| { type: "invalid"; reason: InvalidateReason }
|
||||||
|
|
||||||
|
export type WritableHandleRef<
|
||||||
|
T,
|
||||||
|
InvalidateReason = unknown,
|
||||||
|
> = WritableComputedRef<HandleState<T, InvalidateReason>>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Ref } from "vue"
|
||||||
|
import { HandleRef } from "./handle"
|
||||||
|
import { Workspace, WorkspaceCollection, WorkspaceRequest } from "./workspace"
|
||||||
|
|
||||||
|
export const isValidWorkspaceHandle = (
|
||||||
|
workspaceHandle: HandleRef<Workspace>,
|
||||||
|
providerID: string,
|
||||||
|
workspaceID: string
|
||||||
|
): workspaceHandle is Ref<{
|
||||||
|
data: Workspace
|
||||||
|
type: "ok"
|
||||||
|
}> => {
|
||||||
|
return (
|
||||||
|
workspaceHandle.value.type === "ok" &&
|
||||||
|
workspaceHandle.value.data.providerID === providerID &&
|
||||||
|
workspaceHandle.value.data.workspaceID === workspaceID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isValidCollectionHandle = (
|
||||||
|
collectionHandle: HandleRef<WorkspaceCollection>,
|
||||||
|
providerID: string,
|
||||||
|
workspaceID: string
|
||||||
|
): collectionHandle is Ref<{
|
||||||
|
data: WorkspaceCollection
|
||||||
|
type: "ok"
|
||||||
|
}> => {
|
||||||
|
return (
|
||||||
|
collectionHandle.value.type === "ok" &&
|
||||||
|
collectionHandle.value.data.providerID === providerID &&
|
||||||
|
collectionHandle.value.data.workspaceID === workspaceID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isValidRequestHandle = (
|
||||||
|
requestHandle: HandleRef<WorkspaceRequest>,
|
||||||
|
providerID: string,
|
||||||
|
workspaceID: string
|
||||||
|
): requestHandle is Ref<{
|
||||||
|
data: WorkspaceRequest
|
||||||
|
type: "ok"
|
||||||
|
}> => {
|
||||||
|
return (
|
||||||
|
requestHandle.value.type === "ok" &&
|
||||||
|
requestHandle.value.data.providerID === providerID &&
|
||||||
|
requestHandle.value.data.workspaceID === workspaceID
|
||||||
|
)
|
||||||
|
}
|
||||||
797
packages/hoppscotch-common/src/services/new-workspace/index.ts
Normal file
797
packages/hoppscotch-common/src/services/new-workspace/index.ts
Normal file
@@ -0,0 +1,797 @@
|
|||||||
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { Service } from "dioc"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
Ref,
|
||||||
|
computed,
|
||||||
|
markRaw,
|
||||||
|
shallowReactive,
|
||||||
|
shallowRef,
|
||||||
|
watch,
|
||||||
|
} from "vue"
|
||||||
|
import { Handle } from "./handle"
|
||||||
|
import { WorkspaceProvider } from "./provider"
|
||||||
|
import {
|
||||||
|
RESTCollectionChildrenView,
|
||||||
|
RESTCollectionJSONView,
|
||||||
|
RESTCollectionLevelAuthHeadersView,
|
||||||
|
RESTSearchResultsView,
|
||||||
|
RootRESTCollectionView,
|
||||||
|
} from "./view"
|
||||||
|
import { Workspace, WorkspaceCollection, WorkspaceRequest } from "./workspace"
|
||||||
|
|
||||||
|
export type WorkspaceError<ServiceErr> =
|
||||||
|
| { type: "SERVICE_ERROR"; error: ServiceErr }
|
||||||
|
| { type: "PROVIDER_ERROR"; error: unknown }
|
||||||
|
|
||||||
|
export class NewWorkspaceService extends Service {
|
||||||
|
public static readonly ID = "NEW_WORKSPACE_SERVICE"
|
||||||
|
|
||||||
|
private registeredProviders = shallowReactive(
|
||||||
|
new Map<string, WorkspaceProvider>()
|
||||||
|
)
|
||||||
|
|
||||||
|
public activeWorkspaceHandle: Ref<Handle<Workspace> | undefined> =
|
||||||
|
shallowRef()
|
||||||
|
|
||||||
|
public activeWorkspaceDecor = computed(() => {
|
||||||
|
const activeWorkspaceHandleRef = this.activeWorkspaceHandle.value?.get()
|
||||||
|
|
||||||
|
if (activeWorkspaceHandleRef?.value.type !== "ok") {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.registeredProviders.get(
|
||||||
|
activeWorkspaceHandleRef.value.data.providerID
|
||||||
|
)!.workspaceDecor
|
||||||
|
})
|
||||||
|
|
||||||
|
public workspaceSelectorComponents = computed(() => {
|
||||||
|
const items: Component[] = []
|
||||||
|
|
||||||
|
const sortedProviders = Array.from(this.registeredProviders.values()).sort(
|
||||||
|
(a, b) =>
|
||||||
|
(b.workspaceDecor?.value.workspaceSelectorPriority ?? 0) -
|
||||||
|
(a.workspaceDecor?.value.workspaceSelectorPriority ?? 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const workspace of sortedProviders) {
|
||||||
|
if (workspace.workspaceDecor?.value?.workspaceSelectorComponent) {
|
||||||
|
items.push(workspace.workspaceDecor.value.workspaceSelectorComponent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
})
|
||||||
|
|
||||||
|
override onServiceInit() {
|
||||||
|
// Watch for situations where the handle is invalidated
|
||||||
|
// so the active workspace handle definition can be invalidated
|
||||||
|
watch(
|
||||||
|
() => {
|
||||||
|
return this.activeWorkspaceHandle.value
|
||||||
|
? [
|
||||||
|
this.activeWorkspaceHandle.value,
|
||||||
|
this.activeWorkspaceHandle.value?.get(),
|
||||||
|
]
|
||||||
|
: [this.activeWorkspaceHandle.value]
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (!this.activeWorkspaceHandle.value) return
|
||||||
|
|
||||||
|
const activeWorkspaceHandleRef = this.activeWorkspaceHandle.value.get()
|
||||||
|
|
||||||
|
if (activeWorkspaceHandleRef.value.type === "invalid") {
|
||||||
|
this.activeWorkspaceHandle.value = undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getWorkspaceHandle(
|
||||||
|
providerID: string,
|
||||||
|
workspaceID: string
|
||||||
|
): Promise<E.Either<WorkspaceError<"INVALID_PROVIDER">, Handle<Workspace>>> {
|
||||||
|
const provider = this.registeredProviders.get(providerID)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return Promise.resolve(
|
||||||
|
E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" as const })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResult = await provider.getWorkspaceHandle(workspaceID)
|
||||||
|
|
||||||
|
if (E.isLeft(handleResult)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: handleResult.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(handleResult.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getCollectionHandle(
|
||||||
|
workspaceHandle: Handle<Workspace>,
|
||||||
|
collectionID: string
|
||||||
|
): Promise<
|
||||||
|
E.Either<
|
||||||
|
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||||
|
Handle<WorkspaceCollection>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
const workspaceHandleRef = workspaceHandle.get()
|
||||||
|
|
||||||
|
if (workspaceHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
workspaceHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.getCollectionHandle(
|
||||||
|
workspaceHandle,
|
||||||
|
collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRequestHandle(
|
||||||
|
workspaceHandle: Handle<Workspace>,
|
||||||
|
requestID: string
|
||||||
|
): Promise<
|
||||||
|
E.Either<
|
||||||
|
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||||
|
Handle<WorkspaceRequest>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
const workspaceHandleRef = workspaceHandle.get()
|
||||||
|
|
||||||
|
if (workspaceHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
workspaceHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.getRequestHandle(workspaceHandle, requestID)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createRESTRootCollection(
|
||||||
|
workspaceHandle: Handle<Workspace>,
|
||||||
|
newCollection: Partial<Exclude<HoppCollection, "id">> & { name: string }
|
||||||
|
): Promise<
|
||||||
|
E.Either<
|
||||||
|
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||||
|
Handle<WorkspaceCollection>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
const workspaceHandleRef = workspaceHandle.get()
|
||||||
|
|
||||||
|
if (workspaceHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
workspaceHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.createRESTRootCollection(
|
||||||
|
workspaceHandle,
|
||||||
|
newCollection
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createRESTChildCollection(
|
||||||
|
parentCollectionHandle: Handle<WorkspaceCollection>,
|
||||||
|
newChildCollection: Partial<HoppCollection> & { name: string }
|
||||||
|
): Promise<
|
||||||
|
E.Either<
|
||||||
|
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||||
|
Handle<WorkspaceCollection>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
const parentCollectionHandleRef = parentCollectionHandle.get()
|
||||||
|
|
||||||
|
if (parentCollectionHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
parentCollectionHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.createRESTChildCollection(
|
||||||
|
parentCollectionHandle,
|
||||||
|
newChildCollection
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateRESTCollection(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>,
|
||||||
|
updatedCollection: Partial<HoppCollection>
|
||||||
|
): Promise<
|
||||||
|
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
||||||
|
> {
|
||||||
|
const collectionHandleRef = collectionHandle.get()
|
||||||
|
|
||||||
|
if (collectionHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
collectionHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.updateRESTCollection(
|
||||||
|
collectionHandle,
|
||||||
|
updatedCollection
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeRESTCollection(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>
|
||||||
|
): Promise<
|
||||||
|
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
||||||
|
> {
|
||||||
|
const collectionHandleRef = collectionHandle.get()
|
||||||
|
|
||||||
|
if (collectionHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
collectionHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.removeRESTCollection(collectionHandle)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createRESTRequest(
|
||||||
|
parentCollectionHandle: Handle<WorkspaceCollection>,
|
||||||
|
newRequest: HoppRESTRequest
|
||||||
|
): Promise<
|
||||||
|
E.Either<
|
||||||
|
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||||
|
Handle<WorkspaceRequest>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
const parentCollectionHandleRef = parentCollectionHandle.get()
|
||||||
|
|
||||||
|
if (parentCollectionHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
parentCollectionHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.createRESTRequest(
|
||||||
|
parentCollectionHandle,
|
||||||
|
newRequest
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeRESTRequest(
|
||||||
|
requestHandle: Handle<WorkspaceRequest>
|
||||||
|
): Promise<
|
||||||
|
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
||||||
|
> {
|
||||||
|
const requestHandleRef = requestHandle.get()
|
||||||
|
|
||||||
|
if (requestHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
requestHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.removeRESTRequest(requestHandle)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateRESTRequest(
|
||||||
|
requestHandle: Handle<WorkspaceRequest>,
|
||||||
|
updatedRequest: Partial<HoppRESTRequest>
|
||||||
|
): Promise<
|
||||||
|
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
||||||
|
> {
|
||||||
|
const requestHandleRef = requestHandle.get()
|
||||||
|
|
||||||
|
if (requestHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
requestHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.updateRESTRequest(
|
||||||
|
requestHandle,
|
||||||
|
updatedRequest
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async importRESTCollections(
|
||||||
|
workspaceHandle: Handle<Workspace>,
|
||||||
|
collections: HoppCollection[]
|
||||||
|
): Promise<
|
||||||
|
E.Either<
|
||||||
|
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||||
|
Handle<WorkspaceCollection>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
const workspaceHandleRef = workspaceHandle.get()
|
||||||
|
|
||||||
|
if (workspaceHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
workspaceHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.importRESTCollections(
|
||||||
|
workspaceHandle,
|
||||||
|
collections
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async exportRESTCollections(
|
||||||
|
workspaceHandle: Handle<Workspace>
|
||||||
|
): Promise<
|
||||||
|
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
||||||
|
> {
|
||||||
|
const workspaceHandleRef = workspaceHandle.get()
|
||||||
|
|
||||||
|
if (workspaceHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
workspaceHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.exportRESTCollections(workspaceHandle)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async exportRESTCollection(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>
|
||||||
|
): Promise<
|
||||||
|
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
||||||
|
> {
|
||||||
|
const collectionHandleRef = collectionHandle.get()
|
||||||
|
|
||||||
|
if (collectionHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
collectionHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.exportRESTCollection(collectionHandle)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async reorderRESTCollection(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>,
|
||||||
|
destinationCollectionID: string | null
|
||||||
|
): Promise<
|
||||||
|
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
||||||
|
> {
|
||||||
|
const collectionHandleRef = collectionHandle.get()
|
||||||
|
|
||||||
|
if (collectionHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
collectionHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.reorderRESTCollection(
|
||||||
|
collectionHandle,
|
||||||
|
destinationCollectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async moveRESTCollection(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>,
|
||||||
|
destinationCollectionID: string | null
|
||||||
|
): Promise<
|
||||||
|
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
||||||
|
> {
|
||||||
|
const collectionHandleRef = collectionHandle.get()
|
||||||
|
|
||||||
|
if (collectionHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
collectionHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.moveRESTCollection(
|
||||||
|
collectionHandle,
|
||||||
|
destinationCollectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async reorderRESTRequest(
|
||||||
|
requestHandle: Handle<WorkspaceRequest>,
|
||||||
|
destinationRequestID: string | null
|
||||||
|
): Promise<
|
||||||
|
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
||||||
|
> {
|
||||||
|
const requestHandleRef = requestHandle.get()
|
||||||
|
|
||||||
|
if (requestHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
requestHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.reorderRESTRequest(
|
||||||
|
requestHandle,
|
||||||
|
destinationRequestID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async moveRESTRequest(
|
||||||
|
requestHandle: Handle<WorkspaceRequest>,
|
||||||
|
destinationCollectionID: string
|
||||||
|
): Promise<
|
||||||
|
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
||||||
|
> {
|
||||||
|
const requestHandleRef = requestHandle.get()
|
||||||
|
|
||||||
|
if (requestHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
requestHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.moveRESTRequest(
|
||||||
|
requestHandle,
|
||||||
|
destinationCollectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRESTCollectionChildrenView(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>
|
||||||
|
): Promise<
|
||||||
|
E.Either<
|
||||||
|
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||||
|
Handle<RESTCollectionChildrenView>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
const collectionHandleRef = collectionHandle.get()
|
||||||
|
|
||||||
|
if (collectionHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
collectionHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result =
|
||||||
|
await provider.getRESTCollectionChildrenView(collectionHandle)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRESTRootCollectionView(
|
||||||
|
workspaceHandle: Handle<Workspace>
|
||||||
|
): Promise<
|
||||||
|
E.Either<
|
||||||
|
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||||
|
Handle<RootRESTCollectionView>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
const workspaceHandleRef = workspaceHandle.get()
|
||||||
|
|
||||||
|
if (workspaceHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
workspaceHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.getRESTRootCollectionView(workspaceHandle)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRESTCollectionLevelAuthHeadersView(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>
|
||||||
|
): Promise<
|
||||||
|
E.Either<
|
||||||
|
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||||
|
Handle<RESTCollectionLevelAuthHeadersView>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
const collectionHandleRef = collectionHandle.get()
|
||||||
|
|
||||||
|
if (collectionHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
collectionHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result =
|
||||||
|
await provider.getRESTCollectionLevelAuthHeadersView(collectionHandle)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRESTSearchResultsView(
|
||||||
|
workspaceHandle: Handle<Workspace>,
|
||||||
|
searchQuery: Ref<string>
|
||||||
|
): Promise<
|
||||||
|
E.Either<
|
||||||
|
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||||
|
Handle<RESTSearchResultsView>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
const workspaceHandleRef = workspaceHandle.get()
|
||||||
|
|
||||||
|
if (workspaceHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
workspaceHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.getRESTSearchResultsView(
|
||||||
|
workspaceHandle,
|
||||||
|
searchQuery
|
||||||
|
)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRESTCollectionJSONView(
|
||||||
|
workspaceHandle: Handle<Workspace>
|
||||||
|
): Promise<
|
||||||
|
E.Either<
|
||||||
|
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||||
|
Handle<RESTCollectionJSONView>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
const workspaceHandleRef = workspaceHandle.get()
|
||||||
|
|
||||||
|
if (workspaceHandleRef.value.type === "invalid") {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.registeredProviders.get(
|
||||||
|
workspaceHandleRef.value.data.providerID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await provider.getRESTCollectionJSONView(workspaceHandle)
|
||||||
|
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(result.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerWorkspaceProvider(provider: WorkspaceProvider) {
|
||||||
|
if (this.registeredProviders.has(provider.providerID)) {
|
||||||
|
console.warn(
|
||||||
|
"Ignoring attempt to re-register workspace provider that is already existing:",
|
||||||
|
provider
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.registeredProviders.set(provider.providerID, markRaw(provider))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { Ref } from "vue"
|
||||||
|
|
||||||
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { Handle } from "./handle"
|
||||||
|
import {
|
||||||
|
RESTCollectionChildrenView,
|
||||||
|
RESTCollectionJSONView,
|
||||||
|
RESTCollectionLevelAuthHeadersView,
|
||||||
|
RESTSearchResultsView,
|
||||||
|
RootRESTCollectionView,
|
||||||
|
} from "./view"
|
||||||
|
import {
|
||||||
|
Workspace,
|
||||||
|
WorkspaceCollection,
|
||||||
|
WorkspaceDecor,
|
||||||
|
WorkspaceRequest,
|
||||||
|
} from "./workspace"
|
||||||
|
|
||||||
|
export interface WorkspaceProvider {
|
||||||
|
providerID: string
|
||||||
|
|
||||||
|
workspaceDecor?: Ref<WorkspaceDecor>
|
||||||
|
|
||||||
|
getWorkspaceHandle(
|
||||||
|
workspaceID: string
|
||||||
|
): Promise<E.Either<unknown, Handle<Workspace>>>
|
||||||
|
getCollectionHandle(
|
||||||
|
workspaceHandle: Handle<Workspace>,
|
||||||
|
collectionID: string
|
||||||
|
): Promise<E.Either<unknown, Handle<WorkspaceCollection>>>
|
||||||
|
getRequestHandle(
|
||||||
|
workspaceHandle: Handle<Workspace>,
|
||||||
|
requestID: string
|
||||||
|
): Promise<E.Either<unknown, Handle<WorkspaceRequest>>>
|
||||||
|
|
||||||
|
getRESTRootCollectionView(
|
||||||
|
workspaceHandle: Handle<Workspace>
|
||||||
|
): Promise<E.Either<never, Handle<RootRESTCollectionView>>>
|
||||||
|
getRESTCollectionChildrenView(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>
|
||||||
|
): Promise<E.Either<never, Handle<RESTCollectionChildrenView>>>
|
||||||
|
getRESTCollectionLevelAuthHeadersView(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>
|
||||||
|
): Promise<E.Either<never, Handle<RESTCollectionLevelAuthHeadersView>>>
|
||||||
|
getRESTSearchResultsView(
|
||||||
|
workspaceHandle: Handle<Workspace>,
|
||||||
|
searchQuery: Ref<string>
|
||||||
|
): Promise<E.Either<never, Handle<RESTSearchResultsView>>>
|
||||||
|
getRESTCollectionJSONView(
|
||||||
|
workspaceHandle: Handle<Workspace>
|
||||||
|
): Promise<E.Either<never, Handle<RESTCollectionJSONView>>>
|
||||||
|
|
||||||
|
createRESTRootCollection(
|
||||||
|
workspaceHandle: Handle<Workspace>,
|
||||||
|
newCollection: Partial<Exclude<HoppCollection, "id">> & { name: string }
|
||||||
|
): Promise<E.Either<unknown, Handle<WorkspaceCollection>>>
|
||||||
|
createRESTChildCollection(
|
||||||
|
parentCollectionHandle: Handle<WorkspaceCollection>,
|
||||||
|
newChildCollection: Partial<HoppCollection> & { name: string }
|
||||||
|
): Promise<E.Either<unknown, Handle<WorkspaceCollection>>>
|
||||||
|
updateRESTCollection(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>,
|
||||||
|
updatedCollection: Partial<HoppCollection>
|
||||||
|
): Promise<E.Either<unknown, void>>
|
||||||
|
removeRESTCollection(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>
|
||||||
|
): Promise<E.Either<unknown, void>>
|
||||||
|
createRESTRequest(
|
||||||
|
parentCollectionHandle: Handle<WorkspaceCollection>,
|
||||||
|
newRequest: HoppRESTRequest
|
||||||
|
): Promise<E.Either<unknown, Handle<WorkspaceRequest>>>
|
||||||
|
updateRESTRequest(
|
||||||
|
requestHandle: Handle<WorkspaceRequest>,
|
||||||
|
updatedRequest: Partial<HoppRESTRequest>
|
||||||
|
): Promise<E.Either<unknown, void>>
|
||||||
|
removeRESTRequest(
|
||||||
|
requestHandle: Handle<WorkspaceRequest>
|
||||||
|
): Promise<E.Either<unknown, void>>
|
||||||
|
|
||||||
|
importRESTCollections(
|
||||||
|
workspaceHandle: Handle<Workspace>,
|
||||||
|
collections: HoppCollection[]
|
||||||
|
): Promise<E.Either<unknown, Handle<WorkspaceCollection>>>
|
||||||
|
exportRESTCollections(
|
||||||
|
workspaceHandle: Handle<Workspace>
|
||||||
|
): Promise<E.Either<unknown, void>>
|
||||||
|
exportRESTCollection(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>
|
||||||
|
): Promise<E.Either<unknown, void>>
|
||||||
|
|
||||||
|
reorderRESTCollection(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>,
|
||||||
|
destinationCollectionID: string | null
|
||||||
|
): Promise<E.Either<unknown, void>>
|
||||||
|
moveRESTCollection(
|
||||||
|
collectionHandle: Handle<WorkspaceCollection>,
|
||||||
|
destinationCollectionID: string | null
|
||||||
|
): Promise<E.Either<unknown, void>>
|
||||||
|
reorderRESTRequest(
|
||||||
|
requestHandle: Handle<WorkspaceRequest>,
|
||||||
|
destinationRequestID: string | null
|
||||||
|
): Promise<E.Either<unknown, void>>
|
||||||
|
moveRESTRequest(
|
||||||
|
requestHandle: Handle<WorkspaceRequest>,
|
||||||
|
destinationCollectionID: string
|
||||||
|
): Promise<E.Either<unknown, void>>
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
|||||||
|
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { ref, computed } from "vue"
|
||||||
|
|
||||||
|
import { HandleState } from "../../handle"
|
||||||
|
import { WorkspaceRequest } from "../../workspace"
|
||||||
|
|
||||||
|
export const generateIssuedHandleValues = (
|
||||||
|
collectionsAndRequests: { collectionID: string; requestCount: number }[]
|
||||||
|
) => {
|
||||||
|
const providerID = "PERSONAL_WORKSPACE_PROVIDER"
|
||||||
|
const workspaceID = "personal"
|
||||||
|
const issuedHandleValues: HandleState<WorkspaceRequest, unknown>[] = []
|
||||||
|
|
||||||
|
collectionsAndRequests.forEach(({ collectionID, requestCount }) => {
|
||||||
|
for (let i = 0; i < requestCount; i++) {
|
||||||
|
const requestID = `${collectionID}/${i}`
|
||||||
|
|
||||||
|
issuedHandleValues.push({
|
||||||
|
type: "ok" as const,
|
||||||
|
data: {
|
||||||
|
providerID: providerID,
|
||||||
|
workspaceID: workspaceID,
|
||||||
|
collectionID,
|
||||||
|
requestID,
|
||||||
|
request: {
|
||||||
|
...getDefaultRESTRequest(),
|
||||||
|
name: `req-${requestID}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return issuedHandleValues
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getWritableHandle = (
|
||||||
|
value: HandleState<WorkspaceRequest, unknown>
|
||||||
|
) => {
|
||||||
|
const handleRefData = ref(value)
|
||||||
|
|
||||||
|
const writableHandle = computed({
|
||||||
|
get() {
|
||||||
|
return handleRefData.value
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
handleRefData.value = newValue
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return writableHandle
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,309 @@
|
|||||||
|
import { computed, markRaw, reactive, ref } from "vue"
|
||||||
|
import { useTimestamp } from "@vueuse/core"
|
||||||
|
import { Service } from "dioc"
|
||||||
|
import { WorkspaceProvider } from "../provider"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { Handle, HandleRef } from "../handle"
|
||||||
|
import { Workspace, WorkspaceCollection } from "../workspace"
|
||||||
|
import { NewWorkspaceService } from ".."
|
||||||
|
import TestWorkspaceSelector from "~/components/workspace/TestWorkspaceSelector.vue"
|
||||||
|
import { RESTCollectionChildrenView, RootRESTCollectionView } from "../view"
|
||||||
|
import IconUser from "~icons/lucide/user"
|
||||||
|
import { get } from "lodash-es"
|
||||||
|
|
||||||
|
type TestReqDef = {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestCollDef = {
|
||||||
|
name: string
|
||||||
|
collections: TestCollDef[]
|
||||||
|
requests: TestReqDef[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = useTimestamp({ interval: 3000 })
|
||||||
|
// const timestamp = ref(Date.now())
|
||||||
|
|
||||||
|
const testData = reactive({
|
||||||
|
workspaceA: {
|
||||||
|
name: computed(() => `Workspace A: ${timestamp.value}`),
|
||||||
|
collections: [
|
||||||
|
<TestCollDef>{
|
||||||
|
name: "Collection A",
|
||||||
|
collections: [
|
||||||
|
{
|
||||||
|
name: "Collection B",
|
||||||
|
collections: [
|
||||||
|
{ name: "Collection C", collections: [], requests: [] },
|
||||||
|
],
|
||||||
|
requests: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
requests: [{ name: "Request C" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
workspaceB: {
|
||||||
|
name: "Workspace B",
|
||||||
|
collections: [
|
||||||
|
<TestCollDef>{
|
||||||
|
name: "Collection D",
|
||||||
|
collections: [{ name: "Collection E", collections: [], requests: [] }],
|
||||||
|
requests: [{ name: "Request F" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
;(window as any).testData = testData
|
||||||
|
|
||||||
|
export class TestWorkspaceProviderService
|
||||||
|
extends Service
|
||||||
|
implements WorkspaceProvider
|
||||||
|
{
|
||||||
|
public static readonly ID = "TEST_WORKSPACE_PROVIDER_SERVICE"
|
||||||
|
|
||||||
|
public providerID = "TEST_WORKSPACE_PROVIDER"
|
||||||
|
|
||||||
|
public workspaceDecor = ref({
|
||||||
|
workspaceSelectorComponent: markRaw(TestWorkspaceSelector),
|
||||||
|
headerCurrentIcon: markRaw(IconUser),
|
||||||
|
workspaceSelectorPriority: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
private readonly workspaceService = this.bind(NewWorkspaceService)
|
||||||
|
|
||||||
|
override onServiceInit() {
|
||||||
|
this.workspaceService.registerWorkspaceProvider(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
public createRESTRootCollection(
|
||||||
|
workspaceHandle: HandleRef<Workspace>,
|
||||||
|
collectionName: string
|
||||||
|
): Promise<
|
||||||
|
E.Either<"INVALID_WORKSPACE_HANDLE", Handle<WorkspaceCollection>>
|
||||||
|
> {
|
||||||
|
if (workspaceHandle.value.type !== "ok") {
|
||||||
|
return Promise.resolve(E.left("INVALID_WORKSPACE_HANDLE" as const))
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceID = workspaceHandle.value.data.workspaceID
|
||||||
|
|
||||||
|
const newCollID =
|
||||||
|
testData[workspaceID as keyof typeof testData].collections.length
|
||||||
|
|
||||||
|
testData[workspaceID as keyof typeof testData].collections.push({
|
||||||
|
name: collectionName,
|
||||||
|
collections: [],
|
||||||
|
requests: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.getCollectionHandle(workspaceHandle, newCollID.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
public createRESTChildCollection(
|
||||||
|
parentCollHandle: HandleRef<WorkspaceCollection>,
|
||||||
|
collectionName: string
|
||||||
|
): Promise<E.Either<unknown, Handle<WorkspaceCollection>>> {
|
||||||
|
// TODO: Implement
|
||||||
|
throw new Error("Method not implemented.")
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWorkspaceHandle(
|
||||||
|
workspaceID: string
|
||||||
|
): Promise<E.Either<never, Handle<Workspace>>> {
|
||||||
|
return Promise.resolve(
|
||||||
|
E.right(
|
||||||
|
computed(() => {
|
||||||
|
if (!(workspaceID in testData)) {
|
||||||
|
return {
|
||||||
|
type: "invalid",
|
||||||
|
reason: "WORKSPACE_WENT_OUT" as const,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "ok",
|
||||||
|
data: {
|
||||||
|
providerID: this.providerID,
|
||||||
|
workspaceID,
|
||||||
|
name: testData[workspaceID as keyof typeof testData].name,
|
||||||
|
collectionsAreReadonly: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCollectionHandle(
|
||||||
|
workspaceHandle: HandleRef<Workspace>,
|
||||||
|
collectionID: string
|
||||||
|
): Promise<
|
||||||
|
E.Either<"INVALID_WORKSPACE_HANDLE", Handle<WorkspaceCollection>>
|
||||||
|
> {
|
||||||
|
return Promise.resolve(
|
||||||
|
E.right(
|
||||||
|
computed(() => {
|
||||||
|
if (workspaceHandle.value.type !== "ok") {
|
||||||
|
return {
|
||||||
|
type: "invalid",
|
||||||
|
reason: "WORKSPACE_INVALIDATED" as const,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceID = workspaceHandle.value.data.workspaceID
|
||||||
|
const collectionPath = collectionID
|
||||||
|
.split("/")
|
||||||
|
.flatMap((x) => ["collections", x])
|
||||||
|
|
||||||
|
const result: TestCollDef | undefined = get(
|
||||||
|
testData[workspaceID as keyof typeof testData],
|
||||||
|
collectionPath
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return {
|
||||||
|
type: "invalid",
|
||||||
|
reason: "INVALID_COLL_ID",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "ok",
|
||||||
|
data: {
|
||||||
|
providerID: this.providerID,
|
||||||
|
workspaceID,
|
||||||
|
collectionID,
|
||||||
|
name: result.name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRESTCollectionChildrenView(
|
||||||
|
collectionHandle: HandleRef<WorkspaceCollection>
|
||||||
|
): Promise<E.Either<never, Handle<RESTCollectionChildrenView>>> {
|
||||||
|
return Promise.resolve(
|
||||||
|
E.right(
|
||||||
|
computed(() => {
|
||||||
|
if (collectionHandle.value.type === "invalid") {
|
||||||
|
return {
|
||||||
|
type: "invalid",
|
||||||
|
reason: "COLL_HANDLE_IS_INVALID" as const,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceID = collectionHandle.value.data.workspaceID
|
||||||
|
const collectionID = collectionHandle.value.data.collectionID
|
||||||
|
|
||||||
|
if (!(workspaceID in testData)) {
|
||||||
|
return {
|
||||||
|
type: "invalid",
|
||||||
|
reason: "WORKSPACE_NOT_PRESENT" as const,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionPath = collectionID
|
||||||
|
.split("/")
|
||||||
|
.flatMap((x) => ["collections", x])
|
||||||
|
|
||||||
|
return markRaw({
|
||||||
|
type: "ok",
|
||||||
|
data: {
|
||||||
|
providerID: this.providerID,
|
||||||
|
workspaceID,
|
||||||
|
collectionID,
|
||||||
|
|
||||||
|
loading: ref(false),
|
||||||
|
|
||||||
|
content: computed(() => [
|
||||||
|
...(
|
||||||
|
get(testData[workspaceID as keyof typeof testData], [
|
||||||
|
...collectionPath,
|
||||||
|
"collections",
|
||||||
|
]) as TestCollDef[]
|
||||||
|
).map((item, i) => ({
|
||||||
|
type: "collection" as const,
|
||||||
|
value: {
|
||||||
|
collectionID: `${collectionID}/${i}`,
|
||||||
|
name: item.name,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
...(
|
||||||
|
get(testData[workspaceID as keyof typeof testData], [
|
||||||
|
...collectionPath,
|
||||||
|
"requests",
|
||||||
|
]) as TestReqDef[]
|
||||||
|
).map((item, i) => ({
|
||||||
|
type: "request" as const,
|
||||||
|
value: {
|
||||||
|
requestID: `${collectionID}/${i}`,
|
||||||
|
name: item.name,
|
||||||
|
method: "get",
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRESTRootCollectionView(
|
||||||
|
workspaceHandle: HandleRef<Workspace>
|
||||||
|
): Promise<E.Either<never, Handle<RootRESTCollectionView>>> {
|
||||||
|
return Promise.resolve(
|
||||||
|
E.right(
|
||||||
|
computed(() => {
|
||||||
|
if (workspaceHandle.value.type === "invalid") {
|
||||||
|
return {
|
||||||
|
type: "invalid",
|
||||||
|
reason: "WORKSPACE_IS_INVALID" as const,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceID = workspaceHandle.value.data.workspaceID
|
||||||
|
|
||||||
|
if (!(workspaceID in testData)) {
|
||||||
|
return {
|
||||||
|
type: "invalid",
|
||||||
|
reason: "WORKSPACE_NOT_PRESENT" as const,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return markRaw({
|
||||||
|
type: "ok",
|
||||||
|
data: {
|
||||||
|
providerID: this.providerID,
|
||||||
|
workspaceID,
|
||||||
|
|
||||||
|
loading: ref(false),
|
||||||
|
|
||||||
|
collections: computed(() => {
|
||||||
|
return testData[
|
||||||
|
workspaceID as keyof typeof testData
|
||||||
|
].collections.map((x, i) => ({
|
||||||
|
collectionID: i.toString(),
|
||||||
|
name: x.name,
|
||||||
|
}))
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWorkspaceCandidates() {
|
||||||
|
return computed(() =>
|
||||||
|
Object.keys(testData).map((workspaceID) => ({
|
||||||
|
id: workspaceID,
|
||||||
|
name: testData[workspaceID as keyof typeof testData].name,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { Ref } from "vue"
|
||||||
|
import { HoppInheritedRESTProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
|
|
||||||
|
export type RESTCollectionLevelAuthHeadersView = {
|
||||||
|
auth: HoppInheritedRESTProperty["auth"]
|
||||||
|
headers: HoppInheritedRESTProperty["headers"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RESTCollectionViewCollection = {
|
||||||
|
collectionID: string
|
||||||
|
|
||||||
|
isLastItem: boolean
|
||||||
|
name: string
|
||||||
|
parentCollectionID: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RESTCollectionViewRequest = {
|
||||||
|
collectionID: string
|
||||||
|
requestID: string
|
||||||
|
|
||||||
|
request: HoppRESTRequest
|
||||||
|
isLastItem: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RESTCollectionViewItem =
|
||||||
|
| { type: "collection"; value: RESTCollectionViewCollection }
|
||||||
|
| { type: "request"; value: RESTCollectionViewRequest }
|
||||||
|
|
||||||
|
export interface RootRESTCollectionView {
|
||||||
|
providerID: string
|
||||||
|
workspaceID: string
|
||||||
|
|
||||||
|
loading: Ref<boolean>
|
||||||
|
|
||||||
|
collections: Ref<RESTCollectionViewCollection[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RESTCollectionChildrenView {
|
||||||
|
providerID: string
|
||||||
|
workspaceID: string
|
||||||
|
collectionID: string
|
||||||
|
|
||||||
|
loading: Ref<boolean>
|
||||||
|
|
||||||
|
content: Ref<RESTCollectionViewItem[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RESTSearchResultsView {
|
||||||
|
providerID: string
|
||||||
|
workspaceID: string
|
||||||
|
|
||||||
|
loading: Ref<boolean>
|
||||||
|
|
||||||
|
results: Ref<HoppCollection[]>
|
||||||
|
onSessionEnd: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RESTCollectionJSONView {
|
||||||
|
providerID: string
|
||||||
|
workspaceID: string
|
||||||
|
|
||||||
|
content: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { Component } from "vue"
|
||||||
|
|
||||||
|
export type Workspace = {
|
||||||
|
providerID: string
|
||||||
|
workspaceID: string
|
||||||
|
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkspaceCollection = {
|
||||||
|
providerID: string
|
||||||
|
workspaceID: string
|
||||||
|
collectionID: string
|
||||||
|
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkspaceRequest = {
|
||||||
|
providerID: string
|
||||||
|
workspaceID: string
|
||||||
|
collectionID: string
|
||||||
|
requestID: string
|
||||||
|
|
||||||
|
request: HoppRESTRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkspaceDecor = {
|
||||||
|
headerComponent?: Component
|
||||||
|
|
||||||
|
headerCurrentIcon?: Component | object
|
||||||
|
|
||||||
|
workspaceSelectorComponent?: Component
|
||||||
|
workspaceSelectorPriority?: number
|
||||||
|
}
|
||||||
@@ -109,10 +109,6 @@ export class OauthAuthService extends Service {
|
|||||||
public static readonly ID = "OAUTH_AUTH_SERVICE"
|
public static readonly ID = "OAUTH_AUTH_SERVICE"
|
||||||
|
|
||||||
static redirectURI = `${window.location.origin}/oauth`
|
static redirectURI = `${window.location.origin}/oauth`
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateRandomString = () => {
|
export const generateRandomString = () => {
|
||||||
|
|||||||
@@ -580,8 +580,6 @@ describe("PersistenceService", () => {
|
|||||||
|
|
||||||
invokeSetupLocalPersistence()
|
invokeSetupLocalPersistence()
|
||||||
|
|
||||||
// toastErrorFn = vi.fn()
|
|
||||||
|
|
||||||
expect(getItemSpy).toHaveBeenCalledWith(settingsKey)
|
expect(getItemSpy).toHaveBeenCalledWith(settingsKey)
|
||||||
|
|
||||||
expect(toastErrorFn).not.toHaveBeenCalledWith(settingsKey)
|
expect(toastErrorFn).not.toHaveBeenCalledWith(settingsKey)
|
||||||
|
|||||||
@@ -89,10 +89,6 @@ export class PersistenceService extends Service {
|
|||||||
|
|
||||||
public hoppLocalConfigStorage: StorageLike = localStorage
|
public hoppLocalConfigStorage: StorageLike = localStorage
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
private showErrorToast(localStorageKey: string) {
|
private showErrorToast(localStorageKey: string) {
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
toast.error(
|
toast.error(
|
||||||
|
|||||||
@@ -492,6 +492,15 @@ const HoppRESTResponseSchema = z.discriminatedUnion("type", [
|
|||||||
|
|
||||||
const HoppRESTSaveContextSchema = z.nullable(
|
const HoppRESTSaveContextSchema = z.nullable(
|
||||||
z.discriminatedUnion("originLocation", [
|
z.discriminatedUnion("originLocation", [
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
originLocation: z.literal("workspace-user-collection"),
|
||||||
|
workspaceID: z.optional(z.string()),
|
||||||
|
providerID: z.optional(z.string()),
|
||||||
|
requestID: z.optional(z.string()),
|
||||||
|
requestHandle: z.optional(z.record(z.unknown())),
|
||||||
|
})
|
||||||
|
.strict(),
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
originLocation: z.literal("user-collection"),
|
originLocation: z.literal("user-collection"),
|
||||||
|
|||||||
@@ -27,10 +27,6 @@ export class SecretEnvironmentService extends Service {
|
|||||||
*/
|
*/
|
||||||
public secretEnvironments = reactive(new Map<string, SecretVariable[]>())
|
public secretEnvironments = reactive(new Map<string, SecretVariable[]>())
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new secret environment.
|
* Add a new secret environment.
|
||||||
* @param id ID of the environment
|
* @param id ID of the environment
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
import { nextTick, reactive, ref } from "vue"
|
import { nextTick, reactive, ref } from "vue"
|
||||||
import { SpotlightSearcherResult } from "../../.."
|
import { SpotlightSearcherResult } from "../../.."
|
||||||
import { TestContainer } from "dioc/testing"
|
import { TestContainer } from "dioc/testing"
|
||||||
|
import { Container } from "dioc"
|
||||||
|
|
||||||
async function flushPromises() {
|
async function flushPromises() {
|
||||||
return await new Promise((r) => setTimeout(r))
|
return await new Promise((r) => setTimeout(r))
|
||||||
@@ -32,12 +33,15 @@ describe("StaticSpotlightSearcherService", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
constructor() {
|
// TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
|
||||||
super({
|
constructor(c: Container) {
|
||||||
|
super(c, {
|
||||||
searchFields: ["text"],
|
searchFields: ["text"],
|
||||||
fieldWeights: {},
|
fieldWeights: {},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override onServiceInit() {
|
||||||
this.setDocuments(this.documents)
|
this.setDocuments(this.documents)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,12 +98,15 @@ describe("StaticSpotlightSearcherService", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
constructor() {
|
// TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
|
||||||
super({
|
constructor(c: Container) {
|
||||||
|
super(c, {
|
||||||
searchFields: ["text"],
|
searchFields: ["text"],
|
||||||
fieldWeights: {},
|
fieldWeights: {},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override onServiceInit() {
|
||||||
this.setDocuments(this.documents)
|
this.setDocuments(this.documents)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,12 +166,15 @@ describe("StaticSpotlightSearcherService", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
constructor() {
|
// TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
|
||||||
super({
|
constructor(c: Container) {
|
||||||
|
super(c, {
|
||||||
searchFields: ["text"],
|
searchFields: ["text"],
|
||||||
fieldWeights: {},
|
fieldWeights: {},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override onServiceInit() {
|
||||||
this.setDocuments(this.documents)
|
this.setDocuments(this.documents)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,12 +234,15 @@ describe("StaticSpotlightSearcherService", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
constructor() {
|
// TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
|
||||||
super({
|
constructor(c: Container) {
|
||||||
|
super(c, {
|
||||||
searchFields: ["text"],
|
searchFields: ["text"],
|
||||||
fieldWeights: {},
|
fieldWeights: {},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override onServiceInit() {
|
||||||
this.setDocuments(this.documents)
|
this.setDocuments(this.documents)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,12 +298,15 @@ describe("StaticSpotlightSearcherService", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
constructor() {
|
// TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
|
||||||
super({
|
constructor(c: Container) {
|
||||||
|
super(c, {
|
||||||
searchFields: ["text"],
|
searchFields: ["text"],
|
||||||
fieldWeights: {},
|
fieldWeights: {},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override onServiceInit() {
|
||||||
this.setDocuments(this.documents)
|
this.setDocuments(this.documents)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,12 +370,15 @@ describe("StaticSpotlightSearcherService", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
constructor() {
|
// TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
|
||||||
super({
|
constructor(c: Container) {
|
||||||
|
super(c, {
|
||||||
searchFields: ["text", "alternate"],
|
searchFields: ["text", "alternate"],
|
||||||
fieldWeights: {},
|
fieldWeights: {},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override onServiceInit() {
|
||||||
this.setDocuments(this.documents)
|
this.setDocuments(this.documents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Service } from "dioc"
|
import { Container, Service } from "dioc"
|
||||||
import {
|
import {
|
||||||
type SpotlightSearcher,
|
type SpotlightSearcher,
|
||||||
type SpotlightSearcherResult,
|
type SpotlightSearcherResult,
|
||||||
@@ -67,8 +67,12 @@ export abstract class StaticSpotlightSearcherService<
|
|||||||
|
|
||||||
private _documents: Record<string, Doc> = {}
|
private _documents: Record<string, Doc> = {}
|
||||||
|
|
||||||
constructor(private opts: StaticSpotlightSearcherOptions<Doc>) {
|
// TODO: This pattern is no longer recommended in dioc > 3, move to something else
|
||||||
super()
|
constructor(
|
||||||
|
c: Container,
|
||||||
|
private opts: StaticSpotlightSearcherOptions<Doc>
|
||||||
|
) {
|
||||||
|
super(c)
|
||||||
|
|
||||||
this.minisearch = new MiniSearch({
|
this.minisearch = new MiniSearch({
|
||||||
fields: opts.searchFields as string[],
|
fields: opts.searchFields as string[],
|
||||||
|
|||||||
@@ -50,9 +50,7 @@ export class CollectionsSpotlightSearcherService
|
|||||||
private readonly spotlight = this.bind(SpotlightService)
|
private readonly spotlight = this.bind(SpotlightService)
|
||||||
private readonly workspaceService = this.bind(WorkspaceService)
|
private readonly workspaceService = this.bind(WorkspaceService)
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.spotlight.registerSearcher(this)
|
this.spotlight.registerSearcher(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import IconEdit from "~icons/lucide/edit"
|
|||||||
import IconLayers from "~icons/lucide/layers"
|
import IconLayers from "~icons/lucide/layers"
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
|
|
||||||
import { Service } from "dioc"
|
import { Container, Service } from "dioc"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
@@ -164,15 +164,18 @@ export class EnvironmentsSpotlightSearcherService extends StaticSpotlightSearche
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
constructor() {
|
// TODO: This pattern is no longer recommended in dioc > 3, move to something else
|
||||||
super({
|
constructor(c: Container) {
|
||||||
|
super(c, {
|
||||||
searchFields: ["text", "alternates"],
|
searchFields: ["text", "alternates"],
|
||||||
fieldWeights: {
|
fieldWeights: {
|
||||||
text: 2,
|
text: 2,
|
||||||
alternates: 1,
|
alternates: 1,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override onServiceInit() {
|
||||||
this.setDocuments(this.documents)
|
this.setDocuments(this.documents)
|
||||||
this.spotlight.registerSearcher(this)
|
this.spotlight.registerSearcher(this)
|
||||||
}
|
}
|
||||||
@@ -277,9 +280,7 @@ export class SwitchEnvSpotlightSearcherService
|
|||||||
private readonly workspaceService = this.bind(WorkspaceService)
|
private readonly workspaceService = this.bind(WorkspaceService)
|
||||||
private teamEnvironmentList: TeamEnvironment[] = []
|
private teamEnvironmentList: TeamEnvironment[] = []
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.spotlight.registerSearcher(this)
|
this.spotlight.registerSearcher(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import IconBook from "~icons/lucide/book"
|
|||||||
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
||||||
import IconZap from "~icons/lucide/zap"
|
import IconZap from "~icons/lucide/zap"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { Container } from "dioc"
|
||||||
|
|
||||||
type Doc = {
|
type Doc = {
|
||||||
text: string | string[]
|
text: string | string[]
|
||||||
@@ -89,15 +90,18 @@ export class GeneralSpotlightSearcherService extends StaticSpotlightSearcherServ
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
constructor() {
|
// TODO: This is not recommended as of dioc > 3. Move to onServiceInit instead
|
||||||
super({
|
constructor(c: Container) {
|
||||||
|
super(c, {
|
||||||
searchFields: ["text", "alternates"],
|
searchFields: ["text", "alternates"],
|
||||||
fieldWeights: {
|
fieldWeights: {
|
||||||
text: 2,
|
text: 2,
|
||||||
alternates: 1,
|
alternates: 1,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override onServiceInit() {
|
||||||
this.setDocuments(this.documents)
|
this.setDocuments(this.documents)
|
||||||
this.spotlight.registerSearcher(this)
|
this.spotlight.registerSearcher(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,9 +66,7 @@ export class HistorySpotlightSearcherService
|
|||||||
}
|
}
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.spotlight.registerSearcher(this)
|
this.spotlight.registerSearcher(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ export class InterceptorSpotlightSearcherService
|
|||||||
private readonly spotlight = this.bind(SpotlightService)
|
private readonly spotlight = this.bind(SpotlightService)
|
||||||
private interceptorService = this.bind(InterceptorService)
|
private interceptorService = this.bind(InterceptorService)
|
||||||
|
|
||||||
constructor() {
|
override onServiceInit() {
|
||||||
super()
|
|
||||||
|
|
||||||
this.spotlight.registerSearcher(this)
|
this.spotlight.registerSearcher(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
} from "./base/static.searcher"
|
} from "./base/static.searcher"
|
||||||
|
|
||||||
import IconShare from "~icons/lucide/share"
|
import IconShare from "~icons/lucide/share"
|
||||||
|
import { Container } from "dioc"
|
||||||
|
|
||||||
type Doc = {
|
type Doc = {
|
||||||
text: string
|
text: string
|
||||||
@@ -39,15 +40,18 @@ export class MiscellaneousSpotlightSearcherService extends StaticSpotlightSearch
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
constructor() {
|
// TODO: Constructors are no longer recommended as of dioc > 3, move to onServiceInit
|
||||||
super({
|
constructor(c: Container) {
|
||||||
|
super(c, {
|
||||||
searchFields: ["text", "alternates"],
|
searchFields: ["text", "alternates"],
|
||||||
fieldWeights: {
|
fieldWeights: {
|
||||||
text: 2,
|
text: 2,
|
||||||
alternates: 1,
|
alternates: 1,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override onServiceInit() {
|
||||||
this.setDocuments(this.documents)
|
this.setDocuments(this.documents)
|
||||||
this.spotlight.registerSearcher(this)
|
this.spotlight.registerSearcher(this)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user