Group UI optimization (#89)

* fix: can not navigate to extensions page

* feat: add iconpark

* feat:  sidebar done

* feat: update style

* fix: theme components show in bottom

* feat: homepage almost done

* feat: extension detail readme

* feat: ignore eo-setting

* feat: ignore eo-setting

* feat: extension detail support text

* fix: build error

* feat: mock extension preview in web

* update css style

* feat: extension detail support web preview

* pref: update css style

* feat: update setting component

* feat: update style

* feat: ignore

* remove diver

* feat: add link in github logo

* fix: build ts error

* fix: search extension

* feat: change bind event of click

* fix: setting button can not click

* feat: system setting support web

* fix: flex style issue

* feat: icon button width

* chore: switch data source btn move to setting modal

* feat: i18n

* fix: module inject error

* fix: some css style

* feat: hide cloud icon

* add logo to settingModal

* feat: update settingModal UI

* chore: serve support i18n

* feat: base env

* fix: some error message

* fix: build error

* feat: history done

* feat: done env

* feat: settings logic

* fix: env modal auto open while page load

* pref: api-tabs css style

* fix: api-tabs css style issus

* pref: update settingsModal css style

* fix: set default language

* feat: update env

* feat: add goto env callback

* fix: remote source for web

* fix: remote source for web

* fix: rename API to REST

* feat: fix tips text

* feat: fix icon size

* fix: make api-tabs space evenly

* fix: about component descritions issues

* fix: style of env select position

* fix: settings modal navigate to extension page faile

* feat: modal footer left

* fix: mock some logic

* feat: history icon style

* fix: extension list css style

* update extension css style

* fix: env save failed

* chore: build i18n destop app

* fix: rename event name

* feat: change language

* chore

* test vercel

* test vercel

* delete vercel.json

* fix: lose baseHref

* chore: change angular.json beforeBuild

* fix: 2 bug about env

* perf: extension detail scrollbar style

* feat: add ts.code-snippets

* feat: initial i18n

* fix: open test history

* feat: add uuid in history tab

* fix: custom iconpark component

* test merge

* fix: center plus icon

* fix: setttings should auto save

* translate: en

* feat: change lang

* feat: translate en

* translate en

* translate en

* translate en

* delete package.lock

* fix: root directory run error

* vercel.json

* vercel redirects

* vercel.json

* change

* change

* route by accept-language

* vercel.json

* feat:redirect

* vercel rewrites

* vercel

* chore: translate chinese comment to english

* translate: cn

* push zh.xlf

* fix: manage environment

* translate zh

* fix: some css style issues

* feat: extension detail support web preview

* fix: update extension page css style

* ci: support mac pack

* fix: ci trigger condition

* ci: set time zone

* ci: set time zone

* ci: remove build step

* ci: remove set time zone

* feat: add upload.js

* chore: delete .yarnc

* ci: update trigger condion

* v1.2.3

* fix: release:m1 comand

* feat: add notarize after sign

* Update upload.js

* feat: test

* feat: delete upload.js in release command

* feat: test v1.1.3

* feat: test v1.2.5

* fix: can not minimize with windows

* fix: testResult.response can be null

* feat: test 1.2.6

* feat: upload.js

* test upload

* feat: add test file

* test upload

* test upload

* test upload

* test upload

* test upload

* feat: update upload.js

* feat: add log

* feat: update

* feat: delete upload.yml

* feat: delete useless file

* refactor: add settings service

* feat: use custom update url

* feat: v1.2.2

* ci: auto update by qiniu cdn

* chore: release 1.2.3

* feat: v1.2.4

* remove yaml devp

* chore: release 1.2.4

* update upload.js

* update electron builder config

Co-authored-by: buqiyuan <1743369777@qq.com>
Co-authored-by: 夜鹰 <17kungfuboy@gmail.com>
Co-authored-by: renqian805 <84910084+renqian805@users.noreply.github.com>
This commit is contained in:
Scarqin 2022-07-12 15:42:13 +08:00 committed by GitHub
parent f2ba7b9156
commit 84c5d65f94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
156 changed files with 8074 additions and 3152 deletions

View File

@ -1,46 +0,0 @@
# release.yml
# workflow 's name
name: Build electron App for Win
on:
push:
tags:
- 'v*.*.*'
# Workflow's jobs
jobs:
# job's id
release:
# job's name
name: build and release electron app
# the type of machine to run the job on
runs-on: ${{ matrix.os }}
# Platforms to build on/for
strategy:
matrix:
os: [windows-latest]
steps:
# check out repository
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16
registry-url: https://registry.npmjs.org/
# create electron-builder.json
- name: Create and populate electron-builer.json file
env:
ELE_BUILER_WIN: ${{ secrets.ELE_BUILER_WIN }}
shell: bash
run: |
echo $ELE_BUILER_WIN > electron-builer.json
# install & build app for win
- run: yarn install --frozen-lockfile
- run: yarn global add @angular/cli
- run: yarn release
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}

94
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,94 @@
name: Release
on:
push:
branches:
- feat/preview
jobs:
release:
name: build and release electron app
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
steps:
- name: Check out git repository
uses: actions/checkout@v3.0.0
- name: Install Node.js
uses: actions/setup-node@v3.0.0
with:
node-version: '16'
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v3
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: |
yarn install --frozen-lockfile
echo "${{ secrets.QINIU_ENV_JS }}" > qiniu_env.js
- name: Release for Windows
if: matrix.os == 'windows-latest'
run: |
yarn release
dir ./release
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Release for MacOS
if: matrix.os == 'macos-latest'
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
# BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
# PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH
# echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode --output $PP_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# apply provisioning profile
# mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
# cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
echo "${{ secrets.NOTARIZE_JS }}" > build/notarize.js
yarn release:m1
yarn release
ls ./release
# clean up keychain and provisioning profile
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
- name: Release for Linux
if: matrix.os == 'ubuntu-latest'
run: |
yarn release
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ out/
!/api/*.js
!/build/*.js
!*.config.js
!upload.js
scripts/notarize.js
# dependencies

28
.vscode/ts.code-snippets vendored Normal file
View File

@ -0,0 +1,28 @@
{
"Print to console": {
"scope": "typescript",
"prefix": "log",
"body": [
"console.log('111$1');"
],
"description": "Log output to console"
},
"Create a Angular Component": {
"scope": "typescript",
"prefix": "comp",
"body": [
"import { Component, OnInit } from '@angular/core';",
"@Component({",
"// standalone: true,",
"selector: '$1',",
"template: `<div></div>`,",
"styleUrls: []",
"})",
"export class $2Component implements OnInit {",
"constructor() {}",
"ngOnInit() {}",
"}"
],
"description": "Create a new Angular Component"
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>

3
build/notarize.js Normal file
View File

@ -0,0 +1,3 @@
exports.default = function notarizing(context) {
return context;
};

View File

@ -15,7 +15,13 @@
"out/app/common/**/*",
"!**/*.ts"
],
"publish": ["github"],
"publish": [
{
"provider": "generic",
"url": "https://packages.eoapi.io"
},
"github"
],
"generateUpdatesFilesForAllChannels": true,
"nsis": {
"oneClick": false,
@ -38,11 +44,14 @@
"icon": "src/app/common/images/512x512.png",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"target": ["dmg", "zip"]
},
"dmg": {
"sign": false
},
"afterSign": "build/notarize.js",
"linux": {
"icon": "src/app/common/images/",
"target": ["AppImage"]

View File

@ -1,6 +1,7 @@
{
"name": "eoapi",
"version": "1.2.0",
"souceLocale": "zh-Hans",
"version": "1.2.4",
"main": "out/app/electron-main/main.js",
"description": "A lightweight, extensible API tool",
"homepage": "https://github.com/eolinker/eoapi.git",
@ -21,8 +22,9 @@
"electron:dev:static": "npm run electron:tsc && electron .",
"electron:dev": "npm run electron:tsc && electron . --development",
"build": "npm-run-all -s build:workbench electron:tsc && electron-builder build",
"build:static": "npm run electron:tsc && electron-builder build",
"release": "npm-run-all -s build:workbench electron:tsc && electron-builder --publish=always",
"build:static": "npm-run-all -s build:workbench electron:tsc && electron-builder build",
"release": "npm-run-all -s build:workbench electron:tsc && electron-builder --publish=always && node upload.js",
"release:m1": "npm-run-all -s build:workbench electron:tsc && electron-builder -m=dmg --arm64 -p onTagOrDraft",
"test": "npm-run-all --serial test:*",
"electron:tsc": "tsc -p tsconfig.json",
"copyfile:out": "copyfiles -u 1 src/**/*.json src/app/common/images/** out"
@ -33,8 +35,8 @@
"content-disposition": "^0.5.4",
"copyfiles": "2.4.1",
"crypto-js": "^4.1.1",
"electron-log": "^4.4.7",
"electron-updater": "^5.0.1",
"electron-log": "^4.4.8",
"electron-updater": "^5.0.5",
"express": "4.18.1",
"fix-path": "3.0.0",
"form-data": "^4.0.0",
@ -42,27 +44,29 @@
"iconv-lite": "^0.6.3",
"npm": "6.14.17",
"portfinder": "1.0.28",
"resolve": "^1.22.0",
"qiniu": "^6.1.13",
"resolve": "^1.22.1",
"rxjs": "7.5.5",
"xml2js": "^0.4.23"
},
"devDependencies": {
"@types/node": "17.0.32",
"@typescript-eslint/eslint-plugin": "5.23.0",
"@typescript-eslint/parser": "5.23.0",
"dmg-builder": "23.0.9",
"electron": "19.0.1",
"electron-builder": "23.0.9",
"@types/node": "18.0.0",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"dmg-builder": "23.2.0",
"electron": "19.0.6",
"electron-builder": "23.1.0",
"electron-notarize": "1.2.1",
"electron-reload": "1.5.0",
"eslint": "8.15.0",
"eslint": "8.18.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jsdoc": "39.2.9",
"eslint-plugin-jsdoc": "39.3.3",
"eslint-plugin-prefer-arrow": "1.2.3",
"npm-run-all": "4.1.5",
"ts-node": "10.7.0",
"typescript": "~4.6.4",
"wait-on": "6.0.1"
"ts-node": "10.8.1",
"typescript": "~4.7.4",
"wait-on": "6.0.1",
"yaml": "2.1.1"
},
"__npminstall_done": false,
"node-module-alias": {

View File

@ -1,89 +1,89 @@
import { ipcRenderer, app } from 'electron';
import { isNotEmpty } from 'eo/shared/common/common';
import * as fs from 'fs';
import * as path from 'path';
import {
StorageRes,
StorageResStatus,
StorageHandleArgs,
StorageProcessType,
} from '../../../../workbench/browser/src/app/shared/services/storage/index.model';
import { IndexedDBStorage } from '../../../../workbench/browser/src/app/shared/services/storage/IndexedDB/lib/index';
// import { ipcRenderer, app } from 'electron';
// import { isNotEmpty } from 'eo/shared/common/common';
// import * as fs from 'fs';
// import * as path from 'path';
// import {
// StorageRes,
// StorageResStatus,
// StorageHandleArgs,
// StorageProcessType,
// } from '../../../../workbench/browser/src/app/shared/services/storage/index.model';
// import { IndexedDBStorage } from '../../../../workbench/browser/src/app/shared/services/storage/IndexedDB/lib/index';
class StorageService {
private ipcRenderer: typeof ipcRenderer;
private app: typeof app;
private fs: typeof fs;
private path: typeof path;
constructor() {
this.ipcRenderer = window.require('electron').ipcRenderer;
this.app = window.require('electron').app;
this.fs = window.require('fs');
this.path = window.require('path');
this.storageListen();
}
// class StorageService {
// private ipcRenderer: typeof ipcRenderer;
// private app: typeof app;
// private fs: typeof fs;
// private path: typeof path;
// constructor() {
// this.ipcRenderer = window.require('electron').ipcRenderer;
// this.app = window.require('electron').app;
// this.fs = window.require('fs');
// this.path = window.require('path');
// this.storageListen();
// }
/**
*
* @param args
*/
private storageListenHandle(args: StorageHandleArgs): void {
const action: string = args.action || undefined;
const handleResult: StorageRes = {
status: StorageResStatus.invalid,
data: undefined,
callback: args.callback || null,
};
if (IndexedDBStorage && IndexedDBStorage[action] && typeof IndexedDBStorage[action] === 'function') {
IndexedDBStorage[action](...args.params).subscribe(
(result: any) => {
handleResult.data = result;
if (isNotEmpty(result)) {
handleResult.status = StorageResStatus.success;
} else {
handleResult.status = StorageResStatus.empty;
}
this.storageListenHandleNotify(args.type, handleResult);
},
(error: any) => {
handleResult.status = StorageResStatus.error;
this.storageListenHandleNotify(args.type, handleResult);
}
);
} else {
this.storageListenHandleNotify(args.type, handleResult);
}
}
// /**
// * 存储监听处理
// * @param args
// */
// private storageListenHandle(args: StorageHandleArgs): void {
// const action: string = args.action || undefined;
// const handleResult: StorageRes = {
// status: StorageResStatus.invalid,
// data: undefined,
// callback: args.callback || null,
// };
// if (IndexedDBStorage && IndexedDBStorage[action] && typeof IndexedDBStorage[action] === 'function') {
// IndexedDBStorage[action](...args.params).subscribe(
// (result: any) => {
// handleResult.data = result;
// if (isNotEmpty(result)) {
// handleResult.status = StorageResStatus.success;
// } else {
// handleResult.status = StorageResStatus.empty;
// }
// this.storageListenHandleNotify(args.type, handleResult);
// },
// (error: any) => {
// handleResult.status = StorageResStatus.error;
// this.storageListenHandleNotify(args.type, handleResult);
// }
// );
// } else {
// this.storageListenHandleNotify(args.type, handleResult);
// }
// }
/**
*
* @param type
* @param result
*/
private storageListenHandleNotify(type: string, result: StorageRes): void {
try {
if (StorageProcessType.default === type) {
this.ipcRenderer.send('eo-storage', { type: 'result', result: result });
} else if (StorageProcessType.sync === type) {
const storageTemp = this.path.join(this.app.getPath('home'), '.eo', 'tmp.storage');
this.fs.writeFileSync(storageTemp, JSON.stringify(result));
} else if (StorageProcessType.remote === type) {
window.require('@electron/remote').getGlobal('shareObject').storageResult = result;
}
} catch (e) {
console.log(e);
}
}
// /**
// * 数据存储监听通知返回
// * @param type
// * @param result
// */
// private storageListenHandleNotify(type: string, result: StorageRes): void {
// try {
// if (StorageProcessType.default === type) {
// this.ipcRenderer.send('eo-storage', { type: 'result', result: result });
// } else if (StorageProcessType.sync === type) {
// const storageTemp = this.path.join(this.app.getPath('home'), '.eo', 'tmp.storage');
// this.fs.writeFileSync(storageTemp, JSON.stringify(result));
// } else if (StorageProcessType.remote === type) {
// window.require('@electron/remote').getGlobal('shareObject').storageResult = result;
// }
// } catch (e) {
// console.log(e);
// }
// }
/**
*
* @returns
*/
private storageListen(): void {
this.ipcRenderer.on('eo-storage', (event, args: StorageHandleArgs) => this.storageListenHandle(args));
}
// /**
// * 开启数据存储监听
// * @returns
// */
// private storageListen(): void {
// this.ipcRenderer.on('eo-storage', (event, args: StorageHandleArgs) => this.storageListenHandle(args));
// }
isElectron(): boolean {
return !!(window && window.process && window.process.type);
}
}
// isElectron(): boolean {
// return !!(window && window.process && window.process.type);
// }
// }

View File

@ -54,12 +54,19 @@ function createWindow(): BrowserWindow {
contextIsolation: false, // false if you want to run e2e test with Spectron
},
});
// 启动mock服务
mockServer.start(win as any);
proxyOpenExternal(win);
let loadPage = async () => {
let currentUrl = win.webContents.getURL();
let locale = ['zh', 'en'].find((val) => currentUrl.includes(val));
const file: string =
processEnv === 'development'
? 'http://localhost:4200'
: `file://${path.join(__dirname, '../../../src/workbench/browser/dist/index.html')}`;
: `file://${path.join(
__dirname,
`../../../src/workbench/browser/dist/${locale || app.getLocale()}/index.html`
)}`;
win.loadURL(file);
if (['development'].includes(processEnv)) {
win.webContents.openDevTools({
@ -69,8 +76,6 @@ function createWindow(): BrowserWindow {
UnitWorkerModule.setup({
view: win,
});
// 启动mock服务
await mockServer.start(win as any);
};
win.webContents.on('did-fail-load', (event, errorCode) => {
console.error('did-fail-load', errorCode);
@ -123,7 +128,7 @@ try {
ipcMain.on('message', function (event, arg) {
console.log('recieve render msg=>', arg, arg.action);
//only action from mainView can be executed
if (event.frameId !== 1) return;
// if (event.frameId !== 1) return;
switch (arg.action) {
case 'minimize': {
win.minimize();

View File

@ -5,6 +5,10 @@ const appVersion = require('../../../package.json').version;
export class EoUpdater {
constructor() {
this.watchLog();
// autoUpdater.setFeedURL({
// provider: 'generic',
// url: 'https://packages.eoapi.io',
// });
// 是否自动更新
// autoUpdater.autoDownload = window.eo.getModuleSettings('common.app.autoUpdate') !== false;
if (appVersion.includes('beta')) autoUpdater.channel = 'beta';

View File

@ -125,8 +125,9 @@ window.eo.storageRemote = (args) => {
return output;
};
window.eo.saveSettings = ({ settings, nestedSettings }) => {
return ipcRenderer.sendSync('eo-sync', { action: 'saveSettings', data: { settings, nestedSettings } });
window.eo.saveSettings = (settings) => {
// console.log('window.eo.saveSettings', settings);
return ipcRenderer.sendSync('eo-sync', { action: 'saveSettings', data: { settings } });
};
window.eo.saveModuleSettings = (moduleID, settings) => {

View File

@ -41,10 +41,10 @@ export class Configuration implements ConfigurationInterface {
/**
*
*/
saveSettings({ settings = {}, nestedSettings = {} }): boolean {
saveSettings({ settings = {} }): boolean {
// console.log('settings', settings);
let data = this.loadConfig();
data.settings = settings;
data.nestedSettings = nestedSettings;
return this.saveConfig(data);
}
@ -56,11 +56,7 @@ export class Configuration implements ConfigurationInterface {
saveModuleSettings(moduleID: string, settings: ConfigurationValueInterface): boolean {
let data = this.loadConfig();
data.settings ??= {};
data.nestedSettings ??= {};
data.settings[moduleID] = settings;
const propArr = moduleID.split('.');
const target = propArr.slice(0, -1).reduce((p, k) => p?.[k], data.nestedSettings);
target[propArr.at(-1)] = settings;
return this.saveConfig(data);
}
@ -93,13 +89,38 @@ export class Configuration implements ConfigurationInterface {
* @returns
*/
getModuleSettings<T = any>(section?: string): T {
const localSettings = this.getSettings();
localSettings.nestedSettings ??= {};
if (section) {
return section.split('.')?.reduce((p, k) => p?.[k], localSettings.nestedSettings);
}
return localSettings.nestedSettings;
return this.getConfiguration(section);
}
/**
* key路径获取对应的配置的值
*
* @param key
* @returns
*/
getConfiguration = (keyPath: string) => {
const localSettings = this.getSettings()?.settings || {};
if (Reflect.has(localSettings, keyPath)) {
return Reflect.get(localSettings, keyPath);
}
const keys = Object.keys(localSettings);
const filterKeys = keys.filter((n) => n.startsWith(keyPath));
if (filterKeys.length) {
return filterKeys.reduce((pb, ck) => {
const keyArr = ck.replace(`${keyPath}.`, '').split('.');
const targetKey = keyArr.pop();
const target = keyArr.reduce((p, v) => {
p[v] ??= {};
return p[v];
}, pb);
target[targetKey] = localSettings[ck];
return pb;
}, {});
}
return undefined;
};
}
export default () => new Configuration();

View File

@ -2,7 +2,7 @@ import * as path from 'path';
import { ModuleHandlerOptions, ModuleInfo } from '../types';
import { fileExists, readJson } from 'eo/shared/node/file';
import { isNotEmpty } from 'eo/shared/common/common';
import { readFileSync } from 'node:fs';
/**
*
* @class CoreHandler
@ -37,6 +37,7 @@ export class CoreHandler {
try {
const baseDir: string = this.getModuleDir(name);
moduleInfo = readJson(path.join(baseDir, 'package.json')) as ModuleInfo;
moduleInfo.introduction = readFileSync(path.join(baseDir, 'README.md')).toString();
moduleInfo.baseDir = baseDir;
moduleInfo.main = 'file://' + path.join(moduleInfo.baseDir, moduleInfo.main);
if (moduleInfo.preload?.length > 0) {

View File

@ -26,6 +26,8 @@ export interface ModuleInfo {
version: string;
// 模块描述
description: string;
// 详细说明
introduction: string;
// 模块ID用于关联
moduleID: string;
// 模块名称,用于显示

View File

@ -35,7 +35,7 @@ export class MockServer {
private mockUrl = '';
constructor() {
this.app = express();
this.app ??= express();
this.createProxyServer();
}
@ -66,7 +66,7 @@ export class MockServer {
// if (!protocolReg.test(req.url)) {
// match request type
const isMatchType = this.configuration.getModuleSettings<boolean>('eoapi-features.mock.matchType');
if (req.query.mockID || isMatchType) {
if (req.query.mockID || isMatchType !== false) {
this.view.webContents.send('getMockApiList', JSON.parse(jsonStringify(req)));
ipcMain.once('getMockApiList', (event, message) => {
console.log('getMockApiList message', message);
@ -104,11 +104,11 @@ export class MockServer {
.listen(_port, () => {
const { port } = this.server.address() as AddressInfo;
this.mockUrl = `http://127.0.0.1:${port}`;
console.log(`mock服务已启动${this.mockUrl}`);
console.log(`mock service is started${this.mockUrl}`);
resolve(this.mockUrl);
})
.on('error', (error) => {
console.error('mock服务启动失败: ' + error);
console.error('mock is failed to start: ' + error);
reject(error);
});
});

View File

@ -7,6 +7,7 @@ dist/
/app-builds
/release
src/**/*.js
!build/*.js
!src/karma.conf.js
!src/ng1/**/*.js
*.js.map

View File

@ -1,267 +1 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"cli": {
"analytics": false,
"defaultCollection": "@angular-eslint/schematics"
},
"version": 1,
"newProjectRoot": "projects",
"projects": {
"eoapi": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"schematics": {
"@schematics/angular:application": {
"strict": true
}
},
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"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",
{
"glob": "**/*",
"input": "../../../node_modules/@ant-design/icons-angular/src/inline-svg/",
"output": "/assets/"
}
],
"styles": [
{
"input": "src/assets/theme/classic_forest.scss",
"bundleName": "classic_forest",
"inject": false
},
{
"input": "src/assets/theme/classic_sunrise.scss",
"bundleName": "classic_sunrise",
"inject": false
},
{
"input": "src/assets/theme/classic_toy.scss",
"bundleName": "classic_toy",
"inject": false
},
{
"input": "src/assets/theme/clean_cloud.scss",
"bundleName": "clean_cloud",
"inject": false
},
{
"input": "src/assets/theme/clean_forest.scss",
"bundleName": "clean_forest",
"inject": false
},
{
"input": "src/assets/theme/clean_sunrise.scss",
"bundleName": "clean_sunrise",
"inject": false
},
{
"input": "src/assets/theme/night_black.scss",
"bundleName": "night_black",
"inject": false
},
{
"input": "src/assets/theme/night_cmd.scss",
"bundleName": "night_cmd",
"inject": false
},
{
"input": "src/assets/theme/night_dusk.scss",
"bundleName": "night_dusk",
"inject": false
},
{
"input": "src/assets/theme/night_forest.scss",
"bundleName": "night_forest",
"inject": false
},
"src/styles.scss",
"src/assets/theme/antd.less",
"src/assets/font/iconfont.css",
"src/ng1/index.css"
],
"scripts": [
"src/ng1/lib/angular/angular.js",
"src/ng1/app.module.js",
"src/ng1/component/select-default.js",
"src/ng1/component/sort-and-filter.js",
"src/ng1/component/auto-complete.js",
"src/ng1/component/list-block.js",
"src/ng1/directive/get-dom-length.directive.js",
"src/ng1/directive/drop-down-menu.directive.js",
"src/ng1/directive/sort.directive.js",
"src/ng1/directive/drop-change-space.directive.js",
"src/ng1/directive/inner-html.directive.js",
"src/ng1/directive/insert-html.directive.js",
"src/ng1/directive/copy-common.directive.js"
],
"customWebpackConfig": {
"path": "./angular.webpack.js",
"replaceDuplicatePlugins": true
},
"allowedCommonJsDependencies": [
"brace",
"qs",
"rxjs"
]
},
"configurations": {
"dev": {
"optimization": false,
"outputHashing": "none",
"sourceMap": true,
"namedChunks": false,
"aot": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
]
},
"web": {
"optimization": false,
"outputHashing": "none",
"sourceMap": true,
"namedChunks": false,
"aot": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.web.ts"
}
]
},
"webaot": {
"optimization": false,
"outputHashing": "none",
"sourceMap": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.web.ts"
}
]
},
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"browserTarget": "eoapi:build"
},
"configurations": {
"dev": {
"browserTarget": "eoapi:build:dev"
},
"web": {
"browserTarget": "eoapi:build:web"
},
"webaot": {
"browserTarget": "eoapi:build:web"
},
"production": {
"browserTarget": "eoapi:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "eoapi:build"
}
},
"test": {
"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"
]
}
}
}
},
"eoapi-e2e": {
"root": "e2e",
"projectType": "application",
"architect": {
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"e2e/**/*.ts"
]
}
}
}
}
},
"defaultProject": "eoapi",
"schematics": {
"@schematics/angular:component": {
"prefix": "eo",
"style": "scss"
},
"@schematics/angular:directive": {
"prefix": "eo"
}
}
}
{"$schema":"./node_modules/@angular/cli/lib/config/schema.json","cli":{"analytics":false,"defaultCollection":"@angular-eslint/schematics"},"version":1,"newProjectRoot":"projects","projects":{"eoapi":{"root":"","i18n":{"sourceLocale":{"code":"en","baseHref":""},"locales":{"zh":{"translation":"src/locale/messages.zh.xlf","baseHref":""}}},"sourceRoot":"src","projectType":"application","schematics":{"@schematics/angular:application":{"strict":true}},"architect":{"build":{"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",{"glob":"**/*","input":"../../../node_modules/@ant-design/icons-angular/src/inline-svg/","output":"/assets/"}],"styles":[{"input":"src/assets/theme/classic_forest.scss","bundleName":"classic_forest","inject":false},"src/styles.scss","src/assets/theme/antd.less","src/assets/font/iconfont.css","src/ng1/index.css"],"scripts":["src/ng1/lib/angular/angular.js","src/ng1/app.module.js","src/ng1/component/select-default.js","src/ng1/component/sort-and-filter.js","src/ng1/component/auto-complete.js","src/ng1/component/list-block.js","src/ng1/directive/get-dom-length.directive.js","src/ng1/directive/drop-down-menu.directive.js","src/ng1/directive/sort.directive.js","src/ng1/directive/drop-change-space.directive.js","src/ng1/directive/inner-html.directive.js","src/ng1/directive/insert-html.directive.js","src/ng1/directive/copy-common.directive.js"],"customWebpackConfig":{"path":"./angular.webpack.js","replaceDuplicatePlugins":true},"allowedCommonJsDependencies":["brace","qs","rxjs"]},"configurations":{"dev":{"optimization":false,"outputHashing":"none","sourceMap":true,"namedChunks":false,"extractLicenses":true,"vendorChunk":false,"buildOptimizer":false,"fileReplacements":[{"replace":"src/environments/environment.ts","with":"src/environments/environment.dev.ts"}]},"web":{"optimization":false,"outputHashing":"none","sourceMap":true,"namedChunks":false,"localize":false,"extractLicenses":true,"vendorChunk":false,"buildOptimizer":false,"fileReplacements":[{"replace":"src/environments/environment.ts","with":"src/environments/environment.web.ts"}]},"webCn":{"optimization":false,"outputHashing":"none","sourceMap":true,"namedChunks":false,"localize":["zh"],"extractLicenses":true,"vendorChunk":false,"buildOptimizer":false,"fileReplacements":[{"replace":"src/environments/environment.ts","with":"src/environments/environment.web.ts"}]},"production":{"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"}]}}},"serve":{"builder":"@angular-builders/custom-webpack:dev-server","options":{"browserTarget":"eoapi:build"},"configurations":{"dev":{"browserTarget":"eoapi:build:dev"},"web":{"browserTarget":"eoapi:build:web"},"webCn":{"browserTarget":"eoapi:build:webCn"},"production":{"browserTarget":"eoapi:build:production"}}},"extract-i18n":{"builder":"@angular-devkit/build-angular:extract-i18n","options":{"browserTarget":"eoapi:build"}},"test":{"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"]}}}},"eoapi-e2e":{"root":"e2e","projectType":"application","architect":{"lint":{"builder":"@angular-eslint/builder:lint","options":{"lintFilePatterns":["e2e/**/*.ts"]}}}}},"defaultProject":"eoapi","schematics":{"@schematics/angular:component":{"prefix":"eo","style":"scss"},"@schematics/angular:directive":{"prefix":"eo"}}}

