mirror of
https://gitee.com/eolink_admin/postcat.git
synced 2024-11-29 18:28:09 +08:00
Feat/v0.5.0 - API Case (#279)
* fix(api-test): Import request headers could not be sent * fix: notification alway show in safari * types: add comment * build: update iconpark * refactor: use decoration for shortcut * feat: delete useless code * wip: afterTabActivated * refactor: tab logic * refactor: remove initial model logic * refactor: api edit remove inittimes * refactor: tab execute async request * refactor: edit api * refactor: rename all EoNgFeedbackMessageService to feedback * refactor: remove collection type logic * refactor: add type inherited * feat: remove tab when delete group resource * wip: delete useless * chore: deplywindows config * feat: mock and new people guide develop * fix(import-api): schema form * refactor: api dto * feat: add group mock * fix: storage tab content error * feat: mock & markdown & steps & action develop * feat: tabs route * wip: remove useless * feat: mock & markdown & steps & action develop * feat: mock & markdown & steps & action develop * feat: markdown-loader * feat: mock develop * fix: mock dir error * feat: mock develop * fix: tab refresh error * feat: mock develop * feat: mock joint debugging * feat: change initial api data * fix: get id error * feat: mock develop * feat: add mock url at local indexeddb * fix: isForm chagne detect api page error * feat: mock develop * refactor: all api operate * fix: delete tab not close tab * feat: mock develop * feat: guide develop * feat: add case * feat: mock develop and action develop * feat: mock develop and action develop * feat: detail/delete * feat: group and case fit * feat(api-case): save name and save case * feat: translate * wip: delete useless * feat: mock develop and action develop * fix: test page height * feat: mock develop and guide develop * feat: mock develop and guide develop * refactor(mock): delete useless code * fix: new api error * chore: change build * feat: mock develop and guide develop * fix: delete case error * fix: local case some problem * test(e2e): fixed some case * fix: restore from test page lack of params * fix: resolve bug * fix: resolve bug * fix: resolve bug * feat: change dynamic mock to system mock * fix: delete case no tips * fix: resolve bug * feat: add local test tips * fix: resolve bug * fix: case remote error * fix: resolve bug * fix: resolve bug * fix: group tree shrink problem * fix: add case error * fix: resolve bug * fix: local case edit error * wip: close update log * test: add e2e * fix(new-pie): lack of image * feat: mock and case can't sort * test(e2e): test api& edit api * refactor: remove param.example logic * feat: translate about * feat: import as curl * feat: add scriptList at edit page * feat: translate * fix: open case error * fix: import postcat error * fix: case name lead to page overflow * fix: offline can't install extension * fix: share document read case --------- Co-authored-by: sunzhouyang <sunzhouyang@eolink.com>
This commit is contained in:
parent
fa99f30a25
commit
859c61dddc
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
|
@ -144,9 +144,11 @@ yarn add @angular/cli --global
|
||||
|命令 |描述 |
|
||||
| ------------ | ------------ |
|
||||
|yarn start |开发模式下,同时运行在浏览器和桌面端 |
|
||||
|yarn start:zh|中文开发模式,同时运行在浏览器和桌面端|
|
||||
|yarn start:web |仅运行在浏览器,同时开启后端代理 |
|
||||
|yarn start:electron|仅运行在桌面端 |
|
||||
|
||||
> 本项目 i18n 使用的是编译手段,所以开发时无法切换语言
|
||||
### 打包构建
|
||||
|
||||
|命令 |描述 |
|
||||
|
@ -1,4 +1,4 @@
|
||||
const baseUrl = './src/browser/src/app/shared/services/storage/';
|
||||
const baseUrl = './src/browser/src/app/services/storage/';
|
||||
module.exports = {
|
||||
entry: {
|
||||
// target: ["./test/apiData.ts", "./test/env.ts"]
|
||||
|
14771
package-lock.json
generated
Normal file
14771
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "postcat",
|
||||
"version": "0.4.2",
|
||||
"version": "0.5.0",
|
||||
"main": "out/app/electron-main/main.js",
|
||||
"description": "A lightweight, extensible API tool",
|
||||
"homepage": "https://github.com/Postcatlab/postcat.git",
|
||||
@ -17,13 +17,14 @@
|
||||
"start:web": "yarn workspace postcat-web run start",
|
||||
"start:web:zh": "yarn workspace postcat-web run start:zh",
|
||||
"start:electron": "npm-run-all -p web:start:direct electron:dev",
|
||||
"build": "npx patch-package && npm-run-all -s electron:build:web clear:electron:tsc electron:tsc && npx esno scripts/build.ts",
|
||||
"build": "npx patch-package && npm-run-all -s electron:build:browser clear:electron:tsc electron:tsc && npx esno scripts/build.ts",
|
||||
"build:web": "yarn workspace postcat-web run build:web",
|
||||
"build:browser": "yarn workspace postcat-web run build",
|
||||
"build:static": "npm run clear:electron:tsc&&npm run electron:tsc && npx esno scripts/build.ts",
|
||||
"build:win:noSign": "npm run clear:electron:tsc&&npm run electron:tsc && npx esno scripts/buildNoSign.ts",
|
||||
"electron:build:web": "yarn workspace postcat-web run build",
|
||||
"electron:build:browser": "yarn workspace postcat-web run build",
|
||||
"electron:static": "npm run electron:tsc && electron .",
|
||||
"release": "npm-run-all -s electron:build:web electron:tsc && npx esno scripts/build.ts --publish=always && node scripts/upload.js",
|
||||
"release": "npm-run-all -s electron:build:browser electron:tsc && npx esno scripts/build.ts --publish=always && node scripts/upload.js",
|
||||
"test": "npm-run-all --serial test:*",
|
||||
"e2e": "cd test/e2e&&npx playwright test -c playwright.config.ts",
|
||||
"test:e2e": "yarn e2e",
|
||||
@ -52,6 +53,7 @@
|
||||
"electron-updater": "^5.3.0",
|
||||
"express": "4.18.1",
|
||||
"fix-path": "3.0.0",
|
||||
"html-loader": "4.2.0",
|
||||
"http-server": "14.1.1",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"jquery": "3.6.1",
|
||||
@ -61,17 +63,19 @@
|
||||
"npm": "6.14.17",
|
||||
"pm2": "5.2.2",
|
||||
"portfinder": "1.0.32",
|
||||
"postman-sandbox": "^4.2.3",
|
||||
"qiniu": "^6.0.0",
|
||||
"resolve": "^1.22.1",
|
||||
"showdown": "2.1.0",
|
||||
"socket.io": "4.5.4",
|
||||
"ws": "8.12.0",
|
||||
"xml2js": "0.4.23",
|
||||
"yaml": "2.2.1",
|
||||
"postman-sandbox": "^4.2.3"
|
||||
"yaml": "2.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "~17.3.0",
|
||||
"@commitlint/config-conventional": "~17.3.0",
|
||||
"@playwright/test": "1.32.1",
|
||||
"@types/node": "18.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
@ -133,4 +137,4 @@
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,8 +52,7 @@ function hashFile(file: string, algorithm = 'sha512', encoding: 'base64' | 'hex'
|
||||
.pipe(hash, { end: false });
|
||||
});
|
||||
}
|
||||
|
||||
const config: Configuration = {
|
||||
export const ELECTRON_BUILD_CONFIG: Configuration = {
|
||||
appId: '.postcat.io',
|
||||
productName: 'Postcat',
|
||||
asar: true,
|
||||
@ -95,6 +94,34 @@ const config: Configuration = {
|
||||
schemes: ['eoapi']
|
||||
}
|
||||
],
|
||||
portable: {
|
||||
splashImage: 'src/app/common/images/postcat.bmp'
|
||||
},
|
||||
dmg: {
|
||||
sign: false
|
||||
},
|
||||
afterSign: 'scripts/notarize.js',
|
||||
linux: {
|
||||
icon: 'src/app/common/images/',
|
||||
target: ['AppImage']
|
||||
},
|
||||
mac: {
|
||||
icon: 'src/app/common/images/512x512.png',
|
||||
hardenedRuntime: true,
|
||||
category: 'public.app-category.productivity',
|
||||
gatekeeperAssess: false,
|
||||
entitlements: 'scripts/entitlements.mac.plist',
|
||||
entitlementsInherit: 'scripts/entitlements.mac.plist',
|
||||
target: [
|
||||
{
|
||||
target: 'default',
|
||||
arch: ['x64', 'arm64']
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
const config: Configuration = {
|
||||
...ELECTRON_BUILD_CONFIG,
|
||||
win: {
|
||||
icon: 'src/app/common/images/logo.ico',
|
||||
verifyUpdateCodeSignature: false,
|
||||
@ -108,32 +135,6 @@ const config: Configuration = {
|
||||
signOptions = [configuration, packager!];
|
||||
return doSign(configuration, packager!);
|
||||
}
|
||||
},
|
||||
portable: {
|
||||
splashImage: 'src/app/common/images/postcat.bmp'
|
||||
},
|
||||
mac: {
|
||||
icon: 'src/app/common/images/512x512.png',
|
||||
hardenedRuntime: true,
|
||||
category: 'public.app-category.productivity',
|
||||
gatekeeperAssess: false,
|
||||
entitlements: 'scripts/entitlements.mac.plist',
|
||||
entitlementsInherit: 'scripts/entitlements.mac.plist',
|
||||
// target: ['dmg', 'zip']
|
||||
target: [
|
||||
{
|
||||
target: 'default',
|
||||
arch: ['x64', 'arm64']
|
||||
}
|
||||
]
|
||||
},
|
||||
dmg: {
|
||||
sign: false
|
||||
},
|
||||
afterSign: 'scripts/notarize.js',
|
||||
linux: {
|
||||
icon: 'src/app/common/images/',
|
||||
target: ['AppImage']
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -5,11 +5,10 @@ import minimist from 'minimist';
|
||||
import YAML from 'yaml';
|
||||
|
||||
import pkgInfo from '../package.json';
|
||||
import { ELETRON_APP_CONFIG } from '../src/environment';
|
||||
import { ELECTRON_BUILD_CONFIG } from './build';
|
||||
|
||||
import { execSync, exec, spawn } from 'node:child_process';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { copyFileSync, createReadStream, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { exec, spawn } from 'node:child_process';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import path, { resolve } from 'node:path';
|
||||
import { exit, platform } from 'node:process';
|
||||
|
||||
@ -39,81 +38,10 @@ if (process.platform === 'win32') {
|
||||
}
|
||||
|
||||
const config: Configuration = {
|
||||
appId: '.postcat.io',
|
||||
productName: 'Postcat',
|
||||
asar: true,
|
||||
directories: {
|
||||
output: 'release/'
|
||||
},
|
||||
files: [
|
||||
'out/app/**/*.js*',
|
||||
'out/platform/**/*.js*',
|
||||
'out/environment.js',
|
||||
'out/shared/**/*.js*',
|
||||
'src/browser/dist/**/*',
|
||||
'out/browser/src/**/*.js*',
|
||||
'out/node/test-server/**/*.js*',
|
||||
'out/app/common/**/*',
|
||||
'!**/*.ts'
|
||||
],
|
||||
publish: [
|
||||
'github',
|
||||
{
|
||||
provider: 'generic',
|
||||
url: ELETRON_APP_CONFIG.BASE_DOWNLOAD_URL
|
||||
}
|
||||
],
|
||||
generateUpdatesFilesForAllChannels: true,
|
||||
nsis: {
|
||||
guid: 'Postcat',
|
||||
oneClick: false,
|
||||
allowElevation: true,
|
||||
allowToChangeInstallationDirectory: true,
|
||||
// for win - 将协议写入主机的脚本
|
||||
include: 'scripts/urlProtoco.nsh'
|
||||
},
|
||||
protocols: [
|
||||
// for macOS - 用于在主机注册指定协议
|
||||
{
|
||||
name: 'eoapi',
|
||||
schemes: ['eoapi']
|
||||
}
|
||||
],
|
||||
...ELECTRON_BUILD_CONFIG,
|
||||
win: {
|
||||
icon: 'src/app/common/images/logo.ico',
|
||||
target: ['nsis', 'portable']
|
||||
// extraFiles: [
|
||||
// {
|
||||
// from: './build/Uninstall Postcat.exe',
|
||||
// to: '.'
|
||||
// }
|
||||
// ]
|
||||
},
|
||||
portable: {
|
||||
splashImage: 'src/app/common/images/postcat.bmp'
|
||||
},
|
||||
mac: {
|
||||
icon: 'src/app/common/images/512x512.png',
|
||||
hardenedRuntime: true,
|
||||
category: 'public.app-category.productivity',
|
||||
gatekeeperAssess: false,
|
||||
entitlements: 'scripts/entitlements.mac.plist',
|
||||
entitlementsInherit: 'scripts/entitlements.mac.plist',
|
||||
// target: ['dmg', 'zip']
|
||||
target: [
|
||||
{
|
||||
target: 'default',
|
||||
arch: ['x64', 'arm64']
|
||||
}
|
||||
]
|
||||
},
|
||||
dmg: {
|
||||
sign: false
|
||||
},
|
||||
afterSign: 'scripts/notarize.js',
|
||||
linux: {
|
||||
icon: 'src/app/common/images/',
|
||||
target: ['AppImage']
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
const { Client } = require('ssh2');
|
||||
|
||||
const conn = new Client();
|
||||
const originNodeVersion = '12.22.10';
|
||||
const nodeVersion = '16.19.1';
|
||||
conn
|
||||
.on('ready', () => {
|
||||
console.log('Client :: ready');
|
||||
@ -28,8 +30,8 @@ conn
|
||||
'git reset --hard',
|
||||
'git checkout build/windows',
|
||||
...Array.from({ length: 5 }).map(_ => 'git pull'),
|
||||
'nvm install 16.19.1',
|
||||
'nvm use 16.19.1',
|
||||
`nvm install ${nodeVersion}`,
|
||||
`nvm use ${nodeVersion}`,
|
||||
`
|
||||
cat>./scripts/qiniu_env.js<<EOF
|
||||
${process.env.QINIU_ENV_JS}
|
||||
@ -37,7 +39,7 @@ EOF
|
||||
`,
|
||||
'yarn install',
|
||||
'yarn release',
|
||||
'nvm use 12.22.10',
|
||||
`nvm use ${originNodeVersion}`,
|
||||
'echo Windows 打包发布完成!'
|
||||
].join('\r\n')
|
||||
);
|
||||
|
@ -4,7 +4,6 @@ import Store from 'electron-store';
|
||||
import { LanguageService } from 'pc/app/electron-main/language.service';
|
||||
import { MockServer } from 'pc/platform/node/mock-server';
|
||||
import {
|
||||
GET_EXT_TABS,
|
||||
GET_FEATURE,
|
||||
GET_MOCK_URL,
|
||||
GET_MODULE,
|
||||
@ -229,8 +228,6 @@ try {
|
||||
|
||||
const getWebsocketPort = () => Promise.resolve(websocketPort);
|
||||
|
||||
const getExtTabs = arg => Promise.resolve(moduleManager.getExtTabs(arg.data.extName));
|
||||
|
||||
const loginWith = arg => {
|
||||
if (loginWindow) {
|
||||
loginWindow.destroy();
|
||||
@ -278,7 +275,6 @@ try {
|
||||
[GET_FEATURE]: getFeature,
|
||||
[GET_MOCK_URL]: getMockUrl,
|
||||
[GET_WEBSOCKET_PORT]: getWebsocketPort,
|
||||
[GET_EXT_TABS]: getExtTabs,
|
||||
// * It is eletron, open a new window for login
|
||||
[LOGIN_WITH]: loginWith,
|
||||
[GET_SIDEBAR_VIEW]: getSidebarView,
|
||||
|
1
src/browser/.gitignore
vendored
1
src/browser/.gitignore
vendored
@ -7,6 +7,7 @@ dist/
|
||||
/app-builds
|
||||
/release
|
||||
src/**/*.js
|
||||
!markdown-loader.js
|
||||
!scripts/*.js
|
||||
!src/karma.conf.js
|
||||
!src/assets/font/*.js
|
||||
|
@ -119,7 +119,7 @@
|
||||
},
|
||||
"defaultProject": "postcat",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": { "prefix": "pc", "style": "scss", "inlineStyle": true, "inlineTemplate": true },
|
||||
"@schematics/angular:component": { "prefix": "pc", "style": "scss", "inlineStyle": false, "inlineTemplate": true },
|
||||
"@schematics/angular:directive": { "prefix": "pc" }
|
||||
}
|
||||
}
|
||||
|
@ -50,12 +50,19 @@ module.exports = (config, options) => {
|
||||
type: 'asset/resource',
|
||||
resourceQuery: { not: [/\?ngResource/] }
|
||||
},
|
||||
{
|
||||
// .md结尾的文件使用markdown-loader规则
|
||||
test: /\.md$/,
|
||||
use: ['html-loader', './markdown-loader']
|
||||
},
|
||||
...config.module.rules
|
||||
];
|
||||
|
||||
config.experiments = {
|
||||
topLevelAwait: true
|
||||
};
|
||||
Object.assign(config, {
|
||||
experiments: {
|
||||
topLevelAwait: true
|
||||
}
|
||||
});
|
||||
|
||||
// console.log('config', config.module.rules);
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
7
src/browser/markdown-loader.js
Normal file
7
src/browser/markdown-loader.js
Normal file
@ -0,0 +1,7 @@
|
||||
const markdownIt = require('markdown-it');
|
||||
|
||||
module.exports = source => {
|
||||
let md = new markdownIt();
|
||||
const html = md.render(source);
|
||||
return html;
|
||||
};
|
@ -38,6 +38,7 @@
|
||||
"@xmagic/ngx-wujie": "1.0.0-rc.20",
|
||||
"ajv": "8.12.0",
|
||||
"color": "^4.2.3",
|
||||
"compare-versions": "6.0.0-rc.1",
|
||||
"core-js": "3.27.2",
|
||||
"eo-ng-auto-complete": "0.1.11",
|
||||
"eo-ng-button": "0.1.10",
|
||||
@ -62,8 +63,10 @@
|
||||
"monaco-editor": "0.33.0",
|
||||
"ng-zorro-antd": "15.0.3",
|
||||
"omit-deep-lodash": "1.1.7",
|
||||
"parse-multipart-data": "1.5.0",
|
||||
"qs": "6.11.0",
|
||||
"rxjs": "7.8.0",
|
||||
"shellwords-ts": "3.0.1",
|
||||
"socket.io-client": "4.5.4",
|
||||
"tslib": "^2.5.0",
|
||||
"wujie": "1.0.6",
|
||||
@ -82,6 +85,7 @@
|
||||
"@angular/localize": "15.1.3",
|
||||
"@types/color": "3.0.3",
|
||||
"@types/jasmine": "4.3.1",
|
||||
"@types/parse-multipart": "1.0.0",
|
||||
"@types/jasminewd2": "2.0.10",
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@types/markdown-it": "12.2.3",
|
||||
|
@ -15,8 +15,6 @@ import { LanguageService } from 'pc/browser/src/app/core/services/language/langu
|
||||
import { NotificationService } from 'pc/browser/src/app/core/services/notification.service';
|
||||
import { ExtensionService } from 'pc/browser/src/app/services/extensions/extension.service';
|
||||
import { GlobalProvider } from 'pc/browser/src/app/services/globalProvider';
|
||||
import { IndexedDBStorage } from 'pc/browser/src/app/services/storage/IndexedDB/lib';
|
||||
import { HttpStorage } from 'pc/browser/src/app/services/storage/http/lib';
|
||||
import { BaseUrlInterceptor } from 'pc/browser/src/app/services/storage/http/lib/baseUrl.service';
|
||||
import { APP_CONFIG } from 'pc/browser/src/environments/environment';
|
||||
|
||||
@ -46,8 +44,6 @@ registerLocaleData(zh);
|
||||
providers: [
|
||||
MockService,
|
||||
ExtensionService,
|
||||
IndexedDBStorage,
|
||||
HttpStorage,
|
||||
ThemeService,
|
||||
{ provide: ErrorHandler, useClass: GlobalErrorHandler },
|
||||
{
|
||||
|
@ -1,9 +1,16 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { EoNgButtonModule } from 'eo-ng-button';
|
||||
import { EoNgDropdownModule } from 'eo-ng-dropdown';
|
||||
import { SharedModule } from 'pc/browser/src/app/shared/shared.module';
|
||||
|
||||
import { ElectronService, WebService } from '../../core/services';
|
||||
import { EoIconparkIconModule } from '../eo-ui/iconpark-icon/eo-iconpark-icon.module';
|
||||
|
||||
@Component({
|
||||
selector: 'pc-download-client',
|
||||
standalone: true,
|
||||
imports: [CommonModule, EoIconparkIconModule, SharedModule, EoNgButtonModule, EoNgDropdownModule],
|
||||
template: `<ng-container *ngIf="!electron.isElectron">
|
||||
<button *ngIf="btnType === 'icon'" eo-ng-button nzType="text" eo-ng-dropdown [nzDropdownMenu]="download">
|
||||
<eo-iconpark-icon name="download" size="14px"></eo-iconpark-icon>
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { EoNgButtonModule } from 'eo-ng-button';
|
||||
import { EoNgDropdownModule } from 'eo-ng-dropdown';
|
||||
import { SharedModule } from 'pc/browser/src/app/shared/shared.module';
|
||||
|
||||
import { EoIconparkIconModule } from '../eo-ui/iconpark-icon/eo-iconpark-icon.module';
|
||||
import { DownloadClientComponent } from './download-client.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, EoIconparkIconModule, SharedModule, EoNgButtonModule, EoNgDropdownModule],
|
||||
declarations: [DownloadClientComponent],
|
||||
exports: [DownloadClientComponent]
|
||||
})
|
||||
export class DownloadClientModule {}
|
@ -112,7 +112,7 @@ export class EoMonacoEditorComponent implements AfterViewInit, OnInit, OnChanges
|
||||
}
|
||||
|
||||
constructor(
|
||||
private message: EoNgFeedbackMessageService,
|
||||
private feedback: EoNgFeedbackMessageService,
|
||||
private electron: ElectronService,
|
||||
private theme: ThemeService,
|
||||
elementRef: ElementRef
|
||||
@ -299,7 +299,7 @@ export class EoMonacoEditorComponent implements AfterViewInit, OnInit, OnChanges
|
||||
const value = this.codeEdtor.getValue();
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(value);
|
||||
this.message.success($localize`Copied`);
|
||||
this.feedback.success($localize`Copied`);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
@ -39,7 +39,7 @@ export class TabOperateService {
|
||||
private tabStorage: TabStorageService,
|
||||
private messageService: MessageService,
|
||||
private router: Router,
|
||||
private message: EoNgFeedbackMessageService
|
||||
private feedback: EoNgFeedbackMessageService
|
||||
) {}
|
||||
//Init tab info
|
||||
//Maybe from tab cache info or router url
|
||||
@ -50,7 +50,6 @@ export class TabOperateService {
|
||||
: this.tabStorage.getPersistenceStorage({
|
||||
handleDataBeforeGetCache: inArg.handleDataBeforeGetCache
|
||||
});
|
||||
|
||||
//parse result for router change
|
||||
const tabCache = this.filterValidTab(tabStorage);
|
||||
const validTabItem = this.generateTabFromUrl(this.router.url);
|
||||
@ -140,10 +139,14 @@ export class TabOperateService {
|
||||
* */
|
||||
batchClose(ids) {
|
||||
const tabOrder = this.tabStorage.tabOrder.filter(uuid => !ids.includes(uuid));
|
||||
this.tabStorage.resetTabsByOrdr(tabOrder);
|
||||
this.tabStorage.resetTabsByOrder(tabOrder);
|
||||
if (this.tabStorage.tabOrder.length === 0) {
|
||||
this.newDefaultTab();
|
||||
return;
|
||||
}
|
||||
|
||||
//Update childView
|
||||
this.navigateByTab(this.getCurrentTab());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,8 +158,11 @@ export class TabOperateService {
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
const queryParams = { pageID: tab.params?.pageID, ...tab.params };
|
||||
if (!queryParams.pageID) Reflect.deleteProperty(queryParams, 'pageID');
|
||||
const queryParams = { ...tab.params };
|
||||
//Reset params ID
|
||||
if (tab.params?.pageID) {
|
||||
queryParams.pageID = tab.params.pageID;
|
||||
}
|
||||
this.router.navigate([tab.pathname], {
|
||||
queryParams
|
||||
});
|
||||
@ -168,31 +174,29 @@ export class TabOperateService {
|
||||
*
|
||||
* @param tab
|
||||
*/
|
||||
getSameTab(
|
||||
tab: Partial<TabItem>,
|
||||
opts: {
|
||||
match: 'all' | 'uuid';
|
||||
} = { match: 'all' }
|
||||
): TabItem | null {
|
||||
getSameTab(tab: Partial<TabItem>): TabItem | null {
|
||||
let result = null;
|
||||
if (!tab.params.uuid) {
|
||||
const sameTabIDTab = this.tabStorage.tabsByID.get(tab.uuid);
|
||||
if (sameTabIDTab && sameTabIDTab.pathname === tab.pathname) {
|
||||
return sameTabIDTab;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
//Get exist params.uuid content tab,same pathname and uuid match
|
||||
const mapObj = Object.fromEntries(this.tabStorage.tabsByID);
|
||||
for (const key in mapObj) {
|
||||
if (Object.prototype.hasOwnProperty.call(mapObj, key)) {
|
||||
const tabInfo = mapObj[key];
|
||||
if (tabInfo.pathname !== tab.pathname && opts.match === 'all') continue;
|
||||
if (tabInfo.params.uuid === tab.params.uuid) {
|
||||
result = tabInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//Uuid match first
|
||||
// if (tab.params.uuid) {
|
||||
// //Get exist params.uuid content tab,same pathname and uuid match
|
||||
// const mapObj = Object.fromEntries(this.tabStorage.tabsByID);
|
||||
// for (const key in mapObj) {
|
||||
// if (Object.prototype.hasOwnProperty.call(mapObj, key)) {
|
||||
// const tabInfo = mapObj[key];
|
||||
// if (tabInfo.pathname !== tab.pathname) continue;
|
||||
// if (tabInfo.params.uuid === tab.params.uuid) {
|
||||
// result = tabInfo;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
//PageID match second
|
||||
const sameTabIDTab = this.tabStorage.tabsByID.get(tab.uuid);
|
||||
if (sameTabIDTab && sameTabIDTab.pathname === tab.pathname) {
|
||||
return sameTabIDTab;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -280,10 +284,14 @@ export class TabOperateService {
|
||||
*/
|
||||
if (existTab) {
|
||||
this.selectedIndex = this.tabStorage.tabOrder.findIndex(uuid => uuid === existTab.uuid);
|
||||
//* Update tab info,maybe params changed
|
||||
//!Get newest tab content,If the initialization is too fast, the baseContent content will be overwritten here
|
||||
const newestData = this.getSameTab(routeTab);
|
||||
|
||||
//Reload childView when reselected it
|
||||
this.updateChildView();
|
||||
|
||||
//* Update tab info,maybe params changed
|
||||
this.tabStorage.tabsByID.set(existTab.uuid, { ...existTab, params: { ...existTab.params, ...nextTab.params } });
|
||||
this.tabStorage.setTabByID({ ...newestData, params: { ...newestData.params, ...nextTab.params } });
|
||||
return;
|
||||
}
|
||||
//!Same params.uuid can only open one Tab
|
||||
@ -360,10 +368,10 @@ export class TabOperateService {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.tabStorage.resetTabsByOrdr(tabsObj.left);
|
||||
this.tabStorage.resetTabsByOrder(tabsObj.left);
|
||||
this.selectedIndex = tabsObj.selectedIndex;
|
||||
if (tabsObj.needTips) {
|
||||
this.message.warning($localize`Program will not close unsaved tabs`);
|
||||
this.feedback.warning($localize`Program will not close unsaved tabs`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -377,16 +385,16 @@ export class TabOperateService {
|
||||
for (const key in mapObj) {
|
||||
if (Object.prototype.hasOwnProperty.call(mapObj, key)) {
|
||||
const tab = mapObj[key];
|
||||
if (tab.params.uuid && tab.params.uuid === inTab.params.uuid) {
|
||||
const mergeTab = this.preventBlankTab(tab, inTab);
|
||||
mergeTab.content = tab.content;
|
||||
mergeTab.baseContent = tab.baseContent;
|
||||
mergeTab.extends = Object.assign(mergeTab.extends || {}, tab.extends);
|
||||
this.selectedIndex = this.tabStorage.tabOrder.findIndex(uuid => uuid === tab.uuid);
|
||||
this.tabStorage.updateTab(this.selectedIndex, mergeTab);
|
||||
this.updateChildView();
|
||||
return true;
|
||||
}
|
||||
if (tab.params?.uuid !== inTab.params.uuid) continue;
|
||||
|
||||
const mergeTab = this.preventBlankTab(tab, inTab);
|
||||
mergeTab.content = tab.content;
|
||||
mergeTab.baseContent = tab.baseContent;
|
||||
mergeTab.extends = Object.assign(mergeTab.extends || {}, tab.extends);
|
||||
this.selectedIndex = this.tabStorage.tabOrder.findIndex(uuid => uuid === tab.uuid);
|
||||
this.tabStorage.updateTab(this.selectedIndex, mergeTab);
|
||||
this.updateChildView();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -409,7 +417,7 @@ export class TabOperateService {
|
||||
if (keepID && uuid === keepID) {
|
||||
return true;
|
||||
}
|
||||
if (this.tabStorage.tabsByID.get(uuid).hasChanged) {
|
||||
if (this.tabStorage.tabsByID.get(uuid)?.hasChanged) {
|
||||
tabsObj.needTips = true;
|
||||
return true;
|
||||
}
|
||||
@ -445,7 +453,7 @@ export class TabOperateService {
|
||||
return result;
|
||||
}
|
||||
private updateChildView() {
|
||||
this.messageService.send({ type: 'tabContentInit', data: {} });
|
||||
this.messageService.send({ type: 'tabContentInit', data: { uuid: this.getCurrentTab()?.uuid } });
|
||||
}
|
||||
/**
|
||||
* Get valid tab item
|
||||
@ -463,9 +471,9 @@ export class TabOperateService {
|
||||
if (!tabItem) {
|
||||
return false;
|
||||
}
|
||||
const validTab = this.BASIC_TABS.find(val => val.id === tabItem.id);
|
||||
const validTab = this.BASIC_TABS.find(val => val.uniqueName === tabItem.uniqueName);
|
||||
if (!validTab) {
|
||||
delete cache.tabsByID[id];
|
||||
Reflect.deleteProperty(cache.tabsByID, id);
|
||||
} else {
|
||||
tabItem.pathname = validTab.pathname;
|
||||
}
|
||||
|
@ -19,18 +19,21 @@ export class TabStorageService {
|
||||
this.tabStorageKey = inArg.tabStorageKey;
|
||||
}
|
||||
addTab(tabItem) {
|
||||
if (this.tabsByID.has(tabItem.uuid)) {
|
||||
if (this.tabsByID.has(tabItem.uuid) && this.tabOrder.some(uuid => uuid === tabItem.uuid)) {
|
||||
throw new Error(`EO_ERROR: can't add same id tab`);
|
||||
}
|
||||
this.tabOrder.push(tabItem.uuid);
|
||||
this.tabsByID.set(tabItem.uuid, tabItem);
|
||||
this.setTabByID(tabItem);
|
||||
}
|
||||
updateTab(index, tabItem) {
|
||||
this.tabsByID.delete(this.tabOrder[index]);
|
||||
this.tabOrder[index] = tabItem.uuid;
|
||||
this.setTabByID(tabItem);
|
||||
}
|
||||
setTabByID(tabItem) {
|
||||
this.tabsByID.set(tabItem.uuid, tabItem);
|
||||
}
|
||||
resetTabsByOrdr(order) {
|
||||
resetTabsByOrder(order) {
|
||||
const tabs = new Map();
|
||||
this.tabsByID.forEach((value, key) => {
|
||||
if (!order.includes(key)) {
|
||||
@ -55,7 +58,8 @@ export class TabStorageService {
|
||||
setPersistenceStorage(selectedIndex, opts) {
|
||||
let tabsByID = Object.fromEntries(this.tabsByID);
|
||||
Object.values(tabsByID).forEach(val => {
|
||||
if (val.type === 'preview') {
|
||||
//Remove cache when no change
|
||||
if (val.type === 'preview' || (val.type === 'edit' && !val.hasChanged)) {
|
||||
['baseContent', 'content'].forEach(keyName => {
|
||||
val[keyName] = null;
|
||||
});
|
||||
|
@ -1,4 +1,3 @@
|
||||
<!-- {{ getConsoleTabs() | json }} -->
|
||||
<eo-ng-tabset
|
||||
[(nzSelectedIndex)]="tabOperate.selectedIndex"
|
||||
nzType="editable-card"
|
||||
@ -89,4 +88,5 @@
|
||||
</ul>
|
||||
</eo-ng-dropdown-menu>
|
||||
</div>
|
||||
<!-- {{ getConsoleTabs() }} -->
|
||||
</ng-template>
|
||||
|
@ -7,7 +7,7 @@ import { TabOperateService } from 'pc/browser/src/app/components/eo-ui/tab/tab-o
|
||||
import { TabStorageService } from 'pc/browser/src/app/components/eo-ui/tab/tab-storage.service';
|
||||
import { TabItem, TabOperate } from 'pc/browser/src/app/components/eo-ui/tab/tab.model';
|
||||
import { TraceService } from 'pc/browser/src/app/services/trace.service';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import { filter, Subscription } from 'rxjs';
|
||||
|
||||
import { ModalService } from '../../../services/modal.service';
|
||||
@ -85,7 +85,7 @@ export class EoTabComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
$event.stopPropagation();
|
||||
if (!tab.hasChanged) {
|
||||
if (!tab?.hasChanged) {
|
||||
this.tabOperate.closeTab(index);
|
||||
return;
|
||||
}
|
||||
@ -132,6 +132,7 @@ export class EoTabComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
tabs.push({
|
||||
baseContent: tab.baseContent,
|
||||
uuid: tab.uuid,
|
||||
type: tab.type,
|
||||
title: tab.title,
|
||||
@ -139,7 +140,6 @@ export class EoTabComponent implements OnInit, OnDestroy {
|
||||
params: tab.params
|
||||
});
|
||||
});
|
||||
console.log(tabs);
|
||||
return tabs;
|
||||
}
|
||||
getTabs() {
|
||||
@ -148,17 +148,22 @@ export class EoTabComponent implements OnInit, OnDestroy {
|
||||
return tabs;
|
||||
}
|
||||
/**
|
||||
* Get tab by url with same content
|
||||
* Get tab by tab id
|
||||
*
|
||||
* @param url
|
||||
* @param uuid
|
||||
*/
|
||||
getTabByID(uuid: TabItem['uuid']) {
|
||||
return this.tabStorage.tabsByID.get(uuid);
|
||||
}
|
||||
/**
|
||||
* Get Tab id by child component resource id
|
||||
*
|
||||
* @param uuid queryparams uuid
|
||||
* @returns
|
||||
*/
|
||||
getExistTabByUrl(url: string): TabItem | null {
|
||||
const existTab = this.tabOperate.getSameTab(this.tabOperate.getBasicInfoFromUrl(url));
|
||||
if (!existTab) {
|
||||
return null;
|
||||
}
|
||||
return existTab;
|
||||
getTabByParamsID(uuid: TabItem['params']['uuid']) {
|
||||
const tabID = this.tabStorage.tabOrder.find(tabID => this.tabStorage.tabsByID.get(tabID)?.params?.uuid === uuid);
|
||||
return this.tabStorage.tabsByID.get(tabID);
|
||||
}
|
||||
getCurrentTab() {
|
||||
return this.tabOperate.getCurrentTab();
|
||||
@ -169,14 +174,14 @@ export class EoTabComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* update tab
|
||||
*
|
||||
* @param url when url exist in tabs,replace
|
||||
* @param uuid tab uuid
|
||||
* @param tabItem
|
||||
* @returns
|
||||
*/
|
||||
updatePartialTab(url: string, tabItem: Partial<TabItem>) {
|
||||
const existTab = this.getExistTabByUrl(url);
|
||||
updatePartialTab(uuid: string | number, tabItem: Partial<TabItem>) {
|
||||
const existTab = this.getTabByID(uuid);
|
||||
if (!existTab) {
|
||||
pcConsole.error(`:updatePartialTab fail,can't find exist tab to fixed url:${url}`);
|
||||
pcConsole.error(`:updatePartialTab fail,can't find exist tab to fixed uuid:${uuid}`);
|
||||
return;
|
||||
}
|
||||
const index = this.tabStorage.tabOrder.findIndex(uuid => uuid === existTab.uuid);
|
||||
@ -185,6 +190,7 @@ export class EoTabComponent implements OnInit, OnDestroy {
|
||||
...tabItem,
|
||||
extends: { ...existTab.extends, ...tabItem.extends }
|
||||
});
|
||||
// console.log('updatePartialTabSuccess', this.tabStorage.tabsByID.get(uuid));
|
||||
}
|
||||
/**
|
||||
* Cache tab header/tabs content for restore when page close or component destroy
|
||||
|
@ -1,51 +1,62 @@
|
||||
import type { EventEmitter } from '@angular/core';
|
||||
import { PageUniqueName } from 'pc/browser/src/app/pages/workspace/project/api/api-tab.service';
|
||||
|
||||
export enum TabOperate {
|
||||
closeOther = 'closeOther',
|
||||
closeAll = 'closeAll',
|
||||
closeLeft = 'closeLeft',
|
||||
closeRight = 'closeRight'
|
||||
closeRight = 'closeRight',
|
||||
forceCloseAll = 'forceCloseAll',
|
||||
forceCloseOther = 'forceCloseOther'
|
||||
}
|
||||
export type storageTab = {
|
||||
selectedIndex: number;
|
||||
tabOrder: number[];
|
||||
tabsByID: { [key: number]: TabItem };
|
||||
};
|
||||
export declare interface TabViewComponent {
|
||||
/**
|
||||
* View Component model
|
||||
* Usually restored model from tab cache
|
||||
*/
|
||||
model?: any;
|
||||
|
||||
/**
|
||||
* Initial model for check form is change
|
||||
*/
|
||||
initialModel?: any;
|
||||
|
||||
declare interface TabViewComponent {
|
||||
/**
|
||||
* Emit view component data has init event for initial tab title data/loading..
|
||||
*/
|
||||
eoOnInit: EventEmitter<any>;
|
||||
|
||||
/**
|
||||
* Emit view component data has been saved
|
||||
* Check the page can leave,if false will not switch tab
|
||||
*/
|
||||
afterSaved?: EventEmitter<any>;
|
||||
checkTabCanLeave?(closeTarget: TabItem): Promise<boolean>;
|
||||
beforeTabClose?(): Promise<any>;
|
||||
}
|
||||
export declare interface PreviewTabViewComponent extends TabViewComponent {}
|
||||
export declare interface EditTabViewComponent extends TabViewComponent {
|
||||
/**
|
||||
* Emit view component data has changed event
|
||||
* View Component model
|
||||
* Usually restored model from tab cache
|
||||
*/
|
||||
modelChange?: EventEmitter<any>;
|
||||
|
||||
model: any;
|
||||
/**
|
||||
* A callback method that performs custom init tab-ui, invoked immediately after tab has initialized.
|
||||
*/
|
||||
init?(): void;
|
||||
afterTabActivated(): void;
|
||||
|
||||
/**
|
||||
* Emit view component data has changed event
|
||||
*/
|
||||
modelChange: EventEmitter<any>;
|
||||
|
||||
//* If tab content can't not be saved,these value can be null
|
||||
/**
|
||||
* Edit page tab judge model has changed
|
||||
*/
|
||||
isFormChange?(): boolean;
|
||||
|
||||
/**
|
||||
* Initial model for check form is change
|
||||
*/
|
||||
initialModel?: any;
|
||||
/**
|
||||
* Emit view component data has been saved
|
||||
*/
|
||||
afterSaved?: EventEmitter<any>;
|
||||
}
|
||||
/**
|
||||
* Tab item.
|
||||
@ -58,12 +69,13 @@ export type TabItem = {
|
||||
/**
|
||||
* Unique id,used for identify content
|
||||
*/
|
||||
id: string;
|
||||
isFixed?: boolean;
|
||||
uniqueName: string | PageUniqueName;
|
||||
/**
|
||||
* If true,will not cache tab content
|
||||
* If the tab is fixed, it will not be replaced by other tab
|
||||
*
|
||||
* You can use double-click to fixed the tab prevent it from being replaced
|
||||
*/
|
||||
disabledCache?: boolean;
|
||||
isFixed?: boolean;
|
||||
/**
|
||||
* Preview page or edit page
|
||||
*/
|
||||
|
@ -130,23 +130,23 @@ export class EoTableProComponent implements OnInit, OnChanges {
|
||||
this.nzData.push(eoDeepCopy(this.nzDataItem));
|
||||
}
|
||||
}
|
||||
const hasQuoteKey = this.columns.some(col => col.key?.includes('.'));
|
||||
if (hasQuoteKey && !this.setting.isEdit) {
|
||||
const chains = this.columns
|
||||
.filter(col => col.key?.includes('.'))
|
||||
.map(val => {
|
||||
const arr = val.key.split('.');
|
||||
const valResult = {
|
||||
arr,
|
||||
str: val.key,
|
||||
name: arr.at(-1)
|
||||
};
|
||||
return valResult;
|
||||
});
|
||||
this.nzData = generateQuoteKeyValue(chains, this.nzData, {
|
||||
childKey: this.tableConfig.childKey
|
||||
});
|
||||
}
|
||||
// const hasQuoteKey = this.columns.some(col => col.key?.includes('.'));
|
||||
// if (hasQuoteKey && !this.setting.isEdit) {
|
||||
// const chains = this.columns
|
||||
// .filter(col => col.key?.includes('.'))
|
||||
// .map(val => {
|
||||
// const arr = val.key.split('.');
|
||||
// const valResult = {
|
||||
// arr,
|
||||
// str: val.key,
|
||||
// name: arr.at(-1)
|
||||
// };
|
||||
// return valResult;
|
||||
// });
|
||||
// this.nzData = generateQuoteKeyValue(chains, this.nzData, {
|
||||
// childKey: this.tableConfig.childKey
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
getPureNzData() {
|
||||
|
@ -1,18 +1,16 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { has } from 'lodash-es';
|
||||
import { ExtensionService } from 'pc/browser/src/app/services/extensions/extension.service';
|
||||
import { Message, MessageService } from 'pc/browser/src/app/services/message';
|
||||
import { MessageService } from 'pc/browser/src/app/services/message';
|
||||
import { ApiService } from 'pc/browser/src/app/services/storage/api.service';
|
||||
import { TraceService } from 'pc/browser/src/app/services/trace.service';
|
||||
import { EXPORT_API } from 'pc/browser/src/app/shared/constans/featureName';
|
||||
import { ExtensionChange } from 'pc/browser/src/app/shared/decorators';
|
||||
import { FeatureInfo } from 'pc/browser/src/app/shared/models/extension-manager';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import StorageUtil from 'pc/browser/src/app/shared/utils/storage/storage.utils';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
// shit angular-cli 配不明白
|
||||
// import { version } from '../../../../../../../../package.json' assert { type: 'json' };
|
||||
import pkgInfo from '../../../../../../../package.json';
|
||||
|
||||
@Component({
|
||||
@ -85,7 +83,7 @@ export class ExportApiComponent implements OnInit {
|
||||
if (data) {
|
||||
console.log('projectExport result', data);
|
||||
try {
|
||||
data.postcatVersion = pkgInfo.version;
|
||||
data.postcat = pkgInfo.version;
|
||||
let output = module[action]({ data: data || {} });
|
||||
//Change format
|
||||
if (has(output, 'status') && output.status === 0) {
|
||||
|
@ -9,7 +9,7 @@ import { TraceService } from 'pc/browser/src/app/services/trace.service';
|
||||
import { IMPORT_API } from 'pc/browser/src/app/shared/constans/featureName';
|
||||
import { ExtensionChange } from 'pc/browser/src/app/shared/decorators';
|
||||
import { FeatureInfo } from 'pc/browser/src/app/shared/models/extension-manager';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@ -67,7 +67,7 @@ export class ImportApiComponent implements OnInit {
|
||||
constructor(
|
||||
private router: Router,
|
||||
private trace: TraceService,
|
||||
private eoMessage: EoNgFeedbackMessageService,
|
||||
private feedback: EoNgFeedbackMessageService,
|
||||
private extensionService: ExtensionService,
|
||||
private store: StoreService,
|
||||
private apiService: ApiService,
|
||||
@ -103,7 +103,7 @@ export class ImportApiComponent implements OnInit {
|
||||
async submit(callback) {
|
||||
StorageUtil.set('import_api_modal', this.currentExtension);
|
||||
if (!this.uploadData) {
|
||||
this.eoMessage.error($localize`Please import the file first`);
|
||||
this.feedback.error($localize`Please import the file first`);
|
||||
callback('stayModal');
|
||||
return;
|
||||
}
|
||||
@ -116,16 +116,20 @@ export class ImportApiComponent implements OnInit {
|
||||
const [data, err] = module[action](content);
|
||||
console.log('import data', window.structuredClone?.(data));
|
||||
if (err) {
|
||||
this.eoMessage.error(err.msg);
|
||||
this.feedback.error(err.msg);
|
||||
console.error(err.msg);
|
||||
callback(false);
|
||||
callback('stayModal');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('content', content);
|
||||
data.collections = parseAndCheckCollections(data.collections);
|
||||
data.environmentList = data.environmentList.filter(n => {
|
||||
data.collections = parseAndCheckCollections(data.collections || []);
|
||||
if (!data.collections?.length && !data.environmentList?.length) {
|
||||
this.feedback.warning($localize`The imported file contains ${data.collections.length} APIs, which will be ignored`);
|
||||
callback('stayModal');
|
||||
return;
|
||||
}
|
||||
data.environmentList = (data.environmentList || []).filter(n => {
|
||||
const { validate, data } = parseAndCheckEnv(n);
|
||||
if (validate) {
|
||||
return data;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { CollectionTypeEnum, ImportProjectDto } from 'pc/browser/src/app/services/storage/db/dto/project.dto';
|
||||
import { GroupModuleType, GroupType } from 'pc/browser/src/app/services/storage/db/dto/group.dto';
|
||||
import { ImportProjectDto } from 'pc/browser/src/app/services/storage/db/dto/project.dto';
|
||||
|
||||
import { convertApiData } from '../../../services/storage/db/dataSource/convert';
|
||||
|
||||
@ -18,12 +19,12 @@ export const old2new = (params, projectUuid, workSpaceUuid): ImportProjectDto =>
|
||||
if (item.uri) {
|
||||
const newApiData = convertApiData(item);
|
||||
Object.assign(item, newApiData);
|
||||
item.collectionType = CollectionTypeEnum.API_DATA;
|
||||
item.type = GroupType.virtual;
|
||||
item.module = GroupModuleType.API;
|
||||
}
|
||||
// 分组
|
||||
else {
|
||||
item.collectionType = CollectionTypeEnum.GROUP;
|
||||
|
||||
item.type = GroupType.USER_CREATED;
|
||||
if (item.children?.length) {
|
||||
formatData(item.children);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export class PushApiComponent implements OnInit {
|
||||
private destroy$: Subject<void> = new Subject<void>();
|
||||
constructor(
|
||||
private extensionService: ExtensionService,
|
||||
private eoMessage: EoNgFeedbackMessageService,
|
||||
private feedback: EoNgFeedbackMessageService,
|
||||
private apiService: ApiService,
|
||||
private messageService: MessageService
|
||||
) {}
|
||||
@ -63,6 +63,10 @@ export class PushApiComponent implements OnInit {
|
||||
}
|
||||
const action = feature.action || null;
|
||||
const module = await this.extensionService.getExtensionPackage(this.currentExtension);
|
||||
if (!module) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
if (module?.[action] && typeof module[action] === 'function') {
|
||||
const [data] = await this.apiService.api_projectExportProject({});
|
||||
|
||||
@ -70,7 +74,7 @@ export class PushApiComponent implements OnInit {
|
||||
try {
|
||||
const output = await module[action](data);
|
||||
if (has(output, 'status') && output.status !== 0) {
|
||||
this.eoMessage.error(output.message);
|
||||
this.feedback.error(output.message);
|
||||
callback('stayModal');
|
||||
return;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export class ExtensionSelectComponent {
|
||||
filename = '';
|
||||
tipsMap = { ...featuresTipsMap, ...categoriesTipsMap };
|
||||
|
||||
constructor(private message: EoNgFeedbackMessageService) {}
|
||||
constructor(private feedback: EoNgFeedbackMessageService) {}
|
||||
|
||||
selectExtension({ key, properties }) {
|
||||
this.extensionChange.emit(key);
|
||||
@ -44,7 +44,7 @@ export class ExtensionSelectComponent {
|
||||
parserFile = file =>
|
||||
new Observable((observer: Observer<boolean>) => {
|
||||
if (file.type !== 'application/json') {
|
||||
this.message.error($localize`Only files in JSON format are supported`);
|
||||
this.feedback.error($localize`Only files in JSON format are supported`);
|
||||
observer.complete();
|
||||
return;
|
||||
}
|
||||
@ -55,7 +55,7 @@ export class ExtensionSelectComponent {
|
||||
observer.complete();
|
||||
})
|
||||
.catch(err => {
|
||||
this.message.error(err);
|
||||
this.feedback.error(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -2,16 +2,15 @@ import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@
|
||||
import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { ExtensionService } from 'pc/browser/src/app/services/extensions/extension.service';
|
||||
import { Message, MessageService } from 'pc/browser/src/app/services/message';
|
||||
import { ApiService } from 'pc/browser/src/app/services/storage/api.service';
|
||||
import { TraceService } from 'pc/browser/src/app/services/trace.service';
|
||||
import { EoSchemaFormComponent } from 'pc/browser/src/app/shared/components/schema-form/schema-form.component';
|
||||
import { PULL_API } from 'pc/browser/src/app/shared/constans/featureName';
|
||||
import { ExtensionChange } from 'pc/browser/src/app/shared/decorators';
|
||||
import { FeatureInfo } from 'pc/browser/src/app/shared/models/extension-manager';
|
||||
import { EffectService } from 'pc/browser/src/app/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { EffectService } from 'pc/browser/src/app/shared/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import { eoDeepCopy } from '../../../shared/utils/index.utils';
|
||||
import { SYNC_API_SCHEMA } from './schema';
|
||||
@ -43,9 +42,8 @@ export class SyncApiComponent implements OnInit, OnChanges {
|
||||
private destroy$: Subject<void> = new Subject<void>();
|
||||
constructor(
|
||||
private extensionService: ExtensionService,
|
||||
private eoMessage: EoNgFeedbackMessageService,
|
||||
private feedback: EoNgFeedbackMessageService,
|
||||
private apiService: ApiService,
|
||||
private messageService: MessageService,
|
||||
private store: StoreService,
|
||||
private effectService: EffectService,
|
||||
private trace: TraceService
|
||||
@ -170,7 +168,7 @@ export class SyncApiComponent implements OnInit, OnChanges {
|
||||
const [data, err] = await module[feature.action](this.validateForm?.value);
|
||||
console.log('data', data, err);
|
||||
if (err) {
|
||||
this.eoMessage.error($localize`Sync API from URL error: ${err}`);
|
||||
this.feedback.error($localize`Sync API from URL error: ${err?.message || err}`);
|
||||
return 'stayModal';
|
||||
}
|
||||
// this.eoMessage.success($localize`Sync API from URL Successfully`);
|
||||
@ -201,7 +199,7 @@ export class SyncApiComponent implements OnInit, OnChanges {
|
||||
};
|
||||
const [data, err] = await this.apiService[params.id ? 'api_projectUpdateSyncSetting' : 'api_projectCreateSyncSetting'](params);
|
||||
if (err) {
|
||||
this.eoMessage.error(err.msg);
|
||||
this.feedback.error(err.msg);
|
||||
console.error(err.msg);
|
||||
callback?.('stayModal');
|
||||
return;
|
||||
|
@ -3,7 +3,7 @@ import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
|
||||
import { autorun, reaction } from 'mobx';
|
||||
import { TraceService } from 'pc/browser/src/app/services/trace.service';
|
||||
|
||||
import { StoreService } from '../../store/state.service';
|
||||
import { StoreService } from '../../shared/store/state.service';
|
||||
import { MemberService } from './member.service';
|
||||
|
||||
@Component({
|
||||
@ -60,7 +60,7 @@ export class MemberListComponent implements OnInit {
|
||||
constructor(
|
||||
public store: StoreService,
|
||||
private trace: TraceService,
|
||||
private message: EoNgFeedbackMessageService,
|
||||
private feedback: EoNgFeedbackMessageService,
|
||||
public member: MemberService
|
||||
) {}
|
||||
|
||||
@ -94,20 +94,20 @@ export class MemberListComponent implements OnInit {
|
||||
async changeRole(item) {
|
||||
const isOK: boolean = await this.member.changeRole(item);
|
||||
if (isOK) {
|
||||
this.message.success($localize`Change role successfully`);
|
||||
this.feedback.success($localize`Change role successfully`);
|
||||
this.trace.report('switch_member_permission');
|
||||
this.queryList();
|
||||
return;
|
||||
}
|
||||
this.message.error($localize`Change role Failed`);
|
||||
this.feedback.error($localize`Change role Failed`);
|
||||
}
|
||||
async removeMember(item) {
|
||||
const [data, err]: any = await this.member.removeMember(item);
|
||||
if (err) {
|
||||
this.message.error($localize`Change role error`);
|
||||
this.feedback.error($localize`Change role error`);
|
||||
return;
|
||||
}
|
||||
this.message.success($localize`Remove Member successfully`);
|
||||
this.feedback.success($localize`Remove Member successfully`);
|
||||
this.queryList();
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { NpsPositionDirective } from 'pc/browser/src/app/components/nps-mask/nps-mask-postion.directive';
|
||||
import { APP_CONFIG } from 'pc/browser/src/environments/environment';
|
||||
|
||||
import { StoreService } from '../../../store/state.service';
|
||||
import { StoreService } from '../../../shared/store/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'pc-nps-mask',
|
||||
|
@ -3,7 +3,7 @@ import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators }
|
||||
import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
|
||||
import { MessageService } from 'pc/browser/src/app/services/message/message.service';
|
||||
import { ApiService } from 'pc/browser/src/app/services/storage/api.service';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'eo-account',
|
||||
@ -63,7 +63,7 @@ export class AccountComponent implements OnInit {
|
||||
public store: StoreService,
|
||||
public message: MessageService,
|
||||
public api: ApiService,
|
||||
public eMessage: EoNgFeedbackMessageService
|
||||
public feedback: EoNgFeedbackMessageService
|
||||
) {
|
||||
this.isSaveUsernameBtnLoading = false;
|
||||
this.validatePasswordForm = UntypedFormGroup;
|
||||
@ -92,10 +92,10 @@ export class AccountComponent implements OnInit {
|
||||
password
|
||||
});
|
||||
if (err) {
|
||||
this.eMessage.error($localize`Validation failed`);
|
||||
this.feedback.error($localize`Validation failed`);
|
||||
return;
|
||||
}
|
||||
this.eMessage.success($localize`Password reset success !`);
|
||||
this.feedback.success($localize`Password reset success !`);
|
||||
|
||||
// * Clear password form
|
||||
this.validatePasswordForm.reset();
|
||||
|
@ -49,7 +49,7 @@ export class DataStorageComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private message: EoNgFeedbackMessageService,
|
||||
private feedback: EoNgFeedbackMessageService,
|
||||
private messageS: MessageService,
|
||||
private dataSource: DataSourceService,
|
||||
private settingService: SettingService
|
||||
@ -80,13 +80,13 @@ export class DataStorageComponent implements OnInit {
|
||||
};
|
||||
const isSuccess = await this.dataSource.pingCloudServerUrl(this.validateForm.value['backend.url']);
|
||||
if (isSuccess) {
|
||||
this.message.success($localize`Successfully connect to cloud`);
|
||||
this.feedback.success($localize`Successfully connect to cloud`);
|
||||
StorageUtil.set('IS_SHOW_DATA_SOURCE_TIP', 'false');
|
||||
//Relogin to update user info
|
||||
this.messageS.send({ type: 'login', data: {} });
|
||||
this.saveConf();
|
||||
} else {
|
||||
this.message.error($localize`Failed to connect`);
|
||||
this.feedback.error($localize`Failed to connect`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
|
||||
import { MessageService } from 'pc/browser/src/app/services/message';
|
||||
import { ApiService } from 'pc/browser/src/app/services/storage/api.service';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
|
||||
import { DataSourceService } from '../../../services/data-source/data-source.service';
|
||||
|
||||
@ -46,13 +43,7 @@ import { DataSourceService } from '../../../services/data-source/data-source.ser
|
||||
})
|
||||
export class TokenComponent {
|
||||
token;
|
||||
constructor(
|
||||
private api: ApiService,
|
||||
private message: MessageService,
|
||||
private eoMessage: EoNgFeedbackMessageService,
|
||||
private store: StoreService,
|
||||
private dataSource: DataSourceService
|
||||
) {
|
||||
constructor(private api: ApiService, private dataSource: DataSourceService) {
|
||||
this.token = '';
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import { SystemSettingComponent } from './system-setting.component';
|
||||
|
||||
export const LOCAL_SETTINGS_KEY = 'LOCAL_SETTINGS_KEY';
|
||||
|
||||
//TODO use StorageUtils to replace this
|
||||
export const getSettings = () => {
|
||||
try {
|
||||
let result = JSON.parse(localStorage.getItem(LOCAL_SETTINGS_KEY) || '{}');
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { AccountComponent } from 'pc/browser/src/app/components/system-setting/common/account.component';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
|
||||
import { SettingItem } from '../eo-ui/setting/setting.component';
|
||||
import { AboutComponent, LanguageSwticherComponent, SelectThemeComponent, TokenComponent } from './common';
|
||||
|
@ -20,7 +20,6 @@ export class LanguageService {
|
||||
this.languages.find(val => window.location.pathname.includes(`/${val.path}/`))?.value ||
|
||||
this.setting.settings?.['system.language'] ||
|
||||
(navigator.language.includes('zh') ? 'zh-Hans' : 'en-US');
|
||||
this.trace.setUser({ app_language: this.systemLanguage });
|
||||
}
|
||||
get langHash() {
|
||||
return this.langHashMap.get(this.systemLanguage);
|
||||
@ -28,6 +27,7 @@ export class LanguageService {
|
||||
init() {
|
||||
//System language First
|
||||
this.changeLanguage(this.setting.settings?.['system.language']);
|
||||
this.trace.setVisitor({ app_language: this.systemLanguage });
|
||||
}
|
||||
changeLanguage(localeID) {
|
||||
if (!localeID || localeID === this.systemLanguage) {
|
||||
|
@ -22,11 +22,13 @@ export class NotificationService {
|
||||
return;
|
||||
}
|
||||
|
||||
this.modal.create({
|
||||
stayWhenRouterChange: true,
|
||||
nzTitle: $localize`Release Notes`,
|
||||
nzContent: $localize`There will be downtime updates from ${logInfo.startTime.getHours()}\:00 to ${logInfo.endTime.getHours()}\:00 today, and may be temporarily inaccessible.`
|
||||
});
|
||||
StorageUtil.set('notification_has_show', true, 60 * 60 * 24);
|
||||
//! safari may cause erro when the user first open page,it will show even if the time has passed
|
||||
|
||||
// this.modal.create({
|
||||
// stayWhenRouterChange: true,
|
||||
// nzTitle: $localize`Release Notes`,
|
||||
// nzContent: $localize`There will be downtime updates from ${logInfo.startTime.getHours()}\:00 to ${logInfo.endTime.getHours()}\:00 today, and may be temporarily inaccessible.`
|
||||
// });
|
||||
// StorageUtil.set('notification_has_show', true, 60 * 60 * 24);
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,9 @@ export class ThemeVariableService {
|
||||
'buttonTextHoverText',
|
||||
'menuItemText',
|
||||
'tabsText',
|
||||
/**
|
||||
* Tabs Active Color is default color
|
||||
*/
|
||||
'tabsActiveText',
|
||||
'tabsCardText',
|
||||
'tabsCardItemActiveText',
|
||||
|
@ -7,9 +7,9 @@ import { PROTOCOL } from 'pc/browser/src/app/shared/models/protocol.constant';
|
||||
import { APP_CONFIG } from 'pc/browser/src/environments/environment';
|
||||
|
||||
import packageJson from '../../../../../../../package.json';
|
||||
import { StoreService } from '../../../shared/store/state.service';
|
||||
import { getBrowserType } from '../../../shared/utils/browser-type';
|
||||
import StorageUtil from '../../../shared/utils/storage/storage.utils';
|
||||
import { StoreService } from '../../../store/state.service';
|
||||
|
||||
type DescriptionsItem = {
|
||||
readonly id: string;
|
||||
@ -59,7 +59,7 @@ export class WebService {
|
||||
if (this.isWeb) {
|
||||
this.settingService.putSettings({ 'backend.url': window.location.origin });
|
||||
} else {
|
||||
this.settingService.putSettings({ 'backend.url': APP_CONFIG.serverUrl });
|
||||
this.settingService.putSettings({ 'backend.url': !APP_CONFIG.production ? window.location.origin : APP_CONFIG.serverUrl });
|
||||
}
|
||||
this.getClientResource();
|
||||
}
|
||||
@ -248,7 +248,7 @@ export class WebService {
|
||||
systemInfo = window.electron.getSystemInfo();
|
||||
descriptions.push(...electronDetails);
|
||||
} else {
|
||||
systemInfo = getBrowserType(getSettings()?.['system.language']);
|
||||
systemInfo = getBrowserType();
|
||||
descriptions.push(
|
||||
...Object.entries<string>(systemInfo).map(([key, value]) => ({
|
||||
id: key,
|
||||
|
@ -3,8 +3,8 @@ import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
|
||||
import { autorun } from 'mobx';
|
||||
import { MessageService } from 'pc/browser/src/app/services/message';
|
||||
import { IS_SHOW_REMOTE_SERVER_NOTIFICATION } from 'pc/browser/src/app/shared/models/storageKeys.constant';
|
||||
import { EffectService } from 'pc/browser/src/app/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { EffectService } from 'pc/browser/src/app/shared/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
|
||||
import { StorageUtil } from '../../shared/utils/storage/storage.utils';
|
||||
|
||||
@ -34,7 +34,7 @@ export class LocalWorkspaceTipComponent implements OnInit {
|
||||
@Output() readonly isShowChange: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||
manualClose = StorageUtil.get(IS_SHOW_REMOTE_SERVER_NOTIFICATION) === 'false';
|
||||
constructor(
|
||||
private eoMessage: EoNgFeedbackMessageService,
|
||||
private feedback: EoNgFeedbackMessageService,
|
||||
private message: MessageService,
|
||||
private store: StoreService,
|
||||
private effect: EffectService
|
||||
@ -54,7 +54,7 @@ export class LocalWorkspaceTipComponent implements OnInit {
|
||||
const workspaces = this.store.getWorkspaceList;
|
||||
if (workspaces.length === 1) {
|
||||
// * only local workspace
|
||||
this.eoMessage.warning($localize`You don't have cloud space yet, please new one`);
|
||||
this.feedback.warning($localize`You don't have cloud space yet, please new one`);
|
||||
this.message.send({ type: 'addWorkspace', data: {} });
|
||||
return;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { autorun } from 'mobx';
|
||||
|
||||
import { StoreService } from '../../../store/state.service';
|
||||
import { StoreService } from '../../../shared/store/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'eo-nav-breadcrumb',
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { EffectService } from 'pc/browser/src/app/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { Component } from '@angular/core';
|
||||
import { EffectService } from 'pc/browser/src/app/shared/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
|
||||
import { FeatureControlService } from '../../../../core/services/feature-control/feature-control.service';
|
||||
import { DataSourceService } from '../../../../services/data-source/data-source.service';
|
||||
import { MessageService } from '../../../../services/message';
|
||||
import { ModalService } from '../../../../services/modal.service';
|
||||
|
||||
@Component({
|
||||
selector: 'eo-select-workspace',
|
||||
|
@ -6,7 +6,7 @@ import { FeatureControlService } from '../../../core/services/feature-control/fe
|
||||
import { DataSourceService } from '../../../services/data-source/data-source.service';
|
||||
import { MessageService } from '../../../services/message';
|
||||
import { ApiService } from '../../../services/storage/api.service';
|
||||
import { StoreService } from '../../../store/state.service';
|
||||
import { StoreService } from '../../../shared/store/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'pc-btn-user',
|
||||
@ -44,7 +44,7 @@ export class BtnUserComponent {
|
||||
private message: MessageService,
|
||||
private api: ApiService,
|
||||
public feature: FeatureControlService,
|
||||
private eMessage: EoNgFeedbackMessageService,
|
||||
private feedback: EoNgFeedbackMessageService,
|
||||
public store: StoreService,
|
||||
private dataSourceService: DataSourceService,
|
||||
private setting: SettingService
|
||||
@ -57,7 +57,7 @@ export class BtnUserComponent {
|
||||
}
|
||||
async loginOut() {
|
||||
this.store.clearAuth();
|
||||
this.eMessage.success($localize`Successfully logged out !`);
|
||||
this.feedback.success($localize`Successfully logged out !`);
|
||||
const [, err]: any = await this.api.api_userLogout({});
|
||||
if (err) {
|
||||
return;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
|
||||
import { EffectService } from 'pc/browser/src/app/shared/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import { copy } from 'pc/browser/src/app/shared/utils/index.utils';
|
||||
import { EffectService } from 'pc/browser/src/app/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { interval } from 'rxjs';
|
||||
|
||||
import { DataSourceService } from '../../services/data-source/data-source.service';
|
||||
@ -52,12 +52,12 @@ export class GetShareLinkComponent {
|
||||
private effect: EffectService,
|
||||
public store: StoreService,
|
||||
public dataSourceService: DataSourceService,
|
||||
private message: EoNgFeedbackMessageService
|
||||
private feedback: EoNgFeedbackMessageService
|
||||
) {}
|
||||
handleGetShareLink() {
|
||||
this.dataSourceService.checkRemoteCanOperate(async () => {
|
||||
if (this.store.isLocal) {
|
||||
this.message.info($localize`If you want to share API,Please switch to cloud workspace`);
|
||||
this.feedback.info($localize`If you want to share API,Please switch to cloud workspace`);
|
||||
}
|
||||
this.link = await this.effect.updateShareLink();
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { NzModalService } from 'ng-zorro-antd/modal';
|
||||
import { ExtensionComponent } from 'pc/browser/src/app/pages/components/extension/extension.component';
|
||||
import { MessageService } from 'pc/browser/src/app/services/message';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import { APP_CONFIG } from 'pc/browser/src/environments/environment';
|
||||
import { interval, Subject, takeUntil } from 'rxjs';
|
||||
import { distinct } from 'rxjs/operators';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { NzBreadCrumbModule } from 'ng-zorro-antd/breadcrumb';
|
||||
import { DownloadClientComponent } from 'pc/browser/src/app/components/download-client/download-client.component';
|
||||
|
||||
import { DownloadClientModule } from '../../components/download-client/download-client.module';
|
||||
import { LogoModule } from '../../components/logo/logo.module';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { NavBreadcrumbComponent } from './breadcrumb/nav-breadcrumb.component';
|
||||
@ -14,7 +14,7 @@ import { NavbarComponent } from './navbar.component';
|
||||
import { ShareNavbarComponent } from './share-navbar/share-navbar.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, DownloadClientModule, LogoModule, NzBreadCrumbModule],
|
||||
imports: [SharedModule, DownloadClientComponent, LogoModule, NzBreadCrumbModule],
|
||||
declarations: [
|
||||
NavbarComponent,
|
||||
GetShareLinkComponent,
|
||||
|
@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { APP_CONFIG } from 'pc/browser/src/environments/environment';
|
||||
|
||||
import { ApiService } from '../../../services/storage/api.service';
|
||||
import { StoreService } from '../../../store/state.service';
|
||||
import { StoreService } from '../../../shared/store/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'pc-share-navbar',
|
||||
|
@ -9,10 +9,10 @@
|
||||
}
|
||||
|
||||
.eo-sidebar-shrink {
|
||||
width: 50px;
|
||||
width: var(--layout-sidebar-shrink-width);
|
||||
|
||||
.sidebar-item {
|
||||
height: 50px;
|
||||
height: var(--layout-sidebar-shrink-width);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { Message, MessageService } from 'pc/browser/src/app/services/message';
|
||||
import { SIDEBAR_VIEW } from 'pc/browser/src/app/shared/constans/featureName';
|
||||
import { ExtensionChange, ExtensionMessage } from 'pc/browser/src/app/shared/decorators';
|
||||
import { ExtensionInfo } from 'pc/browser/src/app/shared/models/extension-manager';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { filter } from 'rxjs/operators';
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ChatgptRobotComponent } from 'pc/browser/src/app/pages/components/chatgpt-robot/chatgpt-robot.component';
|
||||
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { ToolbarComponent } from './toolbar.component';
|
||||
|
@ -5,13 +5,11 @@ import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { EoNgButtonModule } from 'eo-ng-button';
|
||||
import GPT3Tokenizer from 'gpt3-tokenizer';
|
||||
import { FeatureControlService } from 'pc/browser/src/app/core/services/feature-control/feature-control.service';
|
||||
import { Message, MessageService } from 'pc/browser/src/app/services/message';
|
||||
import { TraceService } from 'pc/browser/src/app/services/trace.service';
|
||||
import { FEATURE_CONTROL } from 'pc/browser/src/app/shared/constans/featureName';
|
||||
import { ExtensionChange, ExtensionMessage } from 'pc/browser/src/app/shared/decorators';
|
||||
import { ExtensionInfo } from 'pc/browser/src/app/shared/models/extension-manager';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import StorageUtil from 'pc/browser/src/app/shared/utils/storage/storage.utils';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { APP_CONFIG } from 'pc/browser/src/environments/environment';
|
||||
|
||||
import { ChatRobotModule } from '../../../components/chat-robot/chat-robot.module';
|
||||
@ -106,7 +104,6 @@ export class ChatgptRobotComponent implements OnInit {
|
||||
private http: HttpClient,
|
||||
public chat: ChatRobotService,
|
||||
public feature: FeatureControlService,
|
||||
private message: MessageService,
|
||||
private trace: TraceService,
|
||||
private store: StoreService
|
||||
) {}
|
||||
|
@ -20,7 +20,7 @@ export class ExtensionSettingComponent implements OnInit {
|
||||
@Input() extName: string;
|
||||
localSettings = {} as Record<string, any>;
|
||||
|
||||
constructor(private settingService: SettingService, private message: EoNgFeedbackMessageService) {}
|
||||
constructor(private settingService: SettingService, private feedback: EoNgFeedbackMessageService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.init();
|
||||
@ -31,6 +31,6 @@ export class ExtensionSettingComponent implements OnInit {
|
||||
}
|
||||
handleSave = () => {
|
||||
this.settingService.saveSetting(this.localSettings);
|
||||
this.message.success($localize`Save Success`);
|
||||
this.feedback.success($localize`Save Success`);
|
||||
};
|
||||
}
|
||||
|
@ -32,7 +32,7 @@
|
||||
</nz-space>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center" *ngIf="isAvailableElectron">
|
||||
<div class="flex items-center" *ngIf="isAvailablePlatform && isAvailableVersion">
|
||||
<div *ngIf="extensionDetail?.installed" class="mr-[20px]">
|
||||
<eo-ng-switch
|
||||
class="mr-[3px]"
|
||||
@ -59,7 +59,13 @@
|
||||
<span *ngIf="!extensionDetail?.installed" i18n>Install</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col" *ngIf="!isAvailableElectron">
|
||||
<div class="flex flex-col items-end" *ngIf="!isAvailableVersion">
|
||||
<pc-download-client i18n-title title="Upgrade"></pc-download-client>
|
||||
<p class="text-[12px] mt-[10px] text-tips" i18n
|
||||
>* You need to upgrade the client to {{ extensionDetail.engines?.postcat }} first</p
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col items-end" *ngIf="!isAvailablePlatform && isAvailableVersion">
|
||||
<a [href]="APP_CONFIG.serverUrl" target="_bank"><button eo-ng-button nzType="primary" i18n>Jump to Use</button></a>
|
||||
<p class="text-[12px] mt-[10px] text-tips" i18n>* This exension only support web</p>
|
||||
</div>
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import { ElectronService } from 'pc/browser/src/app/core/services';
|
||||
import { LanguageService } from 'pc/browser/src/app/core/services/language/language.service';
|
||||
import { TraceService } from 'pc/browser/src/app/services/trace.service';
|
||||
import { ExtensionInfo } from 'pc/browser/src/app/shared/models/extension-manager';
|
||||
import { APP_CONFIG } from 'pc/browser/src/environments/environment';
|
||||
|
||||
import pkgInfo from '../../../../../../../../package.json';
|
||||
import { WebService } from '../../../../core/services/web/web.service';
|
||||
import { ExtensionService } from '../../../../services/extensions/extension.service';
|
||||
import { EoExtensionInfo } from '../extension.model';
|
||||
@ -20,13 +22,16 @@ export class ExtensionDetailComponent implements OnInit {
|
||||
@Input() nzSelectedIndex = 0;
|
||||
isOperating = false;
|
||||
introLoading = false;
|
||||
isAvailableElectron = true;
|
||||
changelogLoading = false;
|
||||
isNotLoaded = true;
|
||||
extensionDetail: EoExtensionInfo;
|
||||
readonly APP_CONFIG = APP_CONFIG;
|
||||
changeLog = '';
|
||||
changeLogNotFound = false;
|
||||
|
||||
isAvailableVersion = true;
|
||||
isAvailablePlatform = true;
|
||||
|
||||
constructor(
|
||||
public extensionService: ExtensionService,
|
||||
private webService: WebService,
|
||||
@ -68,7 +73,8 @@ export class ExtensionDetailComponent implements OnInit {
|
||||
this.introLoading = false;
|
||||
this.isNotLoaded = false;
|
||||
this.extensionDetail.introduction ||= $localize`This plugin has no documentation yet.`;
|
||||
this.isAvailableElectron = this.checkisAvailableElectron(this.extensionDetail);
|
||||
this.isAvailableVersion = compareVersions(pkgInfo.version, this.extensionDetail.engines?.postcat) >= 0 ? true : false;
|
||||
this.isAvailablePlatform = this.checkisAvailablePlatform(this.extensionDetail);
|
||||
this.fetchChangelog(this.language.systemLanguage);
|
||||
|
||||
setTimeout(() => {
|
||||
@ -140,7 +146,13 @@ ${log}
|
||||
this.fetchChangelog();
|
||||
}
|
||||
};
|
||||
checkisAvailableElectron(pkgInfo): boolean {
|
||||
/**
|
||||
* Check current extension is available in current platform
|
||||
*
|
||||
* @param pkgInfo
|
||||
* @returns
|
||||
*/
|
||||
checkisAvailablePlatform(pkgInfo): boolean {
|
||||
if (this.electron.isElectron && this.extensionDetail.browser && !this.extensionDetail.main) return false;
|
||||
return true;
|
||||
}
|
||||
|
@ -1,21 +1,13 @@
|
||||
<section class="flex-shrink-0 p-0 left tree-sider">
|
||||
<div class="m-[10px]">
|
||||
<!-- <eo-ng-input-group class="!rounded-full">
|
||||
<input type="text" eo-ng-input class="flex-1 w-full px-3" i18n-placeholder="@@Search" placeholder="Search" [(ngModel)]="keyword" [nzAutocomplete]="auto" /> -->
|
||||
<eo-ng-auto-complete
|
||||
[(ngModel)]="keyword"
|
||||
(ngModelChange)="onInput($event)"
|
||||
[nzControl]="true"
|
||||
[nzOptions]="searchOptions"
|
||||
[nzPrefix]="prefixTemplateUser"
|
||||
i18n-nzPlaceholder="@@Search"
|
||||
nzPlaceholder="Search"
|
||||
></eo-ng-auto-complete>
|
||||
<!-- </eo-ng-input-group> -->
|
||||
<ng-template #prefixTemplateUser
|
||||
><svg class="iconpark-icon">
|
||||
<use href="#search"></use></svg
|
||||
></ng-template>
|
||||
</div>
|
||||
<!-- Fixed Group -->
|
||||
<eo-ng-tree-default
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import '../../workspace/project/api/components/group/tree/api-group-tree.component';
|
||||
@import '../../workspace/project/api/components/group/api-group-tree.component';
|
||||
|
||||
:host ::ng-deep {
|
||||
display: flex;
|
||||
@ -9,6 +9,10 @@
|
||||
height: calc(62vh - 32px);
|
||||
overflow: auto;
|
||||
display: block;
|
||||
|
||||
.ant-tree-switcher {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
eo-ng-auto-complete input {
|
||||
|
@ -33,10 +33,6 @@ export const featuresTipsMap = {
|
||||
name: $localize`format`,
|
||||
suggest: '@feature:pushAPI'
|
||||
},
|
||||
syncAPI: {
|
||||
name: $localize`format`,
|
||||
suggest: '@feature:syncAPI'
|
||||
},
|
||||
pullAPI: {
|
||||
name: $localize`format`,
|
||||
suggest: '@feature:pullAPI'
|
||||
|
@ -10,6 +10,7 @@ import { NzInputNumberModule } from 'ng-zorro-antd/input-number';
|
||||
import { NzResultModule } from 'ng-zorro-antd/result';
|
||||
import { NzSpaceModule } from 'ng-zorro-antd/space';
|
||||
import { NzTagModule } from 'ng-zorro-antd/tag';
|
||||
import { DownloadClientComponent } from 'pc/browser/src/app/components/download-client/download-client.component';
|
||||
import { ExtensionDetailComponent } from 'pc/browser/src/app/pages/components/extension/detail/extension-detail.component';
|
||||
import { DownloadCountFormaterPipe } from 'pc/browser/src/app/pages/components/extension/download-count-formater.pipe';
|
||||
import { SharedModule } from 'pc/browser/src/app/shared/shared.module';
|
||||
@ -31,6 +32,7 @@ import { ExtensionListComponent } from './list/extension-list.component';
|
||||
EoNgSwitchModule,
|
||||
EoNgTreeModule,
|
||||
NzResultModule,
|
||||
DownloadClientComponent,
|
||||
ShadowDomEncapsulationModule,
|
||||
NzTagModule,
|
||||
EoNgAutoCompleteModule,
|
||||
|
@ -53,9 +53,6 @@ export class ExtensionListComponent implements OnInit {
|
||||
// 避免频繁切换,导致侧边栏选中状态与右侧展示不一致
|
||||
if (originType === this.type) {
|
||||
this.extensionList = data;
|
||||
this.extensionList.sort((a, b) => {
|
||||
return a.name === 'postcat-chat-robot' ? -1 : 1;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
### Postcat 是一个强大的免费、跨平台、可扩展的 API 工具!
|
||||
|
||||
**和 Postman 等产品相比,它拥有以下备受喜爱的特性:**
|
||||
|
||||
1.❤️ 免费的团队协作:好的产品就应该更多人一起使用,我们不限制免费人数!
|
||||
|
||||
|
||||
|
||||
2.🚀 极具扩展性的插件系统:一键安装 ChatGPT、主题等各类插件来定制属于你的 API 开发工具~
|
||||
|
||||
|
||||
|
||||
3.😊 优秀的用户体验:一切都可以更简单,让我们的工作更高效~
|
||||
|
||||
**当然,我们还包括众多实用的基本功能:**
|
||||
|
||||
1.🍩 多协议支持:REST、Websocket 等协议(即将支持 GraphQL、gRPC、TCP、UDP)
|
||||
|
||||
2.📕 API 设计、文档展示、分享
|
||||
|
||||
3.⚡ API 测试
|
||||
|
||||
4.🎭 Mock Server
|
||||
|
||||
5.🙌 环境管理
|
||||
|
||||
|
||||
...
|
||||
|
||||
|
||||
|
@ -0,0 +1,25 @@
|
||||
::ng-deep {
|
||||
.model-article {
|
||||
.ant-modal-footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
line-height: 1.5rem !important;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
strong {
|
||||
display: inline-block;
|
||||
margin-top: 20px !important;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NewbieGuideComponent } from './newbie-guide.component';
|
||||
|
||||
describe('NewbieGuideComponent', () => {
|
||||
let component: NewbieGuideComponent;
|
||||
let fixture: ComponentFixture<NewbieGuideComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [NewbieGuideComponent]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(NewbieGuideComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,23 @@
|
||||
import { Component } from '@angular/core';
|
||||
import markdownIt from 'markdown-it/dist/markdown-it';
|
||||
import NEWBIE_GUIDE from 'pc/browser/src/app/shared/constans/newbie-guide';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'pc-newbie-guide',
|
||||
template: `
|
||||
<div class="newbie-guide">
|
||||
<img src="assets/images/newbie-guide.png" style="width: 100%" alt="" />
|
||||
<div id="newbie-guide-markdown"></div>
|
||||
</div>
|
||||
`,
|
||||
styleUrls: ['./newbie-guide.component.scss']
|
||||
})
|
||||
export class NewbieGuideComponent {
|
||||
async ngAfterViewInit() {
|
||||
let md = new markdownIt();
|
||||
const newbieGuideHtml = md.render(NEWBIE_GUIDE);
|
||||
|
||||
document.getElementById('newbie-guide-markdown').innerHTML = newbieGuideHtml;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
::ng-deep {
|
||||
.model-article {
|
||||
.ant-modal-footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
line-height: 30px !important;
|
||||
}
|
||||
|
||||
strong {
|
||||
display: inline-block;
|
||||
margin-top: 20px !important;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UpdateLogComponent } from './update-log.component';
|
||||
|
||||
describe('UpdateLogComponent', () => {
|
||||
let component: UpdateLogComponent;
|
||||
let fixture: ComponentFixture<UpdateLogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [UpdateLogComponent]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UpdateLogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,24 @@
|
||||
import { Component } from '@angular/core';
|
||||
import markdownIt from 'markdown-it/dist/markdown-it';
|
||||
import UPDATE_LOG from 'pc/browser/src/app/shared/constans/update-log';
|
||||
|
||||
@Component({
|
||||
selector: 'pc-update-log',
|
||||
template: `
|
||||
<div class="update-log">
|
||||
<div id="update-log-markdown"></div>
|
||||
</div>
|
||||
`,
|
||||
styleUrls: ['./update-log.component.scss']
|
||||
})
|
||||
export class UpdateLogComponent {
|
||||
async ngAfterViewInit() {
|
||||
let md = new markdownIt();
|
||||
const html = md.render(UPDATE_LOG);
|
||||
|
||||
const updateLogHtml = html.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, match =>
|
||||
match.replace(/<img /gi, '<img style="width: 100%" ')
|
||||
);
|
||||
document.getElementById('update-log-markdown').innerHTML = updateLogHtml;
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
# Postcat API 客户端(Client)
|
||||
|
||||
![Postcat API Client](http://data.eolinker.com/course/QbLMSaJ7f3dcd0b075a7031b31f8acb486e0a090f1bdc8d.jpeg)
|
||||
|
||||
<p align="center"><a href="wiki/README.en.md">English</a> | <span>简体中文</span></p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/Postcatlab/postcat"><img src="https://img.shields.io/github/license/Postcatlab/postcat?sanitize=true" alt="License"></a>
|
||||
<a href="https://github.com/Postcatlab/postcat/releases"><img src="https://img.shields.io/github/v/release/Postcatlab/postcat?sanitize=true" alt="Version"></a>
|
||||
<a href="https://github.com/Postcatlab/postcat/releases"><img src="https://img.shields.io/github/downloads/Postcatlab/postcat/total?sanitize=true" alt="Downloads"></a>
|
||||
<a href="https://discord.gg/W3uk39zJCR"><img src="https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true" alt="Chat"></a>
|
||||
</p>
|
||||
|
||||
## 概述
|
||||
|
||||
**Postcat** 是一个强大的开源、免费的、跨平台(Windows、Mac、Linux、Browsers...)的 **API 开发测试工具**,支持 REST、Websocket 等协议(即将支持 GraphQL、gRPC、TCP、UDP),帮助你加速完成 API 开发和测试工作。它非常适合中小团队及个人使用。
|
||||
|
||||
![Postcat UI](https://data.eolink.com/ImGzhCi79d0beb5b8221670dffceb61bf642af1960d3881)
|
||||
|
||||
我们在保证 **Postcat** 轻巧灵活的同时,还为它设计了一个强大的插件系统,让您可以一键使用插件来增强它的功能。
|
||||
|
||||
![Postcat Extensions](https://data.eolink.com/22UMwcV01e087e3549edb91361f15a9ba8047e16d0d3f3f)
|
||||
|
||||
因此 **Postcat** 理论上是一个拥有无限可能的 API 产品,可以从Logo 中看到,我们也形象地为它加上了一件披风,代表它的无限可能。
|
||||
|
||||
|
||||
## 免登录在线使用或下载
|
||||
|
||||
**Postcat** 现在已经支持 Windows、Mac、Linux等系统,你可以通过以下地址访问并下载。同时我们也提供了 Web 端,方便你在任何浏览器上使用。
|
||||
|
||||
**[https://postcat.com/](https://postcat.com//)**
|
||||
|
||||
如果您试用之后觉得不错,**请给我们的Postcat一个 Star 和 Fork~**你的支持是我们不断改进产品的动力!
|
||||
|
||||
## 详细的文档
|
||||
|
||||
[Postcat 文档](https://docs.postcat.com/)
|
||||
|
||||
[插件开发文档](https://developer.postcat.com/api/get-started.html)
|
||||
|
||||
|
||||
## 功能特性和迭代计划(Roadmap)
|
||||
|
||||
- 🚀 多协议支持
|
||||
|
||||
-- 已实现:HTTP REST、Websocket
|
||||
|
||||
-- 即将实现:GraphQL、TCP、UDP、gRPC
|
||||
|
||||
- 📕 API 文档
|
||||
|
||||
- ✨ API 设计
|
||||
|
||||
- ⚡ API 测试
|
||||
|
||||
- 🎭 Mock
|
||||
|
||||
- 🙌 团队协作
|
||||
|
||||
- 🎈 文档分享
|
||||
|
||||
- 🗺 环境
|
||||
|
||||
- 🧶 全局变量
|
||||
|
||||
- 🧩 自定义主题风格
|
||||
|
||||
- 🌐 多语言支持:中文、English
|
||||
|
||||
了解更多具体迭代计划:[Github Project](https://github.com/orgs/Postcatlab/projects/3)
|
||||
</br>也欢迎给我们多多提需求~
|
||||
</br>
|
||||
|
||||
|
||||
## Bug 和需求反馈
|
||||
|
||||
如果想要反馈 Bug、提供产品意见,可以创建一个 [Github issue](https://github.com/Postcatlab/postcat/issues) 联系我们,十分感谢!
|
||||
|
||||
如果您希望和 Postcat 团队近距离交流,讨论产品使用技巧以及了解更多产品最新进展,欢迎加入以下渠道。
|
||||
|
||||
- QQ群号码:981965807
|
||||
|
||||
- QQ群链接:[加入Postcat 用户群](https://jq.qq.com/?_wv=1027&k=Kej1qTUy)
|
||||
|
||||
- 微信群:
|
||||
|
||||
![](http://data.eolinker.com/course/NKhRRF668370911c8b8ea8a0887b5d62e71b0f1a22ad76a.png)
|
||||
|
||||
|
||||
|
||||
## 开发 Postcat
|
||||
|
||||
<details>
|
||||
|
||||
<summary>运行代码</summary>
|
||||
|
||||
</br>
|
||||
|
||||
请确保你已经部署好所需的开发环境:
|
||||
|
||||
- Node.js >= 14.17.x
|
||||
|
||||
- yarn >= 1.22.x
|
||||
|
||||
我们在开发和构建时使用 yarn 作为包管理工具,强烈建议你也这么做,但如果您希望使用 npm 也完全没问题,只是在安装依赖时可能需要多花一些时间。
|
||||
|
||||
### 运行桌面端程序
|
||||
|
||||
```shell
|
||||
|
||||
yarn install
|
||||
|
||||
yarn start
|
||||
|
||||
```
|
||||
|
||||
### 运行浏览器程序
|
||||
|
||||
```shell
|
||||
|
||||
cd src/browser&&npm install
|
||||
|
||||
yarn start
|
||||
|
||||
```
|
||||
|
||||
### 提高效率
|
||||
|
||||
如果想提高开发效率,可以安装 Angular 官方提供的命令行 Angular-cli 快速生成组件、服务等模板。
|
||||
|
||||
```
|
||||
|
||||
yarn add @angular/cli --global
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>内置命令</summary>
|
||||
|
||||
### 运行命令
|
||||
|
||||
|命令 |描述 |
|
||||
| ------------ | ------------ |
|
||||
|yarn start |开发模式下,同时运行在浏览器和桌面端 |
|
||||
|yarn start:zh|中文开发模式,同时运行在浏览器和桌面端|
|
||||
|yarn start:web |仅运行在浏览器,同时开启后端代理 |
|
||||
|yarn start:electron|仅运行在桌面端 |
|
||||
|
||||
> 本项目 i18n 使用的是编译手段,所以开发时无法切换语言
|
||||
### 打包构建
|
||||
|
||||
|命令 |描述 |
|
||||
| ------------ | ------------ |
|
||||
|sudo yarn build|各系统打包 Electron 应用 |
|
||||
|
||||
### 运行测试
|
||||
|
||||
|命令 |描述 |
|
||||
| ------------ | ------------ |
|
||||
|yarn test |执行单元测试 |
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { autorun, reaction } from 'mobx';
|
||||
import { WebService } from 'pc/browser/src/app/core/services';
|
||||
import { LanguageService } from 'pc/browser/src/app/core/services/language/language.service';
|
||||
import { ApiService } from 'pc/browser/src/app/services/storage/api.service';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import { APP_CONFIG } from 'pc/browser/src/environments/environment';
|
||||
|
||||
// * type(0=wechat, 1=qq, 2=github, 3=feishu, 4=corp_wechat, 5=ding_talk, 6=oauth2)
|
||||
@ -60,15 +60,13 @@ export class ThirdLoginComponent implements OnInit {
|
||||
isLoginBtnBtnLoading = false;
|
||||
constructor(private api: ApiService, private web: WebService, public lang: LanguageService) {}
|
||||
ngOnInit() {
|
||||
autorun(() => {
|
||||
this.renderList =
|
||||
this.lang.langHash === 'zh'
|
||||
? [
|
||||
// { logo: 'feishu.png', label: '飞书', type: 'feishu' },
|
||||
{ logo: 'github.png', label: 'Github', type: 'github' }
|
||||
]
|
||||
: [];
|
||||
});
|
||||
this.renderList =
|
||||
this.lang.langHash === 'zh'
|
||||
? [
|
||||
// { logo: 'feishu.png', label: '飞书', type: 'feishu' },
|
||||
{ logo: 'github.png', label: 'Github', type: 'github' }
|
||||
]
|
||||
: [];
|
||||
}
|
||||
logoLink(name) {
|
||||
return `url('./assets/images/${name}')`;
|
||||
|
@ -9,8 +9,8 @@ import { ApiService } from 'pc/browser/src/app/services/storage/api.service';
|
||||
import { LocalService } from 'pc/browser/src/app/services/storage/local.service';
|
||||
import { RemoteService } from 'pc/browser/src/app/services/storage/remote.service';
|
||||
import { TraceService } from 'pc/browser/src/app/services/trace.service';
|
||||
import { EffectService } from 'pc/browser/src/app/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { EffectService } from 'pc/browser/src/app/shared/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import { interval, Subject } from 'rxjs';
|
||||
import { distinct, takeUntil } from 'rxjs/operators';
|
||||
|
||||
@ -185,7 +185,7 @@ export class UserModalComponent implements OnInit, OnDestroy {
|
||||
public store: StoreService,
|
||||
public message: MessageService,
|
||||
public api: ApiService,
|
||||
public eMessage: EoNgFeedbackMessageService,
|
||||
public feedback: EoNgFeedbackMessageService,
|
||||
public effect: EffectService,
|
||||
public dataSource: DataSourceService,
|
||||
public modal: ModalService,
|
||||
@ -235,19 +235,19 @@ export class UserModalComponent implements OnInit, OnDestroy {
|
||||
if (this.store.isLocal) {
|
||||
return;
|
||||
}
|
||||
this.eMessage.error($localize`Oops, server fail`);
|
||||
this.feedback.error($localize`Oops, server fail`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'ping-fail') {
|
||||
this.eMessage.error($localize`Connect failed`);
|
||||
this.feedback.error($localize`Connect failed`);
|
||||
// * 唤起弹窗
|
||||
this.isCheckConnectModalVisible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'ping-success') {
|
||||
this.eMessage.success($localize`Connect success`);
|
||||
this.feedback.success($localize`Connect success`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -403,7 +403,7 @@ export class UserModalComponent implements OnInit, OnDestroy {
|
||||
const isOk = this.validateLoginForm.valid;
|
||||
|
||||
if (!isOk) {
|
||||
this.eMessage.error($localize`Please check you username or password`);
|
||||
this.feedback.error($localize`Please check you username or password`);
|
||||
return;
|
||||
}
|
||||
this.store.clearAuth();
|
||||
@ -413,10 +413,10 @@ export class UserModalComponent implements OnInit, OnDestroy {
|
||||
const [data, err]: any = await this.api.api_userLogin(formData);
|
||||
if (err) {
|
||||
if (err.code === 131000001) {
|
||||
this.eMessage.error($localize`Username must a email`);
|
||||
this.feedback.error($localize`Username must a email`);
|
||||
return;
|
||||
}
|
||||
this.eMessage.error($localize`Please check you username or password`);
|
||||
this.feedback.error($localize`Please check you username or password`);
|
||||
return;
|
||||
}
|
||||
this.trace.setUserID(data.userId);
|
||||
@ -493,10 +493,10 @@ export class UserModalComponent implements OnInit, OnDestroy {
|
||||
// ! Attention: data is array
|
||||
const [data, err]: any = await this.remote.api_workspaceCreate({ titles: [titles] });
|
||||
if (err) {
|
||||
this.eMessage.error($localize`New workspace Failed !`);
|
||||
this.feedback.error($localize`New workspace Failed !`);
|
||||
return;
|
||||
}
|
||||
this.eMessage.success($localize`New workspace successfully !`);
|
||||
this.feedback.success($localize`New workspace successfully !`);
|
||||
this.trace.report('add_workspace_success');
|
||||
const workspace = data.at(0);
|
||||
// * 关闭弹窗
|
||||
@ -564,7 +564,7 @@ export class UserModalComponent implements OnInit, OnDestroy {
|
||||
}))
|
||||
});
|
||||
if (err) {
|
||||
this.eMessage.error($localize`Create Project Failed !`);
|
||||
this.feedback.error($localize`Create Project Failed !`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { NzNotificationRef, NzNotificationService } from 'ng-zorro-antd/notification';
|
||||
import { ElectronService } from 'pc/browser/src/app/core/services';
|
||||
import { ElectronService, WebService } from 'pc/browser/src/app/core/services';
|
||||
import { NewbieGuideComponent } from 'pc/browser/src/app/pages/components/model-article/newbie-guide/newbie-guide.component';
|
||||
import { UpdateLogComponent } from 'pc/browser/src/app/pages/components/model-article/update-log/update-log.component';
|
||||
import { ModalService } from 'pc/browser/src/app/services/modal.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import { filter } from 'rxjs';
|
||||
|
||||
import { SidebarService } from '../layouts/sidebar/sidebar.service';
|
||||
@ -19,12 +23,16 @@ export class PagesComponent implements OnInit {
|
||||
hasShowCookieTips = StorageUtil.get('has_show_cookie_tips');
|
||||
isShowNotification;
|
||||
sidebarViews: any[] = [];
|
||||
|
||||
constructor(
|
||||
private socket: SocketService,
|
||||
public electron: ElectronService,
|
||||
private router: Router,
|
||||
private sidebar: SidebarService,
|
||||
private notification: NzNotificationService
|
||||
private notification: NzNotificationService,
|
||||
private modal: ModalService,
|
||||
private store: StoreService,
|
||||
private web: WebService
|
||||
) {}
|
||||
ngOnInit(): void {
|
||||
// * 通过 socketIO 告知 Node 端,建立 grpc 连接
|
||||
@ -40,6 +48,18 @@ export class PagesComponent implements OnInit {
|
||||
// this.showCookiesTips();
|
||||
// }
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
// TODO: first use
|
||||
const result = this.web.getSystemInfo();
|
||||
const version = result.shift().value;
|
||||
if (!this.electron.isElectron && !this.store.getAppHasInitial && !StorageUtil.get('version')) {
|
||||
this.newbieGuide(version);
|
||||
return;
|
||||
}
|
||||
// if (StorageUtil.get('version') && StorageUtil.get('version') === version) return;
|
||||
// this.updateLog(version);
|
||||
}
|
||||
closeNotification() {
|
||||
this.notification.remove(this.cookieNotification.messageId);
|
||||
}
|
||||
@ -50,6 +70,7 @@ export class PagesComponent implements OnInit {
|
||||
nzPauseOnHover: true
|
||||
});
|
||||
}
|
||||
|
||||
initSidebarVisible(url: string) {
|
||||
if (['home/workspace/overview'].find(val => url.includes(val))) {
|
||||
this.sidebar.visible = false;
|
||||
@ -57,4 +78,38 @@ export class PagesComponent implements OnInit {
|
||||
this.sidebar.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
newbieGuide(version: string) {
|
||||
this.modal.create({
|
||||
nzTitle: $localize`Welcome to Postcat~`,
|
||||
nzWidth: '650px',
|
||||
nzContent: NewbieGuideComponent,
|
||||
nzCancelText: $localize`Got it`,
|
||||
nzBodyStyle: {
|
||||
height: 'calc(100vh* 0.7)',
|
||||
'overflow-y': 'scroll'
|
||||
},
|
||||
nzCentered: true,
|
||||
nzClassName: 'model-article',
|
||||
stayWhenRouterChange: true
|
||||
});
|
||||
StorageUtil.set('version', version);
|
||||
}
|
||||
|
||||
updateLog(version: string) {
|
||||
this.modal.create({
|
||||
nzTitle: $localize`Release Log`,
|
||||
nzWidth: '650px',
|
||||
nzContent: UpdateLogComponent,
|
||||
nzCancelText: $localize`Got it`,
|
||||
nzBodyStyle: {
|
||||
height: 'calc(100vh* 0.7)',
|
||||
'overflow-y': 'scroll'
|
||||
},
|
||||
nzCentered: true,
|
||||
nzClassName: 'model-article',
|
||||
stayWhenRouterChange: true
|
||||
});
|
||||
StorageUtil.set('version', version);
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ import { PagesComponent } from './pages.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [PagesComponent, SidebarComponent, LocalWorkspaceTipComponent, UserModalComponent, ThirdLoginComponent],
|
||||
exports: [],
|
||||
providers: [],
|
||||
schemas: [],
|
||||
exports: [],
|
||||
imports: [
|
||||
ChatgptRobotComponent,
|
||||
PagesRoutingModule,
|
||||
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { StoreService } from '../../store/state.service';
|
||||
import { StoreService } from '../../shared/store/state.service';
|
||||
|
||||
@Injectable()
|
||||
export class RedirectSharedID implements CanActivate {
|
||||
|
@ -1,15 +1,27 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { API_TABS } from 'pc/browser/src/app/pages/workspace/project/api/api-tab.service';
|
||||
import { ApiModule } from 'pc/browser/src/app/pages/workspace/project/api/api.module';
|
||||
import { BASIC_TABS_INFO, TabsConfig } from 'pc/browser/src/app/pages/workspace/project/api/constants/api.model';
|
||||
|
||||
import { NavbarModule } from '../../layouts/navbar/navbar.module';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { ShareRoutingModule } from './share-routing.module';
|
||||
import { ShareComponent } from './view/share-project.component';
|
||||
|
||||
const tabs = API_TABS.map(val => ({ ...val, pathname: `/share${val.pathname}` }));
|
||||
|
||||
@NgModule({
|
||||
imports: [ShareRoutingModule, NavbarModule, CommonModule, SharedModule, ApiModule],
|
||||
declarations: [ShareComponent],
|
||||
providers: []
|
||||
providers: [
|
||||
{
|
||||
provide: BASIC_TABS_INFO,
|
||||
useValue: {
|
||||
BASIC_TABS: tabs,
|
||||
pathByName: tabs.reduce((acc, curr) => ({ ...acc, [curr.uniqueName]: curr.pathname }), {})
|
||||
} as TabsConfig
|
||||
}
|
||||
]
|
||||
})
|
||||
export class ShareProjectModule {}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { GroupComponent } from 'pc/browser/src/app/pages/workspace/project/api/group/group.component';
|
||||
import { GroupComponent } from 'pc/browser/src/app/pages/workspace/project/api/group-edit/group.component';
|
||||
|
||||
import { ShareComponent } from './view/share-project.component';
|
||||
|
||||
@ -24,6 +24,14 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'test',
|
||||
loadChildren: () => import('../workspace/project/api/http/test/api-test.module').then(m => m.ApiTestModule)
|
||||
},
|
||||
{
|
||||
path: 'case',
|
||||
loadChildren: () => import('../workspace/project/api/http/test/api-test.module').then(m => m.ApiTestModule)
|
||||
},
|
||||
{
|
||||
path: 'mock',
|
||||
loadChildren: () => import('../workspace/project/api/http/mock/mock.module').then(m => m.MockModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'eo-share',
|
||||
|
@ -5,8 +5,8 @@ import { autorun } from 'mobx';
|
||||
import { ApiService } from 'pc/browser/src/app/services/storage/api.service';
|
||||
|
||||
import { ModalService } from '../../../../services/modal.service';
|
||||
import { EffectService } from '../../../../store/effect.service';
|
||||
import { StoreService } from '../../../../store/state.service';
|
||||
import { EffectService } from '../../../../shared/store/effect.service';
|
||||
import { StoreService } from '../../../../shared/store/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'eo-workspace-setting',
|
||||
@ -78,7 +78,7 @@ export class WorkspaceSettingComponent {
|
||||
];
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private message: EoNgFeedbackMessageService,
|
||||
private feedback: EoNgFeedbackMessageService,
|
||||
private api: ApiService,
|
||||
private store: StoreService,
|
||||
private modal: ModalService,
|
||||
@ -119,10 +119,10 @@ export class WorkspaceSettingComponent {
|
||||
workSpaceUuids: [wid]
|
||||
});
|
||||
if (err) {
|
||||
this.message.error($localize`Delete failed !`);
|
||||
this.feedback.error($localize`Failed to delete`);
|
||||
return;
|
||||
}
|
||||
this.message.success($localize`Delete Succeeded`);
|
||||
this.feedback.success($localize`Successfully deleted`);
|
||||
await this.effect.updateWorkspaceList();
|
||||
await this.effect.switchWorkspace(this.store.getLocalWorkspace.workSpaceUuid);
|
||||
}
|
||||
@ -146,10 +146,10 @@ export class WorkspaceSettingComponent {
|
||||
title
|
||||
});
|
||||
if (err) {
|
||||
this.message.error($localize`Edit workspace failed`);
|
||||
this.feedback.error($localize`Edit workspace failed`);
|
||||
return;
|
||||
}
|
||||
this.message.success($localize`Edit workspace successfully !`);
|
||||
this.feedback.success($localize`Edit workspace successfully !`);
|
||||
|
||||
//Rest Current Workspace
|
||||
await this.effect.updateWorkspaceList();
|
||||
|
@ -7,7 +7,7 @@ import { TraceService } from 'pc/browser/src/app/services/trace.service';
|
||||
|
||||
import { MemberListComponent } from '../../../../components/member-list/member-list.component';
|
||||
import { MessageService } from '../../../../services/message/message.service';
|
||||
import { StoreService } from '../../../../store/state.service';
|
||||
import { StoreService } from '../../../../shared/store/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'eo-workspace-member',
|
||||
@ -95,7 +95,7 @@ export class WorkspaceMemberComponent implements OnInit {
|
||||
isInvateModalVisible = false;
|
||||
constructor(
|
||||
public store: StoreService,
|
||||
private eMessage: EoNgFeedbackMessageService,
|
||||
private feedback: EoNgFeedbackMessageService,
|
||||
public member: MemberService,
|
||||
private message: MessageService,
|
||||
private trace: TraceService,
|
||||
@ -150,17 +150,17 @@ export class WorkspaceMemberComponent implements OnInit {
|
||||
const btnSelectRunning = async () => {
|
||||
const userIds = this.userCache;
|
||||
if (userIds.length === 0) {
|
||||
this.eMessage.error($localize`Please select a member`);
|
||||
this.feedback.error($localize`Please select a member`);
|
||||
return;
|
||||
}
|
||||
|
||||
const [aData, aErr]: any = await this.member.addMember(userIds);
|
||||
if (aErr) {
|
||||
this.eMessage.error($localize`Add member failed`);
|
||||
this.feedback.error($localize`Add member failed`);
|
||||
return;
|
||||
}
|
||||
this.trace.report('add_workspace_member_success');
|
||||
this.eMessage.success($localize`Add member successfully`);
|
||||
this.feedback.success($localize`Add member successfully`);
|
||||
|
||||
// * 关闭弹窗
|
||||
this.isInvateModalVisible = false;
|
||||
|
@ -2,8 +2,8 @@ import { Injectable } from '@angular/core';
|
||||
import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
|
||||
import { autorun, toJS } from 'mobx';
|
||||
import { ApiService } from 'pc/browser/src/app/services/storage/api.service';
|
||||
import { EffectService } from 'pc/browser/src/app/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { EffectService } from 'pc/browser/src/app/shared/store/effect.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
|
||||
import { Role, ROLE_TITLE_BY_ID } from '../../../../shared/models/member.model';
|
||||
|
||||
|
@ -4,8 +4,8 @@ import { OperateProjectFormComponent } from 'pc/browser/src/app/pages/workspace/
|
||||
import { ModalService } from 'pc/browser/src/app/services/modal.service';
|
||||
import { ApiService } from 'pc/browser/src/app/services/storage/api.service';
|
||||
|
||||
import { EffectService } from '../../../../store/effect.service';
|
||||
import { StoreService } from '../../../../store/state.service';
|
||||
import { EffectService } from '../../../../shared/store/effect.service';
|
||||
import { StoreService } from '../../../../shared/store/state.service';
|
||||
import { ProjectListService } from './project-list.service';
|
||||
|
||||
@Component({
|
||||
|
@ -4,8 +4,8 @@ import { TraceService } from 'pc/browser/src/app/services/trace.service';
|
||||
|
||||
import { SettingService } from '../../../../components/system-setting/settings.service';
|
||||
import { ModalService } from '../../../../services/modal.service';
|
||||
import { EffectService } from '../../../../store/effect.service';
|
||||
import { StoreService } from '../../../../store/state.service';
|
||||
import { EffectService } from '../../../../shared/store/effect.service';
|
||||
import { StoreService } from '../../../../shared/store/state.service';
|
||||
import { OperateProjectFormComponent } from '../../project/components/operate-project-form.compoent';
|
||||
type ListType = 'list' | 'card';
|
||||
@Injectable({
|
||||
|
@ -6,7 +6,7 @@ import { MessageService } from 'pc/browser/src/app/services/message';
|
||||
import { waitNextTick } from 'pc/browser/src/app/shared/utils/index.utils';
|
||||
|
||||
import { FeatureControlService } from '../../../core/services/feature-control/feature-control.service';
|
||||
import { StoreService } from '../../../store/state.service';
|
||||
import { StoreService } from '../../../shared/store/state.service';
|
||||
import { ProjectListService } from './project-list/project-list.service';
|
||||
|
||||
@Component({
|
||||
@ -22,7 +22,7 @@ export class WorkspaceOverviewComponent implements OnInit {
|
||||
public projectList: ProjectListService,
|
||||
public store: StoreService,
|
||||
private router: Router,
|
||||
private message: EoNgFeedbackMessageService,
|
||||
private feedback: EoNgFeedbackMessageService,
|
||||
public feature: FeatureControlService,
|
||||
private postMessage: MessageService
|
||||
) {}
|
||||
@ -31,7 +31,7 @@ export class WorkspaceOverviewComponent implements OnInit {
|
||||
this.router.navigate(['/home/workspace/overview/member']);
|
||||
}
|
||||
if (this.store.isLocal) {
|
||||
this.message.info($localize`You should switch to cloud workspace and invite members.`);
|
||||
this.feedback.info($localize`You should switch to cloud workspace and invite members.`);
|
||||
return;
|
||||
}
|
||||
await waitNextTick();
|
||||
|
@ -33,9 +33,13 @@ const routes: Routes = [
|
||||
path: 'test',
|
||||
loadChildren: () => import('./http/test/api-test.module').then(m => m.ApiTestModule)
|
||||
},
|
||||
{
|
||||
path: 'case',
|
||||
loadChildren: () => import('./http/test/api-test.module').then(m => m.ApiTestModule)
|
||||
},
|
||||
{
|
||||
path: 'mock',
|
||||
loadChildren: () => import('./http/mock/api-mock.module').then(m => m.ApiMockModule)
|
||||
loadChildren: () => import('./http/mock/mock.module').then(m => m.MockModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -54,7 +58,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'group',
|
||||
loadChildren: () => import('./group/group.module').then(m => m.GroupModule)
|
||||
loadChildren: () => import('./group-edit/group.module').then(m => m.GroupModule)
|
||||
}
|
||||
// {
|
||||
// path: 'grpc',
|
||||
|
@ -4,25 +4,32 @@ import { EoNgTreeModule } from 'eo-ng-tree';
|
||||
import { NzEmptyModule } from 'ng-zorro-antd/empty';
|
||||
import { EoMonacoEditorModule } from 'pc/browser/src/app/components/eo-ui/monaco-editor/monaco.module';
|
||||
import { EoTableProModule } from 'pc/browser/src/app/components/eo-ui/table-pro/table-pro.module';
|
||||
import { ActionComponent } from 'pc/browser/src/app/pages/workspace/project/api/components/action/action.component';
|
||||
import { ApiMockTableComponent } from 'pc/browser/src/app/pages/workspace/project/api/components/api-mock-table.component';
|
||||
import { ApiScriptComponent } from 'pc/browser/src/app/pages/workspace/project/api/components/api-script/api-script.component';
|
||||
import { ApiTestFormComponent } from 'pc/browser/src/app/pages/workspace/project/api/components/api-test-form/api-test-form.component';
|
||||
import { ApiTestResultHeaderComponent } from 'pc/browser/src/app/pages/workspace/project/api/components/api-test-result-header/api-test-result-header.component';
|
||||
import { ParamsImportComponent } from 'pc/browser/src/app/pages/workspace/project/api/components/params-import/params-import.component';
|
||||
import { ApiMockService } from 'pc/browser/src/app/pages/workspace/project/api/http/mock/api-mock.service';
|
||||
import { ApiTestService } from 'pc/browser/src/app/pages/workspace/project/api/http/test/api-test.service';
|
||||
import { ApiTableService } from 'pc/browser/src/app/pages/workspace/project/api/service/api-table.service';
|
||||
import { SharedModule } from 'pc/browser/src/app/shared/shared.module';
|
||||
|
||||
import { ApiFormaterPipe } from './pipe/api-formater.pipe';
|
||||
import { ApiParamsNumPipe } from './pipe/api-param-num.pipe';
|
||||
|
||||
const COMPONENTS = [ApiTestFormComponent, ParamsImportComponent, ApiTestResultHeaderComponent, ApiMockTableComponent];
|
||||
const COMPONENTS = [
|
||||
ApiTestFormComponent,
|
||||
ApiScriptComponent,
|
||||
ActionComponent,
|
||||
ParamsImportComponent,
|
||||
ApiTestResultHeaderComponent,
|
||||
ApiMockTableComponent
|
||||
];
|
||||
const SHARE_UI = [EoTableProModule, EoNgTabsModule];
|
||||
const SHARE_PIPE = [ApiFormaterPipe, ApiParamsNumPipe];
|
||||
@NgModule({
|
||||
imports: [SharedModule, EoMonacoEditorModule, EoNgTreeModule, NzEmptyModule, ...SHARE_UI],
|
||||
declarations: [...COMPONENTS, ...SHARE_PIPE],
|
||||
providers: [ApiTableService, ApiTestService, ApiMockService],
|
||||
providers: [ApiTableService],
|
||||
exports: [...COMPONENTS, ...SHARE_PIPE, ...SHARE_UI]
|
||||
})
|
||||
export class ApiSharedModule {}
|
||||
|
@ -1,135 +1,173 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { autorun } from 'mobx';
|
||||
import { TabItem } from 'pc/browser/src/app/components/eo-ui/tab/tab.model';
|
||||
import { requestMethodMap } from 'pc/browser/src/app/pages/workspace/project/api/api.model';
|
||||
import { autorun, reaction, toJS } from 'mobx';
|
||||
import { EditTabViewComponent, TabItem } from 'pc/browser/src/app/components/eo-ui/tab/tab.model';
|
||||
import { BASIC_TABS_INFO, requestMethodMap, TabsConfig } from 'pc/browser/src/app/pages/workspace/project/api/constants/api.model';
|
||||
import { ApiStoreService } from 'pc/browser/src/app/pages/workspace/project/api/store/api-state.service';
|
||||
import { Message } from 'pc/browser/src/app/services/message';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { GroupModuleType, GroupType, ViewGroup } from 'pc/browser/src/app/services/storage/db/models';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import { flatTree } from 'pc/browser/src/app/shared/utils/tree/tree.utils';
|
||||
import { debounceTime, Subject } from 'rxjs';
|
||||
|
||||
import { EoTabComponent } from '../../../../components/eo-ui/tab/tab.component';
|
||||
import { MessageService } from '../../../../services/message';
|
||||
import { isEmptyObj } from '../../../../shared/utils/index.utils';
|
||||
|
||||
import { eoDeepCopy as pcDeepCopy, isEmptyObj } from '../../../../shared/utils/index.utils';
|
||||
export enum PageUniqueName {
|
||||
HttpTest = 'api-http-test',
|
||||
HttpDetail = 'api-http-detail',
|
||||
HttpEdit = 'api-http-edit',
|
||||
HttpCase = 'api-http-case-edit',
|
||||
HttpMock = 'api-http-mock-edit',
|
||||
WsTest = 'api-ws-test',
|
||||
EnvEdit = 'project-env-edit',
|
||||
GroupEdit = 'project-group'
|
||||
}
|
||||
export const API_TABS: Array<Partial<TabItem>> = [
|
||||
{
|
||||
pathname: '/http/test',
|
||||
uniqueName: PageUniqueName.HttpTest,
|
||||
type: 'edit',
|
||||
title: $localize`New Request`,
|
||||
extends: { method: 'POST' }
|
||||
},
|
||||
{
|
||||
pathname: '/env/edit',
|
||||
uniqueName: PageUniqueName.EnvEdit,
|
||||
type: 'edit',
|
||||
icon: 'application',
|
||||
title: $localize`New Environment`
|
||||
},
|
||||
{
|
||||
pathname: '/group/edit',
|
||||
uniqueName: PageUniqueName.GroupEdit,
|
||||
type: 'edit',
|
||||
icon: 'folder-close',
|
||||
title: $localize`:@@AddGroup:New Group`
|
||||
},
|
||||
{
|
||||
pathname: '/http/edit',
|
||||
uniqueName: PageUniqueName.HttpEdit,
|
||||
isFixed: true,
|
||||
type: 'edit',
|
||||
title: $localize`New API`
|
||||
},
|
||||
{ pathname: '/http/detail', uniqueName: PageUniqueName.HttpDetail, type: 'preview', title: $localize`Preview` },
|
||||
{
|
||||
pathname: '/ws/test',
|
||||
uniqueName: PageUniqueName.WsTest,
|
||||
isFixed: true,
|
||||
type: 'edit',
|
||||
extends: { method: 'WS' },
|
||||
title: $localize`New Websocket`
|
||||
},
|
||||
{ pathname: '/http/case', icon: 'diy-test', uniqueName: PageUniqueName.HttpCase, type: 'edit', title: $localize`New Case` },
|
||||
{ pathname: '/http/mock', icon: 'mock', uniqueName: PageUniqueName.HttpMock, type: 'edit', title: $localize`New Mock` }
|
||||
];
|
||||
interface TabEvent {
|
||||
when: 'activated' | 'editing' | 'saved' | 'afterTested';
|
||||
currentTabID: TabItem['uuid'];
|
||||
model?: any;
|
||||
}
|
||||
@Injectable()
|
||||
export class ApiTabService {
|
||||
componentRef;
|
||||
componentRef: EditTabViewComponent | any;
|
||||
apiTabComponent: EoTabComponent;
|
||||
// Set current tab type:'preview'|'edit' for later judgment
|
||||
get currentComponentTab(): Partial<TabItem> {
|
||||
return this.BASIC_TABS.find(val => this.router.url.includes(val.pathname));
|
||||
}
|
||||
private changeContent$: Subject<any> = new Subject();
|
||||
SHARE_TABS: Array<Partial<TabItem>> = [
|
||||
{
|
||||
pathname: '/share/http/test',
|
||||
id: 'share-api-test',
|
||||
type: 'edit',
|
||||
title: $localize`New Request`,
|
||||
extends: { method: 'POST' }
|
||||
},
|
||||
{ pathname: '/share/http/detail', id: 'share-api-detail', type: 'preview', title: $localize`Preview` },
|
||||
{ pathname: '/share/group/edit', id: 'share-group-edit', type: 'preview', title: $localize`Preview` },
|
||||
{
|
||||
pathname: '/share/ws/test',
|
||||
id: 'share-api-test',
|
||||
isFixed: true,
|
||||
type: 'preview',
|
||||
extends: { method: 'WS' },
|
||||
title: $localize`New Websocket`
|
||||
}
|
||||
];
|
||||
API_TABS: Array<Partial<TabItem>> = [
|
||||
{
|
||||
pathname: '/home/workspace/project/api/http/test',
|
||||
id: 'api-http-test',
|
||||
type: 'edit',
|
||||
title: $localize`New Request`,
|
||||
extends: { method: 'POST' }
|
||||
},
|
||||
{
|
||||
pathname: '/home/workspace/project/api/env/edit',
|
||||
id: 'project-env',
|
||||
type: 'edit',
|
||||
icon: 'application',
|
||||
title: $localize`New Environment`
|
||||
},
|
||||
{
|
||||
pathname: '/home/workspace/project/api/group/edit',
|
||||
id: 'project-group',
|
||||
type: 'edit',
|
||||
icon: 'folder-close',
|
||||
title: $localize`:@@AddGroup:New Group`
|
||||
},
|
||||
{ pathname: '/home/workspace/project/api/http/edit', id: 'api-http-edit', isFixed: true, type: 'edit', title: $localize`New API` },
|
||||
{ pathname: '/home/workspace/project/api/http/detail', id: 'api-http-detail', type: 'preview', title: $localize`Preview` },
|
||||
{
|
||||
pathname: '/home/workspace/project/api/ws/test',
|
||||
id: 'api-ws-test',
|
||||
isFixed: true,
|
||||
type: 'edit',
|
||||
extends: { method: 'WS' },
|
||||
title: $localize`New Websocket`
|
||||
},
|
||||
{ pathname: '/home/workspace/project/api/http/mock', id: 'api-http-mock', type: 'preview', title: 'Mock' }
|
||||
];
|
||||
private changeContent$: Subject<TabEvent> = new Subject();
|
||||
BASIC_TABS: Array<Partial<TabItem>>;
|
||||
constructor(private messageService: MessageService, private router: Router, private store: StoreService) {
|
||||
constructor(
|
||||
private messageService: MessageService,
|
||||
private router: Router,
|
||||
private globalStore: StoreService,
|
||||
private store: ApiStoreService,
|
||||
@Inject(BASIC_TABS_INFO) public tabsConfig: TabsConfig
|
||||
) {
|
||||
this.changeContent$.pipe(debounceTime(150)).subscribe(inData => {
|
||||
this.afterContentChanged(inData);
|
||||
this.afterTabContentChanged(inData);
|
||||
});
|
||||
this.messageService.get().subscribe((inArg: Message) => {
|
||||
this.watchApiChange(inArg);
|
||||
});
|
||||
autorun(() => {
|
||||
this.BASIC_TABS = this.store.isShare ? this.SHARE_TABS : this.API_TABS;
|
||||
if (inArg.type !== 'tabContentInit') return;
|
||||
this.updateTabContent(inArg.data.uuid);
|
||||
});
|
||||
this.BASIC_TABS = this.tabsConfig.BASIC_TABS;
|
||||
this.closeTabAfterResourceRemove();
|
||||
}
|
||||
watchApiChange(inArg: Message) {
|
||||
switch (inArg.type) {
|
||||
case 'deleteApiSuccess': {
|
||||
//Close those tab who has been deleted
|
||||
/**
|
||||
* Watch API/Group/Case/Env/Mock change for handle tab status to fit content
|
||||
*
|
||||
* ?It is optimal to control Tab closing through a specific event transmission ID, but this event will always be ignored in use
|
||||
*
|
||||
* @param inArg
|
||||
*/
|
||||
closeTabAfterResourceRemove() {
|
||||
const checkTabIsExist = (groups: ViewGroup[], tab: TabItem) => {
|
||||
const isExist = groups.some(group => {
|
||||
//TODO check group.id is same as resource id
|
||||
if (!tab.params?.uuid) return true;
|
||||
const modelID = Number(tab.params.uuid) || tab.params.uuid;
|
||||
if (modelID === group.id && group.type === GroupType.UserCreated && tab.uniqueName === PageUniqueName.GroupEdit) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
modelID === group.relationInfo?.apiUuid &&
|
||||
group.module === GroupModuleType.API &&
|
||||
[PageUniqueName.HttpEdit, PageUniqueName.HttpDetail, PageUniqueName.HttpTest].includes(tab.uniqueName as PageUniqueName)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
modelID === group.relationInfo?.apiCaseUuid &&
|
||||
group.module === GroupModuleType.Case &&
|
||||
tab.uniqueName === PageUniqueName.HttpCase
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (modelID === group.relationInfo?.id && group.module === GroupModuleType.Mock && tab.uniqueName === PageUniqueName.HttpMock) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return isExist;
|
||||
};
|
||||
//Delete group/api/case/mock
|
||||
reaction(
|
||||
() => this.store.getGroupList,
|
||||
(value, previousValue) => {
|
||||
const currentFlatTree = flatTree(value);
|
||||
const previousFlatTres = flatTree(previousValue);
|
||||
const hasDeleted = currentFlatTree.length < previousFlatTres.length;
|
||||
if (!hasDeleted) return;
|
||||
|
||||
//Close them
|
||||
const closeTabIDs = this.apiTabComponent
|
||||
.getTabs()
|
||||
.filter((val: TabItem) => val.pathname.includes('home/workspace/project/api/http') && inArg.data.uuids.includes(val.params.uuid))
|
||||
.filter((tab: TabItem) => !checkTabIsExist(currentFlatTree, tab))
|
||||
.map(val => val.uuid);
|
||||
this.apiTabComponent.batchCloseTab(closeTabIDs);
|
||||
break;
|
||||
}
|
||||
case 'deleteEnvSuccess': {
|
||||
);
|
||||
|
||||
//Delete env
|
||||
reaction(
|
||||
() => this.store.getEnvList,
|
||||
(value, previousValue) => {
|
||||
const hasDeleted = value.length < previousValue.length;
|
||||
if (!hasDeleted) return;
|
||||
|
||||
const closeTabIDs = this.apiTabComponent
|
||||
.getTabs()
|
||||
.filter(
|
||||
(val: TabItem) =>
|
||||
val.pathname.includes('home/workspace/project/api/env/edit') && inArg.data.uuids.includes(Number(val.params.uuid))
|
||||
(tab: TabItem) =>
|
||||
tab.params?.uuid && value.every(env => env.id.toString() !== tab.params.uuid) && tab.uniqueName === PageUniqueName.EnvEdit
|
||||
)
|
||||
.map(val => val.uuid);
|
||||
this.apiTabComponent.batchCloseTab(closeTabIDs);
|
||||
break;
|
||||
}
|
||||
case 'deleteGroupSuccess': {
|
||||
const closeTabIDs = this.apiTabComponent
|
||||
.getTabs()
|
||||
.filter(
|
||||
(val: TabItem) =>
|
||||
val.pathname.includes('home/workspace/project/api/group/edit') && inArg.data.uuids.includes(Number(val.params.uuid))
|
||||
)
|
||||
.map(val => val.uuid);
|
||||
this.apiTabComponent.batchCloseTab(closeTabIDs);
|
||||
break;
|
||||
}
|
||||
case 'tabContentInit': {
|
||||
this.updateChildView(this.router.url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
batchCloseTabById(uuidList) {
|
||||
const result = this.apiTabComponent
|
||||
.getTabs()
|
||||
.filter(it => uuidList.includes(it.params.uuid))
|
||||
.map(it => it.uuid);
|
||||
this.apiTabComponent.batchCloseTab(result);
|
||||
);
|
||||
}
|
||||
onChildComponentInit(componentRef) {
|
||||
this.componentRef = componentRef;
|
||||
@ -138,35 +176,87 @@ export class ApiTabService {
|
||||
* After tab component/child component init
|
||||
*/
|
||||
onAllComponentInit() {
|
||||
const url = this.router.url;
|
||||
this.updateChildView(url);
|
||||
//We need to wait for tabComponent and childComponent onInit finished
|
||||
this.updateTabContent();
|
||||
}
|
||||
private bindChildComponentChangeEvent() {
|
||||
if (!this.componentRef) {
|
||||
return;
|
||||
}
|
||||
const url = this.router.url;
|
||||
//Bind event tab
|
||||
const bindTabID = this.apiTabComponent.getCurrentTab()?.uuid;
|
||||
this.componentRef.eoOnInit = {
|
||||
emit: model => {
|
||||
this.afterContentChanged({ when: 'init', url, model });
|
||||
//Current is current selected tab
|
||||
const currentTab = this.apiTabComponent.getCurrentTab();
|
||||
if (!model) {
|
||||
pcConsole.warn('[api-tab] eoOnInit cannot pass in null value, this tab will be closed automatically');
|
||||
this.apiTabComponent.batchCloseTab([currentTab.uuid]);
|
||||
return;
|
||||
}
|
||||
//resourceID
|
||||
let modelID: number;
|
||||
switch (currentTab.uniqueName) {
|
||||
case PageUniqueName.HttpEdit:
|
||||
case PageUniqueName.HttpTest:
|
||||
case PageUniqueName.HttpEdit: {
|
||||
modelID = model.apiUuid;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
modelID = model.uuid || model.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//1. The currently active tab is not the one that initiated the request
|
||||
const notCurrentTab = currentTab.uuid !== bindTabID;
|
||||
//2. the request response is not what the current active tab needs
|
||||
const notCurrentResource = modelID && currentTab.params?.uuid && currentTab.params?.uuid !== modelID.toString();
|
||||
const modelFromOtherTab = notCurrentTab || notCurrentResource;
|
||||
if (!modelFromOtherTab) {
|
||||
this.afterTabContentChanged({ when: 'activated', currentTabID: bindTabID, model });
|
||||
return;
|
||||
}
|
||||
|
||||
//! The previous Tab's(bindTab) request may overwrite the current Tab's(currentTab) data.
|
||||
pcConsole.warn(
|
||||
`The current tab data will be restored from the cache to prevent it from being overwritten by the result of the previous Tab asynchronous request.
|
||||
previous tab:${model.name}
|
||||
current tab:${currentTab.title}`
|
||||
);
|
||||
|
||||
//* When data inconsistent, we need to manually reset the model from cache
|
||||
const hasCache = !!currentTab?.content?.[currentTab.uniqueName];
|
||||
if (!currentTab.isLoading && hasCache) {
|
||||
//If the current tab is not the one that initiated the request, we need to restore the data from the cache
|
||||
this.afterTabActivated(currentTab);
|
||||
}
|
||||
|
||||
const actuallyID = this.apiTabComponent.getTabByParamsID(modelID.toString())?.uuid || bindTabID;
|
||||
this.afterTabContentChanged({ when: 'activated', currentTabID: actuallyID, model });
|
||||
}
|
||||
};
|
||||
|
||||
//Edit page has save/editing event
|
||||
if (this.currentComponentTab.type === 'edit') {
|
||||
this.componentRef.afterSaved = {
|
||||
emit: model => {
|
||||
this.afterContentChanged({ when: 'saved', url, model });
|
||||
this.afterTabContentChanged({ when: 'saved', currentTabID: bindTabID, model });
|
||||
}
|
||||
};
|
||||
this.componentRef.modelChange = {
|
||||
emit: model => {
|
||||
this.changeContent$.next({ when: 'editing', url, model });
|
||||
this.changeContent$.next({ when: 'editing', currentTabID: bindTabID, model });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//Test page has tested event
|
||||
if (this.currentComponentTab.pathname.includes('test')) {
|
||||
this.componentRef.afterTested = {
|
||||
emit: model => {
|
||||
this.afterContentChanged({ when: 'afterTested', url, model });
|
||||
this.afterTabContentChanged({ when: 'afterTested', currentTabID: bindTabID, model });
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -180,131 +270,188 @@ export class ApiTabService {
|
||||
if (!needSave) {
|
||||
return;
|
||||
}
|
||||
this.componentRef.saveApi();
|
||||
this.componentRef.beforeTabClose();
|
||||
}
|
||||
getCurrentTabCache(currentTab: TabItem) {
|
||||
const contentID = currentTab.uniqueName;
|
||||
//Get tab from cache
|
||||
return {
|
||||
hasCache: !!currentTab?.content?.[contentID],
|
||||
model: currentTab?.content?.[contentID] || null,
|
||||
initialModel: currentTab?.baseContent?.[contentID] || null
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Reflesh data after Tab init
|
||||
* Call tab component afterTabActivated
|
||||
*
|
||||
* @param currentTab
|
||||
*/
|
||||
afterTabActivated(currentTab: TabItem) {
|
||||
const cacheResult = this.getCurrentTabCache(currentTab);
|
||||
this.componentRef.model = cacheResult.model;
|
||||
this.componentRef.initialModel = cacheResult.initialModel;
|
||||
|
||||
this.componentRef.afterTabActivated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflesh tab content[childComponent] after Tab activated
|
||||
*
|
||||
* @param lastRouter
|
||||
* @param currentRouter
|
||||
* @returns
|
||||
*/
|
||||
updateChildView(url) {
|
||||
updateTabContent(uuid?) {
|
||||
if (!this.apiTabComponent) {
|
||||
return;
|
||||
}
|
||||
this.bindChildComponentChangeEvent();
|
||||
|
||||
if (!this.componentRef?.init) {
|
||||
this.changeContent$.next({ when: 'init', url });
|
||||
pcConsole.error(
|
||||
'Child componentRef need has init function for reflesh data when router change,Please add init function in child component'
|
||||
);
|
||||
return;
|
||||
}
|
||||
//?Why should use getCurrentTab()?
|
||||
//?Why should use getCurrentTab() directly
|
||||
//Because maybe current tab has't finish init
|
||||
const currentTab = this.apiTabComponent.getExistTabByUrl(url);
|
||||
const currentTab = uuid ? this.apiTabComponent.getTabByID(uuid) : this.apiTabComponent.getCurrentTab();
|
||||
|
||||
if (!currentTab) {
|
||||
return;
|
||||
}
|
||||
const contentID = currentTab.id;
|
||||
if (!this.componentRef?.afterTabActivated) {
|
||||
this.changeContent$.next({ when: 'activated', currentTabID: currentTab.uuid });
|
||||
pcConsole.error(
|
||||
'Child componentRef need has afterTabActivated function for reflesh data when router change,Please add afterTabActivated function in child component'
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.afterTabActivated(currentTab);
|
||||
}
|
||||
/**
|
||||
* Generate tab header info,title,method,icon and so on
|
||||
*
|
||||
* @param currentTab
|
||||
* @param model
|
||||
* @returns
|
||||
*/
|
||||
getTabHeaderInfo(currentTab, model): { title: string; method: string } {
|
||||
const result = {
|
||||
title: model.name,
|
||||
method: ''
|
||||
};
|
||||
result.title = model.name;
|
||||
result.method = requestMethodMap[model.apiAttrInfo?.requestMethod];
|
||||
|
||||
//Get tab from cache
|
||||
if (!currentTab.disabledCache) {
|
||||
this.componentRef.model = currentTab?.content?.[contentID] || null;
|
||||
this.componentRef.initialModel = currentTab?.baseContent?.[contentID] || null;
|
||||
} else {
|
||||
this.componentRef.model = null;
|
||||
this.componentRef.initialModel = null;
|
||||
const isTestPage = [PageUniqueName.HttpCase, PageUniqueName.HttpTest, PageUniqueName.WsTest].includes(currentTab.uniqueName);
|
||||
const isEmptyPage = !model.uuid;
|
||||
|
||||
if (!isTestPage) {
|
||||
if (isEmptyPage) {
|
||||
result.title = result.title || this.BASIC_TABS.find(val => val.pathname === currentTab.pathname).title;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
this.componentRef.init();
|
||||
//Test page,generate title and method from model.url
|
||||
switch (currentTab.uniqueName) {
|
||||
case PageUniqueName.WsTest: {
|
||||
result.method = 'WS';
|
||||
break;
|
||||
}
|
||||
case PageUniqueName.HttpTest: {
|
||||
result.method = requestMethodMap[model.request.apiAttrInfo?.requestMethod];
|
||||
break;
|
||||
}
|
||||
}
|
||||
//Only Untitle request need set url to tab title
|
||||
const originTitle = this.BASIC_TABS.find(val => val.pathname === currentTab.pathname)?.title;
|
||||
const isHistoryPage = currentTab.params?.uuid?.includes('history_');
|
||||
if (!model.request.uuid || isHistoryPage) {
|
||||
result.title = model.request.uri || originTitle;
|
||||
} else {
|
||||
result.title = model.request.name || originTitle;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
updateTab(currentTab, inData) {
|
||||
updateTab(currentTab: TabItem, inData: TabEvent) {
|
||||
const model = inData.model;
|
||||
const contentID = currentTab.id;
|
||||
if (!model || isEmptyObj(model)) return;
|
||||
|
||||
const contentID = currentTab.uniqueName;
|
||||
// if (!currentTab.baseContent) {
|
||||
// console.error('nononononnononononnononononnononononnononononnononononnonononon baseContent lose', inData.when, currentTab.uuid);
|
||||
// }
|
||||
//Set tabItem
|
||||
const replaceTab: Partial<TabItem> = {
|
||||
hasChanged: currentTab.hasChanged,
|
||||
isLoading: false,
|
||||
extends: {}
|
||||
};
|
||||
if (model && !isEmptyObj(model)) {
|
||||
//Set title/method
|
||||
replaceTab.title = model.name;
|
||||
replaceTab.extends.method = requestMethodMap[model.apiAttrInfo?.requestMethod];
|
||||
if (currentTab.pathname.includes('test')) {
|
||||
if (currentTab.pathname === '/home/workspace/project/api/ws/test') {
|
||||
replaceTab.extends.method = 'WS';
|
||||
} else {
|
||||
replaceTab.extends.method = requestMethodMap[model.request.apiAttrInfo?.requestMethod];
|
||||
}
|
||||
//Only Untitle request need set url to tab title
|
||||
const originTitle = this.BASIC_TABS.find(val => val.pathname === currentTab.pathname)?.title;
|
||||
if (!model.request.uuid || (currentTab.params.uuid && currentTab.params.uuid.includes('history_'))) {
|
||||
replaceTab.title = model.request.uri || originTitle;
|
||||
} else {
|
||||
replaceTab.title = model.request.name || originTitle;
|
||||
}
|
||||
} else if (!model.uuid) {
|
||||
replaceTab.title = replaceTab.title || this.BASIC_TABS.find(val => val.pathname === currentTab.pathname).title;
|
||||
}
|
||||
//Only hasChanged edit page storage data
|
||||
if (currentTab.type === 'edit') {
|
||||
let currentHasChanged = currentTab.extends?.hasChanged?.[contentID] || false;
|
||||
switch (inData.when) {
|
||||
case 'editing': {
|
||||
// Saved APIs do not need to verify changes
|
||||
if (currentTab.module !== 'test' || !currentTab.params.uuid || currentTab.params.uuid.includes('history')) {
|
||||
//Set hasChange
|
||||
if (!this.componentRef?.isFormChange) {
|
||||
throw new Error(
|
||||
`EO_ERROR:Child componentRef[${this.componentRef.constructor.name}] need has isFormChange function check model change`
|
||||
);
|
||||
}
|
||||
currentHasChanged = this.componentRef.isFormChange();
|
||||
} else {
|
||||
currentHasChanged = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'saved': {
|
||||
currentHasChanged = false;
|
||||
}
|
||||
}
|
||||
//* Share change status within all content page
|
||||
replaceTab.extends.hasChanged = currentTab.extends?.hasChanged || {};
|
||||
replaceTab.extends.hasChanged[contentID] = currentHasChanged;
|
||||
// Editiable tab share hasChanged data
|
||||
if (!currentHasChanged && currentTab.extends?.hasChanged) {
|
||||
const otherEditableTabs = this.BASIC_TABS.filter(val => val.type === 'edit' && val.id !== contentID);
|
||||
currentHasChanged = otherEditableTabs.some(tabItem => currentTab.extends?.hasChanged[tabItem.id]);
|
||||
}
|
||||
replaceTab.hasChanged = currentHasChanged;
|
||||
// Set storage
|
||||
//Set baseContent
|
||||
if (['init', 'saved'].includes(inData.when)) {
|
||||
const initialModel = this.componentRef.initialModel;
|
||||
replaceTab.baseContent = inData.when === 'saved' ? {} : currentTab.baseContent || {};
|
||||
replaceTab.baseContent[contentID] = initialModel && !isEmptyObj(initialModel) ? initialModel : null;
|
||||
}
|
||||
//Set content
|
||||
replaceTab.content = inData.when === 'saved' ? {} : currentTab.content || {};
|
||||
replaceTab.content[contentID] = model && !isEmptyObj(model) ? model : null;
|
||||
}
|
||||
//* Set title/method
|
||||
const tabHeaderInfo = this.getTabHeaderInfo(currentTab, model);
|
||||
replaceTab.title = tabHeaderInfo.title;
|
||||
replaceTab.extends.method = tabHeaderInfo.method;
|
||||
|
||||
//Set isFixed
|
||||
if (replaceTab.hasChanged) {
|
||||
replaceTab.isFixed = true;
|
||||
//* Set Edit page,such as tab title,storage data,unsaved status by check model change
|
||||
if (currentTab.type === 'edit') {
|
||||
//Set tab storage
|
||||
//Set baseContent
|
||||
if (['activated', 'saved'].includes(inData.when)) {
|
||||
const initialModel = pcDeepCopy(inData.model);
|
||||
//Update tab by id,may not be the current selected tab
|
||||
const isCurrentSelectedTab = currentTab.uuid === this.apiTabComponent.getCurrentTab().uuid;
|
||||
//If is current tab,set initialModel automatically
|
||||
if (isCurrentSelectedTab) {
|
||||
this.componentRef.initialModel = initialModel;
|
||||
}
|
||||
//Saved data may update all IntialData
|
||||
replaceTab.baseContent = inData.when === 'saved' ? {} : currentTab.baseContent || {};
|
||||
replaceTab.baseContent[contentID] = initialModel && !isEmptyObj(initialModel) ? initialModel : null;
|
||||
}
|
||||
//Has tested/exsix api set fixed
|
||||
if (currentTab.pathname.includes('test') && (model.testStartTime !== undefined || currentTab.params.uuid)) {
|
||||
replaceTab.isFixed = true;
|
||||
//Set content
|
||||
replaceTab.content = inData.when === 'saved' ? {} : currentTab.content || {};
|
||||
replaceTab.content[contentID] = model && !isEmptyObj(model) ? model : null;
|
||||
|
||||
let currentHasChanged = currentTab.extends?.hasChanged?.[contentID];
|
||||
switch (inData.when) {
|
||||
case 'editing': {
|
||||
//Set hasChange
|
||||
if (!this.componentRef?.isFormChange) {
|
||||
throw new Error(
|
||||
`EO_ERROR:Child componentRef[${this.componentRef.constructor.name}] need has isFormChange function check model change`
|
||||
);
|
||||
}
|
||||
currentHasChanged = this.componentRef.isFormChange();
|
||||
break;
|
||||
}
|
||||
case 'saved': {
|
||||
currentHasChanged = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//* Share change status within all content page
|
||||
replaceTab.extends.hasChanged = currentTab.extends?.hasChanged || {};
|
||||
replaceTab.extends.hasChanged[contentID] = currentHasChanged;
|
||||
|
||||
// Editiable tab share hasChanged data
|
||||
if (!currentHasChanged && currentTab.extends?.hasChanged) {
|
||||
const otherEditableTabs = this.BASIC_TABS.filter(val => val.type === 'edit' && val.uniqueName !== contentID);
|
||||
currentHasChanged = otherEditableTabs.some(tabItem => currentTab.extends?.hasChanged[tabItem.uniqueName]);
|
||||
}
|
||||
replaceTab.hasChanged = currentHasChanged;
|
||||
}
|
||||
this.apiTabComponent.updatePartialTab(inData.url, replaceTab);
|
||||
|
||||
//Set isFixed
|
||||
if (replaceTab.hasChanged) {
|
||||
replaceTab.isFixed = true;
|
||||
}
|
||||
|
||||
//Has tested/exsix api set fixed
|
||||
const isTestPage = [PageUniqueName.HttpCase, PageUniqueName.HttpTest, PageUniqueName.WsTest].includes(
|
||||
currentTab.uniqueName as PageUniqueName
|
||||
);
|
||||
if (isTestPage && model.testStartTime !== undefined) {
|
||||
replaceTab.isFixed = true;
|
||||
}
|
||||
// console.log('updatePartialTab', currentTab.uuid, replaceTab);
|
||||
this.apiTabComponent.updatePartialTab(currentTab.uuid, replaceTab);
|
||||
}
|
||||
/**
|
||||
* After content changed
|
||||
@ -312,32 +459,44 @@ export class ApiTabService {
|
||||
*
|
||||
* @param inData.url get component fit tab data
|
||||
*/
|
||||
afterContentChanged(inData: { when: 'init' | 'editing' | 'saved' | 'afterTested'; url: string; model: any }) {
|
||||
afterTabContentChanged(inData: TabEvent) {
|
||||
if (!this.apiTabComponent) {
|
||||
pcConsole.warn(`ING[api-tab]: apiTabComponent hasn't init yet!`);
|
||||
return;
|
||||
}
|
||||
let currentTab = this.apiTabComponent.getExistTabByUrl(inData.url);
|
||||
|
||||
const currentTab = this.apiTabComponent.getTabByID(inData.currentTabID);
|
||||
if (!currentTab) {
|
||||
pcConsole.warn(`ING[api-tab]: has't find the tab fit child component ,url:${inData.url}`);
|
||||
pcConsole.warn(`ING[api-tab]: has't find the tab fit child component ,url:${inData.currentTabID}`);
|
||||
return;
|
||||
}
|
||||
|
||||
//Unit request is asynchronous,Update other tab test result
|
||||
if (inData?.when === 'afterTested') {
|
||||
//Update other tab test result
|
||||
inData.url = `${inData.model.url}?pageID=${inData.model.id}`;
|
||||
currentTab = this.apiTabComponent.getExistTabByUrl(inData.url);
|
||||
inData.model = { ...currentTab.content.test, ...inData.model.model };
|
||||
}
|
||||
this.updateTab(currentTab, inData);
|
||||
}
|
||||
/**
|
||||
* Handle cache data before restore tab info
|
||||
*
|
||||
* @param tabsInfo
|
||||
* @returns
|
||||
*/
|
||||
handleDataBeforeGetCache = tabsInfo => {
|
||||
if (!tabsInfo?.tabOrder?.[0]) return null;
|
||||
const tab = tabsInfo.tabsByID[tabsInfo.tabOrder[0]];
|
||||
if (!tab) return null;
|
||||
const { wid, pid } = tab.params;
|
||||
if (wid !== this.store.getCurrentWorkspaceUuid || pid !== this.store.getCurrentProjectID) return null;
|
||||
if (wid !== this.globalStore.getCurrentWorkspaceUuid || pid !== this.globalStore.getCurrentProjectID) return null;
|
||||
return tabsInfo;
|
||||
};
|
||||
/**
|
||||
* Handle cache data before storage tab info
|
||||
*
|
||||
* @param tabsInfo
|
||||
* @returns
|
||||
*/
|
||||
handleDataBeforeCache = tabStorage => {
|
||||
Object.values(tabStorage.tabsByID).forEach((val: TabItem) => {
|
||||
//Delete gio key
|
||||
|
@ -1,21 +1,19 @@
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { Component, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { NzResizeEvent } from 'ng-zorro-antd/resizable';
|
||||
import { EoTabComponent } from 'pc/browser/src/app/components/eo-ui/tab/tab.component';
|
||||
import { WebService } from 'pc/browser/src/app/core/services';
|
||||
import { BASIC_TABS_INFO, TabsConfig } from 'pc/browser/src/app/pages/workspace/project/api/constants/api.model';
|
||||
import { ExtensionService } from 'pc/browser/src/app/services/extensions/extension.service';
|
||||
import { ApiData } from 'pc/browser/src/app/services/storage/index.model';
|
||||
import { TraceService } from 'pc/browser/src/app/services/trace.service';
|
||||
import { API_PREVIEW_TAB } from 'pc/browser/src/app/shared/constans/featureName';
|
||||
import { ExtensionChange } from 'pc/browser/src/app/shared/decorators';
|
||||
import { StoreService } from 'pc/browser/src/app/store/state.service';
|
||||
import { StoreService } from 'pc/browser/src/app/shared/store/state.service';
|
||||
import { filter, Subject, takeUntil } from 'rxjs';
|
||||
|
||||
import { SidebarService } from '../../../../layouts/sidebar/sidebar.service';
|
||||
import { Message, MessageService } from '../../../../services/message';
|
||||
import { ExtensionInfo } from '../../../../shared/models/extension-manager';
|
||||
import StorageUtil from '../../../../shared/utils/storage/storage.utils';
|
||||
import { ApiTabService } from './api-tab.service';
|
||||
import { ApiTabService, PageUniqueName } from './api-tab.service';
|
||||
|
||||
const RIGHT_SIDER_WIDTH_KEY = 'RIGHT_SIDER_WIDTH';
|
||||
const LEFT_SIDER_WIDTH_KEY = 'LEFT_SIDER_WIDTH_KEY';
|
||||
@ -58,27 +56,21 @@ export class ApiComponent implements OnInit, OnDestroy {
|
||||
routerLink: 'detail',
|
||||
isShare: true,
|
||||
//ID fit to the routerLink
|
||||
id: 'api-http-detail',
|
||||
id: PageUniqueName.HttpDetail,
|
||||
title: $localize`:@@API Detail:Preview`
|
||||
},
|
||||
{
|
||||
routerLink: 'edit',
|
||||
id: 'api-http-edit',
|
||||
id: PageUniqueName.HttpEdit,
|
||||
title: $localize`Edit`
|
||||
},
|
||||
{
|
||||
routerLink: 'test',
|
||||
isShare: true,
|
||||
id: 'api-http-test',
|
||||
id: PageUniqueName.HttpTest,
|
||||
title: $localize`Test`
|
||||
},
|
||||
{
|
||||
routerLink: 'mock',
|
||||
id: 'api-http-mock',
|
||||
title: 'Mock'
|
||||
}
|
||||
];
|
||||
originModel: ApiData | any;
|
||||
rightSiderWidth = this.getLocalRightSiderWidth();
|
||||
|
||||
tabsIndex = StorageUtil.get('eo_group_tab_select') || 0;
|
||||
@ -90,17 +82,17 @@ export class ApiComponent implements OnInit, OnDestroy {
|
||||
public sidebar: SidebarService,
|
||||
private router: Router,
|
||||
public web: WebService,
|
||||
private messageService: MessageService,
|
||||
private extensionService: ExtensionService,
|
||||
public store: StoreService,
|
||||
private trace: TraceService
|
||||
private trace: TraceService,
|
||||
@Inject(BASIC_TABS_INFO) public tabsConfig: TabsConfig
|
||||
) {
|
||||
this.initExtensionExtra();
|
||||
}
|
||||
@ExtensionChange(API_PREVIEW_TAB, true)
|
||||
async initExtensionExtra() {
|
||||
this.rightExtras = [];
|
||||
if (!this.router.url.includes('home/workspace/project/api/http/detail')) return;
|
||||
if (!this.router.url.includes(this.tabsConfig.pathByName[PageUniqueName.HttpDetail])) return;
|
||||
const apiPreviewTab = this.extensionService.getValidExtensionsByFature(API_PREVIEW_TAB);
|
||||
apiPreviewTab?.forEach(async (value, key) => {
|
||||
const module = await this.extensionService.getExtensionPackage(key);
|
||||
@ -131,10 +123,14 @@ export class ApiComponent implements OnInit, OnDestroy {
|
||||
this.apiTab.onChildComponentInit(componentRef);
|
||||
}
|
||||
initChildBarShowStatus() {
|
||||
const isEnvPage = this.router.url.includes('home/workspace/project/api/env/edit');
|
||||
const isGroupPage = ['share/group/edit', 'home/workspace/project/api/group/edit'].some(n => this.router.url.includes(n));
|
||||
const pathArr = [
|
||||
this.tabsConfig.pathByName[PageUniqueName.HttpDetail],
|
||||
this.tabsConfig.pathByName[PageUniqueName.HttpEdit],
|
||||
this.tabsConfig.pathByName[PageUniqueName.HttpTest]
|
||||
];
|
||||
const isApiPage = pathArr.some(path => this.router.url.includes(path));
|
||||
const isTestHistoryPage = this.route.snapshot.queryParams.uuid?.includes('history_');
|
||||
this.showChildBar = this.route.snapshot.queryParams.uuid && !isTestHistoryPage && !isEnvPage && !isGroupPage;
|
||||
this.showChildBar = this.route.snapshot.queryParams.uuid && !isTestHistoryPage && isApiPage;
|
||||
}
|
||||
onGroupTabSelectChange($event) {
|
||||
StorageUtil.set('eo_group_tab_select', this.tabsIndex);
|
||||
|
@ -8,22 +8,25 @@ import { EoNgTreeModule } from 'eo-ng-tree';
|
||||
import { NzBadgeModule } from 'ng-zorro-antd/badge';
|
||||
import { NzEmptyModule } from 'ng-zorro-antd/empty';
|
||||
import { NzResizableModule, NzResizableService } from 'ng-zorro-antd/resizable';
|
||||
import { ApiTabService } from 'pc/browser/src/app/pages/workspace/project/api/api-tab.service';
|
||||
import { ApiGroupTreeDirective } from 'pc/browser/src/app/pages/workspace/project/api/components/group/tree/api-group-tree.directive';
|
||||
import { ApiTabService, API_TABS } from 'pc/browser/src/app/pages/workspace/project/api/api-tab.service';
|
||||
import { ApiGroupTreeDirective } from 'pc/browser/src/app/pages/workspace/project/api/components/group/api-group-tree.directive';
|
||||
import { ResponseStepsComponent } from 'pc/browser/src/app/pages/workspace/project/api/components/response-steps/response-steps.component';
|
||||
import { BASIC_TABS_INFO, TabsConfig } from 'pc/browser/src/app/pages/workspace/project/api/constants/api.model';
|
||||
import { ApiMockService } from 'pc/browser/src/app/pages/workspace/project/api/http/mock/api-mock.service';
|
||||
import { ApiCaseService } from 'pc/browser/src/app/pages/workspace/project/api/http/test/api-case.service';
|
||||
import { SharedModule } from 'pc/browser/src/app/shared/shared.module';
|
||||
|
||||
import { EoTabModule } from '../../../../components/eo-ui/tab/tab.module';
|
||||
import { ExtensionSelectModule } from '../../../../components/extension-select/extension-select.module';
|
||||
import { ApiRoutingModule } from './api-routing.module';
|
||||
import { ApiComponent } from './api.component';
|
||||
import { ProjectApiService } from './api.service';
|
||||
import { ApiGroupEditComponent } from './components/group/edit/api-group-edit.component';
|
||||
import { ApiGroupTreeComponent } from './components/group/tree/api-group-tree.component';
|
||||
import { ApiGroupTreeComponent } from './components/group/api-group-tree.component';
|
||||
import { HistoryComponent } from './components/history/eo-history.component';
|
||||
import { EnvModule } from './env/env.module';
|
||||
import { ApiTestUtilService } from './service/api-test-util.service';
|
||||
|
||||
const COMPONENTS = [ApiComponent, ApiGroupEditComponent, ApiGroupTreeComponent, HistoryComponent];
|
||||
import { ProjectApiService } from './service/project-api.service';
|
||||
const COMPONENTS = [ApiComponent, ApiGroupTreeComponent, HistoryComponent];
|
||||
const tabs = API_TABS.map(val => ({ ...val, pathname: `/home/workspace/project/api${val.pathname}` }));
|
||||
@NgModule({
|
||||
imports: [
|
||||
ExtensionSelectModule,
|
||||
@ -39,10 +42,25 @@ const COMPONENTS = [ApiComponent, ApiGroupEditComponent, ApiGroupTreeComponent,
|
||||
NzBadgeModule,
|
||||
EoNgLayoutModule,
|
||||
EoNgTabsModule,
|
||||
EoNgTreeModule
|
||||
EoNgTreeModule,
|
||||
ResponseStepsComponent
|
||||
],
|
||||
declarations: [...COMPONENTS, ApiGroupTreeDirective],
|
||||
exports: [ApiComponent],
|
||||
providers: [ProjectApiService, ApiTestUtilService, NzResizableService, ApiTabService]
|
||||
providers: [
|
||||
{
|
||||
provide: BASIC_TABS_INFO,
|
||||
useValue: {
|
||||
BASIC_TABS: tabs,
|
||||
pathByName: tabs.reduce((acc, curr) => ({ ...acc, [curr.uniqueName]: curr.pathname }), {})
|
||||
} as TabsConfig
|
||||
},
|
||||
ApiCaseService,
|
||||
ProjectApiService,
|
||||
ApiTestUtilService,
|
||||
NzResizableService,
|
||||
ApiTabService,
|
||||
ApiMockService
|
||||
]
|
||||
})
|
||||
export class ApiModule {}
|
||||
|
@ -1,80 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
|
||||
|
||||
import { MessageService } from '../../../../services/message';
|
||||
import { ApiService } from '../../../../services/storage/api.service';
|
||||
import { ApiData } from '../../../../services/storage/db/models/apiData';
|
||||
import { StoreService } from '../../../../store/state.service';
|
||||
import { ApiEffectService } from './store/api-effect.service';
|
||||
|
||||
@Injectable()
|
||||
export class ProjectApiService {
|
||||
constructor(
|
||||
private message: EoNgFeedbackMessageService,
|
||||
private messageService: MessageService,
|
||||
private router: Router,
|
||||
private effect: ApiEffectService,
|
||||
private api: ApiService,
|
||||
private globalStore: StoreService
|
||||
) {}
|
||||
async get(uuid): Promise<ApiData> {
|
||||
const [result, err] = await (this.globalStore.isShare
|
||||
? this.api.api_shareApiDataDetail({ apiUuids: [uuid], withParams: 1, sharedUuid: this.globalStore.getShareID })
|
||||
: this.api.api_apiDataDetail({ apiUuids: [uuid], withParams: 1 }));
|
||||
if (err || !result?.[0]) {
|
||||
this.message.error($localize`Can't find this API`);
|
||||
return;
|
||||
}
|
||||
const apiData = result[0];
|
||||
apiData.apiAttrInfo ??= {};
|
||||
apiData.responseList ??= [
|
||||
{
|
||||
responseParams: {
|
||||
headerParams: [],
|
||||
bodyParams: []
|
||||
}
|
||||
}
|
||||
];
|
||||
apiData.responseList[0].responseParams ??= {
|
||||
responseParams: {
|
||||
headerParams: [],
|
||||
bodyParams: []
|
||||
}
|
||||
};
|
||||
return apiData;
|
||||
}
|
||||
async edit(apiData: ApiData) {
|
||||
return await this.api.api_apiDataUpdate({ api: apiData });
|
||||
}
|
||||
async add(apiData: ApiData) {
|
||||
return await this.api.api_apiDataCreate({ apiList: [].concat([apiData]) });
|
||||
}
|
||||
async copy(apiID: string) {
|
||||
const { apiUuid, id, ...apiData } = await this.get(apiID);
|
||||
apiData.name += ' Copy';
|
||||
const [result, err] = await this.add(apiData);
|
||||
if (err) {
|
||||
console.log(err);
|
||||
this.message.error($localize`Copy API failed`);
|
||||
return;
|
||||
}
|
||||
this.router.navigate(['/home/workspace/project/api/http/edit'], {
|
||||
queryParams: { pageID: Date.now(), uuid: result[0].apiUuid }
|
||||
});
|
||||
this.effect.getGroupList();
|
||||
}
|
||||
async delete(apiUuid) {
|
||||
// * delete API
|
||||
const [, err] = await this.api.api_apiDataDelete({
|
||||
apiUuids: [apiUuid]
|
||||
});
|
||||
if (err) {
|
||||
this.message.error($localize`Delete API failed`);
|
||||
return;
|
||||
}
|
||||
this.messageService.send({ type: 'deleteApiSuccess', data: { uuids: [apiUuid] } });
|
||||
this.message.success($localize`Deleted API Successfully`);
|
||||
this.effect.getGroupList();
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<div class="action-contain h-[100%]">
|
||||
<eo-ng-tabset [(nzSelectedIndex)]="selectedIndex" nzTabPosition="left" class="h-[100%]">
|
||||
<eo-ng-tab *ngFor="let item of operationArr" [nzTitle]="item.title" class="h-[100%]">
|
||||
<ng-container *ngIf="item.type === 'pre'">
|
||||
<ng-content select="[name=pre]"></ng-content>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="item.type === 'after'">
|
||||
<ng-content select="[name=after]"></ng-content>
|
||||
</ng-container>
|
||||
</eo-ng-tab>
|
||||
</eo-ng-tabset>
|
||||
</div>
|
@ -0,0 +1,28 @@
|
||||
:host ::ng-deep .action-contain {
|
||||
.ant-tabs-tabpane {
|
||||
padding-left: unset !important;
|
||||
border-left: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.ant-tabs-content-holder {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.ant-tabs-tab {
|
||||
text-align: left !important;
|
||||
margin: 5px !important;
|
||||
border: 1px solid transparent;
|
||||
padding: 5px 20px !important;
|
||||
}
|
||||
|
||||
.ant-tabs-tab:hover,
|
||||
.ant-tabs-tab-active {
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bar-background-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.ant-tabs > .ant-tabs-nav .ant-tabs-nav-list {
|
||||
align-items: unset;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ActionComponent } from './action.component';
|
||||
|
||||
describe('ActionComponent', () => {
|
||||
let component: ActionComponent;
|
||||
let fixture: ComponentFixture<ActionComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ActionComponent]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ActionComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,31 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
interface OperationType {
|
||||
title: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'pc-action',
|
||||
templateUrl: './action.component.html',
|
||||
styleUrls: ['./action.component.scss']
|
||||
})
|
||||
export class ActionComponent {
|
||||
selectedIndex: number;
|
||||
operationArr: OperationType[] = [
|
||||
{
|
||||
title: $localize`Pre-request Script`,
|
||||
type: 'pre'
|
||||
},
|
||||
{
|
||||
title: $localize`After-response Script`,
|
||||
type: 'after'
|
||||
}
|
||||
];
|
||||
type: string = 'pre';
|
||||
|
||||
typeChange(type) {
|
||||
if (type === this.type) return;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
import { Component, Input, OnChanges, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
|
||||
import { ApiMockService } from 'pc/browser/src/app/pages/workspace/project/api/http/mock/api-mock.service';
|
||||
import { ApiMockEditComponent } from 'pc/browser/src/app/pages/workspace/project/api/http/mock/edit/api-mock-edit.component';
|
||||
import { ModalService } from 'pc/browser/src/app/services/modal.service';
|
||||
import { Mock, MockCreateWay } from 'pc/browser/src/app/services/storage/db/models';
|
||||
import { ApiData } from 'pc/browser/src/app/services/storage/db/models/apiData';
|
||||
import { eoDeepCopy, copy } from 'pc/browser/src/app/shared/utils/index.utils';
|
||||
|
||||
import { ApiData } from '../../../../../services/storage/db/models/apiData';
|
||||
import { ApiMockEntity } from '../../../../../services/storage/index.model';
|
||||
|
||||
@Component({
|
||||
selector: 'eo-api-mock-table',
|
||||
template: ` <eo-ng-table-pro [columns]="mockListColumns" [nzData]="mockList"></eo-ng-table-pro>
|
||||
@ -36,19 +34,13 @@ export class ApiMockTableComponent implements OnInit, OnChanges {
|
||||
|
||||
mockListColumns = [];
|
||||
mockPrefix: string;
|
||||
mockList: ApiMockEntity[] = [];
|
||||
mockList: Array<{ url: string } & Mock> = [];
|
||||
|
||||
constructor(private message: EoNgFeedbackMessageService, private modal: ModalService, private apiMock: ApiMockService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.initTable();
|
||||
}
|
||||
async handleDeleteMockItem(item, index) {
|
||||
await this.apiMock.deleteMock(item.id);
|
||||
this.mockList.splice(index, 1)[0];
|
||||
this.mockList = [...this.mockList];
|
||||
this.message.success($localize`Delete Succeeded`);
|
||||
}
|
||||
|
||||
private initTable() {
|
||||
this.mockListColumns = [
|
||||
@ -58,45 +50,19 @@ export class ApiMockTableComponent implements OnInit, OnChanges {
|
||||
key: 'createWay',
|
||||
width: 150,
|
||||
enums: [
|
||||
{ title: $localize`System creation`, value: 'system' },
|
||||
{ title: $localize`Manual creation`, value: 'custom' }
|
||||
{ title: $localize`System creation`, value: MockCreateWay.System },
|
||||
{ title: $localize`Manual creation`, value: MockCreateWay.Custom }
|
||||
]
|
||||
},
|
||||
{ title: 'URL', slot: this.urlCell },
|
||||
{
|
||||
type: 'btnList',
|
||||
btns: [
|
||||
{
|
||||
title: $localize`:@@MockPreview:Preview`,
|
||||
icon: 'preview-open',
|
||||
click: item => {
|
||||
const modal = this.modal.create({
|
||||
nzTitle: $localize`Preview Mock`,
|
||||
nzWidth: '70%',
|
||||
nzContent: ApiMockEditComponent,
|
||||
nzComponentParams: {
|
||||
model: item.data,
|
||||
isEdit: false
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
action: 'edit',
|
||||
showFn: item => item.data.createWay !== 'system',
|
||||
click: (item, index) => {
|
||||
const modal = this.modal.create({
|
||||
nzTitle: $localize`Edit Mock`,
|
||||
nzWidth: '70%',
|
||||
nzContent: ApiMockEditComponent,
|
||||
nzComponentParams: {
|
||||
model: eoDeepCopy(item.data)
|
||||
},
|
||||
nzOnOk: async () => {
|
||||
await this.addOrEditModal(modal.componentInstance.model, index);
|
||||
modal.destroy();
|
||||
}
|
||||
});
|
||||
this.apiMock.toEdit(item.data);
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -104,7 +70,7 @@ export class ApiMockTableComponent implements OnInit, OnChanges {
|
||||
showFn: item => item.data.createWay !== 'system',
|
||||
confirm: true,
|
||||
confirmFn: (item, index) => {
|
||||
this.handleDeleteMockItem(item.data, index);
|
||||
this.apiMock.toDelete(item.data.id);
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -119,7 +85,9 @@ export class ApiMockTableComponent implements OnInit, OnChanges {
|
||||
item.response = this.apiMock.getMockResponseByAPI(this.apiData);
|
||||
}
|
||||
});
|
||||
console.log(this.apiData);
|
||||
this.mockPrefix = this.apiMock.getMockPrefix(this.apiData);
|
||||
console.log(this.mockPrefix);
|
||||
this.setMocksUrl();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,16 @@
|
||||
<div class="flex w-full eo-api-script">
|
||||
<div class="w-[300px] overflow-auto border-0 border-r-[1px] border-solid border-[#f0f0f0]">
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<eo-monaco-editor
|
||||
[(code)]="code"
|
||||
[config]="{ language: 'javascript' }"
|
||||
[autoHeight]="true"
|
||||
[eventList]="['format', 'copy', 'search', 'replace']"
|
||||
(codeChange)="handleChange($event)"
|
||||
[completions]="completions"
|
||||
>
|
||||
</eo-monaco-editor>
|
||||
</div>
|
||||
<div class="w-[230px] overflow-auto border-0 border-l-[1px] border-solid border-[#f0f0f0]">
|
||||
<div class="flex justify-between p-3">
|
||||
<div i18n>Snippets</div>
|
||||
<div>
|
||||
@ -48,15 +59,4 @@
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<eo-monaco-editor
|
||||
[(code)]="code"
|
||||
[config]="{ language: 'javascript' }"
|
||||
[autoHeight]="true"
|
||||
[eventList]="['format', 'copy', 'search', 'replace']"
|
||||
(codeChange)="handleChange($event)"
|
||||
[completions]="completions"
|
||||
>
|
||||
</eo-monaco-editor>
|
||||
</div>
|
||||
</div>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user