fix/v0.3.1 (#245)

* fix: open multiple add api tab cause navigation error

* refactor: tab jump multiple time

* wip: merge e2e

* chore: update build script

* style: rename variable

* fix(api-test): rest/query params change but send still use old data

* refactor: unified replication logic

* fix: local workspace global tip

* fix(api-detail): change env but url not change

* fix: formdata limit max length 255

* build: update reliace

* fix:  Illegal return statement

* fix: some bug

* fix: some bug

* fix: some bug

* feat: 0.3.1

---------

Co-authored-by: bqy_fe <1743369777@qq.com>
This commit is contained in:
Scarqin 2023-03-02 19:28:19 +08:00 committed by GitHub
parent 08d5ed82bb
commit 069762601e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 499 additions and 278 deletions

View File

@ -1,5 +0,0 @@
const config = {
testDir: './',
};
module.exports = config;

View File

@ -1,7 +1,6 @@
{
"name": "Postcat",
"souceLocale": "zh-Hans",
"version": "0.3.0",
"name": "postcat",
"version": "0.3.1",
"main": "out/app/electron-main/main.js",
"description": "A lightweight, extensible API tool",
"homepage": "https://github.com/Postcatlab/postcat.git",
@ -23,7 +22,8 @@
"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 upload.js",
"test": "npm-run-all --serial test:*",
"e2e": "yarn build:prod && npx playwright test -c e2e/playwright.config.ts e2e/",
"e2e": "cd test/e2e&&npx playwright test -c playwright.config.ts",
"test:e2e":"npm-run-all -s start:web test:e2e",
"clear:electron:tsc": "tsc --build --clean",
"electron:tsc": "tsc -p tsconfig.json",
"electron:dev": "npm run electron:tsc && electron . --development",

View File

@ -112,6 +112,8 @@ class EoBrowserWindow {
const opts = {
useContentSize: true, // 这个要设置,不然计算显示区域尺寸不准
frame: os.type() === 'Darwin' ? true : false, //mac use default frame
minWidth: 400,
minHeight: 300,
webPreferences: {
webSecurity: false,
preload: path.join(__dirname, '../../', 'platform', 'electron-browser', 'preload.js'),

View File

@ -1 +1,125 @@
{"$schema":"./node_modules/@angular/cli/lib/config/schema.json","cli":{"analytics":false,"defaultCollection":"@angular-eslint/schematics","cache":{"enabled":true}},"version":1,"newProjectRoot":"projects","projects":{"postcat":{"root":"","i18n":{"sourceLocale":{"code":"en","baseHref":""},"locales":{"zh":{"translation":"locale/messages.zh.xlf","baseHref":""}}},"sourceRoot":"src","projectType":"application","schematics":{"@schematics/angular:application":{"strict":true}},"architect":{"build":{"inlineStyleLanguage":"scss","builder":"@angular-builders/custom-webpack:browser","options":{"localize":true,"aot":true,"outputPath":"dist","index":"src/index.html","main":"src/main.ts","tsConfig":"src/tsconfig.app.json","polyfills":"src/polyfills.ts","assets":["src/icon.ico","src/assets","src/extensions",{"glob":"**/*","input":"../../../node_modules/monaco-editor/min/vs","output":"/assets/vs/"}],"styles":["src/styles/antd.less","src/styles.scss"],"scripts":["src/assets/libs/protocolcheck.js"],"customWebpackConfig":{"path":"./angular.webpack.js","replaceDuplicatePlugins":true},"allowedCommonJsDependencies":["brace","qs","rxjs"]},"configurations":{"dev":{"optimization":false,"outputHashing":"none","sourceMap":true,"namedChunks":false,"localize":false,"preserveSymlinks":true,"extractLicenses":true,"vendorChunk":false,"buildOptimizer":false,"fileReplacements":[{"replace":"src/environments/environment.ts","with":"src/environments/environment.dev.ts"}]},"devCn":{"optimization":false,"outputHashing":"none","sourceMap":true,"namedChunks":false,"preserveSymlinks":true,"localize":["zh"],"extractLicenses":true,"vendorChunk":false,"buildOptimizer":false,"fileReplacements":[{"replace":"src/environments/environment.ts","with":"src/environments/environment.dev.ts"}]},"production":{"preserveSymlinks":false,"optimization":true,"outputHashing":"all","sourceMap":false,"namedChunks":false,"extractLicenses":true,"vendorChunk":false,"buildOptimizer":true,"fileReplacements":[{"replace":"src/environments/environment.ts","with":"src/environments/environment.prod.ts"}]},"docker":{"optimization":true,"outputHashing":"all","sourceMap":false,"namedChunks":false,"extractLicenses":true,"vendorChunk":false,"buildOptimizer":true,"fileReplacements":[{"replace":"src/environments/environment.ts","with":"src/environments/environment.docker.ts"}]}}},"serve":{"builder":"@angular-builders/custom-webpack:dev-server","options":{"browserTarget":"postcat:build","proxyConfig":"proxy.conf.json"},"configurations":{"dev":{"browserTarget":"postcat:build:dev"},"devCn":{"browserTarget":"postcat:build:devCn"},"production":{"browserTarget":"postcat:build:production"}}},"extract-i18n":{"builder":"@angular-devkit/build-angular:extract-i18n","options":{"browserTarget":"postcat:build"}},"test":{"inlineStyleLanguage":"scss","builder":"@angular-builders/custom-webpack:karma","options":{"main":"src/test.ts","polyfills":"src/polyfills-test.ts","tsConfig":"src/tsconfig.spec.json","karmaConfig":"src/karma.conf.js","scripts":[],"styles":["src/styles.scss"],"assets":["src/assets"],"customWebpackConfig":{"path":"./angular.webpack.js","replaceDuplicatePlugins":true}}},"lint":{"builder":"@angular-eslint/builder:lint","options":{"lintFilePatterns":["src/**/*.ts","src/**/*.html"]}}}},"postcat-e2e":{"root":"e2e","projectType":"application","architect":{"lint":{"builder":"@angular-eslint/builder:lint","options":{"lintFilePatterns":["e2e/**/*.ts"]}}}}},"defaultProject":"postcat","schematics":{"@schematics/angular:component":{"prefix":"pc","style":"scss","inlineStyle":true,"inlineTemplate":true},"@schematics/angular:directive":{"prefix":"pc"}}}
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"cli": { "analytics": false, "defaultCollection": "@angular-eslint/schematics", "cache": { "enabled": true } },
"version": 1,
"newProjectRoot": "projects",
"projects": {
"postcat": {
"root": "",
"i18n": {
"sourceLocale": { "code": "en", "baseHref": "" },
"locales": { "zh": { "translation": "locale/messages.zh.xlf", "baseHref": "" } }
},
"sourceRoot": "src",
"projectType": "application",
"schematics": { "@schematics/angular:application": { "strict": true } },
"architect": {
"build": {
"inlineStyleLanguage": "scss",
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"localize": true,
"aot": true,
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"assets": [
"src/icon.ico",
"src/assets",
"src/extensions",
{ "glob": "**/*", "input": "../../../node_modules/monaco-editor/min/vs", "output": "/assets/vs/" }
],
"styles": ["src/styles/antd.less", "src/styles.scss"],
"scripts": ["src/assets/libs/protocolcheck.js"],
"customWebpackConfig": { "path": "./angular.webpack.js", "replaceDuplicatePlugins": true },
"allowedCommonJsDependencies": ["brace", "qs", "rxjs"]
},
"configurations": {
"dev": {
"optimization": false,
"outputHashing": "none",
"sourceMap": true,
"namedChunks": false,
"localize": false,
"preserveSymlinks": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [{ "replace": "src/environments/environment.ts", "with": "src/environments/environment.dev.ts" }]
},
"devCn": {
"optimization": false,
"outputHashing": "none",
"sourceMap": true,
"namedChunks": false,
"preserveSymlinks": true,
"localize": ["zh"],
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [{ "replace": "src/environments/environment.ts", "with": "src/environments/environment.dev.ts" }]
},
"production": {
"preserveSymlinks": false,
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [{ "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" }]
},
"docker": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [{ "replace": "src/environments/environment.ts", "with": "src/environments/environment.docker.ts" }]
}
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"options": { "browserTarget": "postcat:build", "proxyConfig": "proxy.conf.json" },
"configurations": {
"dev": { "browserTarget": "postcat:build:dev" },
"devCn": { "browserTarget": "postcat:build:devCn" },
"production": { "browserTarget": "postcat:build:production" }
}
},
"extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "postcat:build" } },
"test": {
"inlineStyleLanguage": "scss",
"builder": "@angular-builders/custom-webpack:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills-test.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"scripts": [],
"styles": ["src/styles.scss"],
"assets": ["src/assets"],
"customWebpackConfig": { "path": "./angular.webpack.js", "replaceDuplicatePlugins": true }
}
},
"lint": { "builder": "@angular-eslint/builder:lint", "options": { "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] } }
}
},
"postcat-e2e": {
"root": "e2e",
"projectType": "application",
"architect": { "lint": { "builder": "@angular-eslint/builder:lint", "options": { "lintFilePatterns": ["e2e/**/*.ts"] } } }
}
},
"defaultProject": "postcat",
"schematics": {
"@schematics/angular:component": { "prefix": "pc", "style": "scss", "inlineStyle": true, "inlineTemplate": true },
"@schematics/angular:directive": { "prefix": "pc" }
}
}

View File

@ -39,20 +39,20 @@
"ajv": "8.12.0",
"color": "^4.2.3",
"core-js": "3.27.2",
"eo-ng-auto-complete": "0.1.5",
"eo-ng-button": "0.1.5",
"eo-ng-checkbox": "0.1.5",
"eo-ng-dropdown": "0.1.5",
"eo-ng-feedback": "0.1.5",
"eo-ng-input": "0.1.5",
"eo-ng-layout": "0.1.5",
"eo-ng-menu": "0.1.5",
"eo-ng-radio": "0.1.5",
"eo-ng-select": "0.1.5",
"eo-ng-switch": "0.1.5",
"eo-ng-table": "0.1.5",
"eo-ng-tabs": "0.1.5",
"eo-ng-tree": "0.1.5",
"eo-ng-auto-complete": "0.1.11",
"eo-ng-button": "0.1.10",
"eo-ng-checkbox": "0.1.10",
"eo-ng-dropdown": "0.1.10",
"eo-ng-feedback": "0.1.10",
"eo-ng-input": "0.1.10",
"eo-ng-layout": "0.1.10",
"eo-ng-menu": "0.1.10",
"eo-ng-radio": "0.1.10",
"eo-ng-select": "0.1.10",
"eo-ng-switch": "0.1.10",
"eo-ng-table": "0.1.11",
"eo-ng-tabs": "0.1.10",
"eo-ng-tree": "0.1.10",
"is-xml": "0.1.0",
"js-beautify": "1.14.7",
"lodash-es": "4.17.21",

View File

@ -337,9 +337,13 @@ export class ThemeVariableService {
'tableRowHoverBackground',
'treeHoverBackground',
'buttonDefaultHoverBackground',
'layoutFooterItemHoverBackground',
'buttonTextHoverBackground'
'layoutFooterItemHoverBackground'
]
},
{
action: 'darken',
alpha: 0.05,
target: ['buttonTextHoverBackground']
}
]
},
@ -449,7 +453,7 @@ export class ThemeVariableService {
const barBackgroundRule = colorsDefaultRule.find(val => val.source === 'barBackground');
barBackgroundRule.rule.push({
action: 'darken',
alpha: 0.03,
alpha: 0.01,
target: ['itemHoverBackground', 'itemActiveBackground', 'layoutFooterItemHoverBackground']
});

View File

@ -231,7 +231,7 @@ export type ThemeColorRule = {
rule?: ThemeColorSingleRule[];
};
export type ThemeColorSingleRule = {
action?: 'replace' | 'filter' | string;
action?: 'replace' | 'filter' | 'darken' | string;
alpha?: number;
target?: Array<keyof ThemeColors> | string[];
};

View File

@ -32,7 +32,7 @@ import { StorageUtil } from '../../utils/storage/storage.utils';
export class LocalWorkspaceTipComponent implements OnInit {
@Input() isShow: boolean;
@Output() readonly isShowChange: EventEmitter<boolean> = new EventEmitter<boolean>();
manualClose = StorageUtil.get(IS_SHOW_REMOTE_SERVER_NOTIFICATION) === 'false';
constructor(
private eoMessage: EoNgFeedbackMessageService,
private message: MessageService,
@ -40,15 +40,16 @@ export class LocalWorkspaceTipComponent implements OnInit {
private effect: EffectService
) {}
ngOnInit(): void {
this.isShow = StorageUtil.get(IS_SHOW_REMOTE_SERVER_NOTIFICATION) !== 'false';
autorun(() => {
const status = this.store.isLocal && this.store.isLogin && this.isShow;
Promise.resolve().then(() => {
this.isShowChange.emit(status);
});
this.updateIsShow();
});
}
updateIsShow() {
const status = this.store.isLocal && this.store.isLogin && !this.manualClose;
Promise.resolve().then(() => {
this.isShowChange.emit(status);
});
}
switchToTheCloud = () => {
const workspaces = this.store.getWorkspaceList;
if (workspaces.length === 1) {
@ -61,7 +62,8 @@ export class LocalWorkspaceTipComponent implements OnInit {
};
closeNotification() {
this.isShow = false;
StorageUtil.set(IS_SHOW_REMOTE_SERVER_NOTIFICATION, 'false');
this.manualClose = true;
StorageUtil.set(IS_SHOW_REMOTE_SERVER_NOTIFICATION, 'false', 60 * 60 * 24 * 15);
this.updateIsShow();
}
}

View File

@ -36,9 +36,7 @@ import { DataSourceService } from '../../shared/services/data-source/data-source
<nz-spin *ngIf="!link" class="flex-1 mt-[10px]"></nz-spin>
</div>
<ng-container *ngIf="link">
<p nz-typography nzCopyable nzEllipsis [nzCopyText]="link" [nzCopyIcons]="[copedIcon, copedIcon]">
{{ link }}
</p>
<p nz-typography [nzContent]="link" nzCopyable nzEllipsis [nzCopyText]="link" [nzCopyIcons]="[copedIcon, copedIcon]"> </p>
<ng-template #copedIcon>
<button eo-ng-button nzType="text"><eo-iconpark-icon name="copy"></eo-iconpark-icon></button>
</ng-template>
@ -56,23 +54,6 @@ export class GetShareLinkComponent {
public dataSourceService: DataSourceService,
private message: EoNgFeedbackMessageService
) {}
handleCopy() {
if (this.isCopy) {
return;
}
if (!this.link) {
this.isCopy = false;
return;
}
const isOk = copy(this.link);
if (isOk) {
this.message.success($localize`Copied`);
this.isCopy = true;
interval(700).subscribe(() => {
this.isCopy = false;
});
}
}
handleGetShareLink() {
this.dataSourceService.checkRemoteCanOperate(async () => {
if (this.store.isLocal) {

View File

@ -3,7 +3,7 @@ import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
import { ApiMockService } from 'eo/workbench/browser/src/app/pages/workspace/project/api/http/mock/api-mock.service';
import { ApiMockEditComponent } from 'eo/workbench/browser/src/app/pages/workspace/project/api/http/mock/edit/api-mock-edit.component';
import { ModalService } from 'eo/workbench/browser/src/app/shared/services/modal.service';
import { copyText, eoDeepCopy, copy } from 'eo/workbench/browser/src/app/utils/index.utils';
import { eoDeepCopy, copy } from 'eo/workbench/browser/src/app/utils/index.utils';
import { ApiData } from '../../shared/services/storage/db/models/apiData';
import { ApiMockEntity } from '../../shared/services/storage/index.model';
@ -12,12 +12,19 @@ import { ApiMockEntity } from '../../shared/services/storage/index.model';
selector: 'eo-api-mock-table',
template: ` <eo-ng-table-pro [columns]="mockListColumns" [nzData]="mockList"></eo-ng-table-pro>
<ng-template #urlCell let-item="item" let-index="index">
<div class="flex items-center">
<span class="truncate flex-1">
{{ item.url }}
</span>
<button eo-ng-button nzType="text" (click)="copyText(item.url)"><eo-iconpark-icon name="copy"></eo-iconpark-icon></button>
</div>
<p
class="flex-1"
[nzContent]="item.url"
nz-typography
nzCopyable
nzEllipsis
[nzCopyText]="item.url"
[nzCopyIcons]="[copedIcon, copedIcon]"
>
</p>
<ng-template #copedIcon>
<button eo-ng-button nzType="text"><eo-iconpark-icon name="copy"></eo-iconpark-icon></button>
</ng-template>
</ng-template>`
})
export class ApiMockTableComponent implements OnInit, OnChanges {
@ -122,10 +129,6 @@ export class ApiMockTableComponent implements OnInit, OnChanges {
});
}
async copyText(text: string) {
await copyText(text);
this.message.success($localize`Copied`);
}
async addOrEditModal(item, index?) {
if (item.id) {
await this.apiMock.updateMock(item);

View File

@ -69,6 +69,7 @@ export class ApiTableService {
left: true,
type: 'input',
columnVisible: 'fixed',
maxlength: 65535,
key: 'name'
},
dataType: {
@ -94,6 +95,7 @@ export class ApiTableService {
example: {
title: $localize`Example`,
type: 'input',
maxlength: 65535,
key: 'paramAttr.example'
},
editOperate: {
@ -233,6 +235,7 @@ export class ApiTableService {
title: $localize`Name`,
left: true,
type: 'input',
maxlength: 65535,
columnVisible: 'fixed',
disabledFn: inArg.in === 'header' ? item => has(item, 'editable') && !item.editable : undefined,
key: 'name'
@ -252,6 +255,7 @@ export class ApiTableService {
value: {
title: $localize`Value`,
type: 'input',
maxlength: 65535,
key: 'paramAttr.example'
},
editOperate: {

View File

@ -12,6 +12,8 @@
<eo-monaco-editor
class="mt-[15px] border-all"
[(code)]="paramCode"
[maxHeight]="400"
[minHeight]="150"
[editorType]="contentTypeEditor"
[eventList]="['type', 'format', 'copy', 'search', 'replace']"
></eo-monaco-editor>

View File

@ -7,6 +7,7 @@
<nz-code-editor
[ngModel]="$$code"
style="min-height: 100px"
[style.height]="height + 'px'"
[nzEditorOption]="editorOption"
(nzEditorInitialized)="onEditorInitialized($event)"
(ngModelChange)="modelChangeFn($event)"

View File

@ -39,7 +39,9 @@ export class EoMonacoEditorComponent implements AfterViewInit, OnInit, OnChanges
this.setCode(val);
}
/** Scroll bars appear over 20 lines */
@Input() maxLine: number;
@Input() maxHeight: number;
@Input() minHeight = 100;
@Input() autoHeight = false;
@Input() config: JoinedEditorOptions = {};
@Input() editorType = 'json';
/** Automatically identify the type */
@ -89,6 +91,16 @@ export class EoMonacoEditorComponent implements AfterViewInit, OnInit, OnChanges
overviewRulerLanes: 0,
quickSuggestions: { other: true, strings: true }
};
contentHeight = 100;
get height() {
if (this.autoHeight) {
return undefined;
}
if (this.maxHeight && this.contentHeight > this.maxHeight) {
return this.maxHeight;
}
return Math.max(this.minHeight, this.contentHeight);
}
private resizeObserver: ResizeObserver;
private readonly el: HTMLElement; /** monaco config */
get editorOption(): JoinedEditorOptions {
@ -216,9 +228,14 @@ export class EoMonacoEditorComponent implements AfterViewInit, OnInit, OnChanges
}
this.codeEdtor.onDidChangeModelContent(e => {
console.log('e', e);
this.handleChange();
});
this.codeEdtor.onDidContentSizeChange(e => {
this.contentHeight = e.contentHeight;
});
this.codeEdtor.onDidBlurEditorText(e => {
this.handleBlur();
});
@ -259,7 +276,7 @@ export class EoMonacoEditorComponent implements AfterViewInit, OnInit, OnChanges
requestAnimationFrame(async () => {
if (this.codeEdtor) {
this.updateReadOnlyCode(async () => {
await this.codeEdtor.getAction('editor.action.formatDocument').run();
await this.codeEdtor.getAction('editor.action.formatDocument')?.run();
resolve(this.codeEdtor.getValue() || '');
}, this.config.readOnly);
} else {

View File

@ -108,7 +108,7 @@ export class TabOperateService {
targetTab.params = { ...validTabItem.params, ...targetTab.params };
}
this.selectedIndex = tabCache.selectedIndex;
this.navigateTabRoute(targetTab);
this.navigateByTab(targetTab);
}
/**
* Add Default tab
@ -120,10 +120,10 @@ export class TabOperateService {
...eoDeepCopy(this.BASIC_TABS.find(val => val.pathname.includes(routerStr)) || this.BASIC_TABS[0])
};
tabItem.params = {};
tabItem.uuid = tabItem.params.pageID = Date.now();
tabItem.uuid = tabItem.params.pageID = Date.now().toString();
Object.assign(tabItem, { isLoading: false });
this.tabStorage.addTab(tabItem);
this.navigateTabRoute(tabItem as TabItem);
this.navigateByTab(tabItem as TabItem);
}
closeTab(index: number) {
@ -132,7 +132,7 @@ export class TabOperateService {
if (this.tabStorage.tabOrder.length === 0) {
this.newDefaultTab();
} else {
this.navigateTabRoute(this.getCurrentTab());
this.navigateByTab(this.getCurrentTab());
}
}
/**
@ -151,23 +151,29 @@ export class TabOperateService {
*
* @param tab
*/
navigateTabRoute(tab: TabItem) {
navigateByTab(tab: TabItem) {
if (!tab) {
return;
}
const queryParams = { pageID: tab.uuid, ...tab.params };
const queryParams = { pageID: tab.params?.pageID, ...tab.params };
if (!queryParams.pageID) Reflect.deleteProperty(queryParams, 'pageID');
this.router.navigate([tab.pathname], {
queryParams
});
}
/**
* Get exist tab index
* Get exist tab item
*
* @description SameTab means has same uuid and same path
*
* @param type sameTab means has same pageID and same {params.uuid}
* @param tab
* @returns
*/
getSameContentTab(tab: Partial<TabItem>): TabItem | null {
getSameTab(
tab: Partial<TabItem>,
opts: {
match: 'all' | 'uuid';
} = { match: 'all' }
): TabItem | null {
let result = null;
if (!tab.params.uuid) {
const sameTabIDTab = this.tabStorage.tabsByID.get(tab.uuid);
@ -181,7 +187,8 @@ export class TabOperateService {
for (const key in mapObj) {
if (Object.prototype.hasOwnProperty.call(mapObj, key)) {
const tabInfo = mapObj[key];
if (tabInfo.params.uuid === tab.params.uuid && tabInfo.pathname === tab.pathname) {
if (tabInfo.pathname !== tab.pathname && opts.match === 'all') continue;
if (tabInfo.params.uuid === tab.params.uuid) {
result = tabInfo;
break;
}
@ -206,14 +213,11 @@ export class TabOperateService {
}
// Parse query params
new URLSearchParams(urlArr[1]).forEach((value, key) => {
if (key === 'pageID') {
params[key] = Number(value);
return;
}
params[key] = value;
});
const tabUuid = params.pageID || params.uuid || '';
const result = {
uuid: params.pageID,
uuid: tabUuid,
pathname: basicTab.pathname,
icon: basicTab.icon,
params
@ -237,7 +241,9 @@ export class TabOperateService {
pcConsole.error(`: Please check this router has added in BASIC_TABS,current route:${url}`);
return;
}
result.params.pageID = result.params.pageID || Date.now();
if (!result.uuid) {
result.params.pageID = result.params.pageID || Date.now().toString();
}
Object.assign(result, { isLoading: true }, basicTab);
return result as TabItem;
}
@ -249,17 +255,17 @@ export class TabOperateService {
* @param res.url location.pathname+location.search
*/
operateTabAfterRouteChange(res: { url: string }) {
const pureTab = this.getBasicInfoFromUrl(res.url);
const existTab = this.getSameContentTab(pureTab);
const routeTab = this.getBasicInfoFromUrl(res.url);
const existTab = this.getSameTab(routeTab);
const nextTab = this.generateTabFromUrl(res.url);
//!Every tab must has pageID
//If lack pageID,Jump to exist tab item to keep same pageID and so on
if (!pureTab.uuid) {
//!Every tab must has tab uuid
//If lack pageID or page[uuid],Redirect to exist tab item to keep same pageID and so on
if (!routeTab.uuid) {
if (existTab) {
pureTab.uuid = pureTab.params.pageID = existTab.uuid;
routeTab.uuid = routeTab.params.pageID = existTab.uuid;
}
this.navigateTabRoute(nextTab);
this.navigateByTab(nextTab);
return;
}
@ -290,7 +296,7 @@ export class TabOperateService {
}
//Determine whether to replace the current Tab
let canbeReplaceTab = null;
if (this.tabStorage.tabsByID.has(pureTab.uuid)) {
if (this.tabStorage.tabsByID.has(routeTab.uuid)) {
//If the same tab exists, directly replace
canbeReplaceTab = nextTab;
} else {

View File

@ -78,7 +78,7 @@ export class EoTabComponent implements OnInit, OnDestroy {
* Select tab
*/
selectChange($event) {
this.tabOperate.navigateTabRoute(this.getCurrentTab());
this.tabOperate.navigateByTab(this.getCurrentTab());
}
async closeTab({ $event, index, tab }: { $event: Event; index: number; tab: any }) {
if (this.checkTabCanLeave && !(await this.checkTabCanLeave(tab))) {
@ -154,7 +154,7 @@ export class EoTabComponent implements OnInit, OnDestroy {
* @returns
*/
getExistTabByUrl(url: string): TabItem | null {
const existTab = this.tabOperate.getSameContentTab(this.tabOperate.getBasicInfoFromUrl(url));
const existTab = this.tabOperate.getSameTab(this.tabOperate.getBasicInfoFromUrl(url));
if (!existTab) {
return null;
}

View File

@ -291,6 +291,7 @@ export class EoTableProComponent implements OnInit, OnChanges {
{
key: col.key,
title: col.slot,
maxlength: col.maxlength,
left: col.left,
change: col.change,
//Slot priority higher than type
@ -466,7 +467,7 @@ export class EoTableProComponent implements OnInit, OnChanges {
this.theadConf = theaderConf;
this.tbodyConf = tbodyConf;
// pcConsole.log(this.theadConf, this.tbodyConf);
pcConsole.log(this.theadConf, this.tbodyConf);
}
private initColumnWidth() {
this.COLUMN_WIDTH_KEY = `TABLE_COLUMN_WIDTH_${this.setting.id || this.DEFAULT_ID}`;

View File

@ -51,6 +51,7 @@ export interface ColumnItem {
errorTip?: string;
right?: boolean;
left?: boolean;
maxlength?: number;
sortable?: boolean;
filterable?: boolean;
resizeable?: boolean;

View File

@ -51,8 +51,8 @@ export class SyncApiComponent implements OnInit, OnChanges {
) {}
ngOnInit(): void {
this.initData();
this.getSyncSettingList();
this.initData();
this.messageService
.get()
.pipe(takeUntil(this.destroy$))
@ -78,8 +78,10 @@ export class SyncApiComponent implements OnInit, OnChanges {
}
updateExtensionModel() {
const currentFormater = this.store.getSyncSettingList.find(n => n.pluginId === this.model.__formater);
if (currentFormater && this.currentFormater !== currentFormater) {
const currentFormater =
this.store.getSyncSettingList.find(n => n.pluginId === this.model.__formater) || this.store.getSyncSettingList.at(0);
// console.log('currentFormater', { ...currentFormater });
if ((currentFormater && this.currentFormater !== currentFormater) || currentFormater.pluginId !== this.model.__formater) {
this.currentFormater = currentFormater;
this.model = {
...this.model,

View File

@ -3,7 +3,6 @@ import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
import { MessageService } from 'eo/workbench/browser/src/app/shared/services/message';
import { ApiService } from 'eo/workbench/browser/src/app/shared/services/storage/api.service';
import { StoreService } from 'eo/workbench/browser/src/app/shared/store/state.service';
import { copy } from 'eo/workbench/browser/src/app/utils/index.utils';
import { DataSourceService } from '../../../shared/services/data-source/data-source.service';
@ -20,10 +19,19 @@ import { DataSourceService } from '../../../shared/services/data-source/data-sou
</div>
<div i18n>Make sure to copy your token. It will never be displayed again.</div>
<div>
<span class="alert-text">{{ token }}</span>
<button eo-ng-button nzType="text" class="copy-btn mx-2" (click)="handleCopy(token)"
><eo-iconpark-icon name="copy"></eo-iconpark-icon
></button>
<p
class="alert-text"
nz-typography
[nzContent]="token"
nzCopyable
nzEllipsis
[nzCopyText]="token"
[nzCopyIcons]="[copedIcon, copedIcon]"
>
</p>
<ng-template #copedIcon>
<button eo-ng-button nzType="text"><eo-iconpark-icon name="copy"></eo-iconpark-icon></button>
</ng-template>
</div>
</section>
</ng-template>
@ -48,13 +56,6 @@ export class TokenComponent {
this.token = '';
}
handleCopy(text) {
const isOk = copy(text);
if (isOk) {
this.eoMessage.success($localize`Copied`);
}
}
closeAlert() {
// * Reset token and close the alert
this.token = '';

View File

@ -38,10 +38,10 @@ export class PagesComponent implements OnInit {
});
// Show cookie tips
if (!(this.hasShowCookieTips || this.electron.isElectron) && this.lang.systemLanguage === 'en-US') {
StorageUtil.set('has_show_cookie_tips', true);
this.showCookiesTips();
}
// if (!(this.hasShowCookieTips || this.electron.isElectron) && this.lang.systemLanguage === 'en-US') {
// StorageUtil.set('has_show_cookie_tips', true);
// this.showCookiesTips();
// }
}
closeNotification() {
this.notification.remove(this.cookieNotification.messageId);

View File

@ -23,6 +23,7 @@ import { StoreService } from '../../../../shared/store/state.service';
*ngIf="isEdit"
type="text"
eo-ng-input
autofocus
id="title"
formControlName="title"
placeholder="Workspace Name"

View File

@ -1,6 +1,7 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
import { MemberService } from 'eo/workbench/browser/src/app/modules/member-list/member.service';
import { DataSourceService } from 'eo/workbench/browser/src/app/shared/services/data-source/data-source.service';
import { TraceService } from 'eo/workbench/browser/src/app/shared/services/trace.service';
import { makeObservable, observable, action, reaction } from 'mobx';
@ -97,10 +98,13 @@ export class WorkspaceMemberComponent implements OnInit {
private eMessage: EoNgFeedbackMessageService,
public member: MemberService,
private message: MessageService,
private trace: TraceService
private trace: TraceService,
private dataSource: DataSourceService
) {}
createWorkspace() {
this.message.send({ type: 'addWorkspace', data: {} });
this.dataSource.checkRemoteCanOperate(() => {
this.message.send({ type: 'addWorkspace', data: {} });
});
}
ngOnInit(): void {
makeObservable(this);

View File

@ -45,8 +45,8 @@ export class WorkspaceOverviewComponent implements OnInit {
}
);
autorun(async () => {
await waitNextTick();
this.title = this.store.getCurrentWorkspace?.title;
if (!this.store.getCurrentWorkspace) return;
this.title = this.store.getCurrentWorkspace.title;
});
}
}

View File

@ -117,7 +117,6 @@ export class ApiGroupTreeComponent implements OnInit {
this.apiGroupTree = this.store.getApiGroupTree;
waitNextTick().then(() => {
this.initSelectKeys();
console.log(this.expandKeys);
});
});
reaction(
@ -192,7 +191,7 @@ export class ApiGroupTreeComponent implements OnInit {
addAPI(group?) {
const prefix = this.globalStore.isShare ? 'share' : '/home/workspace/project/api';
this.router.navigate([`${prefix}/http/edit`], {
queryParams: { groupId: group?.key }
queryParams: { groupId: group?.key, pageID: Date.now() }
});
}
deleteAPI(apiInfo) {
@ -289,9 +288,6 @@ export class ApiGroupTreeComponent implements OnInit {
);
};
// toggleExpand() {
// this.expandKeys = this.apiGroup.getExpandedNodeList().map(tree => tree.key);
// }
/**
* Group tree item click.
*

View File

@ -70,6 +70,8 @@ export class EnvListComponent implements OnDestroy {
});
}
addEnv(pid = 1) {
this.router.navigate(['/home/workspace/project/api/env/edit']);
this.router.navigate(['/home/workspace/project/api/env/edit'], {
queryParams: { pageID: Date.now() }
});
}
}

View File

@ -1,19 +1,16 @@
<div class="w-full p-base">
<nz-skeleton *ngIf="!store.isLocal" [nzLoading]="!model?.uri" [nzActive]="true"> </nz-skeleton>
<nz-skeleton *ngIf="!globalStore.isLocal" [nzLoading]="!model?.uri" [nzActive]="true"> </nz-skeleton>
<div *ngIf="model?.uri">
<div class="flex items-center justify-between">
<div class="flex flex-wrap items-center min-w-0">
<div class="flex flex-wrap items-center">
<div class="flex items-center">
<nz-tag [class]="model.protocol | apiFormater : 'protocal' | lowercase">{{ model.protocol | apiFormater : 'protocal' }}</nz-tag>
<eo-api-methods-tag [type]="model?.apiAttrInfo?.requestMethod | apiFormater : 'requestMethod'"></eo-api-methods-tag>
<div class="flex items-center w-full mt-[10px]">
<span class="truncate">
{{ url }}
</span>
<button eo-ng-button nzType="text" class="mx-2" (click)="handleCopy(model.uri)"
><eo-iconpark-icon name="copy"></eo-iconpark-icon
></button>
</div>
</div>
<p class="flex-1" [nzContent]="url" nz-typography nzCopyable nzEllipsis [nzCopyText]="url" [nzCopyIcons]="[copedIcon, copedIcon]">
</p>
<ng-template #copedIcon>
<button eo-ng-button nzType="text"><eo-iconpark-icon name="copy"></eo-iconpark-icon></button>
</ng-template>
</div>
<p class="truncate mt-[10px]">{{ model.name }}</p>
<!-- Request Headers -->

View File

@ -1,14 +1,13 @@
import { Component, Output, EventEmitter, Input } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
import { ElectronService } from 'eo/workbench/browser/src/app/core/services';
import { ApiBodyType } from 'eo/workbench/browser/src/app/modules/api-shared/api.model';
import { TabViewComponent } from 'eo/workbench/browser/src/app/modules/eo-ui/tab/tab.model';
import { ApiStoreService } from 'eo/workbench/browser/src/app/pages/workspace/project/api/service/store/api-state.service';
import { ApiData } from 'eo/workbench/browser/src/app/shared/services/storage/db/models/apiData';
import { StoreService } from 'eo/workbench/browser/src/app/shared/store/state.service';
import { copy } from 'eo/workbench/browser/src/app/utils/index.utils';
import { cloneDeep } from 'lodash-es';
import { reaction } from 'mobx';
import { enumsToObject } from '../../../../../../utils/index.utils';
import { ProjectApiService } from '../../api.service';
@ -24,13 +23,7 @@ export class ApiDetailComponent implements TabViewComponent {
CONST = {
BODY_TYPE: enumsToObject(ApiBodyType)
};
get url() {
const isUrl = /^https?:/;
if (isUrl.test(this.model.uri)) {
return this.model.uri;
}
return this.apiStore.getCurrentEnv?.hostUri + this.model.uri;
}
url: string = '';
get TYPE_API_BODY(): typeof ApiBodyType {
return ApiBodyType;
}
@ -38,17 +31,29 @@ export class ApiDetailComponent implements TabViewComponent {
private route: ActivatedRoute,
private projectApi: ProjectApiService,
public electron: ElectronService,
public store: StoreService,
public apiStore: ApiStoreService,
private message: EoNgFeedbackMessageService
) {}
handleCopy(link) {
if (!link) {
return;
}
const isOk = copy(link);
if (isOk) {
this.message.success($localize`Copied`);
public globalStore: StoreService,
public store: ApiStoreService
) {
this.watchEnvChange();
}
watchEnvChange() {
reaction(
() => this.store.getCurrentEnv,
(env: any) => {
this.url = this.getEnvUrl(this.model.uri);
}
);
}
private getEnvUrl(url) {
if (!this.store.getCurrentEnv?.hostUri) return url;
try {
const isUrl = new URL(url);
if (isUrl.origin) {
return url;
}
return this.store.getCurrentEnv.hostUri + url;
} catch (e) {
return this.store.getCurrentEnv.hostUri + url;
}
}
async init() {
@ -62,6 +67,8 @@ export class ApiDetailComponent implements TabViewComponent {
console.error(`Can't no find api`);
}
}
this.url = this.getEnvUrl(this.model.uri);
this.eoOnInit.emit(this.model);
}
}

View File

@ -11,6 +11,7 @@
<eo-monaco-editor
[(code)]="model[0].binaryRawData"
*ngIf="bodyType === TYPE_API_BODY.Raw && model[0]"
[maxHeight]="600"
[config]="{ readOnly: true }"
[autoType]="true"
[eventList]="['type', 'format', 'copy', 'search', 'replace']"

View File

@ -12,14 +12,21 @@
<eo-ng-option *ngFor="let item of REQUEST_METHOD" [nzLabel]="item.key" [nzValue]="item.value"></eo-ng-option>
</eo-ng-select>
<nz-form-item nz-col class="flex-1">
<!-- <button
class="absolute invisible h-0"
nz-popconfirm
nzPopconfirmPlacement="top"
nzPopconfirmTitle="There is domain name information in your path, whether to add it to the environment?"
[nzPopconfirmVisible]="showEnvTips"
></button> -->
<nz-form-control i18n-nzErrorTip nzErrorTip="Please enter API Path">
<input type="text" class="rounded-r" name="uri" (blur)="updateParamsbyUri()" eo-ng-input formControlName="uri" />
<input type="text" class="rounded-r" name="uri" (blur)="blurUri()" eo-ng-input formControlName="uri" />
</nz-form-control>
</nz-form-item>
</eo-ng-input-group>
<eo-ng-input-group nzCompact>
<nz-form-item class="w-[30%] !mb-0" nz-col>
<nz-form-control i18n-nzErrorTip required nzErrorTip="Please select an API group">
<nz-form-item class="w-[30%] !mb-0">
<nz-form-control nz-col i18n-nzErrorTip required nzErrorTip="Please select an API group">
<eo-ng-tree-select
class="group-select"
nzAllowClear="false"
@ -30,13 +37,13 @@
(nzOpenChange)="openGroup()"
[nzShowSearch]="true"
#apiGroup
formControlName="groupId"
[formControl]="$any(validateForm.get('groupId'))"
>
</eo-ng-tree-select>
</nz-form-control>
</nz-form-item>
<nz-form-item nz-col class="flex-1 !mb-0">
<nz-form-control i18n-nzErrorTip nzErrorTip="Please enter API name" [nzValidateStatus]="this.validateForm.controls.name">
<nz-form-item class="flex-1 !mb-0">
<nz-form-control nz-col i18n-nzErrorTip nzErrorTip="Please enter API name" [nzValidateStatus]="this.validateForm.controls.name">
<input type="text" class="rounded-r" name="name" eo-ng-input formControlName="name" />
</nz-form-control>
</nz-form-item>

View File

@ -1,5 +1,5 @@
import { Component, ViewChild, OnDestroy, Input, Output, EventEmitter, OnInit, ViewChildren, TemplateRef, QueryList } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { EoNgFeedbackMessageService } from 'eo-ng-feedback';
import { ApiBodyType, IMPORT_MUI, RequestMethod } from 'eo/workbench/browser/src/app/modules/api-shared/api.model';
@ -41,7 +41,10 @@ export class ApiEditComponent implements OnDestroy, TabViewComponent {
@Output() readonly eoOnInit = new EventEmitter<ApiData>();
@Output() readonly afterSaved = new EventEmitter<ApiData>();
@ViewChild('apiGroup') apiGroup: NzTreeSelectComponent;
validateForm: FormGroup;
// showEnvTips = false;
validateForm: FormGroup = new FormGroup({
groupId: new FormControl('')
});
isSaving = false;
groups: NzTreeNode[];
initTimes = 0;
@ -112,7 +115,8 @@ export class ApiEditComponent implements OnDestroy, TabViewComponent {
.pipe(takeUntil(this.destroy$))
.subscribe((event: KeyboardEvent) => {
const { ctrlKey, metaKey, code } = event;
// 判断 Ctrl+S
// Ctrl+s
if ([ctrlKey, metaKey].includes(true) && code === 'KeyS') {
event.preventDefault();
@ -126,9 +130,11 @@ export class ApiEditComponent implements OnDestroy, TabViewComponent {
bindGetApiParamNum(params) {
return new ApiParamsNumPipe().transform(params);
}
updateParamsbyUri() {
blurUri() {
this.updateParamsbyUri();
}
private updateParamsbyUri() {
const url = this.validateForm.controls['uri'].value;
this.model.requestParams.queryParams = syncUrlAndQuery(url, this.model.requestParams.queryParams, {
nowOperate: 'url',
method: 'keepBoth'
@ -254,7 +260,6 @@ export class ApiEditComponent implements OnDestroy, TabViewComponent {
controls[name] = [this.model[name] || '', [Validators.required]];
});
this.validateForm = this.fb.group(controls);
// pcConsole.log('initBasicForm', controls);
}
private getFormdata(): ApiData {
const { name, uri, groupId } = this.validateForm.value;
@ -274,5 +279,15 @@ export class ApiEditComponent implements OnDestroy, TabViewComponent {
this.emitChangeFun();
});
});
// this.validateForm.get('uri').valueChanges.subscribe(uri => {
// this.showEnvTips = false;
// try {
// const url = new URL(uri);
// if (url.host) {
// this.showEnvTips = true;
// console.log(this.showEnvTips);
// }
// } catch (e) {}
// });
}
}

View File

@ -47,6 +47,7 @@
<eo-monaco-editor
*ngIf="bodyType === TYPE_API_BODY.Raw && model[0]"
[(code)]="model[0].binaryRawData"
[maxHeight]="600"
[eventList]="['type', 'format', 'copy', 'search', 'replace']"
[autoType]="true"
(codeChange)="rawChange($event)"

View File

@ -17,7 +17,6 @@ import { Component, Input, Output } from '@angular/core';
[(code)]="model.response"
id="response"
[autoType]="true"
[maxLine]="15"
class="h-[200px] border-all"
[config]="{ readOnly: !isEdit }"
[eventList]="['type', 'format', 'copy', 'search', 'replace']"

View File

@ -52,6 +52,7 @@
<eo-monaco-editor
[(code)]="code"
[config]="{ language: 'javascript' }"
[autoHeight]="true"
[eventList]="['format', 'copy', 'search', 'replace']"
(codeChange)="handleChange($event)"
[completions]="completions"

View File

@ -34,7 +34,7 @@
></eo-ng-table-pro>
<ng-template #formValue let-item="item" let-index="index">
<div [ngSwitch]="item.dataType">
<input *ngSwitchDefault eo-ng-input [(ngModel)]="item.paramAttr.example" (ngModelChange)="emitModelChange()" />
<input *ngSwitchDefault eo-ng-input maxlength="65535" [(ngModel)]="item.paramAttr.example" (ngModelChange)="emitModelChange()" />
<div *ngSwitchCase="API_PARAMS_TYPE.file">
<label class="cursor-pointer px-[5px] py-[3px] border-all rounded" for="file_{{ index }}" i18n>Choose Files</label>
<span class="ml-[10px]" *ngIf="item.files?.length">{{ item.files?.length }} <span i18n>files selected</span></span>
@ -50,6 +50,7 @@
(codeChange)="rawDataChange($event)"
*ngIf="bodyType === TYPE_API_BODY.Raw && model[0]"
[(code)]="model[0].binaryRawData"
[autoHeight]="true"
[editorType]="editorType"
[config]="editorConfig"
[eventList]="['type', 'format', 'copy', 'search', 'replace']"

View File

@ -14,6 +14,7 @@
<eo-monaco-editor
*ngIf="contentType !== 'formData'"
class="mt-[20px]"
[autoHeight]="true"
[(code)]="model"
[config]="{ readOnly: true }"
[eventList]="['type', 'format', 'copy', 'search']"

View File

@ -59,6 +59,7 @@
<eo-monaco-editor
class="mt-[20px] border-all"
[autoFormat]="true"
[autoHeight]="true"
[autoType]="true"
[code]="responseBody"
[config]="{ readOnly: true }"

View File

@ -137,11 +137,12 @@ export class ApiTestUtilService {
if (!(val.isRequired && val.name)) {
return acc;
}
return { ...acc, [val.name]: val['paramAttr.example'] || val.paramAttr?.example || '' };
return { ...acc, [val.name]: val['paramAttr.example'] || '' };
}, {});
Object.keys(restByName).forEach(restName => {
try {
result = result.replace(new RegExp(`{${restName}}`, 'g'), restByName[restName]);
const pattStr = `{${restName}}`;
result = result.replace(new RegExp(pattStr, 'g'), restByName[restName] || pattStr);
} catch (e) {}
});
return result;
@ -167,7 +168,6 @@ export class ApiTestUtilService {
//* Query Priority is higher than url
nowOperate: 'query'
});
console.log(tmpResult);
result.uri = tmpResult.url;
result.requestParams.queryParams = tmpResult.query;

View File

@ -10,7 +10,7 @@ export class TestServerLocalNodeService extends TestServerService {
}
init(receiveMessage: (message) => void) {
this.electron.ipcRenderer.on('unitTest', (event, args) => {
console.log('[localNode]receiveMessage', args);
// console.log('[localNode]receiveMessage', args);
receiveMessage(this.formatResponseData(args));
});
}

View File

@ -27,7 +27,7 @@ export abstract class TestServerService implements TestServer {
.filter(val => val.name && val.isRequired)
.map((val: BodyParam) => ({
headerName: val.name,
headerValue: val['paramAttr.example'] || val.paramAttr?.example
headerValue: val['paramAttr.example']
}));
};
const formatBody = (inData: Partial<ApiData>) => {
@ -45,7 +45,7 @@ export abstract class TestServerService implements TestServer {
//@ts-ignore
files: val.files?.map(file => file.content),
paramType: val.dataType === ApiParamsType.file ? '1' : '0',
paramInfo: val['paramAttr.example'] || val.paramAttr?.example
paramInfo: val['paramAttr.example']
}));
}
}

View File

@ -100,6 +100,7 @@
<ng-template #messageTmp i18n>Message</ng-template>
<div style="height: calc(100% - 48px)">
<eo-monaco-editor
[autoHeight]="true"
[(code)]="model.msg"
[config]="editorConfig"
[editorType]="editorConfig.language"

View File

@ -12,6 +12,7 @@
<input
*ngIf="isEdit"
eo-ng-input
autofocus
placeholder="Project Name"
name="projectName"
(blur)="changeProjectName(projectName)"

View File

@ -1,3 +1,4 @@
import { trigger, transition, animate, style } from '@angular/animations';
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { FormBuilder, FormGroup, UntypedFormControl, Validators } from '@angular/forms';
import { debounce } from 'lodash-es';
@ -7,11 +8,27 @@ const compMap = {
number: 'input-number',
boolean: 'switch'
} as const;
@Component({
selector: 'eo-schema-form',
animations: [
trigger('myInsertRemoveTrigger', [
transition(':enter', [style({ opacity: 0 }), animate('100ms', style({ opacity: 1 }))]),
transition(':leave', [animate('100ms', style({ opacity: 0 }))])
])
],
styles: [
`
form {
min-height: 200px;
display: none;
}
form:last-of-type {
display: block;
}
`
],
template: `
<form *ngIf="isInit && validateForm" nz-form [formGroup]="validateForm" [nzNoColon]="true" class="form">
<form @myInsertRemoveTrigger *ngIf="isInited && validateForm" nz-form [formGroup]="validateForm" [nzNoColon]="true" class="form">
<nz-form-item nz-col class="flex-1" *ngFor="let field of objectKeys(properties)">
<ng-container *ngIf="properties[field]?.label">
<nz-form-label
@ -92,7 +109,7 @@ export class EoSchemaFormComponent implements OnChanges {
objectKeys = Object.keys;
properties = {};
compMap = compMap;
isInit = true;
isInited = true;
constructor(private fb: FormBuilder) {
this.validateForm = this.fb.group({});
@ -108,12 +125,12 @@ export class EoSchemaFormComponent implements OnChanges {
}
init = debounce(() => {
this.isInit = false;
this.isInited = false;
this.formatProperties();
this.initIfThenElse(this.configuration);
setTimeout(() => {
this.isInit = true;
this.isInited = true;
this.setSettingsModel(this.properties);
});
}, 50);
@ -144,6 +161,7 @@ export class EoSchemaFormComponent implements OnChanges {
}
// https://json-schema.org/understanding-json-schema/reference/conditionals.html#if-then-else
// 在线测试https://jsonschema.dev/s/hpWFy
initIfThenElse(configuration) {
if (Array.isArray(configuration?.allOf)) {
const ifFields = configuration.allOf.reduce((prev, curr) => {

View File

@ -100,7 +100,6 @@ export class MockService {
* @returns
*/
generateResponse(responseBody: BodyParam[]) {
console.log('responseBody', responseBody);
return tree2obj([].concat(responseBody), { key: 'name', valueKey: 'paramAttr.example' });
}
/**

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { NavigationEnd, NavigationError, Router } from '@angular/router';
import { SettingService } from 'eo/workbench/browser/src/app/modules/system-setting/settings.service';
import { Project } from 'eo/workbench/browser/src/app/shared/services/storage/db/models';
import { StorageUtil } from 'eo/workbench/browser/src/app/utils/storage/storage.utils';
@ -190,7 +190,16 @@ export class StoreService {
constructor(private setting: SettingService, private router: Router) {
makeObservable(this); // don't forget to add this if the class has observable fields
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(this.routeListener);
this.router.events
.pipe(
filter(event => {
if (event instanceof NavigationError) {
console.error('NavigationError', event);
}
return event instanceof NavigationEnd;
})
)
.subscribe(this.routeListener);
autorun(() => {
if (this.url) {
this.setPageLevel();

View File

@ -172,24 +172,6 @@ export const getBlobUrl = (inputStream, inputFileType) => {
return tmpUrlObj.createObjectURL(tmpBlob);
};
export const copyText = async (text: string) => {
try {
await navigator.clipboard.writeText(text);
return Promise.resolve(text);
} catch (e) {
const input = document.createElement('input');
input.setAttribute('readonly', 'readonly');
input.setAttribute('value', text);
document.body.appendChild(input);
input.setSelectionRange(0, 9999);
if (document.execCommand('copy')) {
document.execCommand('copy');
console.log($localize`Copied`);
}
document.body.removeChild(input);
return Promise.resolve(text);
}
};
// fn 是需要防抖处理的函数
// wait 是时间间隔
export function debounce(fn, wait = 50) {

View File

@ -1,6 +1,3 @@
// 默认缓存期限为7天
const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
/**
*
*
@ -22,12 +19,12 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
}
/**
* @description
* @param {string} key
* @param {*} value
* @param expire unit second
* @description Set storage
* @param {string} key storage key
* @param {*} value storage value
* @param expire unit second,default unexpire
*/
set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
set(key: string, value: any, expire: number | null = null) {
const stringData = JSON.stringify({
value,
expire: expire !== null ? new Date().getTime() + expire * 1000 : null
@ -36,10 +33,10 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
}
/**
*
* Get storage
*
* @param {string} key
* @param {*=} def
* @param {string} key storage key
* @param {*=} def storage value
*/
get<T = any>(key: string, def: any = null): T {
const item = this.storage.getItem(this.getKey(key));
@ -86,7 +83,7 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
*
* @example
*/
setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
setCookie(name: string, value: any, expire: number | null = 60 * 60 * 24 * 7) {
document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`;
}

View File

@ -91,11 +91,10 @@
<script>
if (window.location.hostname === 'www.postcat.com') {
window.location.href = 'https://postcat.com';
return;
}
</script>
<script type="text/javascript" async>
if (!!window.electron && !window.location.href.includes('http://localhost:4200')) {
if (!!window.electron && !window.location.href.includes('http://localhost')) {
window._gr_ignore_local_rule = true; // * Open local collection
}
!(function (e, t, n, g, i) {

1
test/e2e/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/images/*.png

25
test/e2e/mikasa/README.md Normal file
View File

@ -0,0 +1,25 @@
## 使用步骤
### 环境配置
1. 全局安装 ark 工具包:`yarn add ark-pkg --global`
2. 在 /e2e 目录下安装playwright的相关依赖`yarn`
另:为了让测试用例有语法高亮,请将其命名为 .t 后缀。
### 运行
运行已有的所有测试用例,在 /e2e 目录下执行:
```bash
$ ark mikasa ./ # 编译用例
$ yarn test # 运行用例
```
即可运行并打印出测试报告
另一种情况是,需要单独运行某一个用例,在这种模式下,编译后的代码可以使用 NodeJS 直接运行,多数用在排查问题或写用例时单独看运行效果。
```
$ ark mikasa ./ -d # debug 模式编译
$ node xxx.test.js
```

View File

@ -1,7 +1,6 @@
{
"name": "e2e",
"name": "postcat-e2e",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"codegen":"playwright codegen localhost:4200",

View File

@ -0,0 +1,9 @@
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
headless: true,
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,
video: 'on-first-retry'
}
});

View File

@ -10,7 +10,7 @@ test('test', async ({ page }) => {
await page.getByRole('button', { name: 'Send' }).click();
const request = await responsePromise;
const res = await request.json();
await page.screenshot({ path: './unit-test-basic.png', fullPage: true });
await page.screenshot({ path: './images/unit-test-basic.png', fullPage: true });
});
/**
* XML

1
test/unit/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
!*.js

View File

View File

@ -25,7 +25,7 @@
"eo/*": ["./src/*"]
}
},
"include": ["**/*.d.ts", "./src/**/**.ts", "./src/**/**.js", "scripts/build.js", "e2e/test.ts", "e2e/test.ts"],
"include": ["**/*.d.ts", "./src/**/**.ts", "./src/**/**.js", "scripts/build.js", "test/e2e/test.ts", "test/e2e/test.ts"],
"exclude": ["node_modules", "**/*.spec.ts", "**/browser/**/*.js", "**/browser/**/*.ts", "out"],
"angularCompilerOptions": {
"enableIvy": true

112
yarn.lock
View File

@ -6573,101 +6573,101 @@ env-paths@^2.2.0, env-paths@^2.2.1:
resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
eo-ng-auto-complete@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-auto-complete/-/eo-ng-auto-complete-0.1.5.tgz#1ae16e4c090597c4bbaef5993393cadbf3781688"
integrity sha512-AXFtD30Jj/C/dUb3XCawHv8OqjeljUgfcseXdYPh7VPGeDG1PWalMvCNbArzZBFnzaficO3/piN4+m7eT2T03A==
eo-ng-auto-complete@0.1.11:
version "0.1.11"
resolved "https://registry.npmmirror.com/eo-ng-auto-complete/-/eo-ng-auto-complete-0.1.11.tgz#2083d463fb826e154fccb6db7183eaf3312355c7"
integrity sha512-Cn5YXwvU7EFCOFaNdssQjFCAzhxbm4Ivo5UCmNAHwOD0Xias9eA7TDLGOBZyTtUano10silbp+2SnEriP69GPA==
dependencies:
tslib "^2.3.0"
eo-ng-button@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-button/-/eo-ng-button-0.1.5.tgz#f6c217189c2f617b9db3b05ffd34a7ab97368b5e"
integrity sha512-cn5wg1pgAN6yYTaZxUfDfJeEs+Lw+MZXDJaL6xJXQW52YPCj0hCjiqeZTYQBvjnGNd6wKyso0oKQDX2mCwpZUw==
eo-ng-button@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-button/-/eo-ng-button-0.1.10.tgz#f12a899b4e1d537319e044df1cf22b00f6853299"
integrity sha512-L1HsK8P6Tu0LmD746lORfZ+LLqCQ6uXBQDpAOSt8OL0kmhN+lLyCZhmiLN1MvfU5rVznu9XgKkeHkUrmi+cjZQ==
dependencies:
tslib "^2.3.0"
eo-ng-checkbox@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-checkbox/-/eo-ng-checkbox-0.1.5.tgz#b95375e6a59625d35cb769d96791aaa373e9592d"
integrity sha512-LkAXL4/dbOfF3arGzWdjXFQQfG1UYKhOXgX38STXwYGk50QJasw0/8xb8EIpaK4VNUAOOVym2sm2NBdI9d7NJg==
eo-ng-checkbox@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-checkbox/-/eo-ng-checkbox-0.1.10.tgz#182694a21d9edbde4aaf1cc83afb2e1998158e95"
integrity sha512-cqKNRtaeihWX5NvNbKLJ1ENS16JZqTK6+//m1O3B7aOVofzk5HNr4zybrYtlID4TtcIfI4F0Ytd3tK8T1yMIPw==
dependencies:
tslib "^2.3.0"
eo-ng-dropdown@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-dropdown/-/eo-ng-dropdown-0.1.5.tgz#4df78fc55bd6c90a229775ea3348f4c3969a9b40"
integrity sha512-cxtrAI+pHUOnXkRmseJmUqjerrl5ueaWt1Una5JHMs4NATSyK6ZBzfXWV+oCgZR3hGZNgZdiYE+1pXi9riY1Ag==
eo-ng-dropdown@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-dropdown/-/eo-ng-dropdown-0.1.10.tgz#598ed2b189e98b4e878461d0c74ecc59385f9d90"
integrity sha512-UmiIKRjuZU6ZXPhsCt2JgQoqGaa7V6T70x53VH/EfWo19YCY7wcIE8m9ZkUd2ad9WPtlUCf0g/QF9llhOZ9Eug==
dependencies:
tslib "^2.3.0"
eo-ng-feedback@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-feedback/-/eo-ng-feedback-0.1.5.tgz#a4b6644ff8e1459e28b04661cf58dc84339d93bd"
integrity sha512-lJKIQ1BYd7dtdQamgHDBIO+qoUVkS3u0Tu7yd428w6xvkBj0/HEsVHY+gwGpqShrcdcjyT4ATlCJRFnlWGzOCQ==
eo-ng-feedback@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-feedback/-/eo-ng-feedback-0.1.10.tgz#05b6fd8dc8af22d1da2f4e9e8d2c9d8abfb5dd76"
integrity sha512-Juk4Hubauz676Fgn3MmzD7o9Bsrr8uygBWtiTmUKmsBOjwNvrBoq3Limz3PK3w34Y49qv+zngEezNf0DRaYdEQ==
dependencies:
tslib "^2.3.0"
eo-ng-input@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-input/-/eo-ng-input-0.1.5.tgz#dbb3a829eb64f379e1989cbb7047c5aeee826fcc"
integrity sha512-c8eYFNDJ94VPzbbm5dZfNGkNnHU4AUGb9aNtNhoQVqy4CwLwF7Hy/+bb49zkS3X7AqsqUZ1CHrS32wtzHawzDQ==
eo-ng-input@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-input/-/eo-ng-input-0.1.10.tgz#34a72d2617910c3a1899789011c059828d35b4e4"
integrity sha512-iMhXy5ziDwQ7Qfb2F3pCfP541lg+mMdXkwAfeGH66ctGa892eTnoGz2qQQjr+EbDiXB7R0XwwCnn/Wp9gY8ATw==
dependencies:
tslib "^2.3.0"
eo-ng-layout@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-layout/-/eo-ng-layout-0.1.5.tgz#83200c7663e59847dd514b7d472481fb640e54a3"
integrity sha512-N+xWoqUsKV4RcM+ODJYCoS8wcPJGIA/VSwYcNKFPUqhmoYVWt1ZqZnW80hCJ7AFysLWHRVOna8Bk2u7f76Nz8w==
eo-ng-layout@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-layout/-/eo-ng-layout-0.1.10.tgz#7996bc9c5e30af2212ba6c9850f010846275f1c1"
integrity sha512-hzDrdUEFMI7Xj2MpvTGBI1ugqqTpxKy055O0yCmeVrFpVvAuaK9V5Nk1QXt0Jp1TYiJkWHIO3VpWPF8YJ4NEiQ==
dependencies:
tslib "^2.3.0"
eo-ng-menu@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-menu/-/eo-ng-menu-0.1.5.tgz#cde6b9bfe3ce476b603e4d97b10378eb77db7bb7"
integrity sha512-sXkfxfW8yICGNSm+x5E16/2yWLBmNFdHyJbYbqRhGaf6rgnK3HAZt/XzxBnjKKiulGWK+evtOX1VjPC2DQ73Hg==
eo-ng-menu@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-menu/-/eo-ng-menu-0.1.10.tgz#fb3518a2b51ef16a410d716b0e2caebb92f95f69"
integrity sha512-wjh551GFwqsyW2wG1GgX5qej3K6xSVvXIHrbBGSBggtQ+E3l7iqaYYLhMe52vlPLtz4v6KvD7+sVsGcQug/PwQ==
dependencies:
tslib "^2.3.0"
eo-ng-radio@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-radio/-/eo-ng-radio-0.1.5.tgz#bd86e834074544a12f730f9e15f170c64f8b74f6"
integrity sha512-ifB4LwCsm5yMRUBIABaFO8/37f4y/TbKRcr3I9N/RXuVMriRk+vf0VB4UaQQoDXu5hvht6SRdzvXqJPw8YENhA==
eo-ng-radio@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-radio/-/eo-ng-radio-0.1.10.tgz#7b490820043f5e336883522d4f82f18aa1d90ea7"
integrity sha512-vtLwHJpNlSJIxTGy5xRcNaHIqOBbC1DS8YxeMIdRQFb68eGPWhwpWeijaZD64u+TdPe/trqqgt0Wo0kN57WChw==
dependencies:
tslib "^2.3.0"
eo-ng-select@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-select/-/eo-ng-select-0.1.5.tgz#214bf269a48fd2941d71635a5d6a7acbc595b062"
integrity sha512-j3QBSN07otnZXRj48SuPl4J8orua68o1WqCgk0vKgnC9GR7RaTcinUrsmLMxIYixrA0loCCgWuH7q/A2fzrPaA==
eo-ng-select@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-select/-/eo-ng-select-0.1.10.tgz#96df245f1073bb0c3bcad260ec94b23760bad486"
integrity sha512-ybXd2brWPVnnGBJgDkkmd6RQEH6m/oRGFcnmT0f0TXefwRoyzjVMRjvad03ctgs+91SD6IBRHeI/lnv7fg0P8w==
dependencies:
tslib "^2.3.0"
eo-ng-switch@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-switch/-/eo-ng-switch-0.1.5.tgz#f2898166bd6067564da3074fca3b39ddf2b35336"
integrity sha512-Hd0OKBb2Bx4k4knWtvhpfafKjU62W4BRBwaOw8e/+q6kONEZJa0X0kLydrdmvWEGvYWjGhnRZlxqbhXtGq0DMQ==
eo-ng-switch@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-switch/-/eo-ng-switch-0.1.10.tgz#6e58e0bab6ea2ce53e1f098eda42e6034d5587d6"
integrity sha512-f0/vrK0NohtJEfDj1Cj/09ep2lRY4HH6ML9REz9mvB9y3RNhpFRX+lO7wPvBEIKBy/bVQmpAu1FaIEN9PW7sKA==
dependencies:
tslib "^2.3.0"
eo-ng-table@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-table/-/eo-ng-table-0.1.5.tgz#7ed43ad8f1ce63ecf63dd2c87a6d086eb3d29317"
integrity sha512-BO3c3b3v9OUK0pJ7ThPT3LbE7nM/Zjs7+jEiF2E96g6ZyNa7zlDCE8i/VKLgeZ+jaz8MUE4cavRxNfBAagD+xQ==
eo-ng-table@0.1.11:
version "0.1.11"
resolved "https://registry.npmmirror.com/eo-ng-table/-/eo-ng-table-0.1.11.tgz#a2d3e72a6d4bc0ecc9d41e07a53efafcb2021772"
integrity sha512-45W9Jhskl+VcgU5nmO0GPKmorBXn8swpe8Oq1d3zsujLqxNEI90MgMGMjsxeJAJiUvXbie7v3dM+onKhaVmhQA==
dependencies:
tslib "^2.3.0"
eo-ng-tabs@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-tabs/-/eo-ng-tabs-0.1.5.tgz#085bb7b0724698e7707d7b8c09e41b87d4a47a14"
integrity sha512-nu7ucCbftKgg1/x5j/vIFsTSoQOsGGjcHthwpqhZLJtTZ9vmlqDRd8etMtGDLjj4BnI2vo/Q+yH8x9gR7RCjsA==
eo-ng-tabs@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-tabs/-/eo-ng-tabs-0.1.10.tgz#5cf1cfc28d56f29168459082dea18556a37b04ba"
integrity sha512-yJ81B8rjBqf0GikenGKrOnWEOvwaUvA9CPNCMPD0pCCCgoo0Ve7UhSilIlHaIpLFkw36meZ/HeReBfohV3Hkbw==
dependencies:
tslib "^2.3.0"
eo-ng-tree@0.1.5:
version "0.1.5"
resolved "https://registry.npmmirror.com/eo-ng-tree/-/eo-ng-tree-0.1.5.tgz#eee91a99201e809c78ff3b18023aabc49478e620"
integrity sha512-wgSR1bJV3dcqps8BUE0+O17VefrTqUvRoapjXA7zOcQbuNeu0JGIMiB2xQJvO5ssZ8EYwJx6st935GydP9d+wQ==
eo-ng-tree@0.1.10:
version "0.1.10"
resolved "https://registry.npmmirror.com/eo-ng-tree/-/eo-ng-tree-0.1.10.tgz#7e4f58b7544fc76b69923fa4edc981185f33f0a2"
integrity sha512-R57GMgZGZsOC+pIxubpiRKEJCyxCqtMTLCLoeYMB7KwjEBl1XD6irid/19vq7AcXsAFXK8z/Hb/p0YymkW8nEg==
dependencies:
tslib "^2.3.0"