View File

@ -0,0 +1,56 @@
//change angular.json
const fs = require('fs');
const { execSync } = require('child_process');
class webPlatformBuilder {
resetBuildConfig(json) {
delete json.projects.eoapi.i18n.sourceLocale.baseHref;
Object.keys(json.projects.eoapi.i18n.locales).forEach((val) => {
delete json.projects.eoapi.i18n.locales[val].baseHref;
});
return json;
}
executeBuild() {
execSync('ng build -c production', { stdio: 'inherit' });
}
}
class appPlatformBuilder {
resetBuildConfig(json) {
json.projects.eoapi.i18n.sourceLocale.baseHref = '';
Object.keys(json.projects.eoapi.i18n.locales).forEach((val) => {
json.projects.eoapi.i18n.locales[val].baseHref = '';
});
return json;
}
executeBuild() {
execSync('ng build --base-href ./', { stdio: 'inherit' });
}
}
class PlatformBuilder {
constructor(platForm) {
switch (platForm) {
case 'web': {
this.instance = new webPlatformBuilder();
break;
}
case 'app': {
this.instance = new appPlatformBuilder();
break;
}
}
}
build() {
let buildConfigJson = require( '../angular.json');
buildConfigJson = this.instance.resetBuildConfig(buildConfigJson);
let that=this;
fs.writeFile('./angular.json', JSON.stringify(buildConfigJson), function (err) {
if (err) {
console.error('build/beforeBuild.js:', err);
}
that.instance.executeBuild();
});
}
}
let platform = process.argv[2] || 'app';
let platformBuilder = new PlatformBuilder(platform);
platformBuilder.build();

View File

