Compare commits
10 Commits
fix/pretti
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cce117b0a | ||
|
|
abc7b4b6f3 | ||
|
|
05e32ef9e4 | ||
|
|
f0a1fc319c | ||
|
|
385cabc6aa | ||
|
|
397b26a9f3 | ||
|
|
9a40058329 | ||
|
|
7ec2380ed5 | ||
|
|
3d4825305d | ||
|
|
26e564288b |
@@ -51,7 +51,7 @@ VITE_ADMIN_URL=http://localhost:3100
|
|||||||
|
|
||||||
# Backend URLs
|
# Backend URLs
|
||||||
VITE_BACKEND_GQL_URL=http://localhost:3170/graphql
|
VITE_BACKEND_GQL_URL=http://localhost:3170/graphql
|
||||||
VITE_BACKEND_WS_URL=wss://localhost:3170/graphql
|
VITE_BACKEND_WS_URL=ws://localhost:3170/graphql
|
||||||
VITE_BACKEND_API_URL=http://localhost:3170/v1
|
VITE_BACKEND_API_URL=http://localhost:3170/v1
|
||||||
|
|
||||||
# Terms Of Service And Privacy Policy Links (Optional)
|
# Terms Of Service And Privacy Policy Links (Optional)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"dev": "pnpm -r do-dev",
|
"dev": "pnpm -r do-dev",
|
||||||
"gen-gql": "cross-env GQL_SCHEMA_EMIT_LOCATION='../../../gql-gen/backend-schema.gql' pnpm -r generate-gql-sdl",
|
"gen-gql": "cross-env GQL_SCHEMA_EMIT_LOCATION='../../../gql-gen/backend-schema.gql' pnpm -r generate-gql-sdl",
|
||||||
"generate": "pnpm -r do-build-prod",
|
"generate": "pnpm -r do-build-prod",
|
||||||
"start": "http-server packages/hoppscotch-web/dist -p 3000",
|
"start": "http-server packages/hoppscotch-selfhost-web/dist -p 3000",
|
||||||
"lint": "pnpm -r do-lint",
|
"lint": "pnpm -r do-lint",
|
||||||
"typecheck": "pnpm -r do-typecheck",
|
"typecheck": "pnpm -r do-typecheck",
|
||||||
"lintfix": "pnpm -r do-lintfix",
|
"lintfix": "pnpm -r do-lintfix",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-backend",
|
"name": "hoppscotch-backend",
|
||||||
"version": "2023.4.3",
|
"version": "2023.4.4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export class TeamInvitationService {
|
|||||||
template: 'team-invitation',
|
template: 'team-invitation',
|
||||||
variables: {
|
variables: {
|
||||||
invitee: creator.displayName ?? 'A Hoppscotch User',
|
invitee: creator.displayName ?? 'A Hoppscotch User',
|
||||||
action_url: `https://hoppscotch.io/join-team?id=${invitation.id}`,
|
action_url: `${process.env.VITE_BASE_URL}/join-team?id=${invitation.id}`,
|
||||||
invite_team_name: team.name,
|
invite_team_name: team.name,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
"filter": "过滤",
|
"filter": "过滤",
|
||||||
"go_back": "返回",
|
"go_back": "返回",
|
||||||
"go_forward": "Go forward",
|
"go_forward": "前进",
|
||||||
"group_by": "分组方式",
|
"group_by": "分组方式",
|
||||||
"label": "标签",
|
"label": "标签",
|
||||||
"learn_more": "了解更多",
|
"learn_more": "了解更多",
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
"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": "撤消",
|
||||||
@@ -118,16 +118,16 @@
|
|||||||
},
|
},
|
||||||
"collection": {
|
"collection": {
|
||||||
"created": "集合已创建",
|
"created": "集合已创建",
|
||||||
"different_parent": "Cannot reorder collection with different parent",
|
"different_parent": "不能用不同的父类来重新排序集合",
|
||||||
"edit": "编辑集合",
|
"edit": "编辑集合",
|
||||||
"invalid_name": "请提供有效的集合名称",
|
"invalid_name": "请提供有效的集合名称",
|
||||||
"invalid_root_move": "Collection already in the root",
|
"invalid_root_move": "该集合已经在根级了",
|
||||||
"moved": "Moved Successfully",
|
"moved": "移动完成",
|
||||||
"my_collections": "我的集合",
|
"my_collections": "我的集合",
|
||||||
"name": "我的新集合",
|
"name": "我的新集合",
|
||||||
"name_length_insufficient": "集合名字至少需要 3 个字符",
|
"name_length_insufficient": "集合名字至少需要 3 个字符",
|
||||||
"new": "新建集合",
|
"new": "新建集合",
|
||||||
"order_changed": "Collection Order Updated",
|
"order_changed": "集合顺序已更新",
|
||||||
"renamed": "集合已更名",
|
"renamed": "集合已更名",
|
||||||
"request_in_use": "请求正在使用中",
|
"request_in_use": "请求正在使用中",
|
||||||
"save_as": "另存为",
|
"save_as": "另存为",
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
"remove_team": "你确定要删除该团队吗?",
|
"remove_team": "你确定要删除该团队吗?",
|
||||||
"remove_telemetry": "你确定要退出遥测服务吗?",
|
"remove_telemetry": "你确定要退出遥测服务吗?",
|
||||||
"request_change": "你确定你要放弃当前的请求,未保存的修改将被丢失。",
|
"request_change": "你确定你要放弃当前的请求,未保存的修改将被丢失。",
|
||||||
"save_unsaved_tab": "Do you want to save changes made in this tab?",
|
"save_unsaved_tab": "你想保存在此标签页中所作的修改吗?",
|
||||||
"sync": "您确定要同步该工作区吗?"
|
"sync": "您确定要同步该工作区吗?"
|
||||||
},
|
},
|
||||||
"count": {
|
"count": {
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
"members": "团队为空",
|
"members": "团队为空",
|
||||||
"parameters": "该请求没有任何参数",
|
"parameters": "该请求没有任何参数",
|
||||||
"pending_invites": "此团队无待办邀请",
|
"pending_invites": "此团队无待办邀请",
|
||||||
"profile": "登录以查看你的个人档案",
|
"profile": "登录以查看你的个人资料",
|
||||||
"protocols": "协议为空",
|
"protocols": "协议为空",
|
||||||
"schema": "连接至 GraphQL 端点",
|
"schema": "连接至 GraphQL 端点",
|
||||||
"shortcodes": "Shortcodes 为空",
|
"shortcodes": "Shortcodes 为空",
|
||||||
@@ -209,7 +209,7 @@
|
|||||||
"browser_support_sse": "该浏览器似乎不支持 SSE。",
|
"browser_support_sse": "该浏览器似乎不支持 SSE。",
|
||||||
"check_console_details": "检查控制台日志以获悉详情",
|
"check_console_details": "检查控制台日志以获悉详情",
|
||||||
"curl_invalid_format": "cURL 格式不正确",
|
"curl_invalid_format": "cURL 格式不正确",
|
||||||
"danger_zone": "Danger zone",
|
"danger_zone": "危险区域",
|
||||||
"delete_account": "您的帐号目前为这些团队的拥有者:",
|
"delete_account": "您的帐号目前为这些团队的拥有者:",
|
||||||
"delete_account_description": "您在删除帐号前必须先将您自己从团队中移除、转移拥有权,或是删除团队。",
|
"delete_account_description": "您在删除帐号前必须先将您自己从团队中移除、转移拥有权,或是删除团队。",
|
||||||
"empty_req_name": "空请求名称",
|
"empty_req_name": "空请求名称",
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
"incorrect_email": "电子邮箱错误",
|
"incorrect_email": "电子邮箱错误",
|
||||||
"invalid_link": "无效链接",
|
"invalid_link": "无效链接",
|
||||||
"invalid_link_description": "你点击的链接无效或已过期。",
|
"invalid_link_description": "你点击的链接无效或已过期。",
|
||||||
"json_parsing_failed": "Invalid JSON",
|
"json_parsing_failed": "不合法的 JSON",
|
||||||
"json_prettify_invalid_body": "无法美化无效的请求头,处理 JSON 语法错误并重试",
|
"json_prettify_invalid_body": "无法美化无效的请求头,处理 JSON 语法错误并重试",
|
||||||
"network_error": "好像发生了网络错误,请重试。",
|
"network_error": "好像发生了网络错误,请重试。",
|
||||||
"network_fail": "无法发送请求",
|
"network_fail": "无法发送请求",
|
||||||
@@ -316,14 +316,14 @@
|
|||||||
"zen_mode": "ZEN 模式"
|
"zen_mode": "ZEN 模式"
|
||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"close_unsaved_tab": "You have unsaved changes",
|
"close_unsaved_tab": "有未保存的变更",
|
||||||
"collections": "集合",
|
"collections": "集合",
|
||||||
"confirm": "确认",
|
"confirm": "确认",
|
||||||
"edit_request": "编辑请求",
|
"edit_request": "编辑请求",
|
||||||
"import_export": "导入/导出"
|
"import_export": "导入/导出"
|
||||||
},
|
},
|
||||||
"mqtt": {
|
"mqtt": {
|
||||||
"already_subscribed": "您已经订阅了此主題。",
|
"already_subscribed": "您已经订阅了此主题。",
|
||||||
"clean_session": "清除会话",
|
"clean_session": "清除会话",
|
||||||
"clear_input": "清除输入",
|
"clear_input": "清除输入",
|
||||||
"clear_input_on_send": "发送后清除输入",
|
"clear_input_on_send": "发送后清除输入",
|
||||||
@@ -355,7 +355,7 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"doc": "文档",
|
"doc": "文档",
|
||||||
"graphql": "GraphQL",
|
"graphql": "GraphQL",
|
||||||
"profile": "个人档案",
|
"profile": "个人资料",
|
||||||
"realtime": "实时",
|
"realtime": "实时",
|
||||||
"rest": "REST",
|
"rest": "REST",
|
||||||
"settings": "设置"
|
"settings": "设置"
|
||||||
@@ -377,7 +377,7 @@
|
|||||||
"owner_description": "所有者可以添加、编辑和删除请求、集合及团队成员。",
|
"owner_description": "所有者可以添加、编辑和删除请求、集合及团队成员。",
|
||||||
"roles": "角色",
|
"roles": "角色",
|
||||||
"roles_description": "角色用以控制共享集合的访问权限。",
|
"roles_description": "角色用以控制共享集合的访问权限。",
|
||||||
"updated": "档案已更新",
|
"updated": "已更新",
|
||||||
"viewer": "查看者",
|
"viewer": "查看者",
|
||||||
"viewer_description": "查看者只可查看与使用请求。"
|
"viewer_description": "查看者只可查看与使用请求。"
|
||||||
},
|
},
|
||||||
@@ -396,8 +396,8 @@
|
|||||||
"text": "文字"
|
"text": "文字"
|
||||||
},
|
},
|
||||||
"copy_link": "复制链接",
|
"copy_link": "复制链接",
|
||||||
"different_collection": "Cannot reorder requests from different collections",
|
"different_collection": "不能对来自不同集合的请求进行重新排序",
|
||||||
"duplicated": "Request duplicated",
|
"duplicated": "重复的请求",
|
||||||
"duration": "持续时间",
|
"duration": "持续时间",
|
||||||
"enter_curl": "输入 cURL",
|
"enter_curl": "输入 cURL",
|
||||||
"generate_code": "生成代码",
|
"generate_code": "生成代码",
|
||||||
@@ -405,10 +405,10 @@
|
|||||||
"header_list": "请求头列表",
|
"header_list": "请求头列表",
|
||||||
"invalid_name": "请提供请求名称",
|
"invalid_name": "请提供请求名称",
|
||||||
"method": "方法",
|
"method": "方法",
|
||||||
"moved": "Request moved",
|
"moved": "请求移动完成",
|
||||||
"name": "请求名称",
|
"name": "请求名称",
|
||||||
"new": "新请求",
|
"new": "新请求",
|
||||||
"order_changed": "Request Order Updated",
|
"order_changed": "请求顺序更新完成",
|
||||||
"override": "覆盖",
|
"override": "覆盖",
|
||||||
"override_help": "设置 <kbd>Content-Type</kbd> 头",
|
"override_help": "设置 <kbd>Content-Type</kbd> 头",
|
||||||
"overriden": "覆盖",
|
"overriden": "覆盖",
|
||||||
@@ -479,10 +479,10 @@
|
|||||||
"language": "语言",
|
"language": "语言",
|
||||||
"light_mode": "亮色",
|
"light_mode": "亮色",
|
||||||
"official_proxy_hosting": "官方代理由 Hoppscotch 托管。",
|
"official_proxy_hosting": "官方代理由 Hoppscotch 托管。",
|
||||||
"profile": "个人档案",
|
"profile": "个人资料",
|
||||||
"profile_description": "更新你的档案详情",
|
"profile_description": "更新你的资料",
|
||||||
"profile_email": "电子邮箱地址",
|
"profile_email": "电子邮箱地址",
|
||||||
"profile_name": "档案名称",
|
"profile_name": "名称",
|
||||||
"proxy": "网络代理",
|
"proxy": "网络代理",
|
||||||
"proxy_url": "代理网址",
|
"proxy_url": "代理网址",
|
||||||
"proxy_use_toggle": "使用代理中间件发送请求",
|
"proxy_use_toggle": "使用代理中间件发送请求",
|
||||||
@@ -532,7 +532,7 @@
|
|||||||
"documentation": "前往文档页面",
|
"documentation": "前往文档页面",
|
||||||
"forward": "前往下一页面",
|
"forward": "前往下一页面",
|
||||||
"graphql": "前往 GraphQL 页面",
|
"graphql": "前往 GraphQL 页面",
|
||||||
"profile": "前往个人档案页面",
|
"profile": "前往个人资料页面",
|
||||||
"realtime": "前往实时页面",
|
"realtime": "前往实时页面",
|
||||||
"rest": "前往 REST 页面",
|
"rest": "前往 REST 页面",
|
||||||
"settings": "前往设置页面",
|
"settings": "前往设置页面",
|
||||||
@@ -574,7 +574,7 @@
|
|||||||
},
|
},
|
||||||
"socketio": {
|
"socketio": {
|
||||||
"communication": "通讯",
|
"communication": "通讯",
|
||||||
"connection_not_authorized": "此SocketIO连接未使用任何验证。",
|
"connection_not_authorized": "此 SocketIO 连接未使用任何验证。",
|
||||||
"event_name": "事件名称",
|
"event_name": "事件名称",
|
||||||
"events": "事件",
|
"events": "事件",
|
||||||
"log": "日志",
|
"log": "日志",
|
||||||
@@ -614,12 +614,12 @@
|
|||||||
"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": "重连失败",
|
||||||
"subscribed_failed": "无法订阅此主題:{topic}",
|
"subscribed_failed": "无法订阅此主题:{topic}",
|
||||||
"subscribed_success": "成功订阅此主題:{topic}",
|
"subscribed_success": "成功订阅此主题:{topic}",
|
||||||
"unsubscribed_failed": "无法取消订阅此主題:{topic}",
|
"unsubscribed_failed": "无法取消订阅此主题:{topic}",
|
||||||
"unsubscribed_success": "成功取消订阅此主題:{topic}",
|
"unsubscribed_success": "成功取消订阅此主题:{topic}",
|
||||||
"waiting_send_request": "等待发送请求"
|
"waiting_send_request": "等待发送请求"
|
||||||
},
|
},
|
||||||
"support": {
|
"support": {
|
||||||
@@ -639,7 +639,7 @@
|
|||||||
"body": "请求体",
|
"body": "请求体",
|
||||||
"collections": "集合",
|
"collections": "集合",
|
||||||
"documentation": "帮助文档",
|
"documentation": "帮助文档",
|
||||||
"environments": "Environments",
|
"environments": "环境",
|
||||||
"headers": "请求头",
|
"headers": "请求头",
|
||||||
"history": "历史记录",
|
"history": "历史记录",
|
||||||
"mqtt": "MQTT",
|
"mqtt": "MQTT",
|
||||||
@@ -664,7 +664,7 @@
|
|||||||
"email_do_not_match": "邮箱无法与你的帐户信息匹配。请联系你的团队者。",
|
"email_do_not_match": "邮箱无法与你的帐户信息匹配。请联系你的团队者。",
|
||||||
"exit": "退出团队",
|
"exit": "退出团队",
|
||||||
"exit_disabled": "团队所有者无法退出团队",
|
"exit_disabled": "团队所有者无法退出团队",
|
||||||
"invalid_coll_id": "Invalid collection ID",
|
"invalid_coll_id": "无效的集合 ID",
|
||||||
"invalid_email_format": "电子邮箱格式无效",
|
"invalid_email_format": "电子邮箱格式无效",
|
||||||
"invalid_id": "无效的团队 ID,请联系你的团队者。",
|
"invalid_id": "无效的团队 ID,请联系你的团队者。",
|
||||||
"invalid_invite_link": "无效的邀请链接",
|
"invalid_invite_link": "无效的邀请链接",
|
||||||
@@ -688,7 +688,7 @@
|
|||||||
"member_removed": "用户已移除",
|
"member_removed": "用户已移除",
|
||||||
"member_role_updated": "用户角色已更新",
|
"member_role_updated": "用户角色已更新",
|
||||||
"members": "成员",
|
"members": "成员",
|
||||||
"more_members": "+{count} more",
|
"more_members": "+{count} 更多",
|
||||||
"name_length_insufficient": "团队名称至少为 6 个字符",
|
"name_length_insufficient": "团队名称至少为 6 个字符",
|
||||||
"name_updated": "团队名称已更新",
|
"name_updated": "团队名称已更新",
|
||||||
"new": "新团队",
|
"new": "新团队",
|
||||||
@@ -696,13 +696,13 @@
|
|||||||
"new_name": "我的新团队",
|
"new_name": "我的新团队",
|
||||||
"no_access": "你没有编辑集合的权限",
|
"no_access": "你没有编辑集合的权限",
|
||||||
"no_invite_found": "未找到邀请。请联系你的团队者。",
|
"no_invite_found": "未找到邀请。请联系你的团队者。",
|
||||||
"no_request_found": "Request not found.",
|
"no_request_found": "请求不存在",
|
||||||
"not_found": "没有找到团队,请联系您的团队所有者。",
|
"not_found": "没有找到团队,请联系您的团队所有者。",
|
||||||
"not_valid_viewer": "你不是有效的查看者。请联系你的团队者。",
|
"not_valid_viewer": "你不是有效的查看者。请联系你的团队者。",
|
||||||
"parent_coll_move": "Cannot move collection to a child collection",
|
"parent_coll_move": "不能将集合移动到一个子集合",
|
||||||
"pending_invites": "待办邀请",
|
"pending_invites": "待办邀请",
|
||||||
"permissions": "权限",
|
"permissions": "权限",
|
||||||
"same_target_destination": "Same target and destination",
|
"same_target_destination": "目标相同",
|
||||||
"saved": "团队已保存",
|
"saved": "团队已保存",
|
||||||
"select_a_team": "选择团队",
|
"select_a_team": "选择团队",
|
||||||
"title": "团队",
|
"title": "团队",
|
||||||
@@ -732,9 +732,9 @@
|
|||||||
"url": "URL"
|
"url": "URL"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"change": "Change workspace",
|
"change": "切换工作空间",
|
||||||
"personal": "My Workspace",
|
"personal": "我的工作空间",
|
||||||
"team": "Team Workspace",
|
"team": "团队工作空间",
|
||||||
"title": "Workspaces"
|
"title": "工作空间"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/common",
|
"name": "@hoppscotch/common",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.4.3",
|
"version": "2023.4.4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
"dev:vite": "vite",
|
"dev:vite": "vite",
|
||||||
@@ -92,6 +92,7 @@
|
|||||||
"vuedraggable-es": "^4.1.1",
|
"vuedraggable-es": "^4.1.1",
|
||||||
"wonka": "^4.0.15",
|
"wonka": "^4.0.15",
|
||||||
"workbox-window": "^6.5.4",
|
"workbox-window": "^6.5.4",
|
||||||
|
"xml-formatter": "^3.4.1",
|
||||||
"yargs-parser": "^21.1.1"
|
"yargs-parser": "^21.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
/>
|
/>
|
||||||
<CollectionsEditRequest
|
<CollectionsEditRequest
|
||||||
:show="showModalEditRequest"
|
:show="showModalEditRequest"
|
||||||
v-bind:model-value="editingRequest ? editingRequest.name : ''"
|
:model-value="editingRequest ? editingRequest.name : ''"
|
||||||
:loading-state="modalLoadingState"
|
:loading-state="modalLoadingState"
|
||||||
@submit="updateEditingRequest"
|
@submit="updateEditingRequest"
|
||||||
@hide-modal="displayModalEditRequest(false)"
|
@hide-modal="displayModalEditRequest(false)"
|
||||||
|
|||||||
@@ -11,13 +11,14 @@
|
|||||||
class="bg-transparent border-b border-dividerLight select-wrapper"
|
class="bg-transparent border-b border-dividerLight select-wrapper"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="selectedEnv.type !== 'NO_ENV_SELECTED'"
|
:icon="IconLayers"
|
||||||
:label="selectedEnv.name"
|
:label="
|
||||||
class="flex-1 !justify-start pr-8 rounded-none"
|
mdAndLarger
|
||||||
/>
|
? selectedEnv.type !== 'NO_ENV_SELECTED'
|
||||||
<HoppButtonSecondary
|
? selectedEnv.name
|
||||||
v-else
|
: `${t('environment.select')}`
|
||||||
:label="`${t('environment.select')}`"
|
: ''
|
||||||
|
"
|
||||||
class="flex-1 !justify-start pr-8 rounded-none"
|
class="flex-1 !justify-start pr-8 rounded-none"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -46,93 +47,145 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<div v-if="environmentType === 'my-environments'" class="flex flex-col">
|
<HoppSmartTabs
|
||||||
<hr v-if="myEnvironments.length > 0" />
|
v-model="selectedEnvTab"
|
||||||
<HoppSmartItem
|
styles="sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-0 top-0 bg-primary"
|
||||||
v-for="(gen, index) in myEnvironments"
|
render-inactive-tabs
|
||||||
:key="`gen-${index}`"
|
>
|
||||||
:label="gen.name"
|
<HoppSmartTab
|
||||||
:info-icon="index === selectedEnv.index ? IconCheck : undefined"
|
:id="'my-environments'"
|
||||||
:active-info-icon="index === selectedEnv.index"
|
:label="`${t('environment.my_environments')}`"
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
selectedEnvironmentIndex = { type: 'MY_ENV', index: index }
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex flex-col">
|
|
||||||
<div
|
|
||||||
v-if="teamEnvLoading"
|
|
||||||
class="flex flex-col items-center justify-center p-4"
|
|
||||||
>
|
>
|
||||||
<HoppSmartSpinner class="my-4" />
|
|
||||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
|
||||||
</div>
|
|
||||||
<hr v-if="teamEnvironmentList.length > 0" />
|
|
||||||
<div v-if="isTeamSelected" class="flex flex-col">
|
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
v-for="(gen, index) in teamEnvironmentList"
|
v-for="(gen, index) in myEnvironments"
|
||||||
:key="`gen-team-${index}`"
|
:key="`gen-${index}`"
|
||||||
:label="gen.environment.name"
|
:icon="IconLayers"
|
||||||
:info-icon="
|
:label="gen.name"
|
||||||
gen.id === selectedEnv.teamEnvID ? IconCheck : undefined
|
:info-icon="index === selectedEnv.index ? IconCheck : undefined"
|
||||||
"
|
:active-info-icon="index === selectedEnv.index"
|
||||||
:active-info-icon="gen.id === selectedEnv.teamEnvID"
|
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
selectedEnvironmentIndex = {
|
selectedEnvironmentIndex = { type: 'MY_ENV', index: index }
|
||||||
type: 'TEAM_ENV',
|
|
||||||
teamEnvID: gen.id,
|
|
||||||
teamID: gen.teamID,
|
|
||||||
environment: gen.environment,
|
|
||||||
}
|
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
<div
|
||||||
<div
|
v-if="myEnvironments.length === 0"
|
||||||
v-if="!teamEnvLoading && isAdapterError"
|
class="flex flex-col items-center justify-center text-secondaryLight"
|
||||||
class="flex flex-col items-center py-4"
|
>
|
||||||
|
<img
|
||||||
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
|
loading="lazy"
|
||||||
|
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-2"
|
||||||
|
:alt="`${t('empty.environments')}`"
|
||||||
|
/>
|
||||||
|
<span class="pb-2 text-center">
|
||||||
|
{{ t("empty.environments") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</HoppSmartTab>
|
||||||
|
<HoppSmartTab
|
||||||
|
:id="'team-environments'"
|
||||||
|
:label="`${t('environment.team_environments')}`"
|
||||||
|
:disabled="!isTeamSelected || workspace.type === 'personal'"
|
||||||
>
|
>
|
||||||
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
<div
|
||||||
{{ errorMessage }}
|
v-if="teamListLoading"
|
||||||
</div>
|
class="flex flex-col items-center justify-center p-4"
|
||||||
</div>
|
>
|
||||||
|
<HoppSmartSpinner class="my-4" />
|
||||||
|
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="isTeamSelected" class="flex flex-col">
|
||||||
|
<HoppSmartItem
|
||||||
|
v-for="(gen, index) in teamEnvironmentList"
|
||||||
|
:key="`gen-team-${index}`"
|
||||||
|
:icon="IconLayers"
|
||||||
|
:label="gen.environment.name"
|
||||||
|
:info-icon="
|
||||||
|
gen.id === selectedEnv.teamEnvID ? IconCheck : undefined
|
||||||
|
"
|
||||||
|
:active-info-icon="gen.id === selectedEnv.teamEnvID"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
selectedEnvironmentIndex = {
|
||||||
|
type: 'TEAM_ENV',
|
||||||
|
teamEnvID: gen.id,
|
||||||
|
teamID: gen.teamID,
|
||||||
|
environment: gen.environment,
|
||||||
|
}
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="teamEnvironmentList.length === 0"
|
||||||
|
class="flex flex-col items-center justify-center text-secondaryLight"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
|
loading="lazy"
|
||||||
|
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-2"
|
||||||
|
:alt="`${t('empty.environments')}`"
|
||||||
|
/>
|
||||||
|
<span class="pb-2 text-center">
|
||||||
|
{{ t("empty.environments") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!teamListLoading && teamAdapterError"
|
||||||
|
class="flex flex-col items-center py-4"
|
||||||
|
>
|
||||||
|
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
||||||
|
{{ getErrorMessage(teamAdapterError) }}
|
||||||
|
</div>
|
||||||
|
</HoppSmartTab>
|
||||||
|
</HoppSmartTabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</tippy>
|
</tippy>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
import IconLayers from "~icons/lucide/layers"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { Environment } from "@hoppscotch/data"
|
import { useReadonlyStream, useStream } from "~/composables/stream"
|
||||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
|
||||||
import { useStream } from "~/composables/stream"
|
|
||||||
import {
|
import {
|
||||||
|
environments$,
|
||||||
selectedEnvironmentIndex$,
|
selectedEnvironmentIndex$,
|
||||||
setSelectedEnvironmentIndex,
|
setSelectedEnvironmentIndex,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
|
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
||||||
|
import { useColorMode } from "@composables/theming"
|
||||||
|
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
|
||||||
|
|
||||||
|
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||||
|
const mdAndLarger = breakpoints.greater("md")
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
type EnvironmentType = "my-environments" | "team-environments"
|
type EnvironmentType = "my-environments" | "team-environments"
|
||||||
|
|
||||||
const props = defineProps<{
|
const myEnvironments = useReadonlyStream(environments$, [])
|
||||||
environmentType: EnvironmentType
|
|
||||||
myEnvironments: Environment[]
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
teamEnvironmentList: TeamEnvironment[]
|
|
||||||
teamEnvLoading: boolean
|
const teamEnvListAdapter = new TeamEnvironmentAdapter(undefined)
|
||||||
isAdapterError: boolean
|
const teamListLoading = useReadonlyStream(teamEnvListAdapter.loading$, false)
|
||||||
errorMessage: GQLError<string>
|
const teamAdapterError = useReadonlyStream(teamEnvListAdapter.error$, null)
|
||||||
isTeamSelected: boolean
|
const teamEnvironmentList = useReadonlyStream(
|
||||||
}>()
|
teamEnvListAdapter.teamEnvironmentList$,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
const selectedEnvironmentIndex = useStream(
|
const selectedEnvironmentIndex = useStream(
|
||||||
selectedEnvironmentIndex$,
|
selectedEnvironmentIndex$,
|
||||||
@@ -140,15 +193,35 @@ const selectedEnvironmentIndex = useStream(
|
|||||||
setSelectedEnvironmentIndex
|
setSelectedEnvironmentIndex
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const isTeamSelected = computed(
|
||||||
|
() => workspace.value.type === "team" && workspace.value.teamID !== undefined
|
||||||
|
)
|
||||||
|
|
||||||
|
const selectedEnvTab = ref<EnvironmentType>("my-environments")
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => workspace.value,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal.type === "personal") {
|
||||||
|
selectedEnvTab.value = "my-environments"
|
||||||
|
} else {
|
||||||
|
selectedEnvTab.value = "team-environments"
|
||||||
|
if (newVal.teamID) {
|
||||||
|
teamEnvListAdapter.changeTeamID(newVal.teamID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const selectedEnv = computed(() => {
|
const selectedEnv = computed(() => {
|
||||||
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
||||||
return {
|
return {
|
||||||
type: "MY_ENV",
|
type: "MY_ENV",
|
||||||
index: selectedEnvironmentIndex.value.index,
|
index: selectedEnvironmentIndex.value.index,
|
||||||
name: props.myEnvironments[selectedEnvironmentIndex.value.index].name,
|
name: myEnvironments.value[selectedEnvironmentIndex.value.index].name,
|
||||||
}
|
}
|
||||||
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
||||||
const teamEnv = props.teamEnvironmentList.find(
|
const teamEnv = teamEnvironmentList.value.find(
|
||||||
(env) =>
|
(env) =>
|
||||||
env.id ===
|
env.id ===
|
||||||
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||||
@@ -170,4 +243,17 @@ const selectedEnv = computed(() => {
|
|||||||
|
|
||||||
// Template refs
|
// Template refs
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
|
|
||||||
|
const getErrorMessage = (err: GQLError<string>) => {
|
||||||
|
if (err.type === "network_error") {
|
||||||
|
return t("error.network_error")
|
||||||
|
} else {
|
||||||
|
switch (err.error) {
|
||||||
|
case "team_environment/not_found":
|
||||||
|
return t("team_environment.not_found")
|
||||||
|
default:
|
||||||
|
return t("error.something_went_wrong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,15 +4,6 @@
|
|||||||
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
|
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
|
||||||
>
|
>
|
||||||
<WorkspaceCurrent :section="t('tab.environments')" />
|
<WorkspaceCurrent :section="t('tab.environments')" />
|
||||||
<EnvironmentsSelector
|
|
||||||
:environment-type="environmentType.type"
|
|
||||||
:my-environments="myEnvironments"
|
|
||||||
:team-env-loading="loading"
|
|
||||||
:team-environment-list="teamEnvironmentList"
|
|
||||||
:is-adapter-error="adapterError !== null"
|
|
||||||
:error-message="adapterError ? getErrorMessage(adapterError) : ''"
|
|
||||||
:is-team-selected="environmentType.selectedTeam !== undefined"
|
|
||||||
/>
|
|
||||||
<EnvironmentsMyEnvironment
|
<EnvironmentsMyEnvironment
|
||||||
environment-index="Global"
|
environment-index="Global"
|
||||||
:environment="globalEnvironment"
|
:environment="globalEnvironment"
|
||||||
@@ -46,13 +37,11 @@ import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|||||||
import { useReadonlyStream, useStream } from "@composables/stream"
|
import { useReadonlyStream, useStream } from "@composables/stream"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import {
|
import {
|
||||||
environments$,
|
|
||||||
globalEnv$,
|
globalEnv$,
|
||||||
selectedEnvironmentIndex$,
|
selectedEnvironmentIndex$,
|
||||||
setSelectedEnvironmentIndex,
|
setSelectedEnvironmentIndex,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { workspaceStatus$ } from "~/newstore/workspace"
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
@@ -147,24 +136,19 @@ onLoggedIn(() => {
|
|||||||
|
|
||||||
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
// Used to switch environment type and team when user switch workspace in the global workspace switcher
|
// Switch to my environments if workspace is personal and to team environments if workspace is team
|
||||||
// Check if there is a teamID in the workspace, if yes, switch to team environment and select the team
|
// also resets selected environment if workspace is personal and the previous selected environment was a team environment
|
||||||
// If there is no teamID, switch to my environment
|
|
||||||
watch(workspace, (newWorkspace) => {
|
watch(workspace, (newWorkspace) => {
|
||||||
if (newWorkspace.type === "personal") {
|
if (newWorkspace.type === "personal") {
|
||||||
switchToMyEnvironments()
|
switchToMyEnvironments()
|
||||||
setSelectedEnvironmentIndex({
|
|
||||||
type: "NO_ENV_SELECTED",
|
|
||||||
})
|
|
||||||
} else if (newWorkspace.type === "team") {
|
|
||||||
const team = myTeams.value?.find((t) => t.id === newWorkspace.teamID)
|
|
||||||
updateSelectedTeam(team)
|
|
||||||
|
|
||||||
if (selectedEnvironmentIndex.value.type !== "MY_ENV") {
|
if (selectedEnvironmentIndex.value.type !== "MY_ENV") {
|
||||||
setSelectedEnvironmentIndex({
|
setSelectedEnvironmentIndex({
|
||||||
type: "NO_ENV_SELECTED",
|
type: "NO_ENV_SELECTED",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else if (newWorkspace.type === "team") {
|
||||||
|
const team = myTeams.value?.find((t) => t.id === newWorkspace.teamID)
|
||||||
|
updateSelectedTeam(team)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -207,8 +191,6 @@ defineActionHandler(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const myEnvironments = useReadonlyStream(environments$, [])
|
|
||||||
|
|
||||||
const selectedEnvironmentIndex = useStream(
|
const selectedEnvironmentIndex = useStream(
|
||||||
selectedEnvironmentIndex$,
|
selectedEnvironmentIndex$,
|
||||||
{ type: "NO_ENV_SELECTED" },
|
{ type: "NO_ENV_SELECTED" },
|
||||||
@@ -251,17 +233,4 @@ watch(
|
|||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const getErrorMessage = (err: GQLError<string>) => {
|
|
||||||
if (err.type === "network_error") {
|
|
||||||
return t("error.network_error")
|
|
||||||
} else {
|
|
||||||
switch (err.error) {
|
|
||||||
case "team_environment/not_found":
|
|
||||||
return t("team_environment.not_found")
|
|
||||||
default:
|
|
||||||
return t("error.something_went_wrong")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -165,8 +165,8 @@ const props = withDefaults(
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
action: "edit" | "new"
|
action: "edit" | "new"
|
||||||
editingEnvironmentIndex: number | "Global" | null
|
editingEnvironmentIndex?: number | "Global" | null
|
||||||
editingVariableName: string | null
|
editingVariableName?: string | null
|
||||||
envVars?: () => Environment["variables"]
|
envVars?: () => Environment["variables"]
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ import * as A from "fp-ts/Array"
|
|||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { flow, pipe } from "fp-ts/function"
|
import { flow, pipe } from "fp-ts/function"
|
||||||
import { parseTemplateStringE } from "@hoppscotch/data"
|
import { Environment, parseTemplateStringE } from "@hoppscotch/data"
|
||||||
import { refAutoReset } from "@vueuse/core"
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { clone } from "lodash-es"
|
import { clone } from "lodash-es"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
@@ -173,16 +173,20 @@ const props = withDefaults(
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
action: "edit" | "new"
|
action: "edit" | "new"
|
||||||
editingEnvironment: TeamEnvironment | null
|
editingEnvironment?: TeamEnvironment | null
|
||||||
editingTeamId: string | undefined
|
editingTeamId: string | undefined
|
||||||
editingVariableName: string | null
|
editingVariableName?: string | null
|
||||||
isViewer: boolean
|
isViewer?: boolean
|
||||||
|
envVars?: () => Environment["variables"]
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
show: false,
|
show: false,
|
||||||
action: "edit",
|
action: "edit",
|
||||||
editingEnvironment: null,
|
editingEnvironment: null,
|
||||||
editingTeamId: "",
|
editingTeamId: "",
|
||||||
|
editingVariableName: null,
|
||||||
|
isViewer: false,
|
||||||
|
envVars: () => [],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -226,10 +230,16 @@ watch(
|
|||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
if (props.editingEnvironment === null) {
|
if (props.action === "new") {
|
||||||
name.value = null
|
name.value = null
|
||||||
vars.value = []
|
vars.value = pipe(
|
||||||
} else {
|
props.envVars() ?? [],
|
||||||
|
A.map((e: { key: string; value: string }) => ({
|
||||||
|
id: idTicker.value++,
|
||||||
|
env: clone(e),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
} else if (props.editingEnvironment !== null) {
|
||||||
name.value = props.editingEnvironment.environment.name ?? null
|
name.value = props.editingEnvironment.environment.name ?? null
|
||||||
vars.value = pipe(
|
vars.value = pipe(
|
||||||
props.editingEnvironment.environment.variables ?? [],
|
props.editingEnvironment.environment.variables ?? [],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="sticky z-10 flex justify-between flex-1 flex-shrink-0 overflow-x-auto border-b top-upperSecondaryStickyFold border-dividerLight bg-primary"
|
class="sticky z-10 flex justify-between flex-1 flex-shrink-0 overflow-x-auto border-b top-upperPrimaryStickyFold border-dividerLight bg-primary"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="team === undefined || team.myRole === 'VIEWER'"
|
v-if="team === undefined || team.myRole === 'VIEWER'"
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ import { useToast } from "@composables/toast"
|
|||||||
import { isJSONContentType } from "~/helpers/utils/contenttypes"
|
import { isJSONContentType } from "~/helpers/utils/contenttypes"
|
||||||
import jsonLinter from "~/helpers/editor/linting/json"
|
import jsonLinter from "~/helpers/editor/linting/json"
|
||||||
import { readFileAsText } from "~/helpers/functional/files"
|
import { readFileAsText } from "~/helpers/functional/files"
|
||||||
|
import xmlFormat from "xml-formatter"
|
||||||
|
|
||||||
type PossibleContentTypes = Exclude<
|
type PossibleContentTypes = Exclude<
|
||||||
ValidContentTypes,
|
ValidContentTypes,
|
||||||
@@ -197,26 +198,10 @@ const prettifyRequestBody = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const prettifyXML = (xml: string) => {
|
const prettifyXML = (xml: string) => {
|
||||||
const PADDING = " ".repeat(2) // set desired indent size here
|
return xmlFormat(xml, {
|
||||||
const reg = /(>)(<)(\/*)/g
|
indentation: " ",
|
||||||
let pad = 0
|
collapseContent: true,
|
||||||
xml = xml.replace(reg, "$1\r\n$2$3")
|
lineSeparator: "\n",
|
||||||
return xml
|
})
|
||||||
.split("\r\n")
|
|
||||||
.map((node) => {
|
|
||||||
let indent = 0
|
|
||||||
if (node.match(/.+<\/\w[^>]*>$/)) {
|
|
||||||
indent = 0
|
|
||||||
} else if (node.match(/^<\/\w/) && pad > 0) {
|
|
||||||
pad -= 1
|
|
||||||
} else if (node.match(/^<\w[^>]*[^\/]>.*$/)) {
|
|
||||||
indent = 1
|
|
||||||
} else {
|
|
||||||
indent = 0
|
|
||||||
}
|
|
||||||
pad += indent
|
|
||||||
return PADDING.repeat(pad - indent) + node
|
|
||||||
})
|
|
||||||
.join("\r\n")
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -197,11 +197,20 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<EnvironmentsMyDetails
|
<EnvironmentsMyDetails
|
||||||
:show="showModalDetails"
|
:show="showMyEnvironmentDetailsModal"
|
||||||
action="new"
|
action="new"
|
||||||
:env-vars="getAdditionVars"
|
:env-vars="getAdditionVars"
|
||||||
@hide-modal="displayModalAdd(false)"
|
@hide-modal="displayModalAdd(false)"
|
||||||
/>
|
/>
|
||||||
|
<EnvironmentsTeamsDetails
|
||||||
|
:show="showTeamEnvironmentDetailsModal"
|
||||||
|
action="new"
|
||||||
|
:env-vars="getAdditionVars"
|
||||||
|
:editing-team-id="
|
||||||
|
workspace.type === 'team' ? workspace.teamID : undefined
|
||||||
|
"
|
||||||
|
@hide-modal="displayModalAdd(false)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -225,6 +234,7 @@ import IconClose from "~icons/lucide/x"
|
|||||||
|
|
||||||
import { useColorMode } from "~/composables/theming"
|
import { useColorMode } from "~/composables/theming"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: HoppTestResult | null | undefined
|
modelValue: HoppTestResult | null | undefined
|
||||||
@@ -239,10 +249,15 @@ const testResults = useVModel(props, "modelValue", emit)
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const showModalDetails = ref(false)
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
|
const showMyEnvironmentDetailsModal = ref(false)
|
||||||
|
const showTeamEnvironmentDetailsModal = ref(false)
|
||||||
|
|
||||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||||
showModalDetails.value = shouldDisplay
|
if (workspace.value.type === "personal")
|
||||||
|
showMyEnvironmentDetailsModal.value = shouldDisplay
|
||||||
|
else showTeamEnvironmentDetailsModal.value = shouldDisplay
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
baseHighlightStyle,
|
baseHighlightStyle,
|
||||||
} from "@helpers/editor/themes/baseTheme"
|
} from "@helpers/editor/themes/baseTheme"
|
||||||
import { HoppEnvironmentPlugin } from "@helpers/editor/extensions/HoppEnvironment"
|
import { HoppEnvironmentPlugin } from "@helpers/editor/extensions/HoppEnvironment"
|
||||||
|
import xmlFormat from "xml-formatter"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
// TODO: Migrate from legacy mode
|
// TODO: Migrate from legacy mode
|
||||||
|
|
||||||
@@ -152,6 +153,27 @@ const getLanguage = (langMime: string): Language | null => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses xml-formatter to format the XML document
|
||||||
|
* @param doc Document to parse
|
||||||
|
* @param langMime Language mime type
|
||||||
|
* @returns Parsed document if mime type is xml, else returns the original document
|
||||||
|
*/
|
||||||
|
const parseDoc = (
|
||||||
|
doc: string | undefined,
|
||||||
|
langMime: string
|
||||||
|
): string | undefined => {
|
||||||
|
if (langMime === "application/xml" && doc) {
|
||||||
|
return xmlFormat(doc, {
|
||||||
|
indentation: " ",
|
||||||
|
collapseContent: true,
|
||||||
|
lineSeparator: "\n",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getEditorLanguage = (
|
const getEditorLanguage = (
|
||||||
langMime: string,
|
langMime: string,
|
||||||
linter: LinterDefinition | undefined,
|
linter: LinterDefinition | undefined,
|
||||||
@@ -261,7 +283,7 @@ export function useCodemirror(
|
|||||||
view.value = new EditorView({
|
view.value = new EditorView({
|
||||||
parent: el,
|
parent: el,
|
||||||
state: EditorState.create({
|
state: EditorState.create({
|
||||||
doc: value.value,
|
doc: parseDoc(value.value, options.extendedEditorConfig.mode ?? ""),
|
||||||
extensions,
|
extensions,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { pipe, flow } from "fp-ts/function"
|
|||||||
import { tupleToRecord } from "~/helpers/functional/record"
|
import { tupleToRecord } from "~/helpers/functional/record"
|
||||||
import { safeParseJSON } from "~/helpers/functional/json"
|
import { safeParseJSON } from "~/helpers/functional/json"
|
||||||
import { optionChoose } from "~/helpers/functional/option"
|
import { optionChoose } from "~/helpers/functional/option"
|
||||||
|
import xmlFormat from "xml-formatter"
|
||||||
|
|
||||||
const isJSON = flow(safeParseJSON, O.isSome)
|
const isJSON = flow(safeParseJSON, O.isSome)
|
||||||
|
|
||||||
@@ -213,45 +214,18 @@ export function parseBody(
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prettifies XML string
|
* Prettifies XML string using xml-formatter
|
||||||
* @param sourceXml The string to format
|
* @param sourceXml The string to format
|
||||||
* @returns Indented XML string (uses spaces)
|
* @returns Indented XML string (uses spaces)
|
||||||
*/
|
*/
|
||||||
function prettifyXml(sourceXml: string) {
|
function prettifyXml(sourceXml: string) {
|
||||||
return pipe(
|
return pipe(
|
||||||
O.tryCatch(() => {
|
O.tryCatch(() => {
|
||||||
const xmlDoc = new DOMParser().parseFromString(
|
return xmlFormat(sourceXml, {
|
||||||
sourceXml,
|
indentation: " ",
|
||||||
"application/xml"
|
collapseContent: true,
|
||||||
)
|
lineSeparator: "\n",
|
||||||
|
})
|
||||||
if (xmlDoc.querySelector("parsererror")) {
|
|
||||||
throw new Error("Unstructured Body")
|
|
||||||
}
|
|
||||||
|
|
||||||
const xsltDoc = new DOMParser().parseFromString(
|
|
||||||
[
|
|
||||||
// describes how we want to modify the XML - indent everything
|
|
||||||
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
|
|
||||||
' <xsl:strip-space elements="*"/>',
|
|
||||||
' <xsl:template match="para[content-style][not(text())]">', // change to just text() to strip space in text nodes
|
|
||||||
' <xsl:value-of select="normalize-space(.)"/>',
|
|
||||||
" </xsl:template>",
|
|
||||||
' <xsl:template match="node()|@*">',
|
|
||||||
' <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>',
|
|
||||||
" </xsl:template>",
|
|
||||||
' <xsl:output indent="yes"/>',
|
|
||||||
"</xsl:stylesheet>",
|
|
||||||
].join("\n"),
|
|
||||||
"application/xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const xsltProcessor = new XSLTProcessor()
|
|
||||||
xsltProcessor.importStylesheet(xsltDoc)
|
|
||||||
const resultDoc = xsltProcessor.transformToDocument(xmlDoc)
|
|
||||||
const resultXml = new XMLSerializer().serializeToString(resultDoc)
|
|
||||||
|
|
||||||
return resultXml
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
40
packages/hoppscotch-common/src/newstore/syncing.ts
Normal file
40
packages/hoppscotch-common/src/newstore/syncing.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { distinctUntilChanged, pluck } from "rxjs"
|
||||||
|
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||||
|
|
||||||
|
type SyncState = {
|
||||||
|
isInitialSync: boolean
|
||||||
|
shouldSync: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type CurrentSyncingState = {
|
||||||
|
currentSyncingItem: SyncState
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: CurrentSyncingState = {
|
||||||
|
currentSyncingItem: {
|
||||||
|
isInitialSync: false,
|
||||||
|
shouldSync: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const dispatchers = defineDispatchers({
|
||||||
|
changeCurrentSyncStatus(_, { syncItem }: { syncItem: SyncState }) {
|
||||||
|
return {
|
||||||
|
currentSyncingItem: syncItem,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const currentSyncStore = new DispatchingStore(initialState, dispatchers)
|
||||||
|
|
||||||
|
export const currentSyncingStatus$ = currentSyncStore.subject$.pipe(
|
||||||
|
pluck("currentSyncingItem"),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
|
|
||||||
|
export function changeCurrentSyncStatus(syncItem: SyncState) {
|
||||||
|
currentSyncStore.dispatch({
|
||||||
|
dispatcher: "changeCurrentSyncStatus",
|
||||||
|
payload: { syncItem },
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -56,6 +56,9 @@
|
|||||||
@update:model-value="onTabUpdate"
|
@update:model-value="onTabUpdate"
|
||||||
/>
|
/>
|
||||||
</HoppSmartWindow>
|
</HoppSmartWindow>
|
||||||
|
<template #actions>
|
||||||
|
<EnvironmentsSelector class="h-full" />
|
||||||
|
</template>
|
||||||
</HoppSmartWindows>
|
</HoppSmartWindows>
|
||||||
</template>
|
</template>
|
||||||
<template #sidebar>
|
<template #sidebar>
|
||||||
@@ -63,8 +66,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</AppPaneLayout>
|
</AppPaneLayout>
|
||||||
<CollectionsEditRequest
|
<CollectionsEditRequest
|
||||||
:show="showRenamingReqNameModal"
|
|
||||||
v-model="reqName"
|
v-model="reqName"
|
||||||
|
:show="showRenamingReqNameModal"
|
||||||
@submit="renameReqName"
|
@submit="renameReqName"
|
||||||
@hide-modal="showRenamingReqNameModal = false"
|
@hide-modal="showRenamingReqNameModal = false"
|
||||||
/>
|
/>
|
||||||
@@ -84,7 +87,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onBeforeUnmount, watch, onBeforeMount } from "vue"
|
import { ref, onMounted, onBeforeUnmount, onBeforeMount } from "vue"
|
||||||
import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
||||||
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
||||||
import { useRoute } from "vue-router"
|
import { useRoute } from "vue-router"
|
||||||
@@ -120,6 +123,11 @@ import { useToast } from "~/composables/toast"
|
|||||||
import { PersistableRESTTabState } from "~/helpers/rest/tab"
|
import { PersistableRESTTabState } from "~/helpers/rest/tab"
|
||||||
import { watchDebounced } from "@vueuse/core"
|
import { watchDebounced } from "@vueuse/core"
|
||||||
import { oauthRedirect } from "~/helpers/oauth"
|
import { oauthRedirect } from "~/helpers/oauth"
|
||||||
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
import {
|
||||||
|
changeCurrentSyncStatus,
|
||||||
|
currentSyncingStatus$,
|
||||||
|
} from "~/newstore/syncing"
|
||||||
|
|
||||||
const savingRequest = ref(false)
|
const savingRequest = ref(false)
|
||||||
const confirmingCloseForTabID = ref<string | null>(null)
|
const confirmingCloseForTabID = ref<string | null>(null)
|
||||||
@@ -131,7 +139,10 @@ const toast = useToast()
|
|||||||
|
|
||||||
const tabs = getActiveTabs()
|
const tabs = getActiveTabs()
|
||||||
|
|
||||||
const confirmSync = ref(false)
|
const confirmSync = useReadonlyStream(currentSyncingStatus$, {
|
||||||
|
isInitialSync: false,
|
||||||
|
shouldSync: true,
|
||||||
|
})
|
||||||
const tabStateForSync = ref<PersistableRESTTabState | null>(null)
|
const tabStateForSync = ref<PersistableRESTTabState | null>(null)
|
||||||
|
|
||||||
function bindRequestToURLParams() {
|
function bindRequestToURLParams() {
|
||||||
@@ -226,29 +237,6 @@ const onSaveModalClose = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(confirmSync, (newValue) => {
|
|
||||||
if (newValue) {
|
|
||||||
toast.show(t("confirm.sync"), {
|
|
||||||
duration: 0,
|
|
||||||
action: [
|
|
||||||
{
|
|
||||||
text: `${t("action.yes")}`,
|
|
||||||
onClick: (_, toastObject) => {
|
|
||||||
syncTabState()
|
|
||||||
toastObject.goAway(0)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `${t("action.no")}`,
|
|
||||||
onClick: (_, toastObject) => {
|
|
||||||
toastObject.goAway(0)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const syncTabState = () => {
|
const syncTabState = () => {
|
||||||
if (tabStateForSync.value) loadTabsFromPersistedState(tabStateForSync.value)
|
if (tabStateForSync.value) loadTabsFromPersistedState(tabStateForSync.value)
|
||||||
}
|
}
|
||||||
@@ -287,6 +275,35 @@ function startTabStateSync(): Subscription {
|
|||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showSyncToast = () => {
|
||||||
|
toast.show(t("confirm.sync"), {
|
||||||
|
duration: 0,
|
||||||
|
action: [
|
||||||
|
{
|
||||||
|
text: `${t("action.yes")}`,
|
||||||
|
onClick: (_, toastObject) => {
|
||||||
|
syncTabState()
|
||||||
|
changeCurrentSyncStatus({
|
||||||
|
isInitialSync: true,
|
||||||
|
shouldSync: true,
|
||||||
|
})
|
||||||
|
toastObject.goAway(0)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${t("action.no")}`,
|
||||||
|
onClick: (_, toastObject) => {
|
||||||
|
changeCurrentSyncStatus({
|
||||||
|
isInitialSync: true,
|
||||||
|
shouldSync: false,
|
||||||
|
})
|
||||||
|
toastObject.goAway(0)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function setupTabStateSync() {
|
function setupTabStateSync() {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@@ -302,9 +319,15 @@ function setupTabStateSync() {
|
|||||||
const tabStateFromSync =
|
const tabStateFromSync =
|
||||||
await platform.sync.tabState.loadTabStateFromSync()
|
await platform.sync.tabState.loadTabStateFromSync()
|
||||||
|
|
||||||
if (tabStateFromSync) {
|
if (tabStateFromSync && !confirmSync.value.isInitialSync) {
|
||||||
tabStateForSync.value = tabStateFromSync
|
tabStateForSync.value = tabStateFromSync
|
||||||
confirmSync.value = true
|
showSyncToast()
|
||||||
|
// Have to set isInitialSync to true here because the toast is shown
|
||||||
|
// and the user does not click on any of the actions
|
||||||
|
changeCurrentSyncStatus({
|
||||||
|
isInitialSync: true,
|
||||||
|
shouldSync: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/selfhost-web",
|
"name": "@hoppscotch/selfhost-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.4.3",
|
"version": "2023.4.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:vite": "vite",
|
"dev:vite": "vite",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-sh-admin",
|
"name": "hoppscotch-sh-admin",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.4.3",
|
"version": "2023.4.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
|
|||||||
@@ -100,7 +100,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<slot name="actions" />
|
<div v-if="hasActions" :class="mdAndLarger ? 'w-64' : 'w-16'">
|
||||||
|
<slot name="actions" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
@@ -111,10 +113,10 @@
|
|||||||
:class="{
|
:class="{
|
||||||
'!block': scrollThumb.show,
|
'!block': scrollThumb.show,
|
||||||
}"
|
}"
|
||||||
:style="{
|
:style="[
|
||||||
'--thumb-width': scrollThumb.width + 'px',
|
`--thumb-width: ${scrollThumb.width}px`,
|
||||||
}"
|
`width: calc(100% - ${hasActions ? mdAndLarger ? '19rem' : '7rem' : '3rem'})`,
|
||||||
style="width: calc(100% - 3rem)"
|
]"
|
||||||
id="myRange"
|
id="myRange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,8 +142,9 @@ import {
|
|||||||
inject,
|
inject,
|
||||||
watch,
|
watch,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
useSlots,
|
||||||
} from "vue"
|
} from "vue"
|
||||||
import { useElementSize } from "@vueuse/core"
|
import { breakpointsTailwind, useBreakpoints, useElementSize } from "@vueuse/core"
|
||||||
import type { Slot } from "vue"
|
import type { Slot } from "vue"
|
||||||
import draggable from "vuedraggable-es"
|
import draggable from "vuedraggable-es"
|
||||||
import { HoppUIPluginOptions, HOPP_UI_OPTIONS } from "./../../index"
|
import { HoppUIPluginOptions, HOPP_UI_OPTIONS } from "./../../index"
|
||||||
@@ -164,6 +167,9 @@ export type TabProvider = {
|
|||||||
removeTabEntry: (tabID: string) => void
|
removeTabEntry: (tabID: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||||
|
const mdAndLarger = breakpoints.greater("md")
|
||||||
|
|
||||||
const { t } = inject<HoppUIPluginOptions>(HOPP_UI_OPTIONS) ?? {}
|
const { t } = inject<HoppUIPluginOptions>(HOPP_UI_OPTIONS) ?? {}
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -191,6 +197,12 @@ const emit = defineEmits<{
|
|||||||
(e: "addTab"): void
|
(e: "addTab"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
|
const hasActions = computed(() => {
|
||||||
|
return !!slots.actions
|
||||||
|
})
|
||||||
|
|
||||||
const throwError = (message: string): never => {
|
const throwError = (message: string): never => {
|
||||||
throw new Error(message)
|
throw new Error(message)
|
||||||
}
|
}
|
||||||
|
|||||||
541
pnpm-lock.yaml
generated
541
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user