@ -4,13 +4,12 @@
"private": true,
"scripts": {
"ng": "ng",
"start": "ng serve -c web",
"build": "ng build --base-href ./",
"build:dev": "yarn build -- -c dev",
"build:prod": "yarn build -- -c production",
"build:web": "ng build -c production",
"start": "ng serve -c web -o",
"start:zh": "ng serve -c webCn -o",
"build": "node ./build/build.js",
"build:web": "node ./build/build.js web",
"ng:serve": "ng serve -c web -o",
"ng:web": "ng serve -c webaot -o",
"lang:gen": "ng extract-i18n --output-path src/locale",
"test": "ng test --watch=false",
"test:watch": "ng test",
"e2e": "yarn build:prod && playwright test -c e2e/playwright.config.ts e2e/",
@ -19,69 +18,70 @@
"lint": "ng lint"
},
"dependencies": {
"@angular/animations": "13.3.6",
"@angular/cdk": "13.3.6",
"@angular/common": "13.3.6",
"@angular/compiler": "13.3.6",
"@angular/core": "13.3.6",
"@angular/forms": "13.3.6",
"@angular/language-service": "13.3.6",
"@angular/platform-browser": "13.3.6",
"@angular/platform-browser-dynamic": "13.3.6",
"@angular/router": "13.3.6",
"@angular/upgrade": "^13.3.6",
"@angular-cli/base-href-webpack": "1.0.16",
"@angular/animations": "14.0.3",
"@angular/cdk": "14.0.3",
"@angular/common": "14.0.3",
"@angular/compiler": "14.0.3",
"@angular/core": "14.0.3",
"@angular/forms": "14.0.3",
"@angular/language-service": "14.0.3",
"@angular/platform-browser": "14.0.3",
"@angular/platform-browser-dynamic": "14.0.3",
"@angular/router": "14.0.3",
"@angular/upgrade": "^14.0.3",
"@ant-design/icons-angular": "13.1.0",
"@babel/runtime": "7.18.0",
"@ngxs/store": "3.7.3",
"@babel/runtime": "7.18.3",
"@ngxs/store": "3.7.4",
"angular": "1.8.2",
"brace": "0.11.1",
"js-beautify": "1.14.3",
"js-beautify": "1.14.4",
"markdown-it": "13.0.1",
"ng-zorro-antd": "13.2.1",
"ng-zorro-antd": "13.3.2",
"ngx-ace-wrapper": "12.0.0",
"qs": "6.10.3",
"qs": "6.11.0",
"rxjs": "7.5.5",
"tslib": "^2.4.0",
"zone.js": "~0.11.5"
"zone.js": "~0.11.6"
},
"devDependencies": {
"@angular-builders/custom-webpack": "13.1.0",
"@angular-devkit/build-angular": "13.3.5",
"@angular-eslint/builder": "13.2.1",
"@angular-eslint/eslint-plugin": "13.2.1",
"@angular-eslint/eslint-plugin-template": "13.2.1",
"@angular-eslint/schematics": "13.2.1",
"@angular-eslint/template-parser": "13.2.1",
"@angular/cli": "13.3.5",
"@angular/compiler-cli": "13.3.6",
"@angular-builders/custom-webpack": "14.0.0",
"@angular-devkit/build-angular": "14.0.3",
"@angular-eslint/builder": "14.0.0",
"@angular-eslint/eslint-plugin": "14.0.0",
"@angular-eslint/eslint-plugin-template": "14.0.0",
"@angular-eslint/schematics": "14.0.0",
"@angular-eslint/template-parser": "14.0.0",
"@angular/cli": "14.0.3",
"@angular/compiler-cli": "14.0.3",
"@angular/localize": "14.0.3",
"@ngx-translate/core": "14.0.0",
"@ngx-translate/http-loader": "7.0.0",
"@types/jasmine": "4.0.3",
"@types/jasminewd2": "2.0.10",
"@types/markdown-it": "12.2.3",
"@types/node": "17.0.32",
"@typescript-eslint/eslint-plugin": "5.23.0",
"@typescript-eslint/parser": "5.23.0",
"@types/node": "18.0.0",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"autoprefixer": "10.4.7",
"conventional-changelog-cli": "2.2.2",
"dexie": "3.2.1",
"eslint": "8.15.0",
"dexie": "3.2.2",
"eslint": "8.18.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jsdoc": "39.2.9",
"eslint-plugin-jsdoc": "39.3.3",
"eslint-plugin-prefer-arrow": "1.2.3",
"jasmine-core": "4.1.1",
"jasmine-core": "4.2.0",
"jasmine-spec-reporter": "7.0.0",
"karma": "6.3.19",
"karma": "6.4.0",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jasmine": "5.0.0",
"karma-jasmine-html-reporter": "1.7.0",
"lodash": "4.17.21",
"node-polyfill-webpack-plugin": "1.1.4",
"karma-jasmine": "5.1.0",
"karma-jasmine-html-reporter": "2.0.0",
"node-polyfill-webpack-plugin": "2.0.0",
"postcss": "8.4.14",
"tailwindcss": "3.0.24",
"ts-node": "10.7.0",
"typescript": "~4.6.4",
"webpack": "5.72.1"
"tailwindcss": "3.1.4",
"ts-node": "10.8.1",
"typescript": "~4.7.4",
"webpack": "5.73.0"
},
"engines": {
"node": ">=14.17.0"

View File

@ -31,7 +31,7 @@ export class AppComponent {
this.remoteService.switchDataSource();
}, 5000);
this.modal.info({
nzContent: '无法连接到远程数据源,请检查后重新连接,为了不影响使用,程序将帮您跳转到本地',
nzContent: $localize `:{can not connect}:Unable to connect to remote data sources, please check and reconnect. In order not to affect use, the app will help you jump to local`,
nzFooter: null,
nzCentered: true,
nzClosable: false,

View File

@ -1,6 +1,6 @@
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, LOCALE_ID } from '@angular/core';
import { EouiModule } from 'eo/workbench/browser/src/app/eoui/eoui.module';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
@ -16,12 +16,14 @@ import { EnvState } from './shared/store/env.state';
import { UpgradeModule } from '@angular/upgrade/static';
import { MessageService } from './shared/services/message';
import { IndexedDBStorage } from 'eo/workbench/browser/src/app/shared/services/storage/IndexedDB/lib/';
import { HttpStorage, BaseUrlInterceptor } from 'eo/workbench/browser/src/app/shared/services/storage/http/lib';
import { HttpStorage } from 'eo/workbench/browser/src/app/shared/services/storage/http/lib';
import { StorageService } from 'eo/workbench/browser/src/app/shared/services/storage';
import { RemoteService } from 'eo/workbench/browser/src/app/shared/services/remote/remote.service';
import { SettingService } from 'eo/workbench/browser/src/app/core/services/settings/settings.service';
import { NzMessageService } from 'ng-zorro-antd/message';
import { NzModalService } from 'ng-zorro-antd/modal';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { BaseUrlInterceptor } from 'eo/workbench/browser/src/app/shared/services/storage/http/lib/baseUrl.service';
@NgModule({
declarations: [AppComponent],
@ -37,9 +39,10 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http';
NgxsModule.forRoot([EnvState]),
],
providers: [
SettingService,
StorageService,
RemoteService,
MessageService,
StorageService,
IndexedDBStorage,
HttpStorage,
NzMessageService,
@ -51,6 +54,7 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http';
},
{ provide: HTTP_INTERCEPTORS, useClass: BaseUrlInterceptor, multi: true },
],
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
bootstrap: [AppComponent],
})
export class AppModule {

View File

@ -16,18 +16,18 @@ export class AppService {
this.ipcRenderer.on('getMockApiList', async (event, req = {}) => {
const sender = event.sender;
console.log('req', req);
const isEnabledMatchType = window.eo?.getModuleSettings?.('eoapi-features.mock.matchType');
const isEnabledMatchType = window.eo?.getModuleSettings?.('eoapi-features.mock.matchType') !== false;
// console.log('wo接收到了哇', event, message);
const { mockID } = req.query;
if (Number.isInteger(Number(mockID))) {
try {
const mock = await this.getMockByMockID(Number(mockID));
const apiData = await this.getApiData(Number(mock.apiDataID));
if (isEnabledMatchType) {
if (!mock && isEnabledMatchType) {
const result = await this.matchApiData(1, req);
return sender.send('getMockApiList', result);
} else {
mock.response = this.generateResponse(apiData.responseBody) || mock.response;
mock.response = mock?.response ?? this.generateResponse(apiData.responseBody);
}
sender.send('getMockApiList', mock);
} catch (error) {
@ -42,7 +42,7 @@ export class AppService {
const response = await this.matchApiData(1, req);
sender.send('getMockApiList', response);
} else {
sender.send('getMockApiList', { response: { message: `没有找到ID为${mockID}的mock` }, url: req.url });
sender.send('getMockApiList', { response: { message: $localize `No mock found with ID ${mockID}` }, url: req.url });
}
});
}

View File

@ -0,0 +1,73 @@
import { Injectable, Inject } from '@angular/core';
export const LOCAL_SETTINGS_KEY = 'LOCAL_SETTINGS_KEY';
export const getSettings = () => {
try {
return JSON.parse(localStorage.getItem(LOCAL_SETTINGS_KEY) || '{}');
} catch (error) {
return {};
}
};
@Injectable({
providedIn: 'root',
})
export class SettingService {
get settings() {
return this.getSettings();
}
/** get local settings */
getSettings() {
return getSettings();
}
putSettings(settings: Record<string, any> = {}) {
this.saveSetting({ ...this.settings, ...settings });
}
deleteSettings(keys: string[]) {
const settings = { ...this.settings };
keys.forEach((key) => Reflect.deleteProperty(settings, key));
this.saveSetting(settings);
}
saveSetting(settings: string | Record<string, any> = {}) {
if (typeof settings === 'object') {
localStorage.setItem(LOCAL_SETTINGS_KEY, JSON.stringify(settings));
} else {
localStorage.setItem(LOCAL_SETTINGS_KEY, settings);
}
}
/**
* Get the value of the corresponding configuration according to the key path
*
* @param key
* @returns
*/
getConfiguration = (keyPath: string) => {
const localSettings = this.getSettings();
if (Reflect.has(localSettings, keyPath)) {
return Reflect.get(localSettings, keyPath);
}
const keys = Object.keys(localSettings);
const filterKeys = keys.filter((n) => n.startsWith(keyPath));
if (filterKeys.length) {
return filterKeys.reduce((pb, ck) => {
const keyArr = ck.replace(`${keyPath}.`, '').split('.');
const targetKey = keyArr.pop();
const target = keyArr.reduce((p, v) => {
p[v] ??= {};
return p[v];
}, pb);
target[targetKey] = localSettings[ck];
return pb;
}, {});
}
return undefined;
};
}

View File

@ -1,9 +1,9 @@
export const THEMES = [
{
title: '经典',
title: $localize`Classic`,
lists: [
{
key: '森林',
key: $localize`Forest`,
value: 'classic_forest',
},
// {

View File

@ -1,7 +1,7 @@
<div>
<div class="button_list">
<span *ngIf="eventList.includes('type') && !hiddenList.includes('type')">
内容类型
<span *ngIf="eventList.includes('type') && !hiddenList.includes('type')" i18n>
Content Type
<nz-select [(ngModel)]="editorType">
<nz-option *ngFor="let item of typeList" [nzValue]="item.value" [nzLabel]="item.label"></nz-option>
</nz-select>

View File

@ -16,27 +16,27 @@ type EventType = 'format' | 'copy' | 'search' | 'replace' | 'type' | 'download'
const eventHash = new Map()
.set('format', {
label: '整理格式',
label: $localize`Format`,
icon: 'deployment-unit',
})
.set('copy', {
label: '复制',
label: $localize`:@@Copy:Copy`,
icon: 'copy',
})
.set('search', {
label: '搜索',
label: $localize`:@@Search:Search`,
icon: 'search',
})
.set('download', {
label: '下载',
label: $localize`Download`,
icon: 'download',
})
.set('newTab', {
label: '新开标签',
label: $localize`New Tab`,
icon: 'file-text',
})
.set('replace', {
label: '替换',
label: $localize`Replace`,
icon: 'security-scan',
});
@ -155,7 +155,7 @@ export class EoEditorComponent implements AfterViewInit, OnInit, OnChanges {
const value = session.getValue();
if (navigator.clipboard) {
navigator.clipboard.writeText(value);
this.message.success('复制成功');
this.message.success($localize`Copied`);
return;
}
break;

View File

@ -16,13 +16,14 @@ import { EoMessageComponent } from './message/eo-message.component';
// ! Directive
import { CellDirective } from './table/eo-table/cell.directive';
import { EoIconparkIconModule } from 'eo/workbench/browser/src/app/eoui/iconpark-icon/eo-iconpark-icon.module';
const antdModules = [NzTableModule, NzIconModule, NzButtonModule, NzInputModule, NzSelectModule];
const DEFAULT_ACE_CONFIG: AceConfigInterface = {};
@NgModule({
declarations: [EoTableComponent, EoEditorComponent, EoMessageComponent, CellDirective],
imports: [CommonModule, FormsModule, AceModule, ...antdModules],
imports: [CommonModule, FormsModule, AceModule, EoIconparkIconModule, ...antdModules],
exports: [EoTableComponent, EoEditorComponent, EoMessageComponent, CellDirective],
providers: [
{

View File

@ -0,0 +1,17 @@
import { Component, Input } from '@angular/core';
@Component({
// standalone: true,
selector: 'eo-iconpark-icon',
template: `<iconpark-icon [name]="name" [ngStyle]="{ fontSize: size }"></iconpark-icon>`,
styleUrls: [],
host: {
class: 'inline-flex',
},
})
export class EoIconparkIconComponent {
@Input() name: string;
@Input() size = '18px';
constructor() {}
}

View File

@ -0,0 +1,11 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EoIconparkIconComponent } from './eo-iconpark-icon.component';
@NgModule({
declarations: [EoIconparkIconComponent],
imports: [CommonModule],
exports: [EoIconparkIconComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class EoIconparkIconModule {}

View File

@ -1,10 +1,6 @@
import { NgModule } from '@angular/core';
import {
ListBlockCommonComponent,
} from './component.ajs';
const declarations: Array<any> = [
ListBlockCommonComponent
];
import { ListBlockCommonComponent } from './component.ajs';
const declarations: Array<any> = [ListBlockCommonComponent];
@NgModule({
declarations: declarations,
imports: [],

View File

@ -1,25 +1,72 @@
<nz-layout class="layout">
<nz-sider nzTheme="light" nzWidth="250">
<nz-content>
<div class="inner-content">
<eo-api-group-tree></eo-api-group-tree>
</div>
<!-- <div class="side-container"> -->
<nz-content class="api-tabs">
<nz-tabset nzCentered [nzAnimated]="false" [(nzSelectedIndex)]="tabsIndex">
<nz-tab [nzTitle]="apiTitle">
<ng-template #apiTitle>
<span i18n-nzTooltipTitle nzTooltipTitle="Collections" nz-tooltip class="text-lg">
<eo-iconpark-icon name="folder-open"></eo-iconpark-icon>
</span>
</ng-template>
<div class="inner-content">
<eo-api-group-tree></eo-api-group-tree>
</div>
</nz-tab>
<nz-tab [nzTitle]="historyTitle">
<ng-template #historyTitle>
<span i18n-nzTooltipTitle nzTooltipTitle="History" nz-tooltip class="text-lg">
<eo-iconpark-icon name="history"></eo-iconpark-icon>
</span>
</ng-template>
<eo-history></eo-history>
</nz-tab>
<nz-tab [nzTitle]="envTitle">
<ng-template #envTitle>
<span i18n-nzTooltipTitle nzTooltipTitle="Environment" nz-tooltip class="text-lg">
<eo-iconpark-icon name="instruction"></eo-iconpark-icon>
</span>
</ng-template>
<eo-env></eo-env>
</nz-tab>
</nz-tabset>
</nz-content>
<!-- </div> -->
</nz-sider>
<nz-layout class="right-layout">
<nz-content>
<div class="inner-content">
<div class="tabs-bar f_row">
<div class="flex items-center tabs-bar">
<eo-api-tab class="fg1"></eo-api-tab>
<div class="env">
<eo-env></eo-env>
<div class="flex items-center fix-mt">
<nz-select
[(ngModel)]="envUuid"
[(nzOpen)]="isOpen"
(nzOpenChange)="handleEnvSelectStatus($event)"
[nzDropdownRender]="renderTemplate"
nzAllowClear
i18n-nzPlaceHolder="Environment Dropdown placeholder"
nzPlaceHolder="Environment"
>
<nz-option *ngFor="let item of envList" [nzValue]="item.uuid" [nzLabel]="item.name"></nz-option>
</nz-select>
<ng-template #renderTemplate>
<nz-divider></nz-divider>
<a class="text-sx manager-env" nz-button nzType="link" (click)="gotoEnvManager()" i18n>Manage Environment</a>
</ng-template>
</div>
</div>
<div class="content_container {{ this.id ? 'has_tab_page' : '' }}">
<nz-tabset class="inside_page_tab" [nzAnimated]="false" *ngIf="this.id" nzLinkRouter>
<nz-tab *ngFor="let tab of TABS">
<a *nzTabLink nz-tab-link (click)="clickContentMenu(tab)" [routerLink]="[tab.routerLink]"
queryParamsHandling="merge">{{ tab.title }}</a>
<a
*nzTabLink
nz-tab-link
(click)="clickContentMenu(tab)"
[routerLink]="[tab.routerLink]"
queryParamsHandling="merge"
>{{ tab.title }}</a
>
</nz-tab>
</nz-tabset>
<router-outlet></router-outlet>

View File

@ -2,20 +2,41 @@ nz-content {
background-color: var(--MAIN_BG);
}
:lang(zh) {
nz-select {
width: 132px;
}
}
:lang(en) {
nz-select {
width: 162px;
}
}
nz-sider {
width: 250px;
background-color: #fff;
}
::ng-deep {
::ng-deep {
.ant-layout-sider-children {
border-right: 1px solid #f0f0f0;
padding: 5px;
padding: 0 10px;
.group-btn-add,
.group-search {
margin-bottom: 12px;
}
}
.api-tabs .ant-tabs-nav-list {
width: 100%;
justify-content: space-evenly;
.ant-tabs-tab {
flex: 1;
justify-content: center;
margin: 0;
}
}
.tabs-bar {
background-color: var(--SEC_BG);
padding: 5px 10px 0 15px;
@ -23,9 +44,6 @@ nz-sider {
margin-bottom: -1px;
align-items: center;
justify-content: space-between;
.env {
height: 36px;
}
}
.content_container {
border-top: 1px solid var(--BORDER);
@ -56,3 +74,22 @@ nz-sider {
color: var(--BLACK_TAG_BG);
}
}
.api-tabs {
::ng-deep .ant-tabs-tab {
padding: 0.2em 0;
}
}
.fix-mt {
margin-top: -6px;
}
nz-divider {
margin: 0.1em 0;
}
.manager-env {
color: #00785a;
}

View File

@ -1,9 +1,12 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { StorageRes, StorageResStatus } from '../../shared/services/storage/index.model';
import { Subject, takeUntil } from 'rxjs';
import { Store } from '@ngxs/store';
import { Message, MessageService } from '../../shared/services/message';
import { ApiService } from './api.service';
import { StorageService } from '../../shared/services/storage';
import { Change } from '../../shared/store/env.state';
import { RemoteService } from 'eo/workbench/browser/src/app/shared/services/remote/remote.service';
@Component({
@ -20,17 +23,22 @@ export class ApiComponent implements OnInit, OnDestroy {
TABS = [
{
routerLink: 'detail',
title: '文档',
title: $localize`:@@API Detail:Preview`,
},
{
routerLink: 'edit',
title: '编辑',
title: $localize`Edit`,
},
{
routerLink: 'test',
title: '测试',
title: $localize`Test`,
},
];
isOpen = false;
envInfo: any = {};
envList: Array<any> = [];
activeUuid: number | string = 0;
tabsIndex = 0;
private destroy$: Subject<void> = new Subject<void>();
constructor(
@ -38,9 +46,23 @@ export class ApiComponent implements OnInit, OnDestroy {
private apiService: ApiService,
private messageService: MessageService,
private storage: StorageService,
private remoteService: RemoteService
private remoteService: RemoteService,
private store: Store
) {}
get envUuid(): number {
return Number(localStorage.getItem('env:selected')) || 0;
}
set envUuid(value) {
this.activeUuid = value;
if (value !== null) {
localStorage.setItem('env:selected', value == null ? '' : value.toString());
} else {
localStorage.removeItem('env:selected');
}
this.changeStoreEnv(value);
}
ngOnInit(): void {
this.watchChangeRouter();
this.watchApiAction();
@ -51,6 +73,18 @@ export class ApiComponent implements OnInit, OnDestroy {
title: 'Mock',
});
}
this.envUuid = Number(localStorage.getItem('env:selected'));
// * load All env
this.getAllEnv().then((result: any[]) => {
this.envList = result || [];
});
this.messageService.get().subscribe(({ type }) => {
if (type === 'updateEnv') {
this.getAllEnv().then((result: any[]) => {
this.envList = result || [];
});
}
});
}
ngOnDestroy() {
this.destroy$.next();
@ -98,6 +132,38 @@ export class ApiComponent implements OnInit, OnDestroy {
});
}
clickContentMenu(data) {
this.messageService.send({ type: 'beforeChangeRouter', data: data });
this.messageService.send({ type: 'beforeChangeRouter', data });
}
gotoEnvManager() {
// * switch to env
this.tabsIndex = 2;
}
getAllEnv(uuid?: number) {
const projectID = 1;
return new Promise((resolve) => {
this.storage.run('environmentLoadAllByProjectID', [projectID], async (result: StorageRes) => {
if (result.status === StorageResStatus.success) {
return resolve(result.data || []);
}
return resolve([]);
});
});
}
private changeStoreEnv(uuid) {
if (uuid == null) {
this.store.dispatch(new Change(null));
return;
}
this.storage.run('environmentLoadAllByProjectID', [1], (result: StorageRes) => {
if (result.status === StorageResStatus.success) {
const data = result.data.find((val) => val.uuid === Number(uuid));
this.store.dispatch(new Change(data));
}
});
}
handleEnvSelectStatus(event: boolean) {}
}

View File

@ -30,6 +30,7 @@ import { NzInputModule } from 'ng-zorro-antd/input';
import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
import { NzDividerModule } from 'ng-zorro-antd/divider';
import { NzCardModule } from 'ng-zorro-antd/card';
import { NzSelectModule } from 'ng-zorro-antd/select';
import { NzModalModule } from 'ng-zorro-antd/modal';
import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
@ -38,8 +39,10 @@ import { ApiTabComponent } from './tab/api-tab.component';
import { ApiService } from './api.service';
import { ElectronService } from '../../core/services';
import { ApiOverviewComponent } from './overview/api-overview.component';
import { HistoryComponent } from './history/eo-history.component';
import { ApiMockComponent } from './mock/api-mock.component';
import { IndexedDBStorage } from 'eo/workbench/browser/src/app/shared/services/storage/IndexedDB/lib/';
import { SharedModule } from 'eo/workbench/browser/src/app/shared/shared.module';
const COMPONENTS = [
ApiComponent,
@ -52,6 +55,7 @@ const COMPONENTS = [
ImportApiComponent,
ExtensionSelectComponent,
ApiMockComponent,
HistoryComponent,
];
@NgModule({
imports: [
@ -79,7 +83,9 @@ const COMPONENTS = [
EnvModule,
NzCardModule,
NzModalModule,
NzSelectModule,
NzPopconfirmModule,
SharedModule,
],
declarations: [...COMPONENTS],
exports: [],

View File

@ -20,10 +20,10 @@ export class ApiService {
delete({ name, uuid }: ApiData): void {
this.nzModalService.confirm({
nzTitle: '删除确认?',
nzContent: `确认要删除数据 <strong title="${name}">${
nzTitle: $localize`Deletion Confirmation?`,
nzContent: $localize`Are you sure you want to delete the data <strong title="${name}">${
name.length > 50 ? name.slice(0, 50) + '...' : name
}</strong> `,
}</strong> ? You cannot restore it once deleted!`,
nzOnOk: () => {
this.storage.run('apiDataRemove', [uuid], (result: StorageRes) => {
if (result.status === StorageResStatus.success) {

View File

@ -6,37 +6,45 @@
</div>
<p class="api_name text_omit">{{ apiData.name }}</p>
<div *ngIf="apiData.requestHeaders && apiData.requestHeaders.length">
<p class="api_line">请求头部</p>
<p class="api_line" i18n>Request Headers</p>
<eo-api-detail-header [model]="apiData.requestHeaders"></eo-api-detail-header>
</div>
<div *ngIf="apiData.queryParams && apiData.queryParams.length">
<p class="api_line">Query 参数</p>
<p class="api_line" i18n>Query</p>
<eo-api-detail-query [model]="apiData.queryParams"></eo-api-detail-query>
</div>
<div *ngIf="apiData.restParams && apiData.restParams.length">
<p class="api_line">Rest 参数</p>
<p class="api_line" i18n>REST</p>
<eo-api-detail-rest [model]="apiData.restParams"></eo-api-detail-rest>
</div>
<div *ngIf="apiData.requestBody?.length">
<div class="api_line">
Body 请求参数<nz-tag class="ml10" nzColor="default">{{ CONST.BODY_TYPE[apiData.requestBodyType] }}</nz-tag>
<nz-tag *ngIf="apiData.requestBodyType==='json'" nzColor="default">最外层结构为:{{
CONST.JSON_ROOT_TYPE[apiData.requestBodyJsonType] }}</nz-tag>
<span i18n>Body</span>
<nz-tag class="ml10" nzColor="default">{{ CONST.BODY_TYPE[apiData.requestBodyType] }}</nz-tag>
<nz-tag *ngIf="apiData.requestBodyType === 'json'" nzColor="default" i18n
>The outermost structure is: {{ CONST.JSON_ROOT_TYPE[apiData.requestBodyJsonType] }}</nz-tag
>
</div>
<eo-api-detail-body [bodyType]="apiData.requestBodyType" [model]="apiData.requestBody"
[jsonRootType]="apiData.requestBodyJsonType"></eo-api-detail-body>
<eo-api-detail-body
[bodyType]="apiData.requestBodyType"
[model]="apiData.requestBody"
[jsonRootType]="apiData.requestBodyJsonType"
></eo-api-detail-body>
</div>
<div *ngIf="apiData.responseHeaders && apiData.responseHeaders.length">
<p class="api_line">返回头部</p>
<p class="api_line" i18n>Response Headers</p>
<eo-api-detail-header [model]="apiData.responseHeaders"></eo-api-detail-header>
</div>
<div *ngIf="apiData.responseBody && apiData.responseBody.length">
<p class="api_line">返回参数</p>
<eo-api-detail-body [bodyType]="apiData.responseBodyType" [model]="apiData.responseBody"
[jsonRootType]="apiData.responseBodyJsonType"></eo-api-detail-body>
<p class="api_line" i18n>Response</p>
<eo-api-detail-body
[bodyType]="apiData.responseBodyType"
[model]="apiData.responseBody"
[jsonRootType]="apiData.responseBodyJsonType"
></eo-api-detail-body>
</div>
<div *ngIf="isElectron">
<p class="api_line">MOCK </p>
<p class="api_line">MOCK</p>
<eo-api-detail-mock [apiData]="apiData"></eo-api-detail-mock>
</div>
</section>

View File

@ -57,7 +57,7 @@ export class ApiDetailService {
}
}
initListConf(opts) {
opts.title = opts.title || '参数';
opts.title = opts.title || $localize`Param`;
return {
setting: {
draggable: true,
@ -66,22 +66,22 @@ export class ApiDetailService {
itemStructure: Object.assign({}, opts.itemStructure),
tdList: [
{
thKey: opts.nameTitle || `${opts.title}`,
thKey: opts.nameTitle || $localize`${opts.title} Name`,
type: 'text',
modelKey: 'name',
placeholder: opts.nameTitle || `${opts.title}`,
placeholder: opts.nameTitle || $localize`${opts.title} Name`,
width: 250,
mark: 'name',
},
{
thKey: '必填',
thKey: $localize`Required`,
type: 'html',
html: '{{item.required?"是":""}}',
html: $localize`{{item.required?"True":""}}`,
width: 80,
mark: 'require',
},
{
thKey: '说明',
thKey: $localize`:@@Description:Description`,
type: 'text',
modelKey: 'description',
width: 250,
@ -89,7 +89,7 @@ export class ApiDetailService {
},
{
thKey: '示例',
thKey: $localize`Example`,
type: 'text',
modelKey: 'example',
width: 200,
@ -116,28 +116,28 @@ export class ApiDetailService {
itemStructure: Object.assign({}, opts.itemStructure),
tdList: [
{
thKey: '参数名',
thKey: $localize`Param Name`,
type: 'depthHtml',
html: '<span class="param-name-span">{{item.name}}</span>',
width: 260,
mark: 'name',
},
{
thKey: '类型',
thKey: $localize`Type`,
type: 'text',
modelKey: 'type',
mark: 'type',
width: 80,
},
{
thKey: '必填',
thKey: $localize`Required`,
type: 'html',
html: '{{item.required?"是":""}}',
html: $localize`{{item.required?"True":""}}`,
width: 60,
mark: 'require',
},
{
thKey: '说明',
thKey: $localize`:@@Description:Description`,
type: 'text',
modelKey: 'description',
width: 260,
@ -145,20 +145,20 @@ export class ApiDetailService {
},
{
thKey: '示例',
thKey: $localize`Example`,
type: 'text',
modelKey: 'example',
width: 200,
mark: 'example',
},
{
thKey: `<button type="button" class="eo-operate-btn" ng-click="$ctrl.data.isSpreedBtnClick=!$ctrl.data.isSpreedBtnClick;$ctrl.data.isSpreed=true;$ctrl.mainObject.baseFun.spreedAll($event);$ctrl.data.isSpreed=false;">{{$ctrl.data.isSpreedBtnClick?"全部收缩":"全部展开"}}</button>`,
thKey: $localize`<button type="button" class="eo-operate-btn" ng-click="$ctrl.data.isSpreedBtnClick=!$ctrl.data.isSpreedBtnClick;$ctrl.data.isSpreed=true;$ctrl.mainObject.baseFun.spreedAll($event);$ctrl.data.isSpreed=false;">{{$ctrl.data.isSpreedBtnClick?"Shrink All":"Expand All"}}</button>`,
type: 'html',
html: `<span class="eo-operate-btn fs12" ng-show="item.minimum ||
html: $localize`<span class="eo-operate-btn fs12" ng-show="item.minimum ||
item.maximum ||
item.minLength ||
item.maxLength ||
(item.enum && item.enum.length > 0 && item.enum[0].value)">{{item.isClick?"":""}}</span>`,
(item.enum && item.enum.length > 0 && item.enum[0].value)">{{item.isClick?"Shrink":"Expand"}}</span>`,
mark: 'fn_btn',
width: '100px',
class: 'undivide_line_lbcc',

View File

@ -4,8 +4,7 @@ import { ApiEditBody, ApiBodyType, JsonRootType } from '../../../../shared/servi
import { ApiDetailService } from '../api-detail.service';
@Component({
selector: 'eo-api-detail-body',
templateUrl: './api-detail-body.component.html',
styleUrls: ['./api-detail-body.component.scss'],
templateUrl: './api-detail-body.component.html'
})
export class ApiDetailBodyComponent implements OnInit, OnChanges, OnDestroy {
@Input() model: string | ApiEditBody[] | any;
@ -55,7 +54,6 @@ export class ApiDetailBodyComponent implements OnInit, OnChanges, OnDestroy {
}
private initListConf() {
this.listConf = this.apiDetail.initBodyListConf({
title: '参数',
itemStructure: this.itemStructure,
});
}

View File

@ -22,8 +22,8 @@ export class ApiDetailHeaderComponent implements OnInit, OnChanges {
private initListConf() {
this.listConf = this.detailService.initListConf({
dragCacheVar: 'DRAG_VAR_API_EDIT_HEADER',
title: '头部',
nameTitle: '标签',
title: $localize`:@@Header:Header`,
nameTitle: $localize`Key`,
});
}
}

View File

@ -1,6 +1,6 @@
<eo-table [(model)]="mocklList" [columns]="mockListColumns" [dataModel]="{ name: '', value: '', description: '' }">
<ng-template cell="url" let-scope="scope" let-index="index">
<span nzTooltipTitle="点击复制" nzTooltipPlacement="top" nz-tooltip (click)="copyText(scope.url )" class="truncate">
<span i18n-nzTooltipTitle nzTooltipTitle="Click to Copy" nzTooltipPlacement="top" nz-tooltip (click)="copyText(scope.url )" class="truncate">
{{ scope.url }}
</span>
</ng-template>

View File

@ -26,12 +26,12 @@ export class ApiDetailMockComponent implements OnInit, OnChanges {
listConf: object = {};
isVisible = false;
createWayMap = {
system: '系统自动创建',
custom: '手动创建',
system: $localize `System creation`,
custom: $localize `Manual creation`,
};
mockListColumns = [
{ title: '名称', key: 'name' },
{ title: '创建方式', slot: 'createWay' },
{ title: $localize`Name`, key: 'name' },
{ title: $localize`Created Type`, slot: 'createWay' },
{ title: 'URL', slot: 'url' },
];
private destroy$: Subject<void> = new Subject<void>();
@ -140,6 +140,6 @@ export class ApiDetailMockComponent implements OnInit, OnChanges {
async copyText(text: string) {
await copyText(text);
this.message.success('复制成功');
this.message.success($localize`Copied`);
}
}

View File

@ -1,7 +1,7 @@
<eo-message></eo-message>
<div class="!pt-0 p15">
<div class="sticky top-0 z-10 pt-6 bg-white">
<button type="submit" nz-button nztype="primary" class="eo_theme_btn_success" (click)="saveApi()">保存</button>
<button type="submit" nz-button nztype="primary" class="eo_theme_btn_success" (click)="saveApi()" i18n>Save</button>
<nz-divider></nz-divider>
</div>
<form nz-form [nzLayout]="'vertical'" [formGroup]="validateForm">
@ -14,22 +14,22 @@
<nz-option *ngFor="let item of REQUEST_METHOD" [nzLabel]="item.key" [nzValue]="item.value"></nz-option>
</nz-select>
<nz-form-item nz-col class="fg1">
<nz-form-control nzErrorTip="请输入 API Path">
<nz-form-control i18n-nzErrorTip nzErrorTip="Please enter API Path">
<input type="text" [(ngModel)]="apiData.uri" name="uri" id="uri" nz-input formControlName="uri" />
</nz-form-control>
</nz-form-item>
</nz-input-group>
<nz-form-label nzFor="name">分组 / API 名称 </nz-form-label>
<nz-form-label nzFor="name" i18n>Group / API Name</nz-form-label>
<nz-input-group nzCompact>
<nz-form-item nz-col>
<nz-form-control class="w_250" nzErrorTip="请选择 API 分组">
<nz-form-control class="w_250" i18n-nzErrorTip nzErrorTip="Please select an API group">
<nz-tree-select nzAllowClear="false" [nzExpandedKeys]="expandKeys" [nzDropdownMatchSelectWidth]="false"
[nzNodes]="groups" [(ngModel)]="apiData.groupID" [nzShowSearch]="true" #apiGroup formControlName="groupID">
</nz-tree-select>
</nz-form-control>
</nz-form-item>
<nz-form-item nz-col class="fg1">
<nz-form-control nzErrorTip="请输入 API 名称">
<nz-form-control i18n-nzErrorTip nzErrorTip="Please enter API name">
<input type="text" name="name" id="name" nz-input formControlName="name" />
</nz-form-control>
</nz-form-item>
@ -37,17 +37,18 @@
</form>
<!-- 请求参数 -->
<nz-collapse class="eo_collapse mt20" [nzGhost]="true">
<nz-collapse-panel #panel [nzActive]="true" nzHeader="请求参数" nzShowArrow="false" [nzExtra]="extraTpl">
<nz-collapse-panel #panel [nzActive]="true" i18n-nzHeader nzHeader="Request" nzShowArrow="false"
[nzExtra]="extraTpl">
<ng-template #extraTpl>
{{ panel.nzActive ? '收缩' : '展开' }}
<span i18n>{{ panel.nzActive ? 'Shrink' : 'Expand' }}</span>
<span class="iconfont icon-chevron-{{ panel.nzActive ? 'up' : 'down' }}"></span>
</ng-template>
<nz-tabset [nzAnimated]="false" [nzSelectedIndex]="1" class="mt10">
<!-- 请求头部 -->
<!-- Request Headers -->
<nz-tab [nzTitle]="headerTitleTmp" [nzForceRender]="true">
<ng-template #headerTitleTmp>
请求头部
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.requestHeaders)">{{
<span i18n>Request Headers</span>
<span class="eo-tab-icon ml-[4px]" *ngIf="bindGetApiParamNum(apiData.requestHeaders)">{{
apiData.requestHeaders | apiParamsNum
}}</span>
</ng-template>
@ -56,7 +57,7 @@
<!-- 请求体 -->
<nz-tab [nzTitle]="bodyTitleTmp" [nzForceRender]="true">
<ng-template #bodyTitleTmp>
请求体
<span i18n>Body</span>
<span class="iconfont icon-circle eo-tab-theme-icon" *ngIf="
['formData', 'json', 'xml'].includes(apiData.requestBodyType)
? bindGetApiParamNum(apiData.requestBody)
@ -69,8 +70,8 @@
</nz-tab>
<nz-tab [nzTitle]="queryTitleTmp" [nzForceRender]="true">
<ng-template #queryTitleTmp>
Query 参数
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.queryParams)">{{
<span i18n>Query</span>
<span class="eo-tab-icon ml-[4px]" *ngIf="bindGetApiParamNum(apiData.queryParams)">{{
apiData.queryParams | apiParamsNum
}}</span>
</ng-template>
@ -78,8 +79,8 @@
</nz-tab>
<nz-tab [nzTitle]="restTitleTmp" [nzForceRender]="true">
<ng-template #restTitleTmp>
REST 参数
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.restParams)">{{
<span i18n>REST</span>
<span class="eo-tab-icon ml-[4px]" *ngIf="bindGetApiParamNum(apiData.restParams)">{{
apiData.restParams | apiParamsNum
}}</span>
</ng-template>
@ -90,16 +91,17 @@
</nz-collapse>
<!-- 响应内容 -->
<nz-collapse class="eo_collapse mt40" [nzGhost]="true">
<nz-collapse-panel #panelRes [nzActive]="true" nzHeader="响应内容" nzShowArrow="false" [nzExtra]="extraTplRes">
<nz-collapse-panel #panelRes [nzActive]="true" i18n-nzHeader nzHeader="Response" nzShowArrow="false"
[nzExtra]="extraTplRes">
<ng-template #extraTplRes>
{{ panelRes.nzActive ? '收缩' : '展开' }}
<span i18n>{{ panelRes.nzActive ? 'Shrink' : 'Expand' }}</span>
<span class="iconfont icon-chevron-{{ panelRes.nzActive ? 'up' : 'down' }}"></span>
</ng-template>
<nz-tabset [nzAnimated]="false" [nzSelectedIndex]="1" class="mt10">
<nz-tab [nzTitle]="responseHeaderTitleTmp" [nzForceRender]="true">
<ng-template #responseHeaderTitleTmp>
返回头部
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.responseHeaders)">{{
<span i18n>Response Headers</span>
<span class="eo-tab-icon ml-[4px]" *ngIf="bindGetApiParamNum(apiData.responseHeaders)">{{
apiData.responseHeaders | apiParamsNum
}}</span>
</ng-template>
@ -108,7 +110,7 @@
</nz-tab>
<nz-tab [nzTitle]="responseTitleTmp" [nzForceRender]="true">
<ng-template #responseTitleTmp>
返回结果
<span i18n>Response</span>
<span class="iconfont icon-circle eo-tab-theme-icon" *ngIf="
['formData', 'json', 'xml'].includes(apiData.responseBodyType)
? bindGetApiParamNum(apiData.responseBody)
@ -122,3 +124,4 @@
</nz-tabset>
</nz-collapse-panel>
</nz-collapse>
</div>

View File

@ -59,7 +59,7 @@ export class ApiEditComponent implements OnInit, OnDestroy {
this.groups = [];
const treeItems: any = [
{
title: '根目录',
title: $localize`Root directory`,
//!actually is 0,but 0 will hidden in nz component,so use -1 replace 0
key: '-1',
weight: 0,

View File

@ -6,7 +6,7 @@ export class ApiEditService {
constructor(private modalService: ModalService) {}
showMore(inputArg, opts: { nzOnOk: (result: any) => void; title: string }) {
const modal = this.modalService.create({
nzTitle: `${opts.title}详情`,
nzTitle: $localize`${opts.title} Detail`,
nzContent: ApiParamsExtraSettingComponent,
nzClosable: false,
nzWidth: '60%',
@ -144,15 +144,15 @@ export class ApiEditService {
itemExpression: 'ng-if="$index+1!==$ctrl.list.length"',
},
{
thKey: '参数名',
thKey: $localize`Param Name`,
type: 'depthInput',
modelKey: 'name',
placeholder: '参数名',
placeholder: $localize`Param Name`,
width: 300,
mark: 'name',
},
{
thKey: '类型',
thKey: $localize`Type`,
type: 'select',
key: 'key',
value: 'value',
@ -164,26 +164,26 @@ export class ApiEditService {
width: 100,
},
{
thKey: '必填',
thKey: $localize`Required`,
type: 'checkbox',
modelKey: 'required',
width: 80,
mark: 'require',
},
{
thKey: '说明',
thKey: $localize`:@@Description:Description`,
type: 'input',
modelKey: 'description',
placeholder: '参数说明',
placeholder: $localize`Param Description`,
width: 300,
mark: 'description',
},
{
thKey: '示例',
thKey: $localize`Example`,
type: 'input',
modelKey: 'example',
placeholder: '参数示例',
placeholder: $localize`Param Example`,
width: 200,
hide: 1,
mark: 'example',
@ -193,12 +193,12 @@ export class ApiEditService {
class: 'w_250',
btnList: [
{
key: '添加子字段',
key: $localize`Add Child`,
operateName: 'addChild',
itemExpression: `ng-if="$ctrl.mainObject.setting.isLevel"`,
},
{
key: '更多设置',
key: $localize`More Settings`,
operateName: 'more',
fun: (inputArg) => {
this.showMore(inputArg, {
@ -210,14 +210,14 @@ export class ApiEditService {
itemExpression: `eo-attr-tip-placeholder="more_setting_btn" `,
},
{
key: '插入',
key: $localize`Insert`,
operateName: 'insert',
itemExpression: `ng-if="!($ctrl.mainObject.setting.munalHideOperateColumn&&$first)"`
itemExpression: `ng-if="!($ctrl.mainObject.setting.munalHideOperateColumn&&$first)"`,
},
{
key: '删除',
key: $localize`:@@Delete:Delete`,
operateName: 'delete',
itemExpression: 'ng-if="!($ctrl.mainObject.setting.munalHideOperateColumn&&$first)"'
itemExpression: 'ng-if="!($ctrl.mainObject.setting.munalHideOperateColumn&&$first)"',
},
],
},

View File

@ -9,7 +9,7 @@
[rootType]="jsonRootType"></params-import>
</div>
<div *ngIf="bodyType === 'json'">
<p class="fs12 c999 mb5">JSON 根类型:</p>
<p class="fs12 c999 mb5" i18n>JSON Root Type:</p>
<nz-select class="w_100 mb10" [(ngModel)]="jsonRootType" (ngModelChange)="jsonRootTypeChange.emit(jsonRootType)">
<nz-option *ngFor="let item of CONST.JSON_ROOT_TYPE" [nzLabel]="item.key" [nzValue]="item.value"></nz-option>
</nz-select>

View File

@ -14,23 +14,23 @@ export class ApiParamsExtraSettingComponent implements OnInit {
},
tdList: [
{
thKey: '参数名',
thKey: $localize`Param Name`,
type: 'text',
modelKey: 'name',
},
{
thKey: '必填',
thKey: $localize`Required`,
type: 'html',
html: '{{item.required?"是":"否"}}',
html: '{{item.required?"True":"False"}}',
class: 'w_100',
},
{
thKey: '说明',
thKey: $localize`:@@Description:Description`,
type: 'text',
modelKey: 'description',
},
{
thKey: '类型',
thKey: $localize`Type`,
type: 'text',
modelKey: 'type',
},

View File

@ -1,4 +1,4 @@
<div class="param_header">
<params-import [(baseData)]="model" contentType="formData" modalTitle="头部"></params-import>
<params-import [(baseData)]="model" contentType="formData" i18n-modalTitle="@@Header" modalTitle="Header"></params-import>
</div>
<list-block-common-component [mainObject]="listConf" [(list)]="model"></list-block-common-component>

View File

@ -33,8 +33,8 @@ export class ApiEditHeaderComponent implements OnInit, OnChanges, AfterViewCheck
this.listConf = this.editService.initListConf({
dragCacheVar: 'DRAG_VAR_API_EDIT_HEADER',
itemStructure: this.itemStructure,
title: '头部',
nameTitle: '标签',
title: $localize`:@@Header:Header`,
nameTitle: $localize`Key`,
nzOnOkMoreSetting: (inputArg) => {
this.model[inputArg.$index] = inputArg.item;
},

View File

@ -1,4 +1,4 @@
<div class="param_header">
<params-import [(baseData)]="model" contentType="query" modalTitle="Query参数"></params-import>
<params-import [(baseData)]="model" contentType="query" modalTitle="Query"></params-import>
</div>
<list-block-common-component [mainObject]="listConf" [(list)]="model"></list-block-common-component>

View File

@ -1,13 +1,14 @@
<form nz-form [nzLayout]="'vertical'" [formGroup]="validateForm" (ngSubmit)="submit()" *ngIf="!isDelete">
<nz-form-item nz-col class="fg1">
<nz-form-label nzFor="name">分组名称 </nz-form-label>
<nz-form-control nzErrorTip="请输入分组名称">
<nz-form-label nzFor="name" i18n>Group Name</nz-form-label>
<nz-form-control i18n-nzErrorTip nzErrorTip="Please enter group name">
<input type="text" autofocus nz-input id="name" formControlName="name" [(ngModel)]="group.name" />
</nz-form-control>
</nz-form-item>
</form>
<p *ngIf="isDelete">
删除
<strong title="{{ group.name }}">{{ group.name.length > 50 ? group.name.slice(0, 50) + '...' : group.name }}</strong>
后,该分组下的数据都会删除。该操作无法撤销,确认删除吗?
<p *ngIf="isDelete" i18n>
Data from<strong title="{{ group.name }}">{{
group.name.length > 50 ? group.name.slice(0, 50) + '...' : group.name
}}</strong>
will be deleted. This cannot be undone. Are you sure you want to delete?
</p>

View File

@ -1,48 +1,38 @@
<header class="group_header">
<nz-input-group>
<div nz-row [nzGutter]="8">
<div nz-col nzFlex="4">
<nz-input-group [nzPrefix]="prefixIcon" nzCompact>
<input type="text" nz-input placeholder="搜索" [(ngModel)]="searchValue" />
</nz-input-group>
<ng-template #prefixIcon>
<i nz-icon nzType="search"></i>
</ng-template>
</div>
<div nz-col nzFlex="2">
<nz-button-group>
<button nzType="primary" nz-button (click)="operateApiEvent({ event: $event, eventName: 'gotoAddApi' })">
<i nz-icon nzType="plus"></i>
API
</button>
<button nzType="primary" nz-button nz-dropdown [nzDropdownMenu]="menu" nzPlacement="bottomRight">
<i nz-icon nzType="caret-down" nzTheme="outline"></i>
</button>
</nz-button-group>
<nz-dropdown-menu #menu="nzDropdownMenu">
<ul nz-menu>
<li nz-menu-item (click)="operateApiEvent({ event: $event, eventName: 'gotoAddApi' })"><a>新建API</a></li>
<li nz-menu-item (click)="addGroup()"><a>新建分组</a></li>
</ul>
</nz-dropdown-menu>
</div>
</div>
</nz-input-group>
<header class="flex py-2">
<input type="text" class="flex-1 px-3 input" i18n-placeholder="@@Search" placeholder="Search"
[(ngModel)]="searchValue" />
<div class="flex items-center justify-center ml-3 text-base btn shrink-0" nzType="primary" nz-button nz-dropdown
[nzDropdownMenu]="menu" nzPlacement="bottomRight"
(click)="operateApiEvent({ event: $event, eventName: 'gotoAddApi' })">
<eo-iconpark-icon name="plus"></eo-iconpark-icon>
</div>
<nz-dropdown-menu #menu="nzDropdownMenu">
<ul nz-menu>
<li nz-menu-item (click)="operateApiEvent({ event: $event, eventName: 'gotoAddApi' })" i18n><a>New API</a></li>
<li nz-menu-item (click)="addGroup()"><a i18n>New Group</a></li>
</ul>
</nz-dropdown-menu>
</header>
<!-- Fixed Group -->
<div class="group_container fixed_group_tree pt10" *ngIf="electron.isElectron">
<div class="group_container fixed_group_tree pt10">
<!-- <div class="group_container fixed_group_tree pt10" *ngIf="electron.isElectron"> -->
<nz-tree [nzData]="fixedTreeNode" [nzSelectedKeys]="nzSelectedKeys" nzBlockNode (nzClick)="clickTreeItem($event)"
[nzTreeTemplate]="nzFixedTreeTemplate"></nz-tree>
<ng-template #nzFixedTreeTemplate let-node let-origin="origin">
<div class="pl5 tree_node" *ngIf="node.origin?.isFixed">
<div class="f_row_ac">
<i class="mr10" nz-icon nzType="home" nzTheme="outline"></i>
<div class="tree_node" *ngIf="node.origin?.isFixed">
<div class="flex items-center">
<span class="mr-2 text-sm">
<eo-iconpark-icon name="home">
</eo-iconpark-icon>
</span>
<span class="text_omit node_title">{{ node.title }}</span>
</div>
</div>
</ng-template>
</div>
<div class="bbd" *ngIf="electron.isElectron"></div>
<div class="bbd"></div>
<!-- <div class="bbd" *ngIf="electron.isElectron"></div> -->
<!-- Custom Group -->
<div class="group_container group_tree pt10">
<nz-tree [nzData]="treeNodes" [nzSelectedKeys]="nzSelectedKeys" #apiGroup [nzSearchValue]="searchValue"
@ -50,11 +40,10 @@
(nzExpandChange)="toggleExpand()" nzDraggable nzBlockNode (nzOnDrop)="treeItemDrop($event)"
[nzTreeTemplate]="nzTreeTemplate"></nz-tree>
<ng-template #nzTreeTemplate let-node let-origin="origin">
<div class="pl5">
<div>
<!-- Folder -->
<div class="tree_node f_row f_js_ac" *ngIf="!node.isLeaf">
<div class="f_row_ac">
<span class="iconfont icon-folder-outline fs16 mr5"></span>
<span class="text_omit node_title">{{ node.title }}</span>
</div>
<span class="tree_node_operate">
@ -64,16 +53,16 @@
<nz-dropdown-menu #groupMenu="nzDropdownMenu">
<ul nz-menu>
<li nz-menu-item (click)="operateApiEvent({ event: $event, eventName: 'gotoAddApi', node: node })">
<a>添加 API</a>
<a i18n>Add API</a>
</li>
<li nz-menu-item (click)="addSubGroup(node)">
<a>新建子分组</a>
<a i18n>Add Subgroup</a>
</li>
<li nz-menu-item (click)="editGroup(node)">
<a>编辑</a>
<a i18n>Edit</a>
</li>
<li nz-menu-item (click)="deleteGroup(node)">
<a>删除</a>
<a i18n="@@Delete">Delete</a>
</li>
</ul>
</nz-dropdown-menu>
@ -98,15 +87,15 @@
<nz-dropdown-menu #apiDataMenu="nzDropdownMenu">
<ul nz-menu>
<li nz-menu-item (click)="operateApiEvent({ event: $event, eventName: 'gotoEditApi', node: node })">
<a>编辑</a>
<a i18n>Edit</a>
</li>
<li nz-menu-item
(click)="operateApiEvent({ event: $event, eventName: 'gotoCopyApi', node: apiDataItems[node.key] })">
<a>复制</a>
<a i18n="@@Copy">Copy</a>
</li>
<li nz-menu-item
(click)="operateApiEvent({ event: $event, eventName: 'gotoDeleteApi', node: apiDataItems[node.key] })">
<a>删除</a>
<a i18n="@@Delete">Delete</a>
</li>
</ul>
</nz-dropdown-menu>

View File

@ -1,4 +1,38 @@
.input {
width: 0px;
height: 30px;
border: none;
background-color: rgba(0,0,0,0.05);
border-radius: 3px;
&::placeholder {
color: rgba(0,0,0,0.5);
font-size: 0.9em;
}
}
.btn {
width: 32px;
background-color: var(--BTN_PRIMARY_BG);
color: #fff;
border-radius: 3px;
cursor: pointer;
}
::ng-deep {
.ant-tree .ant-tree-treenode {
padding: 0;
margin: 2px 0;
border-radius: 3px;
transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s;
&:hover {
background-color: #f5f5f5;
}
}
.ant-tree-treenode-selected {
background-color: var(--NAVBAR_BTN_BG);
}
.ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected {
background-color: transparent;
transition: none;
}
.ant-tree-node-selected * {
// color: var(--TEXT_ACTIVE);
}
@ -19,6 +53,7 @@
height: calc(100vh - var(--NAVBAR_HEIGHT) - 88px);
.ant-tree-switcher {
width: 12px;
padding-left: 4px;
}
.ant-tree-indent-unit {
width: 8px;
@ -28,9 +63,6 @@
padding: 0 2px;
}
}
.ng-star-inserted {
padding-left: 0;
}
}
.ant-dropdown-menu {
min-width: 100px;
@ -39,6 +71,7 @@
max-width: 50px;
min-width: 32px;
font-size: 11px;
font-weight: 300;
}
.node_title {
max-width: 145px;

View File

@ -50,7 +50,7 @@ export class ApiGroupTreeComponent implements OnInit, OnDestroy {
treeNodes: GroupTreeItem[] | NzTreeNode[] | any;
fixedTreeNode: GroupTreeItem[] | NzTreeNode[] = [
{
title: '概况',
title: $localize `:@@API Index:Index`,
key: 'overview',
weight: 0,
parentID: '0',
@ -246,7 +246,7 @@ export class ApiGroupTreeComponent implements OnInit, OnDestroy {
* Create group.
*/
addGroup() {
this.groupModal('添加分组', { group: this.buildGroupModel(), action: 'new' });
this.groupModal($localize`Add Group`, { group: this.buildGroupModel(), action: 'new' });
}
/**
@ -255,7 +255,7 @@ export class ApiGroupTreeComponent implements OnInit, OnDestroy {
* @param node NzTreeNode
*/
addSubGroup(node: NzTreeNode) {
this.groupModal('添加子分组', { group: this.buildGroupModel(node.key), action: 'sub' });
this.groupModal($localize`Add Subgroup`, { group: this.buildGroupModel(node.key), action: 'sub' });
}
/**
@ -264,7 +264,7 @@ export class ApiGroupTreeComponent implements OnInit, OnDestroy {
* @param node NzTreeNode
*/
editGroup(node: NzTreeNode) {
this.groupModal('编辑分组', { group: this.nodeToGroup(node), action: 'edit' });
this.groupModal($localize`Edit Group`, { group: this.nodeToGroup(node), action: 'edit' });
}
/**
@ -273,7 +273,7 @@ export class ApiGroupTreeComponent implements OnInit, OnDestroy {
* @param node NzTreeNode
*/
deleteGroup(node: NzTreeNode) {
this.groupModal('删除分组', { group: this.nodeToGroup(node), treeItems: this.treeItems, action: 'delete' });
this.groupModal($localize`Delete Group`, { group: this.nodeToGroup(node), treeItems: this.treeItems, action: 'delete' });
}
/**

View File

@ -0,0 +1,20 @@
<div>
<div class="flex items-center justify-between h-10 px-2 history-title">
<span class="font-bold" i18n>History</span>
<div class="flex items-center justify-center cursor-pointer shrink-0 h-7" nzTooltipTitle="Clear All" nz-tooltip
nz-popconfirm nzPopconfirmTitle="Are you sure delete all history?" (nzOnConfirm)="clearAllHistory()"
(nzOnCancel)="cancel()" nzOkText="Yes" nzCancelText="No" nzPopconfirmPlacement="topRight">
<span class="flex items-center justify-center icon">
<eo-iconpark-icon name="delete">
</eo-iconpark-icon>
</span>
</div>
</div>
<div *ngFor="let item of historyList" class="flex items-center h-8 p-2 text-xs cursor-pointer hover:bg-gray-100"
(click)="gotoTestHistory(item)">
<span class="block w-12 font-light method_type" [ngClass]="methodColor(item.request.method)">{{
item.request.method
}}</span>
<span class="flex-1 overflow-hidden text-gray-600 truncate">{{ item.request.uri }}</span>
</div>
</div>

View File

@ -0,0 +1,30 @@
.method_type {
&.green {
color: green
}
&.red {
color: red
}
&.blue {
color: blue
}
&.pink {
color: pink
}
}
.icon {
width: 30px;
height: 30px;
color: rgba(0,0,0,0.5);
border-radius: 3px;
font-size: 1.3em;
transition: all .4s ease;
&:hover {
background-color: rgba(0,0,0,0.05);
}
}
.history-title {
border-bottom: 1px solid #eee;
}

View File

@ -0,0 +1,54 @@
import { Component, OnInit } from '@angular/core';
import { IndexedDBStorage } from '../../../../../../../workbench/browser/src/app/shared/services/storage/IndexedDB/lib/index';
import { MessageService } from '../../../shared/services/message';
@Component({
selector: 'eo-history',
templateUrl: './eo-history.component.html',
styleUrls: ['./eo-history.component.scss'],
})
export class HistoryComponent implements OnInit {
historyList = [];
colorHash = new Map().set('get', 'green').set('post', 'blue').set('delete', 'red').set('put', 'pink');
constructor(public storageInstance: IndexedDBStorage, private message: MessageService) {}
ngOnInit() {
const observer = this.loadAllTest();
observer.subscribe((result: any) => {
// console.log(result.data);
this.historyList = result.data;
});
this.message.get().subscribe(({ type }) => {
if (type === 'updateHistory') {
this.loadAllTest().subscribe((result: any) => {
this.historyList = result.data;
});
}
});
}
loadAllTest() {
return this.storageInstance.apiTestHistoryLoadAllByProjectID(1);
}
methodColor(type) {
return this.colorHash.get(type.toLowerCase());
}
gotoTestHistory(data) {
// this.message.send({ type: 'gotoApiTest', data });
this.message.send({
type: 'gotoApiTest',
data: {
...data,
origin: { method: data.request.method.toUpperCase(), title: '测试历史', key: `history_${data.uuid}` },
},
});
}
clearAllHistory() {
const uuids = this.historyList.map((it) => it.uuid);
this.historyList = [];
this.storageInstance.apiTestHistoryBulkRemove(uuids);
}
cancel() {}
}

View File

@ -1,13 +1,20 @@
<div class="p-[18px]">
<button nz-button nzType="primary" (click)="addOrEditModal()">添加 Mock</button>
<button nz-button nzType="primary" (click)="addOrEditModal()" i18n>Add Mock</button>
<div class="mt-[20px]">
<eo-table [(model)]="mocklList" [columns]="mockListColumns" [dataModel]="{ name: '', value: '', description: '' }">
<ng-template cell="name" let-scope="scope" let-index="index">
<div class="truncate w-[120px]">{{scope.name}}</div>
<div class="truncate w-[120px]">{{ scope.name }}</div>
</ng-template>
<ng-template cell="url" let-scope="scope" let-index="index">
<span nzTooltipTitle="点击复制" nzTooltipPlacement="top" nz-tooltip (click)="copyText(scope.url )" class="truncate">
<span
i18n-nzTooltipTitle
nzTooltipTitle="Click to Copy"
nzTooltipPlacement="top"
nz-tooltip
(click)="copyText(scope.url)"
class="truncate"
>
{{ scope.url }}
</span>
</ng-template>
@ -16,11 +23,21 @@
</ng-template>
<ng-template cell="action" let-scope="scope" let-index="index">
<div class="flex justify-evenly">
<a nz-button nzType="link" *ngIf="scope.name || scope.url" (click)="addOrEditModal(index)">
{{ scope.createWay === 'system' ? '预览' : '编辑' }}</a>
<a nz-button nzType="link" *ngIf="(scope.name || scope.url) && scope.createWay !== 'system'" nz-popconfirm
nzPopconfirmTitle="您确定要删除此Mock吗?" nzPopconfirmPlacement="topRight"
(nzOnConfirm)="handleDeleteMockItem(index)">删除</a>
<a nz-button nzType="link" *ngIf="scope.name || scope.url" (click)="addOrEditModal(index)" i18n>
{{ scope.createWay === 'system' ? 'Preview' : 'Edit' }}</a
>
<a
nz-button
nzType="link"
*ngIf="(scope.name || scope.url) && scope.createWay !== 'system'"
nz-popconfirm
i18n-nzPopconfirmTitle
nzPopconfirmTitle="Are you sure you want to delete this Mock?"
nzPopconfirmPlacement="topRight"
(nzOnConfirm)="handleDeleteMockItem(index)"
i18n="@@Delete"
>Delete</a
>
</div>
</ng-template>
</eo-table>
@ -32,25 +49,33 @@
<div class="w-full main-content">
<form nz-form nzLayout="vertical">
<nz-form-item>
<nz-form-label nzFor="currentEditMock.name">Mock 名称</nz-form-label>
<nz-form-label i18n nzFor="currentEditMock.name">Mock Name</nz-form-label>
<nz-form-control>
<input nz-input name="name" type="text" [(ngModel)]="currentEditMock.name"
[readonly]="currentEditMock.createWay=== 'system'" />
<input
nz-input
name="name"
type="text"
[(ngModel)]="currentEditMock.name"
[readonly]="currentEditMock.createWay === 'system'"
/>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzFor="currentEditMock.response">返回值</nz-form-label>
<nz-form-label i18n nzFor="currentEditMock.response">Response</nz-form-label>
<nz-form-control>
<eo-editor [(code)]="responseStr" (codeChange)="rawDataChange()"
[disabled]="currentEditMock.createWay=== 'system'"
[eventList]="['type', 'format', 'copy', 'download', 'newTab', 'search', 'replace']"></eo-editor>
<eo-editor
[(code)]="responseStr"
(codeChange)="rawDataChange()"
[disabled]="currentEditMock.createWay === 'system'"
[eventList]="['type', 'format', 'copy', 'download', 'newTab', 'search', 'replace']"
></eo-editor>
</nz-form-control>
</nz-form-item>
</form>
</div>
</section>
<div *nzModalFooter class="footer">
<button nz-button nzType="primary" (click)="handleSave()">保存</button>
<button nz-button nzType="default" (click)="handleCancel()">取消</button>
<button nz-button nzType="primary" i18n (click)="handleSave()">Save</button>
<button nz-button nzType="default" i18n (click)="handleCancel()">Cancel</button>
</div>
</nz-modal>
</nz-modal>

View File

@ -25,18 +25,18 @@ export class ApiMockComponent implements OnInit, OnChanges {
}
get modalTitle() {
return `${
this.currentEditMockIndex === -1 ? '添加' : this.currentEditMock.createWay === 'system' ? '预览' : '编辑'
this.currentEditMockIndex === -1 ? $localize`Add` : this.currentEditMock.createWay === 'system' ? $localize`Preview` : $localize`Edit`
} Mock`;
}
mocklList: ApiMockEntity[] = [];
apiData: ApiData;
createWayMap = {
system: '系统自动创建',
custom: '手动创建',
system: $localize `System creation`,
custom: $localize `Manual creation`,
};
mockListColumns = [
{ title: '名称', slot: 'name', width: '20%' },
{ title: '创建方式', slot: 'createWay', width: '15%' },
{ title: $localize`Name`, slot: 'name', width: '20%' },
{ title: $localize`Created Type`, slot: 'createWay', width: '15%' },
{ title: 'URL', slot: 'url', width: '50%' },
{ title: '', slot: 'action', width: '15%', fixed: true },
];
@ -223,7 +223,7 @@ export class ApiMockComponent implements OnInit, OnChanges {
await this.removeMock(Number(target.uuid));
this.mocklList.splice(index, 1)[0];
this.mocklList = [...this.mocklList];
this.message.success('删除成功');
this.message.success($localize`Delete Succeeded`);
}
async handleSave() {
this.isVisible = false;
@ -232,12 +232,12 @@ export class ApiMockComponent implements OnInit, OnChanges {
if (this.isEdit) {
await this.updateMock(this.currentEditMock, Number(this.currentEditMock.uuid));
this.message.success('修改成功');
this.message.success($localize`Edited successfully`);
this.mocklList[this.currentEditMockIndex] = this.currentEditMock;
} else {
const result = await this.createMock(this.currentEditMock);
Object.assign(this.currentEditMock, result.data);
this.message.success('新增成功');
this.message.success($localize`Added successfully`);
this.mocklList.push(this.currentEditMock);
}
this.currentEditMock.url = this.getApiUrl(this.currentEditMock);
@ -258,6 +258,6 @@ export class ApiMockComponent implements OnInit, OnChanges {
async copyText(text: string) {
await copyText(text);
this.message.success('复制成功');
this.message.success($localize`Copied`);
}
}

View File

@ -1,5 +1,5 @@
<div class="overview_container">
<h1 class="fs20 fwb">概况</h1>
<h1 class="fs20 fwb" i18n="@@API Index">Index</h1>
<nz-divider></nz-divider>
<section class="grid gap-8 grid-cols-2 xl:grid-cols-3">
<div class="card_item" *ngFor="let item of overviewList">

View File

@ -24,21 +24,21 @@ export class ApiOverviewComponent implements OnDestroy {
constructor(private modalService: ModalService, private router: Router, private message: EoMessageService) {}
overviewList = [
{
title: '导入',
title: $localize`Import`,
icon: 'import',
desc: '导入 API 数据',
desc: $localize`Import API data`,
type: 'import',
},
{
title: '导出',
title: $localize`Export`,
icon: 'export',
desc: '导出 API 数据',
desc: $localize`Export API data`,
type: 'export',
},
{
title: '推送',
title: $localize`Push`,
icon: 'sync',
desc: '将 API 推送/同步到其他平台',
desc: $localize`Push/Sync API to other platforms`,
type: 'push',
},
];
@ -52,10 +52,10 @@ export class ApiOverviewComponent implements OnDestroy {
nzOnOk: () => {
this.modal.componentInstance.submit((status) => {
if (status) {
this.message.success(`${title}成功`);
this.message.success($localize`${title} successfully`);
this.modal.destroy();
} else {
this.message.error(`${title}失败`);
this.message.error($localize`Failed to ${title}`);
}
});
},

View File

@ -1,5 +1,11 @@
<nz-tabset [(nzSelectedIndex)]="selectedIndex" nzType="editable-card" nzHideAdd="true" (nzClose)="closeTab($event)"
(nzSelectChange)="pickTab()" [nzTabBarExtraContent]="extraTemplate">
<nz-tabset
[(nzSelectedIndex)]="selectedIndex"
nzType="editable-card"
nzHideAdd="true"
(nzClose)="closeTab($event)"
(nzSelectChange)="pickTab()"
[nzTabBarExtraContent]="extraTemplate"
>
<nz-tab *ngFor="let tab of tabSerive.tabs; let i = index" nzClosable [nzTitle]="titleTemplate">
<ng-template #titleTemplate>
<span class="mr5 method_text_{{ tab.method }}" *ngIf="tab.method">{{ tab.method }}</span>
@ -16,11 +22,11 @@
</a>
<nz-dropdown-menu #menu="nzDropdownMenu">
<ul nz-menu>
<li nz-menu-item (click)="operateTab('closeOther')">关闭所有标签(当前标签除外)</li>
<li nz-menu-item (click)="operateTab('closeAll')">关闭所有标签</li>
<li nz-menu-item (click)="operateTab('closeOther')" i18n>Close All Tags (excluding current tabs)</li>
<li nz-menu-item (click)="operateTab('closeAll')" i18n>Close All Tabs</li>
<!-- <li nz-menu-item>关闭已保存</li> -->
<li nz-menu-item (click)="operateTab('closeLeft')">关闭左侧标签页</li>
<li nz-menu-item (click)="operateTab('closeRight')">关闭右侧标签页</li>
<li nz-menu-item (click)="operateTab('closeLeft')" i18n>Close Tabs To The Left</li>
<li nz-menu-item (click)="operateTab('closeRight')" i18n>Close Tabs to Right</li>
</ul>
</nz-dropdown-menu>
</ng-template>

View File

@ -23,11 +23,11 @@ export class ApiTabComponent implements OnInit, OnDestroy {
* Default tabs of api.
*/
defaultTabs = {
edit: { path: '/home/api/edit', title: '新 API' },
test: { path: '/home/api/test', title: '新 API' },
detail: { path: '/home/api/detail', title: 'API 详情' },
overview: { path: '/home/api/overview', title: '概况', key: 'overview' },
mock: { path: '/home/api/mock', title: 'mock', key: 'mock' },
edit: { path: '/home/api/edit', title: $localize`New API` },
test: { path: '/home/api/test', title: $localize`New API` },
detail: { path: '/home/api/detail', title: $localize`:@@API Detail:Preview` },
overview: { path: '/home/api/overview', title: $localize`:@@API Index:Index`, key: 'overview' },
mock: { path: '/home/api/mock', title: 'Mock', key: 'mock' },
};
MAX_TAB_LIMIT = 15;
@ -234,17 +234,24 @@ export class ApiTabComponent implements OnInit, OnDestroy {
this.appendOrSwitchTab('detail', inArg.data.origin);
break;
case 'detailOverview': {
console.log(inArg.data.origin);
// console.log(inArg.data.origin);
this.appendOrSwitchTab('overview', inArg.data.origin);
break;
}
case 'gotoApiTest':
this.appendOrSwitchTab('test', inArg.data.origin);
// ! It is bad way for delay render detail of api history.
setTimeout(() => {
this.messageService.send({ type: 'renderHistory', data: inArg.data });
}, 20);
break;
case 'gotoEditApi':
this.appendOrSwitchTab('edit', inArg.data.origin);
break;
case 'copyApi':
this.storage.run('apiDataCreate', [{ ...inArg.data }, inArg.data.uuid], (result: StorageRes) => {
if (result.status === StorageResStatus.success) {
this.message.success('复制成功');
this.message.success($localize`Copied successfully`);
this.appendOrSwitchTab('edit', {
...inArg.data,
...result.data,
@ -253,7 +260,7 @@ export class ApiTabComponent implements OnInit, OnDestroy {
});
this.messageService.send({ type: `copyApiSuccess`, data: result.data });
} else {
this.message.success('失败');
this.message.success($localize`Failed to copy`);
}
});
break;

View File

@ -4,7 +4,7 @@ import { AppModule } from '../../../app.module';
import { TabItem } from './tab.model';
@Injectable({
providedIn:AppModule
providedIn: AppModule,
})
export class ApiTabService {
tabs: Array<TabItem> = [];
@ -30,13 +30,15 @@ export class ApiTabService {
this.tabCache[inData.tab.uuid] = inData.data;
}
removeData(tabID) {
if (!this.tabCache.hasOwnProperty(tabID)) return;
if (!this.tabCache.hasOwnProperty(tabID)) {
return;
}
delete this.tabCache[tabID];
}
destroy(){
destroy() {
this.saveTabData$.complete();
this.tabChange$.complete();
this.tabs=[];
this.tabCache={};
this.tabs = [];
this.tabCache = {};
}
}

View File

@ -16,34 +16,35 @@
<input type="text" name="uri" nz-input formControlName="uri" [(ngModel)]="apiData.uri"
(change)="changeUri()" />
<button type="submit" nz-button nzType="primary" class="ml10" (click)="clickTest()">
{{ status === 'testing' ? '终止' : '发送' }}
<span *ngIf="status === 'testing'" i18n>Abort</span>
<span *ngIf="status !== 'testing'" i18n>Send</span>
<span *ngIf="status === 'testing' && waitSeconds" class="ml5">{{ waitSeconds }}</span>
</button>
<button type="button" *ngIf="!apiData.uuid" nz-button nzType="default" (click)="saveTestDataToApi()"
class="ml10">
保存为新 API
class="ml10" i18n>
Save as API
</button>
</nz-input-group>
</nz-form-control>
</nz-form-item>
</form>
<div class="scroll_container">
<!-- 请求参数 -->
<!-- Request Info -->
<nz-tabset [nzTabBarStyle]="{ 'padding-left': '10px' }" [nzAnimated]="false" [nzSelectedIndex]="1">
<!-- 请求头部 -->
<!-- Request Headers -->
<nz-tab [nzTitle]="headerTitleTmp" [nzForceRender]="true">
<ng-template #headerTitleTmp>
请求头部
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.requestHeaders)">{{
<span i18n>Request Headers</span>
<span class="eo-tab-icon ml-[4px]" *ngIf="bindGetApiParamNum(apiData.requestHeaders)">{{
apiData.requestHeaders | apiParamsNum
}}</span>
</ng-template>
<eo-api-test-header class="eo_theme_iblock bbd" [(model)]="apiData.requestHeaders"></eo-api-test-header>
</nz-tab>
<!-- 请求信息 -->
<!--Request Info -->
<nz-tab [nzTitle]="bodyTitleTmp" [nzForceRender]="true">
<ng-template #bodyTitleTmp>
请求体
<span i18n>Body</span>
<span class="iconfont icon-circle eo-tab-theme-icon" *ngIf="
['formData', 'json', 'xml'].includes(apiData.requestBodyType)
? bindGetApiParamNum(apiData.requestBody)
@ -56,8 +57,8 @@
</nz-tab>
<nz-tab [nzTitle]="queryTitleTmp" [nzForceRender]="true">
<ng-template #queryTitleTmp>
Query 参数
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.queryParams)">{{
<span i18n>Query</span>
<span class="eo-tab-icon ml-[4px]" *ngIf="bindGetApiParamNum(apiData.queryParams)">{{
apiData.queryParams | apiParamsNum
}}</span>
</ng-template>
@ -66,35 +67,35 @@
</nz-tab>
<nz-tab [nzTitle]="restTitleTmp" [nzForceRender]="true">
<ng-template #restTitleTmp>
REST 参数
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.restParams)">{{
<span i18n>REST</span>
<span class="eo-tab-icon ml-[4px]" *ngIf="bindGetApiParamNum(apiData.restParams)">{{
apiData.restParams | apiParamsNum
}}</span>
</ng-template>
<eo-api-test-rest class="eo_theme_iblock bbd" [model]="apiData.restParams"></eo-api-test-rest>
</nz-tab>
</nz-tabset>
<!-- 响应信息 -->
<!-- Response -->
<nz-tabset [nzTabBarStyle]="{ 'padding-left': '10px' }" [(nzSelectedIndex)]="tabIndexRes" [nzAnimated]="false"
class="mt10 response_container">
<nz-tab nzTitle="返回结果">
<eo-api-test-result-response [model]="testResult.response"></eo-api-test-result-response>
<nz-tab i18n-nzTitle nzTitle="Response">
<eo-api-test-result-response [model]="testResult?.response"></eo-api-test-result-response>
</nz-tab>
<div>
<nz-tab nzTitle="返回头部">
<eo-api-test-result-header [model]="testResult.response?.headers"></eo-api-test-result-header>
<nz-tab i18n-nzTitle nzTitle="Response Headers">
<eo-api-test-result-header [model]="testResult?.response?.headers"></eo-api-test-result-header>
</nz-tab>
</div>
<nz-tab nzTitle="请求内容">
<nz-tab i18n-nzTitle nzTitle="Body">
<eo-api-test-result-request-body [model]="testResult.request?.requestBody"></eo-api-test-result-request-body>
</nz-tab>
<nz-tab nzTitle="请求头部">
<nz-tab i18n-nzTitle nzTitle="Request Headers">
<eo-api-test-result-header [model]="testResult.request?.requestHeaders"></eo-api-test-result-header>
</nz-tab>
<nz-tab nzTitle="测试历史">
<eo-api-test-history [apiID]="apiData.uuid" (clickItem)="restoreHistory($event)" #historyComponent>
</eo-api-test-history>
</nz-tab>
</nz-tabset>
</div>
</div>
<div class="invisible">
<eo-api-test-history [apiID]="apiData.uuid" (clickItem)="restoreHistory($event)" #historyComponent>
</eo-api-test-history>
</div>

View File

@ -43,7 +43,7 @@ export class ApiTestComponent implements OnInit, OnDestroy {
};
status: 'start' | 'testing' | 'tested' = 'start';
waitSeconds = 0;
tabIndexRes: number = 0;
tabIndexRes = 0;
testResult: any = {
response: {},
request: {},
@ -90,7 +90,7 @@ export class ApiTestComponent implements OnInit, OnDestroy {
* @param item test history data
*/
restoreHistory(item) {
let result = this.apiTest.getTestDataFromHistory(item);
const result = this.apiTest.getTestDataFromHistory(item);
console.log('restoreHistory', result);
//restore request
this.apiData = result.testData;
@ -108,7 +108,7 @@ export class ApiTestComponent implements OnInit, OnDestroy {
});
}
saveTestDataToApi() {
let apiData = this.apiTest.getApiFromTestData({
const apiData = this.apiTest.getApiFromTestData({
history: this.testResult,
testData: this.apiData,
});
@ -134,6 +134,11 @@ export class ApiTestComponent implements OnInit, OnDestroy {
this.initApi(Number(this.route.snapshot.queryParams.uuid));
this.watchTabChange();
this.watchEnvChange();
this.messageService.get().subscribe(({ type, data }) => {
if (type === 'renderHistory') {
this.restoreHistory(data);
}
});
}
ngOnDestroy() {
this.destroy$.next();
@ -168,6 +173,8 @@ export class ApiTestComponent implements OnInit, OnDestroy {
},
id
);
// console.log('test');
this.messageService.send({ type: 'updateHistory', data: {} });
}
}
/**
@ -175,7 +182,7 @@ export class ApiTestComponent implements OnInit, OnDestroy {
*/
private receiveMessage(message) {
console.log('receiveMessage', message);
let tmpHistory = {
const tmpHistory = {
general: message.general,
request: message.report.request,
response: message.response,
@ -183,7 +190,7 @@ export class ApiTestComponent implements OnInit, OnDestroy {
// other tab test finish,support multiple tab test same time
if (message.id && this.apiTab.tabID !== message.id) {
this.apiTab.tabCache[message.id].testResult = tmpHistory;
let tab = this.apiTab.tabs.find((val) => val.uuid === message.id);
const tab = this.apiTab.tabs.find((val) => val.uuid === message.id);
if (tab) {
this.addHistory(message, tab.key);
}
@ -199,7 +206,7 @@ export class ApiTestComponent implements OnInit, OnDestroy {
*/
private changeStatus(status) {
this.status = status;
let that = this;
const that = this;
switch (status) {
case 'testing': {
this.timer$ = interval(1000)
@ -229,7 +236,7 @@ export class ApiTestComponent implements OnInit, OnDestroy {
this.initBasicForm();
//recovery from tab
if (this.apiTab.currentTab && this.apiTab.tabCache[this.apiTab.tabID]) {
let tabData = this.apiTab.tabCache[this.apiTab.tabID];
const tabData = this.apiTab.tabCache[this.apiTab.tabID];
this.apiData = tabData.apiData;
this.testResult = tabData.testResult;
return;
@ -290,7 +297,9 @@ export class ApiTestComponent implements OnInit, OnDestroy {
request: {},
};
this.status$.next('start');
if (this.timer$) this.timer$.unsubscribe();
if (this.timer$) {
this.timer$.unsubscribe();
}
this.waitSeconds = 0;
this.tabIndexRes = 0;
}

View File

@ -8,9 +8,9 @@ import { text2UiData } from '../../../utils/data-transfer/data-transfer.utils';
export class ApiTestService {
constructor() {}
initListConf(opts) {
opts.title = opts.title || '参数';
opts.nameTitle = opts.nameTitle || `${opts.title}`;
opts.valueTitle = opts.valueTitle || `${opts.title}`;
opts.title = opts.title || $localize`Param`;
opts.nameTitle = opts.nameTitle || $localize`${opts.title} Name`;
opts.valueTitle = opts.valueTitle || $localize`${opts.title} Value`;
return {
setting: {
// draggable: true,
@ -50,7 +50,7 @@ export class ApiTestService {
class: 'w_250',
btnList: [
{
key: '删除',
key: $localize`:@@Delete:Delete`,
operateName: 'delete',
},
],
@ -93,15 +93,15 @@ export class ApiTestService {
mark: 'require',
},
{
thKey: '参数名',
thKey: $localize`Param Name`,
type: 'depthInput',
modelKey: 'name',
placeholder: '参数名',
placeholder: $localize`Param Name`,
width: 300,
mark: 'name',
},
{
thKey: '类型',
thKey: $localize`Type`,
type: 'select',
key: 'key',
value: 'value',
@ -114,12 +114,12 @@ export class ApiTestService {
},
{
thKey: '参数值',
thKey: $localize`Value`,
type: 'autoCompleteAndFile',
modelKey: 'value',
switchVar: 'type',
swicthFile: 'file',
placeholder: '参数值',
placeholder: $localize`Value`,
width: 300,
mark: 'value',
},
@ -128,12 +128,12 @@ export class ApiTestService {
class: 'w_250',
btnList: [
{
key: '添加子字段',
key: $localize`Add Child`,
operateName: 'addChild',
itemExpression: `eo-attr-tip-placeholder="add_child_btn" ng-if="$ctrl.mainObject.setting.isLevel"`,
},
{
key: '删除',
key: $localize`:@@Delete:Delete`,
operateName: 'delete',
itemExpression: 'ng-if="!($ctrl.mainObject.setting.munalHideOperateColumn&&$first)"',
},

View File

@ -9,7 +9,7 @@
[rootType]="jsonRootType"></params-import>
</div>
<div *ngIf="bodyType === 'json'">
<p class="fs12 c999 mb5">JSON 根类型:</p>
<p class="fs12 c999 mb5" i18n>JSON Root Type:</p>
<nz-select class="w_100 mb10" [(ngModel)]="jsonRootType">
<nz-option *ngFor="let item of CONST.JSON_ROOT_TYPE" [nzLabel]="item.key" [nzValue]="item.value"></nz-option>
</nz-select>
@ -24,5 +24,5 @@
<eo-editor [(code)]="model" (codeChange)="rawDataChange()" *ngIf="bodyType === 'raw'"
[eventList]="['type', 'format', 'copy', 'download', 'newTab', 'search', 'replace']"></eo-editor>
<!-- Binary -->
<textarea class="btd" rows="4" *ngIf="bodyType === 'binary'" nzBorderless placeholder="参数描述" nz-input
<textarea class="btd" rows="4" *ngIf="bodyType === 'binary'" nzBorderless i18n-placeholder="@@Description" placeholder="Description" nz-input
[(ngModel)]="model"></textarea>

View File

@ -167,7 +167,7 @@ export class ApiTestBodyComponent implements OnInit, OnChanges, OnDestroy {
}
private initListConf() {
this.listConf = this.apiTest.initBodyListConf({
title: '参数',
title: $localize`Param`,
itemStructure: this.itemStructure,
watchFormLastChange: () => {
this.modelChange.emit(this.model);
@ -180,7 +180,7 @@ export class ApiTestBodyComponent implements OnInit, OnChanges, OnDestroy {
var val = inputArg.file[i];
if (val.size > 2 * 1024 * 1024) {
inputArg.item.value = '';
this.message.error('文件大小均需小于2M');
this.message.error($localize`File size must be less than 2M`);
return;
}
}

View File

@ -1,4 +1,4 @@
<div class="param_header">
<params-import [(baseData)]="model" contentType="formData" modalTitle="头部"></params-import>
<params-import [(baseData)]="model" contentType="formData" i18n-modalTitle="@@Header" modalTitle="Header"></params-import>
</div>
<list-block-common-component [mainObject]="listConf" [(list)]="model"></list-block-common-component>

View File

@ -47,9 +47,9 @@ export class ApiTestHeaderComponent implements OnInit, OnChanges {
this.listConf = this.editService.initListConf({
dragCacheVar: 'DRAG_VAR_API_HEADER',
itemStructure: this.itemStructure,
title: '头部',
nameTitle: '标签',
valueTitle: '内容',
title: $localize`:@@Header:Header`,
nameTitle: $localize`Key`,
valueTitle: $localize`Value`,
watchFormLastChange: () => {
this.modelChange$.next();
},

View File

@ -1,17 +1,19 @@
<header>
<button
nz-popconfirm
nzPopconfirmTitle="此操作无法恢复,确认操作?"
i18n-nzPopconfirmTitle
nzPopconfirmTitle="This operation cannot be restored, do you want to do it?"
(nzOnConfirm)="deleteAll()"
nzPopconfirmPlacement="topLeft"
nz-button
nzType="primary"
nzDanger
i18n
>
清空测试历史
Empty Test History
</button>
</header>
<nz-divider></nz-divider>
<list-block-common-component class="api_test_history_list" [(list)]="model" [mainObject]="listConf"> </list-block-common-component>
<nz-empty class="pb20" *ngIf="!model||!model.length" nzNotFoundImage="simple"></nz-empty>
<list-block-common-component class="api_test_history_list" [(list)]="model" [mainObject]="listConf">
</list-block-common-component>
<nz-empty class="pb20" *ngIf="!model || !model.length" nzNotFoundImage="simple"></nz-empty>

View File

@ -55,9 +55,9 @@ export class ApiTestHistoryComponent implements OnInit {
this.storage.run('apiTestHistoryBulkRemove', [this.model.map((val) => val.uuid)], (result: StorageRes) => {
if (result.status === StorageResStatus.success) {
this.model = [];
this.message.success('删除成功');
this.message.success($localize`Delete Succeeded`);
} else {
this.message.error('删除失败');
this.message.error($localize`Failed to delete`);
console.error(result.data);
}
});
@ -83,24 +83,24 @@ export class ApiTestHistoryComponent implements OnInit {
},
tdList: [
{
thKey: '测试时间',
thKey: $localize`Test Time`,
type: 'text',
modelKey: 'testTime',
class: 'pl20 w_180',
},
{
thKey: '请求地址',
thKey: $localize`URL`,
type: 'html',
html: '<span class="method_text_{{item.request.method}} method_label mr5">{{item.request.method}}</span>{{item.request.uri}}',
},
{
thKey: '返回状态',
thKey: $localize`Status Code`,
type: 'html',
class: 'w_100',
html: `<span class="{{item.codeClass}}">{{item.response.statusCode}}</span>`,
},
{
thKey: '请求时长(ms)',
thKey: $localize`Request Time(ms)`,
type: 'html',
html: '{{item.response.testDeny}}',
class: 'w_120',
@ -110,7 +110,7 @@ export class ApiTestHistoryComponent implements OnInit {
class: 'w_100',
btnList: [
{
key: '删除',
key: $localize`:@@Delete:Delete`,
operateName: 'delete',
fun: (inArg) => {
this.delete(inArg);
@ -126,9 +126,9 @@ export class ApiTestHistoryComponent implements OnInit {
this.storage.run('apiTestHistoryRemove', [inArg.item.uuid], (result: StorageRes) => {
if (result.status === StorageResStatus.success) {
this.model.splice(inArg.$index, 1);
this.message.success('删除成功');
this.message.success($localize`Delete Succeeded`);
} else {
this.message.success('删除失败');
this.message.success($localize`Failed to delete`);
console.error(result.data);
}
});

View File

@ -1,4 +1,4 @@
<div class="param_header">
<params-import [(baseData)]="model" contentType="query" modalTitle="Query参数"></params-import>
<params-import [(baseData)]="model" contentType="query" i18n-modalTitle modalTitle="Query"></params-import>
</div>
<list-block-common-component [mainObject]="listConf" [(list)]="model"></list-block-common-component>

View File

@ -1,6 +1,6 @@
<nz-empty *ngIf="!model || !model.length" nzNotFoundImage="simple" [nzNotFoundContent]="contentTpl">
<ng-template #contentTpl>
<span>暂无头部</span>
<span i18n>No Headers</span>
</ng-template>
</nz-empty>
<ul *ngIf="model && model.length" class="p20">

View File

@ -1,6 +1,6 @@
<nz-empty *ngIf="!model || !model.length" nzNotFoundImage="simple" [nzNotFoundContent]="contentTpl">
<ng-template #contentTpl>
<span>暂无请求体</span>
<span i18n>No Request Body</span>
</ng-template>
</nz-empty>
<div *ngIf="model && model.length" class="p20">

View File

@ -1,7 +1,7 @@
<div class="pb15" *ngIf="!model.responseType">
<nz-empty nzNotFoundImage="simple" [nzNotFoundContent]="contentTpl">
<ng-template #contentTpl>
<span> 点击发送按钮获取测试报告 </span>
<span i18n>Click the Send button to get a test report</span>
</ng-template>
</nz-empty>
</div>
@ -14,21 +14,25 @@
</div>
</div>
<div class="text-center" *ngSwitchCase="'stream'">
<div *ngIf="!responseIsImg">
无法预览非文本类型的数据,您可以
<button class="eo_theme_btn_default mlr5" type="button" (click)="downloadResponseText()">下载返回结果</button>
,并用其他程序打开。
<div *ngIf="!responseIsImg" i18n>
Unable to preview non-text type data, you can<button
class="eo_theme_btn_default mlr5"
type="button"
(click)="downloadResponseText()"
>
download back result</button
>and open it with other programs.
</div>
<!-- <div class="mt20" *ngIf="responseIsImg">
<img class="maw_100percent" [src]="model.blobUrl" />
</div> -->
</div>
<div class="text-center" *ngSwitchCase="'longText'">
响应结果超出可预览的大小,您可以
<button class="eo_theme_btn_default mlr5" type="button" (click)="downloadResponseText()">下载返回结果</button>
<!-- 或者
<div class="text-center" *ngSwitchCase="'longText'" i18n>
Unable to preview non-text type data, you can
<button class="eo_theme_btn_default mlr5" type="button" (click)="downloadResponseText()">download back result</button>
<!-- or
<button class="eo_theme_btn_default" type="button" (click)="newTabResponseText()">在新标签页中显示返回结果</button>
并用其他程序打开。 -->
and open it with other programs. -->
</div>
<eo-editor
*ngSwitchDefault

View File

@ -1,63 +1,73 @@
<div class="py-3">
<div class="py-4">
<a nz-button [routerLink]="['/home/extension/list']" nzType="link">
<i nz-icon nzType="left" nzTheme="outline"></i>返回列表
</a>
<div class="py-3 extension-detail">
<div class="sticky top-0 z-50 bg-white">
<div class="pb-3">
<a nz-button nzType="link" (click)="backToList()">
<i nz-icon nzType="left" nzTheme="outline"></i><span i18n>Back</span>
</a>
</div>
<div class="bbd"></div>
</div>
<div class="bbd"></div>
<section class="">
<div class="flex p-8">
<i
class="bd_all block border rounded-lg h-40 w-40 bg-cover bg-center bg-no-repeat mr-8"
[ngStyle]="{ 'background-image': 'url(' + (extensionDetail?.logo || '') + ')' }"
></i>
<section class="h-full p-4 max-w-[80vw] mx-auto">
<div class="flex">
<i class="block w-24 h-24 mr-8 bg-center bg-no-repeat bg-cover border rounded-lg bd_all"
[ngStyle]="{ 'background-image': 'url(' + (extensionDetail?.logo || '') + ')' }"></i>
<div class="flex flex-col flex-1">
<div class="flex flex-col">
<span class="text-xl mb-2 font-bold">{{ extensionDetail?.moduleName }}</span>
<span>作者: {{ extensionDetail?.author }}</span>
<!-- <span class="mb-4">Tags: {{ extensionDetail?.keywords }}</span> -->
<span class="mb-2">版本: {{ extensionDetail?.version }}</span>
<span class="mb-2 text-xl font-bold">{{ extensionDetail?.moduleName }}</span>
<p class="w-full h-20">{{ extensionDetail?.description }}</p>
</div>
<div class="flex">
<div class="flex items-center" *ngIf="!extensionDetail?.installed">
<button
nz-button
nzType="primary"
[nzLoading]="isOperating"
(click)="manageExtension('install', extensionDetail?.name)"
>
安装
</button>
<!-- <span class="text-gray-500">安装完成后需要重启</span> -->
</div>
<button
*ngIf="extensionDetail?.installed"
nz-button
nzType="primary"
nzDanger
[nzLoading]="isOperating"
(click)="manageExtension('uninstall', extensionDetail?.name)"
>
卸载
</button>
</div>
</div>
</div>
<div>
<nz-tabset [nzAnimated]="false">
<nz-tab nzTitle="概述"> {{ extensionDetail?.description }} </nz-tab>
<nz-tab nzTitle="更多信息">
<nz-descriptions [nzColumn]="1" nzTitle="">
<nz-descriptions-item nzTitle="作者">{{ extensionDetail?.author }}</nz-descriptions-item>
<nz-descriptions-item nzTitle="版本">{{ extensionDetail?.version }}</nz-descriptions-item>
<nz-descriptions-item nzTitle="反馈">
<a class="eo_link" target="_blank" [href]="extensionDetail?.bugs?.url">Issue</a>
</nz-descriptions-item>
</nz-descriptions>
</nz-tab>
<!-- <nz-tab nzTitle="设置" *ngIf="extensionDetail?.configuration && extensionDetail?.configuration.properties">Content of Tab Pane 3</nz-tab> -->
</nz-tabset>
<div class="flex w-full mt-6 h-[calc(100vh-350px)]">
<div class="flex-auto">
<h2 class="text-lg font-bold" i18n>Intro</h2>
<!-- <nz-divider></nz-divider> -->
<div class="h-full overflow-auto markdown-desc">
<nz-skeleton [nzLoading]="introLoading" [nzActive]="true">
<eo-shadow-dom [text]="extensionDetail?.introduction" [options]="{ html: true }">
</eo-shadow-dom>
</nz-skeleton>
</div>
</div>
<div class="w-[1px] bg-[#f2f2f2] mx-[10px]"></div>
<div class="w-[200px] 2xl:w-[250px] overflow-auto h-full">
<h2 class="text-lg font-bold" i18n>Install</h2>
<div class="flex items-center mt-[22px]" *ngIf="!extensionDetail?.installed">
<button *ngIf="isElectron" nz-button nzType="primary" nzBlock nzSize="large" [nzLoading]="isOperating"
(click)="manageExtension('install', extensionDetail?.name)">
Install
</button>
<div *ngIf="!isElectron">
<button nz-button nzType="primary" nz-dropdown [nzDropdownMenu]="download" class="!w-full" i18n>Download Client
</button>
<nz-dropdown-menu #download="nzDropdownMenu">
<ul nz-menu>
<ng-container *ngFor="let item of resourceInfo; let index = index">
<a [href]="item.link" nz-menu-item>{{ item.name }}</a>
<li nz-menu-divider *ngIf="index !== resourceInfo.length - 1"></li>
</ng-container>
</ul>
</nz-dropdown-menu>
<div class="bg-[#FF1744] p-[6px] mt-[14px] rounded-[3px] text-white" i18n>
The extensions can only be installed on the client at present. Please download the client first~
</div>
</div>
</div>
<button *ngIf="extensionDetail?.installed" nz-button nzBlock nzType="primary" nzDanger nzSize="large"
[nzLoading]="isOperating" class="mt-[12px]" (click)="manageExtension('uninstall', extensionDetail?.name)" i18n>
Uninstall
</button>
<h2 class="text-lg font-bold mt-[30px]" i18n>Support</h2>
<nz-descriptions [nzColumn]="1" nzTitle="">
<nz-descriptions-item i18n-nzTitle nzTitle="Author">{{ extensionDetail?.author }}</nz-descriptions-item>
<nz-descriptions-item i18n-nzTitle nzTitle="Version">{{ extensionDetail?.version }}
</nz-descriptions-item>
</nz-descriptions>
</div>
</div>
</section>
</div>

View File

@ -1,5 +1,26 @@
::ng-deep {
eo-extension-detail .ant-tabs-content-holder {
padding-top: 20px;
eo-extension-detail {
.ant-tabs-content-holder {
padding-top: 20px;
}
}
}
::ng-deep .extension-detail {
.markdown-desc {
overflow: auto;
&::-webkit-scrollbar {
width:0;
}
&:hover::-webkit-scrollbar {
width:8px;
}
}
.ant-descriptions-row > td {
padding-bottom: 8px;
}
.ant-descriptions-item-content,.ant-descriptions-item-label {
color: #999;
}
}

View File

@ -1,6 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { ElectronService } from 'eo/workbench/browser/src/app/core/services';
import { EoExtensionInfo } from '../extension.model';
import { ResourceInfo } from '../../../shared/models/client.model';
import { ExtensionService } from '../extension.service';
@Component({
@ -10,16 +12,87 @@ import { ExtensionService } from '../extension.service';
})
export class ExtensionDetailComponent implements OnInit {
isOperating = false;
introLoading = false;
extensionDetail: EoExtensionInfo;
constructor(private extensionService: ExtensionService, private route: ActivatedRoute) {
resourceInfo = ResourceInfo;
get isElectron() {
return this.electronService.isElectron;
}
constructor(
private extensionService: ExtensionService,
private route: ActivatedRoute,
private router: Router,
private electronService: ElectronService
) {
this.getDetail();
this.getInstaller();
}
async getDetail() {
this.extensionDetail = await this.extensionService.getDetail(this.route.snapshot.queryParams.id,this.route.snapshot.queryParams.name);
this.extensionDetail = await this.extensionService.getDetail(
this.route.snapshot.queryParams.id,
this.route.snapshot.queryParams.name
);
if (!this.extensionDetail?.installed) {
await this.fetchReadme();
}
this.extensionDetail.introduction ||= $localize`This plugin has no documentation yet.`;
}
async fetchReadme() {
try {
this.introLoading = true;
const response = await fetch(`https://unpkg.com/${this.extensionDetail.name}/README.md`);
if (response.status === 200) {
this.extensionDetail.introduction = await response.text();
}
} catch (error) {
} finally {
this.introLoading = false;
}
}
private findLinkInSingleAssets(assets, item) {
let result = '';
const assetIndex = assets.findIndex(
(asset) =>
new RegExp(`${item.suffix}$`, 'g').test(asset.browser_download_url) &&
(!item.keyword || asset.browser_download_url.includes(item.keyword))
);
if (assetIndex === -1) {
return result;
}
result = assets[assetIndex].browser_download_url;
assets.splice(assetIndex, 1);
return result;
}
private findLink(allAssets, item) {
let result = '';
allAssets.some((assets) => {
result = this.findLinkInSingleAssets(assets, item);
return result;
});
return result;
}
getInstaller() {
fetch('https://api.github.com/repos/eolinker/eoapi/releases')
.then((response) => response.json())
.then((data) => {
[...this.resourceInfo]
.sort((a1, a2) => a2.suffix.length - a1.suffix.length)
.forEach((item) => {
item.link = this.findLink(
data.map((val) => val.assets),
item
);
});
});
}
manageExtension(operate: string, id) {
this.isOperating = true;
console.log(this.isOperating)
console.log(this.isOperating);
/**
* * WARNING:Sending a synchronous message will block the whole
* renderer process until the reply is received, so use this method only as a last
@ -29,14 +102,25 @@ export class ExtensionDetailComponent implements OnInit {
switch (operate) {
case 'install': {
this.extensionDetail.installed = this.extensionService.install(id);
this.getDetail();
break;
}
case 'uninstall': {
this.extensionDetail.installed = !this.extensionService.uninstall(id);
this.fetchReadme();
break;
}
}
this.isOperating = false;
}, 100);
}
ngOnInit(): void {}
backToList() {
this.router.navigate(['/home/extension/list'], {
queryParams: {
type: this.route.snapshot.queryParams.type,
},
});
}
}

View File

@ -1,16 +1,42 @@
<section class="main">
<section class="left flex-shrink-0">
<div class="mb-2"><i class="mr-2" nz-icon nzType="appstore" nzTheme="fill"></i>插件分类</div>
<div
class="plugin-link px-1 py-2"
[ngClass]="{ active: selectGroup === item.id }"
*ngFor="let item of groups"
(click)="clickGroup(item.id)"
>
{{ item.title }}<span *ngIf="item.showNum"> {{ extensionService.extensionIDs.length }}</span>
<section class="flex-shrink-0 p-0 left">
<!-- <input type="text" nz-input [(ngModel)]="keyword" (ngModelChange)="onSeachChange($event)" placeholder="search" /> -->
<div class="mb-2">
<input type="text" class="flex-1 w-full px-3 input" i18n-placeholder="@@Search" placeholder="Search"
[(ngModel)]="keyword" (ngModelChange)="onSeachChange($event)" />
</div>
<!-- Fixed Group -->
<div class="group_container ">
<nz-tree [nzData]="fixedTreeNode" [nzSelectedKeys]="nzSelectedKeys" nzBlockNode (nzClick)="clickTreeItem($event)"
[nzTreeTemplate]="nzFixedTreeTemplate"></nz-tree>
<ng-template #nzFixedTreeTemplate let-node let-origin="origin">
<div class="pl-[18px] tree_node" *ngIf="node.origin?.isFixed">
<div class="f_row_ac">
<i class="mr10" nz-icon nzType="home" nzTheme="outline"></i>
<span class="text_omit node_title">{{ node.title }}</span>
</div>
</div>
</ng-template>
</div>
<nz-divider class="!mt-[5px] !mb-[7px]"></nz-divider>
<nz-tree [nzData]="treeNodes" [nzSelectedKeys]="nzSelectedKeys" #apiGroup [nzHideUnMatched]="true"
(nzClick)="clickTreeItem($event)" nzBlockNode [nzTreeTemplate]="nzTreeTemplate"></nz-tree>
<ng-template #nzTreeTemplate let-node let-origin="origin">
<div class="pl-[18px]">
<div class="tree_node f_row f_js_ac" *ngIf="!node.origin?.isFixed && node.isLeaf">
<div class="overflow-hidden f_row_ac text-ellipsis">
<b class="method_text method_text_{{ node.origin.method }} mr5" *ngIf="node.origin.method">
{{ node.origin.method }}
</b>
<span class="text_omit node_title">{{ node.title }}</span>
</div>
</div>
</div>
<!-- <div *ngIf="node.component?.showIndicator" class="ant-tree-drop-indicator ng-star-inserted"
style="bottom: -3px; left: 4px; right: 0px;"></div> -->
</ng-template>
</section>
<section class="right fg1 px-4">
<section class="px-4 right fg1">
<router-outlet></router-outlet>
</section>
</section>

View File

@ -1,10 +1,50 @@
.main {
::ng-deep .main {
height: 100%;
width: 100%;
display: flex;
.input {
height: 30px;
border: none;
background-color: rgba(0,0,0,0.05);
border-radius: 3px;
&::placeholder {
color: rgba(0,0,0,0.5);
font-size: 0.9em;
}
}
.tree_node {
font-size: 12px;
}
.ant-tree .ant-tree-treenode {
padding: 0;
margin: 2px 0;
border-radius: 3px;
transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s;
&:hover {
background-color: #f5f5f5;
}
}
.ant-tree-treenode-selected {
background-color: var(--NAVBAR_BTN_BG);
}
.ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected {
background-color: transparent;
transition: none;
}
.ant-tree .ant-tree-node-content-wrapper{
border-radius: 0;
&.ant-tree-node-selected {
opacity: .8;
}
}
.ant-tree-switcher {
width: 0;
}
.left {
background: #f8f8f8;
border-right: 1px solid #f8f8f8;
width: 250px;
padding: 10px;

View File

@ -1,5 +1,10 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { ElectronService } from 'eo/workbench/browser/src/app/core/services';
import { GroupTreeItem } from 'eo/workbench/browser/src/app/shared/models';
import { MessageService } from 'eo/workbench/browser/src/app/shared/services/message';
import { NzFormatEmitEvent, NzTreeNode, NzTreeNodeOptions } from 'ng-zorro-antd/tree';
import { filter, Subject } from 'rxjs';
import { ExtensionGroupType } from './extension.model';
import { ExtensionService } from './extension.service';
@ -9,24 +14,34 @@ import { ExtensionService } from './extension.service';
styleUrls: ['./extension.component.scss'],
})
export class ExtensionComponent implements OnInit {
groups = [
keyword = '';
nzSelectedKeys: (number | string)[] = [];
treeNodes: NzTreeNodeOptions[] = [
{
id: 'all',
title: '全部插件',
},
{
id: 'official',
title: '官方插件',
},
{
id: 'installed',
title: '已安装',
showNum: true
key: 'official',
title: $localize`Official`,
isLeaf: true,
},
];
selectGroup: ExtensionGroupType|string = ExtensionGroupType.all;
constructor(public extensionService: ExtensionService, private router: Router) {
}
fixedTreeNode: GroupTreeItem[] | NzTreeNode[] = [
{
title: $localize`All`,
key: 'all',
weight: 0,
parentID: '0',
isLeaf: true,
isFixed: true,
},
];
selectGroup: ExtensionGroupType | string = ExtensionGroupType.all;
constructor(
public extensionService: ExtensionService,
private router: Router,
public electron: ElectronService,
private route: ActivatedRoute,
private messageService: MessageService
) {}
clickGroup(id) {
this.selectGroup = id;
this.router
@ -35,5 +50,46 @@ export class ExtensionComponent implements OnInit {
})
.finally();
}
ngOnInit(): void {}
ngOnInit(): void {
this.watchRouterChange();
this.setSelectedKeys();
}
onSeachChange(keyword) {
this.messageService.send({ type: 'searchPluginByKeyword', data: keyword });
}
private watchRouterChange() {
this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe((res: any) => {
this.setSelectedKeys();
});
}
/**
* Group tree item click.
*
* @param event
*/
clickTreeItem(event: NzFormatEmitEvent): void {
const eventName = event.node?.origin.isFixed ? 'clickFixedItem' : 'clickItem';
switch (eventName) {
case 'clickFixedItem': {
this.clickGroup(event.node.key);
break;
}
case 'clickItem': {
this.clickGroup(event.node.key);
break;
}
}
}
private setSelectedKeys() {
if (this.route.snapshot.queryParams.type) {
this.nzSelectedKeys = [this.route.snapshot.queryParams.type];
} else {
this.nzSelectedKeys = [this.fixedTreeNode[0].key];
}
}
}

View File

@ -14,11 +14,30 @@ import { NzInputModule } from 'ng-zorro-antd/input';
import { NzTabsModule } from 'ng-zorro-antd/tabs';
import { NzDescriptionsModule } from 'ng-zorro-antd/descriptions';
import { NzTagModule } from 'ng-zorro-antd/tag';
import { NzDividerModule } from 'ng-zorro-antd/divider';
import { NzTreeModule } from 'ng-zorro-antd/tree';
import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
import { NzSkeletonModule } from 'ng-zorro-antd/skeleton';
import { SharedModule } from 'eo/workbench/browser/src/app/shared/shared.module';
@NgModule({
imports: [FormsModule,NzTabsModule,NzTagModule,NzDescriptionsModule,NzInputModule,NzButtonModule,NzIconModule,ExtensionRoutingModule, CommonModule],
providers:[ExtensionService],
imports: [
SharedModule,
FormsModule,
NzTabsModule,
NzTagModule,
NzDescriptionsModule,
NzInputModule,
NzButtonModule,
NzIconModule,
ExtensionRoutingModule,
CommonModule,
NzDividerModule,
NzTreeModule,
NzDropDownModule,
NzSkeletonModule,
],
providers: [ExtensionService],
declarations: [ExtensionComponent, ExtensionListComponent, ExtensionDetailComponent],
})
export class ExtensionModule {}

View File

@ -1,5 +1,6 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { isElectron } from 'eo/shared/common/common';
import { lastValueFrom } from 'rxjs';
import { ModuleInfo } from '../../utils/module-loader';
import { EoExtensionInfo } from './extension.model';
@ -14,7 +15,7 @@ export class ExtensionService {
this.getInstalledList();
}
getInstalledList() {
this.localModules = window.eo.getModules();
this.localModules = window.eo?.getModules() || new Map();
this.updateExtensionIDs();
}
public async requestList() {

View File

@ -1,51 +1,24 @@
<div class="px-3">
<div class="py-4">
<nz-input-group class="w-60" [nzPrefix]="prefixTemplateSearch">
<input
type="text"
nz-input
[(ngModel)]="keyword"
(ngModelChange)="onSeachChange($event)"
placeholder="搜索关键字"
/>
</nz-input-group>
</div>
<ng-template #prefixTemplateSearch><i nz-icon nzType="search"></i></ng-template>
<div class="bbd"></div>
<div class="list-block grid gap-6 py-5 grid-cols-4">
<div class="grid grid-cols-2 gap-6 py-5 list-block">
<div
class="bd_all w-full h-76 py-2 px-3 rounded-lg flex flex-col flex-wrap items-center plugin-block"
*ngFor="let it of renderList"
(click)="clickExtension(it)"
>
<span class="h-8 w-full flex justify-between items-center">
<nz-tag *ngIf="extensionService.localModules.has(it.moduleID)" [nzColor]="'var(--MAIN_THEME_COLOR)'"
>已安装</nz-tag
>
<span
*ngIf="!extensionService.localModules.has(it.moduleID)"
class="text-xs p-1 bd_all rounded-sm text-green-700 border-green-700"
>未安装</span
>
<!-- <i
nz-icon
nzType="setting"
(click)="handleSetingPlugin(it)"
*ngIf="
extensionService.localModules.has(it.moduleID) &&
extensionService.localModules.get(it.moduleID).configuration
"
nzTheme="outline"
></i> -->
</span>
<i
class="block w-20 h-20 my-3 rounded-lg bg-cover bg-center bg-no-repeat"
[ngClass]="{ 'bg-gray-100': it.logo }"
[ngStyle]="{ 'background-image': 'url(' + (it.logo || '') + ')' }"
></i>
<span class="text-lg font-bold">{{ it.moduleName }}</span>
<span class="text-gray-400 my-2">{{ it.author }}</span>
<span class="text-gray-500 my-1 desc">{{ it.description }}</span>
class="bd_all w-full min-h-[140px] p-5 rounded-lg flex flex-col flex-wrap items-center plugin-block hover:border-green-700 hover:shadow-lg transition-shadow duration-300"
*ngFor="let it of renderList" (click)="clickExtension(it)">
<div class="flex w-full">
<div class=" block w-[40px] h-[40px] rounded-lg bg-cover bg-center bg-no-repeat mr-[20px]"
[ngClass]="{ 'bg-gray-100': it.logo }" [ngStyle]="{ 'background-image': 'url(' + (it.logo || '') + ')' }">
</div>
<div class="flex flex-col flex-auto">
<span class="text-lg font-bold">{{ it.moduleName }}</span>
<span class="my-2 text-gray-400">{{ it.author }}</span>
<span class="my-1 text-gray-500 desc">{{ it.description }}</span>
</div>
<div>
<span *ngIf="isElectron ? extensionService.localModules.has(it.moduleID) : it.installed"
class="p-1 text-xs text-green-700 border-green-700 rounded-sm bd_all" i18n>Installed</span>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,6 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { debounceTime, distinctUntilChanged, lastValueFrom, Subject } from 'rxjs';
import { isElectron } from 'eo/shared/common/common';
import { Message, MessageService } from 'eo/workbench/browser/src/app/shared/services/message';
import { debounceTime, distinctUntilChanged, takeUntil, Subject } from 'rxjs';
import { ExtensionGroupType } from '../extension.model';
import { ExtensionService } from '../extension.service';
class ExtensionList {
@ -10,7 +12,7 @@ class ExtensionList {
}
search(keyword: string) {
return this.list.filter(
(it) => it.moduleID.includes(keyword) || it.name.includes(keyword) || it.keywords.includes(keyword)
(it) => it.moduleID.includes(keyword) || it.name.includes(keyword) || it.keywords?.includes(keyword)
);
}
}
@ -23,8 +25,16 @@ export class ExtensionListComponent implements OnInit {
type: ExtensionGroupType = ExtensionGroupType.all;
keyword = '';
renderList = [];
isElectron = isElectron();
seachChanged$: Subject<string> = new Subject<string>();
constructor(public extensionService: ExtensionService, private route: ActivatedRoute, private router: Router) {
private destroy$: Subject<void> = new Subject<void>();
constructor(
public extensionService: ExtensionService,
private route: ActivatedRoute,
private router: Router,
private messageService: MessageService
) {
this.type = this.route.snapshot.queryParams.type;
this.seachChanged$.pipe(debounceTime(500), distinctUntilChanged()).subscribe(async (keyword) => {
this.renderList = await this.searchPlugin(keyword);
@ -32,6 +42,7 @@ export class ExtensionListComponent implements OnInit {
}
async ngOnInit() {
this.watchSearchConditionChange();
this.watchSearchKeywordChange();
}
async searchPlugin(keyword = '') {
if (this.type === 'installed') {
@ -54,7 +65,12 @@ export class ExtensionListComponent implements OnInit {
clickExtension(item) {
this.router
.navigate(['home/extension/detail'], {
queryParams: { id: item.moduleID, name: item.name, jump: 'setting' },
queryParams: {
type: this.route.snapshot.queryParams.type,
id: item.moduleID,
name: item.name,
jump: 'setting',
},
})
.finally();
}
@ -64,4 +80,18 @@ export class ExtensionListComponent implements OnInit {
this.renderList = await this.searchPlugin();
});
}
private watchSearchKeywordChange() {
this.messageService
.get()
.pipe(takeUntil(this.destroy$))
.subscribe((inArg: Message) => {
switch (inArg.type) {
case 'searchPluginByKeyword': {
this.onSeachChange(inArg.data);
break;
}
}
});
}
}

View File

@ -0,0 +1,56 @@
<div class="eo_navbar f_row f_js_ac">
<div>
<img class="logo" src="assets/images/logo.svg" />
<a href="https://github.com/eolinker/eoapi" target="_blank">
<img class="mx-4" src="https://img.shields.io/github/stars/eolinker/eoapi?style=social" alt="" />
</a>
</div>
<div class="flex items-center">
<span class="icon mx-1 flex items-center justify-center" i18n-title title="Open Settings" (click)="handleShowModal()">
<eo-iconpark-icon name="setting-two"></eo-iconpark-icon>
</span>
<!-- <span
i18n-title
class="icon mx-1 flex items-center justify-center"
title="{{ dataSourceText }}数据源"
(click)="switchDataSource()"
>
<eo-iconpark-icon [name]="isRemote ? 'link-cloud-sucess' : 'link-cloud-faild'"></eo-iconpark-icon>
</span> -->
<span class="icon mx-1 flex items-center justify-center" nz-dropdown [nzDropdownMenu]="menu">
<eo-iconpark-icon name="help">
</eo-iconpark-icon>
</span>
<nz-dropdown-menu #menu="nzDropdownMenu">
<ul nz-menu nzSelectable>
<a href="https://eoapi.io/" target="_blank" nz-menu-item i18n>Document</a>
<li nz-menu-divider></li>
<a href="https://github.com/eolinker/eoapi/issues/new" target="_blank" nz-menu-item i18n>Report Issue</a>
</ul>
</nz-dropdown-menu>
<div *ngIf="!OS_TYPE.includes('mac') && isElectron">
<span nz-tooltip i18n-nzTooltipTitle nzTooltipTitle="Minimize" nzTooltipPlacement="left" class="iconfont icon-jianhao mr10 fs24 cp"
(click)="minimize()">
</span>
<span nz-tooltip i18n-nzTooltipTitle [nzTooltipTitle]="isMaximized ? 'Restore' : 'Maximize'" nzTooltipPlacement="left"
class="iconfont icon-{{ isMaximized ? 'copy' : 'duoxuanweixuanzhong' }} mr10 fs24 cp"
(click)="toggleMaximize()">
</span>
<span nz-tooltip i18n-nzTooltipTitle nzTooltipTitle="Close" nzTooltipPlacement="left" class="iconfont icon-guanbi pr15 fs24 cp"
(click)="close()">
</span>
</div>
<div *ngIf="!isElectron">
<div class="btn py-1.5 px-2 mx-1 flex items-center" nz-dropdown i18n [nzDropdownMenu]="download">Download</div>
<nz-dropdown-menu #download="nzDropdownMenu">
<ul nz-menu>
<ng-container *ngFor="let item of resourceInfo; let index = index">
<a [href]="item.link" nz-menu-item>{{ item.name }}</a>
<li nz-menu-divider *ngIf="index !== resourceInfo.length - 1"></li>
</ng-container>
</ul>
</nz-dropdown-menu>
</div>
</div>
</div>
<eo-setting [(isShowModal)]="isSettingVisible"></eo-setting>

View File

@ -1,3 +1,27 @@
.btn {
border-radius: 3px;
background-color: var(--BTN_PRIMARY_BG);
color: #fff;
font-size: 0.9em;
height: 30px;
cursor: pointer;
}
.icon {
-webkit-app-region: no-drag;
width: 30px;
height: 30px;
font-size: 1.5em;
color: rgba(0,0,0,0.5);
cursor: pointer;
border-radius: 3px;
transition: all .4s ease;
&:hover {
background-color: rgba(0,0,0,0.05);
}
}
.iconfont{
-webkit-app-region: no-drag;
}
.eo_navbar {
-webkit-app-region: drag;
position: sticky;
@ -5,6 +29,7 @@
z-index: 11;
left: 0;
top: 0;
padding: 0 8px;
// background-color: var(--NAVBAR_BG);
// color: var(--NAVBAR_TEXT);
// border-bottom: 1px solid var(--NAVBAR_BORDER_BOTTOM);

View File

@ -1,10 +1,10 @@
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { ElectronService } from '../../../core/services';
import { ElectronService } from '../../core/services';
import { ModuleInfo } from 'eo/platform/node/extension-manager';
import { MessageService } from '../../../shared/services/message';
import { MessageService } from '../../shared/services/message';
import { NzConfigService } from 'ng-zorro-antd/core/config';
import { RemoteService } from 'eo/workbench/browser/src/app/shared/services/remote/remote.service';
import { ResourceInfo } from '../../shared/models/client.model';
@Component({
selector: 'eo-navbar',
templateUrl: './navbar.component.html',
@ -13,6 +13,7 @@ import { RemoteService } from 'eo/workbench/browser/src/app/shared/services/remo
export class NavbarComponent implements OnInit {
isMaximized = false;
isElectron = false;
isSettingVisible = false;
messageTop;
@ViewChild('notificationTemplate', { static: true })
notificationTemplate!: TemplateRef<{}>;
@ -29,30 +30,7 @@ export class NavbarComponent implements OnInit {
}
OS_TYPE = navigator.platform.toLowerCase();
modules: Map<string, ModuleInfo>;
resourceInfo = [
{
id: 'win',
name: 'Windows 客户端',
icon: 'windows',
keyword: 'Setup',
suffix: 'exe',
link: '',
},
{
id: 'mac',
name: 'macOS(Intel) 客户端',
icon: 'mac',
suffix: 'dmg',
link: '',
},
{
id: 'mac',
name: 'macOS(M1) 客户端',
icon: 'mac',
suffix: 'arm64.dmg',
link: '',
},
];
resourceInfo = ResourceInfo;
constructor(
private electron: ElectronService,
@ -124,6 +102,10 @@ export class NavbarComponent implements OnInit {
}
}
handleShowModal() {
this.isSettingVisible = true;
}
/**
* switch data
*/

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
import { NavbarComponent } from 'eo/workbench/browser/src/app/pages/navbar/navbar.component';
import { SettingModule } from 'eo/workbench/browser/src/app/shared/components/setting/setting.module';
import { CommonModule } from '@angular/common';
import { SharedModule } from 'eo/workbench/browser/src/app/shared/shared.module';
@NgModule({
imports: [CommonModule, NzDropDownModule, NzToolTipModule, SettingModule, SharedModule],
declarations: [NavbarComponent],
exports: [NavbarComponent],
})
export class NavbarModule {}

View File

@ -3,7 +3,6 @@ import { NgModule } from '@angular/core';
import { PagesComponent } from './pages.component';
import { PageBlankComponent } from '../shared/components/page-blank/page-blank.component';
import { PageFeaturePreviewComponent } from '../shared/components/page-feature-preview/page-feature-preview.component';
const routes: Routes = [
{
path: '',
@ -18,10 +17,6 @@ const routes: Routes = [
path: 'blank',
component: PageBlankComponent,
},
{
path: 'preview',
component: PageFeaturePreviewComponent,
},
{
path: 'api',
loadChildren: () => import('./api/api.module').then((m) => m.ApiModule),

View File

@ -2,19 +2,29 @@
<div [style.--remote-notification-height]="isShowNotification && isElectron ? '50px' : '0px'">
<div *ngIf="isShowNotification && isElectron" class="remote-notification">
<i nz-icon [nzType]="isRemote ? 'cloud' : 'exclamation-circle'" nzTheme="outline" class="text-[13px] mr-[2px]"></i>
目前数据储存在{{dataSourceText}},如需协作请切换
<a class="eo-blod" (click)="switchDataSource()">{{ isRemote ? '本地' : '远程' }}数据源</a>
<i nz-icon nzType="close" nzTheme="outline" class="absolute right-[20px] cursor-pointer"
(click)="closeNotification()"></i>
<span i18n>Current data storage exists{{ dataSourceText }},please switch if you want to collaborate</span>
<a class="eo-blod" (click)="switchDataSource()" i18n>{{ isRemote ? 'Remote Server' : 'Localhost' }} Data Storage</a>
<i
nz-icon
nzType="close"
nzTheme="outline"
class="absolute right-[20px] cursor-pointer"
(click)="closeNotification()"
></i>
</div>
<div class="home_container f_row">
<eo-setting></eo-setting>
<eo-sidebar></eo-sidebar>
<div class="home fg1">
<router-outlet *ngIf="!loadedIframe"></router-outlet>
<iframe *ngIf="!this.sidebar.currentModule.isOffical" id="app_iframe" frameborder="no" border="0"
style="width: calc(100vw - 90px);height: calc(100vh - var(--NAVBAR_HEIGHT) - 4px);"></iframe>
<iframe
*ngIf="!this.sidebar.currentModule.isOffical"
id="app_iframe"
frameborder="no"
border="0"
style="width: calc(100vw - 90px); height: calc(100vh - var(--NAVBAR_HEIGHT) - 4px)"
></iframe>
</div>
</div>
</div>
<!-- <eo-toolbar></eo-toolbar> -->
<eo-toolbar></eo-toolbar>

View File

@ -3,11 +3,10 @@ import { SafeResourceUrl } from '@angular/platform-browser';
import { SidebarService } from 'eo/workbench/browser/src/app/shared/components/sidebar/sidebar.service';
import { Message } from 'eo/workbench/browser/src/app/shared/services/message/message.model';
import { MessageService } from 'eo/workbench/browser/src/app/shared/services/message/message.service';
import { Subject, takeUntil } from 'rxjs';
import { Subject, takeUntil, debounceTime } from 'rxjs';
import { isElectron } from 'eo/shared/common/common';
import { RemoteService } from 'eo/workbench/browser/src/app/shared/services/remote/remote.service';
import { IS_SHOW_REMOTE_SERVER_NOTIFICATION } from 'eo/workbench/browser/src/app/shared/services/storage/storage.service';
import { debounce } from 'lodash';
@Component({
selector: 'eo-pages',
@ -26,6 +25,7 @@ export class PagesComponent implements OnInit {
return this.remoteService.dataSourceText;
}
private destroy$: Subject<void> = new Subject<void>();
private rawChange$: Subject<string> = new Subject<string>();
get isShowNotification() {
return !this.isRemote && this.isShow;
}
@ -35,11 +35,15 @@ export class PagesComponent implements OnInit {
public sidebar: SidebarService,
private messageService: MessageService,
private remoteService: RemoteService
) {}
) {
this.rawChange$.pipe(debounceTime(500), takeUntil(this.destroy$)).subscribe(() => {
this.updateState();
});
}
ngOnInit(): void {
this.watchSidebarItemChange();
this.watchRemoteServerChange();
this.updateState();
this.rawChange$.next('');
}
private watchSidebarItemChange() {
this.sidebar.appChanged$.subscribe(() => {
@ -48,7 +52,7 @@ export class PagesComponent implements OnInit {
setTimeout(() => {
//add loading
this.loadedIframe = false;
let iframe = document.getElementById('app_iframe') as HTMLIFrameElement;
const iframe = document.getElementById('app_iframe') as HTMLIFrameElement;
//load resource
iframe.src = this.sidebar.currentModule.main;
//loading finish
@ -65,7 +69,7 @@ export class PagesComponent implements OnInit {
this.remoteService.switchDataSource();
};
updateState = debounce(async () => {
updateState = async () => {
if (!this.isRemote && localStorage.getItem(IS_SHOW_REMOTE_SERVER_NOTIFICATION) !== 'false') {
const [isSuccess] = await this.remoteService.pingRmoteServerUrl();
this.isShow = isSuccess;
@ -73,7 +77,7 @@ export class PagesComponent implements OnInit {
// if (!) {
// this.isClose = false;
// }
}, 500);
};
private watchRemoteServerChange() {
this.messageService
@ -82,7 +86,7 @@ export class PagesComponent implements OnInit {
.subscribe((inArg: Message) => {
switch (inArg.type) {
case 'onDataSourceChange': {
this.updateState();
this.rawChange$.next(inArg.type);
break;
}
}

View File

@ -5,10 +5,11 @@ import { SettingModule } from '../shared/components/setting/setting.module';
import { PagesComponent } from './pages.component';
import { SharedModule } from '../shared/shared.module';
import { NzIconModule } from 'ng-zorro-antd/icon';
import { NavbarModule } from 'eo/workbench/browser/src/app/pages/navbar/navbar.module';
@NgModule({
imports: [PagesRoutingModule, SettingModule, CommonModule, SharedModule, NzIconModule, NavbarModule],
declarations: [PagesComponent],
imports: [PagesRoutingModule, SettingModule, CommonModule, SharedModule, NzIconModule],
exports: [],
})
export class PagesModule {}

View File

@ -1,5 +0,0 @@
<div class="about">
<nz-descriptions nzTitle="关于" [nzColumn]="1">
<nz-descriptions-item *ngFor="let item of list" [nzTitle]="item.label">{{item.value}}</nz-descriptions-item>
</nz-descriptions>
</div>

View File

@ -1,8 +0,0 @@
.about ::ng-deep .ant-descriptions-item-label {
width: 84px;
position: relative;
&::after {
position: absolute;
right: 0;
}
}

View File

@ -1,24 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AboutComponent } from './about.component';
describe('AboutComponent', () => {
let component: AboutComponent;
let fixture: ComponentFixture<AboutComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AboutComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AboutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,104 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { ElectronService } from '../../../core/services';
import pkg from '../../../../../../../../package.json';
const dependencies = {
...pkg.dependencies,
...pkg.devDependencies,
} as const;
type DescriptionsItem = {
readonly id: string;
readonly label: string;
value: string;
};
const descriptions: DescriptionsItem[] = [
{
id: 'version',
label: '当前版本号',
value: pkg.version,
},
{
id: 'publishTime',
label: '发布日期',
value: '',
},
{
id: 'homeDir',
label: '安装目录',
value: '',
},
{
id: 'electron',
label: 'Electron',
value: '',
},
{
id: 'chrome',
label: 'Chromium',
value: '',
},
{
id: 'node',
label: 'Node.js',
value: '',
},
{
id: 'v8',
label: 'V8',
value: '',
},
{
id: 'os',
label: 'OS',
value: '',
},
];
@Component({
selector: 'eo-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss'],
})
export class AboutComponent implements OnInit {
list = descriptions;
constructor(private electron: ElectronService) {}
ngOnInit(): void {
fetch('https://api.github.com/repos/eolinker/eoapi/releases')
.then((response) => response.json())
.then((data) => {
const publishTime = data.find((n) => n.tag_name.slice(1) === pkg.version)?.published_at;
const publishObj = this.list.find((n) => n.id === 'publishTime');
if (publishTime) {
publishObj.value = new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
weekday: 'long',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
})
.format(new Date(publishTime))
.replace(/星期[^]?/, '');
} else {
publishObj.value = `当前版本(v${pkg.version})尚未发布`;
}
});
const systemInfo = this.getSystemInfo();
this.list.forEach((item) => {
if (item.id in systemInfo) {
item.value = systemInfo[item.id];
}
});
}
getSystemInfo() {
const systemInfo = this.electron.ipcRenderer.sendSync('get-system-info');
return systemInfo;
}
}

Some files were not shown because too many files have changed in this diff Show More