@@ -42,19 +38,11 @@

环境变量:在接口文档或测试的过程中,使用{{ varName }}即可引用该环境变量

- + - + diff --git a/src/workbench/browser/src/app/pages/env/env.component.scss b/src/workbench/browser/src/app/pages/env/env.component.scss index 675c770f7..f34e4075b 100644 --- a/src/workbench/browser/src/app/pages/env/env.component.scss +++ b/src/workbench/browser/src/app/pages/env/env.component.scss @@ -1,7 +1,15 @@ +$boxHeight: 60vh; +@mixin item-active { + color: var(--MAIN_THEME_COLOR); + background-color: #f4f4f4; + border-color: transparent; + border-radius: 5px; +} + .flex { display: flex; width: 100%; - height: 60vh; + height: $boxHeight; } .b-2 { @@ -11,16 +19,34 @@ .side_bar { display: flex; width: 240px; - overflow-y: auto; .list { width: 100%; + ::ng-deep .ant-list-items { + overflow-y: auto; + max-height: calc($boxHeight - 42px); + } .list_item { cursor: pointer; display: flex; align-items: center; justify-content: space-between; - .active { - color: var(--MAIN_THEME_COLOR); + transition: background-color 0.15s; + + &:hover { + background-color: #f4f4f4; + border-radius: 5px; + } + &:not(:first-of-type) { + border-top: 1px solid #f0f0f0; + } + &:not(:last-of-type) { + border-bottom-color: transparent; + } + &.active { + @include item-active; + &:not(:last-of-type) + .list_item { + border-top-color: transparent; + } } } .footer { @@ -31,8 +57,8 @@ i { color: var(--MAIN_THEME_COLOR); } - &:hover { - background-color: #f4f4f4; + &.active { + @include item-active; } } } From 047a5fd3e90b554623ccbd4499d6533a25bec98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=9C=E9=B9=B0?= <17kungfuboy@gmail.com> Date: Wed, 11 May 2022 16:07:48 +0800 Subject: [PATCH 3/8] feat: test exstension-push-eolink, it works --- src/platform/electron-browser/preload.ts | 5 +- .../app/shared/components/sync-api/push.ts | 24 --------- .../sync-api/sync-api.component.html | 2 +- .../components/sync-api/sync-api.component.ts | 50 +++++++++++-------- 4 files changed, 35 insertions(+), 46 deletions(-) delete mode 100644 src/workbench/browser/src/app/shared/components/sync-api/push.ts diff --git a/src/platform/electron-browser/preload.ts b/src/platform/electron-browser/preload.ts index d7dd45dec..e9b8c9ebf 100644 --- a/src/platform/electron-browser/preload.ts +++ b/src/platform/electron-browser/preload.ts @@ -130,7 +130,10 @@ window.eo.saveSettings = (settings) => { }; window.eo.saveModuleSettings = (moduleID, settings) => { - return ipcRenderer.sendSync('eo-sync', { action: 'saveModuleSettings', data: { moduleID: moduleID, settings: settings } }); + return ipcRenderer.sendSync('eo-sync', { + action: 'saveModuleSettings', + data: { moduleID: moduleID, settings: settings }, + }); }; window.eo.deleteModuleSettings = (moduleID) => { diff --git a/src/workbench/browser/src/app/shared/components/sync-api/push.ts b/src/workbench/browser/src/app/shared/components/sync-api/push.ts deleted file mode 100644 index fe53ffc97..000000000 --- a/src/workbench/browser/src/app/shared/components/sync-api/push.ts +++ /dev/null @@ -1,24 +0,0 @@ -import http from 'ky'; -const URL = `http://apis.apikit.deveolink.com/api/v2/api_studio/management/api/importOpenApi`; - -export const sync_to_remote = async (data, { projectId, SecretKey }) => { - const formData = new FormData(); - formData.append( - 'file[]', - new Blob([JSON.stringify(data)], { - type: 'application/json', - }) - ); - formData.append('project_id', projectId); - formData.append('update_type', 'all'); - formData.append('api_status', '0'); - const response = await http - .post(URL, { - headers: { - 'Eo-Secret-Key': SecretKey, - }, - body: formData, - }) - .json(); - return response; -}; diff --git a/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.html b/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.html index 425a85c9a..44ed21654 100644 --- a/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.html +++ b/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.html @@ -1,3 +1,3 @@ - + diff --git a/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.ts b/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.ts index 982304102..9f502aae8 100644 --- a/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.ts +++ b/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.ts @@ -1,6 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { NzModalRef } from 'ng-zorro-antd/modal'; +import { StorageHandleResult, StorageHandleStatus } from '../../../../../../../platform/browser/IndexedDB'; import { StorageService } from '../../services/storage'; +import packageJson from '../../../../../../../../package.json'; @Component({ selector: 'eo-sync-api', @@ -8,31 +10,39 @@ import { StorageService } from '../../services/storage'; styleUrls: ['./sync-api.component.scss'], }) export class SyncApiComponent implements OnInit { - exportType: string = 'eolink'; + pushType: ''; supportList: any[] = []; + featureList = window.eo.getFeature('apimanager.sync'); constructor(private modalRef: NzModalRef, private storage: StorageService) {} ngOnInit(): void { - const extensionList = window.eo.getModules(); - this.supportList = Object.values([...extensionList]) - .map((it) => it[1]) - .filter((it) => it.moduleType === 'feature') - .filter((it) => it.features['apimanager.sync']) - .map((it: any) => ({ - key: it.moduleName, - image: it.logo, - title: it.features['apimanager.sync'].label, - })); + this.featureList?.forEach((feature: object, key: string) => { + this.supportList.push({ + key: key, + image: feature['icon'], + title: feature['label'], + }); + }); } async submit() { - console.log('sync'); - // const res = await sync_to_remote( - // { a: 1 }, - // { - // projectId: 'ZXXhzTG756db7e34a8d7c803f001edf1a1c545bcbf27719', - // SecretKey: 'SgAZ5Lk60f776c1a235a3c3e62543c3793c36d6cc511db9', - // } - // ); - // console.log(res); + const feature = this.featureList.get(this.pushType); + const action = feature.action || null; + const module = window.eo.loadFeatureModule(this.pushType); + if (module && module[action] && typeof module[action] === 'function') { + this.storage.run('projectExport', [], async (result: StorageHandleResult) => { + if (result.status === StorageHandleStatus.success) { + result.data.version = packageJson.version; + try { + const output = await module[action](result.data, { + projectId: 'ZXXhzTG756db7e34a8d7c803f001edf1a1c545bcbf27719', + SecretKey: 'SgAZ5Lk60f776c1a235a3c3e62543c3793c36d6cc511db9', + }); + console.log(output); + } catch (e) { + console.log(e); + } + } + }); + } } } From 8109b862ee3f451110b9b8a0a37bb5b0aec0d01e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=9C=E9=B9=B0?= <17kungfuboy@gmail.com> Date: Fri, 13 May 2022 11:18:24 +0800 Subject: [PATCH 4/8] feat: delete useless code --- src/workbench/browser/src/main.ts | 5 ++--- tsconfig.json | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/workbench/browser/src/main.ts b/src/workbench/browser/src/main.ts index d3ad50118..f65f6c820 100644 --- a/src/workbench/browser/src/main.ts +++ b/src/workbench/browser/src/main.ts @@ -9,6 +9,5 @@ if (APP_CONFIG.production) { } platformBrowserDynamic() - .bootstrapModule(AppModule, { - }) - .catch(err => console.error(err)); + .bootstrapModule(AppModule, {}) + .catch((err) => console.error(err)); diff --git a/tsconfig.json b/tsconfig.json index 785060093..2ad2f39f2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,6 @@ "lib": ["es2017", "es2016", "es2015", "dom"], "baseUrl": "./", "paths": { - "@utils/*": ["src/core/api-manager/browser/src/app/utils/*"] } }, "include": ["**/**.ts"], From 039c1a60ab4139d4c59c8533c8883094558c7601 Mon Sep 17 00:00:00 2001 From: bqy_fe <1743369777@qq.com> Date: Wed, 18 May 2022 09:04:17 +0800 Subject: [PATCH 5/8] feat(UI): global settings (#25) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(UI): global settings * update code * remove redundant code * feat: support scrolll when content overflow * feat: auto save to local * pref: update css style * test * fix: sync configuration when show setting modal * fix: window is not define * fix: key undefind * feat: debug push extension * fix: auto save * feat: add selectTheme * fix: setting icon css style Co-authored-by: 夜鹰 <17kungfuboy@gmail.com> --- src/app/electron-main/main.ts | 6 +- src/app/electron-main/updater.ts | 2 + src/platform/electron-browser/preload.ts | 4 +- src/platform/node/configuration/lib/index.ts | 51 +-- .../node/extension-manager/lib/manager.ts | 12 +- .../node/extension-manager/types/manager.ts | 15 +- src/shared/node/file.ts | 10 +- .../app/core/services/theme/theme.model.ts | 92 ++--- .../setting/common/ConfigurationRegistry.ts | 8 + .../eoapi-settings/common-settings.json | 24 ++ .../eoapi-settings/extensiton-settings.json | 12 + .../eoapi-settings/feature-settings.json | 39 ++ .../setting/eoapi-settings/index.ts | 19 + .../eoapi-settings/theme-settings.json | 22 ++ .../setting/eoapi-settings/types.ts | 36 ++ .../components/setting/setting.component.html | 100 ++++- .../components/setting/setting.component.scss | 34 +- .../components/setting/setting.component.ts | 372 ++++++++++++++---- .../components/setting/setting.module.ts | 12 +- .../components/setting/settingLayout.ts | 20 + .../components/sync-api/sync-api.component.ts | 11 +- .../select-theme/select-theme.component.html | 56 +-- .../components/toolbar/toolbar.component.html | 5 +- .../browser/src/app/shared/shared.module.ts | 4 +- .../browser/src/assets/theme/antd.less | 15 +- src/workbench/browser/src/styles.scss | 4 +- 26 files changed, 762 insertions(+), 223 deletions(-) create mode 100644 src/workbench/browser/src/app/shared/components/setting/common/ConfigurationRegistry.ts create mode 100644 src/workbench/browser/src/app/shared/components/setting/eoapi-settings/common-settings.json create mode 100644 src/workbench/browser/src/app/shared/components/setting/eoapi-settings/extensiton-settings.json create mode 100644 src/workbench/browser/src/app/shared/components/setting/eoapi-settings/feature-settings.json create mode 100644 src/workbench/browser/src/app/shared/components/setting/eoapi-settings/index.ts create mode 100644 src/workbench/browser/src/app/shared/components/setting/eoapi-settings/theme-settings.json create mode 100644 src/workbench/browser/src/app/shared/components/setting/eoapi-settings/types.ts create mode 100644 src/workbench/browser/src/app/shared/components/setting/settingLayout.ts diff --git a/src/app/electron-main/main.ts b/src/app/electron-main/main.ts index 087d4d1ca..d15c34e07 100644 --- a/src/app/electron-main/main.ts +++ b/src/app/electron-main/main.ts @@ -1,7 +1,7 @@ import { app, BrowserWindow, ipcMain, screen } from 'electron'; import { EoUpdater } from './updater'; import * as path from 'path'; -import * as os from 'os' +import * as os from 'os'; import ModuleManager from '../../platform/node/extension-manager/lib/manager'; import { ModuleInfo, ModuleManagerInterface } from '../../platform/node/extension-manager'; import { StorageHandleStatus, StorageProcessType } from '../../platform/browser/IndexedDB'; @@ -18,7 +18,7 @@ export const subView = { appView: null, mainView: null, }; -const eoUpdater = new EoUpdater() +const eoUpdater = new EoUpdater(); const moduleManager: ModuleManagerInterface = ModuleManager(); const configuration: ConfigurationInterface = Configuration(); // Remote @@ -219,7 +219,7 @@ try { } else if (arg.action === 'getFeature') { returnValue = moduleManager.getFeature(arg.data.featureKey); } else if (arg.action === 'saveSettings') { - returnValue = configuration.saveSettings(arg.data.settings); + returnValue = configuration.saveSettings(arg.data); } else if (arg.action === 'saveModuleSettings') { returnValue = configuration.saveModuleSettings(arg.data.moduleID, arg.data.settings); } else if (arg.action === 'deleteModuleSettings') { diff --git a/src/app/electron-main/updater.ts b/src/app/electron-main/updater.ts index bef51404e..fd8112167 100644 --- a/src/app/electron-main/updater.ts +++ b/src/app/electron-main/updater.ts @@ -5,6 +5,8 @@ const appVersion = require('../../../package.json').version; export class EoUpdater { constructor() { this.watchLog(); + // 是否自动更新 + // autoUpdater.autoDownload = window.eo.getModuleSettings('common.app.autoUpdate') !== false; if (appVersion.includes('beta')) autoUpdater.channel = 'beta'; console.log('appVersion', appVersion, autoUpdater.channel); } diff --git a/src/platform/electron-browser/preload.ts b/src/platform/electron-browser/preload.ts index e9b8c9ebf..2715a5478 100644 --- a/src/platform/electron-browser/preload.ts +++ b/src/platform/electron-browser/preload.ts @@ -125,8 +125,8 @@ window.eo.storageRemote = (args) => { return output; }; -window.eo.saveSettings = (settings) => { - return ipcRenderer.sendSync('eo-sync', { action: 'saveSettings', data: { settings: settings } }); +window.eo.saveSettings = ({ settings, nestedSettings }) => { + return ipcRenderer.sendSync('eo-sync', { action: 'saveSettings', data: { settings, nestedSettings } }); }; window.eo.saveModuleSettings = (moduleID, settings) => { diff --git a/src/platform/node/configuration/lib/index.ts b/src/platform/node/configuration/lib/index.ts index c13da5a34..dc93c75a6 100644 --- a/src/platform/node/configuration/lib/index.ts +++ b/src/platform/node/configuration/lib/index.ts @@ -4,7 +4,6 @@ import * as path from 'path'; import { fileExists, readJson, writeJson } from '../../../../shared/node/file'; export class Configuration implements ConfigurationInterface { - /** * 配置文件地址 */ @@ -36,65 +35,71 @@ export class Configuration implements ConfigurationInterface { * 保存配置文件 */ private saveConfig(data: ConfigurationValueInterface): boolean { - return writeJson(this.configPath, data); + return writeJson(this.configPath, data, true); } /** * 保存全局配置 */ - saveSettings(settings: ConfigurationValueInterface): boolean { + saveSettings({ settings = {}, nestedSettings = {} }): boolean { let data = this.loadConfig(); data.settings = settings; - return this.saveConfig(data); + data.nestedSettings = nestedSettings; + return this.saveConfig(data); } /** * 保存模块配置 - * @param moduleID - * @param settings + * @param moduleID + * @param settings */ saveModuleSettings(moduleID: string, settings: ConfigurationValueInterface): boolean { let data = this.loadConfig(); - if (!data.settings) { - data.settings = {}; - } + 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); } /** * 删除模块配置 - * @param moduleID - * @returns + * @param moduleID + * @returns */ deleteModuleSettings(moduleID: string): boolean { let data = this.loadConfig(); if (data.settings && data.settings[moduleID]) { - delete(data.settings[moduleID]); + delete data.settings[moduleID]; return this.saveConfig(data); } return false; } - + /** - * 获取全局配置 + * 获取全局配置 * @returns */ getSettings(): ConfigurationValueInterface { const data = this.loadConfig(); - return data.settings; + return data; } /** - * 获取模块配置 - * @param moduleID - * @returns + * 获取模块配置, 以小数点分割的属性链,如:common.app.update + * @param section + * @returns */ - getModuleSettings(moduleID: string): ConfigurationValueInterface { - const settings = this.getSettings(); - return settings[moduleID] || {}; - } - + getModuleSettings(section?: string): ConfigurationValueInterface { + const localSettings = this.getSettings(); + localSettings.nestedSettings ??= {}; + if (section) { + return section.split('.').reduce((p, k) => p[k], localSettings.nestedSettings); + } + return localSettings.nestedSettings; + } } export default () => new Configuration(); diff --git a/src/platform/node/extension-manager/lib/manager.ts b/src/platform/node/extension-manager/lib/manager.ts index ff7653e33..6f6e2f6c0 100644 --- a/src/platform/node/extension-manager/lib/manager.ts +++ b/src/platform/node/extension-manager/lib/manager.ts @@ -117,7 +117,7 @@ export class ModuleManager implements ModuleManagerInterface { /** * 获取所有功能点列表 - * @returns + * @returns */ getFeatures(): Map> { return this.features; @@ -134,11 +134,11 @@ export class ModuleManager implements ModuleManagerInterface { } return this.modules.get(moduleID); } - + /** * 获取某个功能点的集合 - * @param featureKey - * @returns + * @param featureKey + * @returns */ getFeature(featureKey: string): Map { return this.features.get(featureKey); @@ -181,7 +181,7 @@ export class ModuleManager implements ModuleManagerInterface { /** * 清除功能点集合中的模块功能点 - * @param moduleInfo + * @param moduleInfo */ private deleteFeatures(moduleInfo: ModuleInfo) { if (moduleInfo.features && typeof moduleInfo.features === 'object' && isNotEmpty(moduleInfo.features)) { @@ -200,7 +200,7 @@ export class ModuleManager implements ModuleManagerInterface { private setup(moduleInfo: ModuleInfo) { if (isNotEmpty(moduleInfo.moduleID)) { this.set(moduleInfo); - } + } } /** diff --git a/src/platform/node/extension-manager/types/manager.ts b/src/platform/node/extension-manager/types/manager.ts index ddf18e52a..d49f3bf64 100644 --- a/src/platform/node/extension-manager/types/manager.ts +++ b/src/platform/node/extension-manager/types/manager.ts @@ -11,7 +11,7 @@ import { ModuleHandlerResult } from './handler'; export enum ModuleType { system = 'system', app = 'app', - feature = 'feature' + feature = 'feature', } /** @@ -58,11 +58,20 @@ export interface ModuleInfo { sidePosition?: SidePosition; // 配置项 configuration?: ModuleConfiguration; + /** 贡献点 */ + contributes: ModuleContributes; // 功能点配置 features?: { - [index: string]: object + [index: string]: object; }; } +/** + * 贡献点 + */ + +export type ModuleContributes = { + configuration: ModuleConfiguration; +}; /** * 模块配置项接口 @@ -71,7 +80,7 @@ export interface ModuleConfiguration { title: string; properties: { [index: string]: ModuleConfigurationField; - } + }; } /** diff --git a/src/shared/node/file.ts b/src/shared/node/file.ts index 2dc15b145..e7f91466e 100644 --- a/src/shared/node/file.ts +++ b/src/shared/node/file.ts @@ -6,15 +6,15 @@ import * as path from 'path'; * @param file * @param data */ -export const writeJson = (file: string, data: object): boolean => { - return writeFile(file, JSON.stringify(data)); +export const writeJson = (file: string, data: object, formatted = false): boolean => { + return writeFile(file, formatted ? JSON.stringify(data, null, 2) : JSON.stringify(data)); }; /** * Read json file, then return object. * @param file */ -export const readJson = (file: string): (object | null) => { +export const readJson = (file: string): object | null => { const data: string = readFile(file); if ('' === data) { return null; @@ -37,7 +37,7 @@ export const readFile = (file: string): string => { /** * Delete file. * @param file string - * @returns + * @returns */ export const deleteFile = (file: string): boolean => { try { @@ -46,7 +46,7 @@ export const deleteFile = (file: string): boolean => { } catch (e) { return false; } -} +}; /** * Write data into file. diff --git a/src/workbench/browser/src/app/core/services/theme/theme.model.ts b/src/workbench/browser/src/app/core/services/theme/theme.model.ts index 0a533ec04..406310a56 100644 --- a/src/workbench/browser/src/app/core/services/theme/theme.model.ts +++ b/src/workbench/browser/src/app/core/services/theme/theme.model.ts @@ -6,52 +6,52 @@ export const THEMES = [ key: '森林', value: 'classic_forest', }, - { - key: '日出', - value: 'classic_sunrise', - }, - { - key: '玩具', - value: 'classic_toy', - }, - ], - }, - { - title: '简洁', - lists: [ - { - key: '森林', - value: 'clean_forest', - }, - { - key: '日出', - value: 'clean_sunrise', - }, - { - key: '云', - value: 'clean_cloud', - }, - ], - }, - { - title: '深色', - lists: [ - { - key: '夜晚', - value: 'night_black', - }, - { - key: '森林', - value: 'night_forest', - }, - { - key: '命令行', - value: 'night_cmd', - }, - { - key: '日落', - value: 'night_dusk', - }, + // { + // key: '日出', + // value: 'classic_sunrise', + // }, + // { + // key: '玩具', + // value: 'classic_toy', + // }, ], }, + // { + // title: '简洁', + // lists: [ + // { + // key: '森林', + // value: 'clean_forest', + // }, + // { + // key: '日出', + // value: 'clean_sunrise', + // }, + // { + // key: '云', + // value: 'clean_cloud', + // }, + // ], + // }, + // { + // title: '深色', + // lists: [ + // { + // key: '夜晚', + // value: 'night_black', + // }, + // { + // key: '森林', + // value: 'night_forest', + // }, + // { + // key: '命令行', + // value: 'night_cmd', + // }, + // { + // key: '日落', + // value: 'night_dusk', + // }, + // ], + // }, ]; diff --git a/src/workbench/browser/src/app/shared/components/setting/common/ConfigurationRegistry.ts b/src/workbench/browser/src/app/shared/components/setting/common/ConfigurationRegistry.ts new file mode 100644 index 000000000..836406cd1 --- /dev/null +++ b/src/workbench/browser/src/app/shared/components/setting/common/ConfigurationRegistry.ts @@ -0,0 +1,8 @@ +/** + * 注册配置项 + */ +class ConfigurationRegistry { + constructor() {} +} + +export const configurationRegistry = new ConfigurationRegistry(); diff --git a/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/common-settings.json b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/common-settings.json new file mode 100644 index 000000000..7292d342b --- /dev/null +++ b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/common-settings.json @@ -0,0 +1,24 @@ +{ + "private": true, + "name": "Eoapi-Common", + "version": "0.0.1", + "moduleName": "通用", + "moduleID": "eoapi-common", + "author": "eoapi", + "publisher": "eoapi", + "contributes": { + "configuration": { + "type": "object", + "title": "通用", + "order": 1, + "properties": { + "common.app.autoUpdate": { + "type": "boolean", + "default": true, + "label": "自动升级", + "description": "勾选后,检测到应用升级后自动从远程下载最新的应用更新" + } + } + } + } +} diff --git a/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/extensiton-settings.json b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/extensiton-settings.json new file mode 100644 index 000000000..550fde0d2 --- /dev/null +++ b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/extensiton-settings.json @@ -0,0 +1,12 @@ +{ + "private": true, + "name": "Eoapi-Extensions", + "version": "0.0.1", + "moduleName": "扩展", + "moduleID": "Eoapi-Extensions", + "author": "eoapi", + "publisher": "eoapi", + "contributes": { + "configuration": [] + } +} diff --git a/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/feature-settings.json b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/feature-settings.json new file mode 100644 index 000000000..41628b1b0 --- /dev/null +++ b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/feature-settings.json @@ -0,0 +1,39 @@ +{ + "private": true, + "name": "Eoapi-Features", + "version": "0.0.1", + "moduleName": "功能", + "moduleID": "Eoapi-Features", + "author": "eoapi", + "publisher": "eoapi", + "contributes": { + "configuration": [ + { + "type": "object", + "title": "API测试", + "order": 1, + "properties": { + "features.remoteServer.redirctUrl": { + "type": "boolean", + "default": false, + "label": "自动重定向", + "description": "自动重定向至redirect url" + } + } + }, + { + "type": "object", + "title": "扩展", + "order": 2, + "properties": { + "features.remoteServer.autoUpdate": { + "type": "boolean", + "default": true, + "label": "自动更新", + "description": "勾选后,检测到拓展升级后自动从远程下载最新的插件更新" + } + } + } + ] + } +} diff --git a/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/index.ts b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/index.ts new file mode 100644 index 000000000..f1b1bd93b --- /dev/null +++ b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/index.ts @@ -0,0 +1,19 @@ +import commonSettings from './common-settings.json'; +import extensitonSettings from './extensiton-settings.json'; +import featureSettings from './feature-settings.json'; +import themeSettings from './theme-settings.json'; + +export type eoapiSettingsKey = keyof typeof eoapiSettings; + +export const eoapiSettings = { + /** 通用设置 */ + 'Eoapi-Common': commonSettings, + /** 功能设置 */ + 'Eoapi-Extensions': extensitonSettings, + /** 扩展配置 */ + 'Eoapi-Features': featureSettings, + /** 主题配置 */ + 'Eoapi-theme': themeSettings, +} as const; + +export default eoapiSettings; diff --git a/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/theme-settings.json b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/theme-settings.json new file mode 100644 index 000000000..7a14385eb --- /dev/null +++ b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/theme-settings.json @@ -0,0 +1,22 @@ +{ + "private": true, + "name": "Eoapi-theme", + "version": "0.0.1", + "moduleName": "主题", + "moduleID": "eoapi-theme", + "author": "eoapi", + "publisher": "eoapi", + "contributes": { + "configuration": { + "type": "object", + "order": 1, + "properties": { + "select.theme": { + "type": "component", + "label": "主题列表", + "default": "eo-select-theme" + } + } + } + } +} diff --git a/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/types.ts b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/types.ts new file mode 100644 index 000000000..fcc85dcf1 --- /dev/null +++ b/src/workbench/browser/src/app/shared/components/setting/eoapi-settings/types.ts @@ -0,0 +1,36 @@ +export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'null' | 'array' | 'object'; + +/** 设置模块实体 */ +export type TOCEntry = { + id: string; + label: string; + order?: number; + children?: TOCEntry[]; + settings?: Array; +}; + +export type Setting = {}; + +export type ConfigurationProperty = { + [prop: string]: { + /** 配置项的值类型 */ + type?: JSONSchemaType | JSONSchemaType[]; + /** 枚举,用于下拉框 */ + enum?: any[]; + /** 用于对枚举项的说明 */ + enumDescriptions?: string[]; + /** 配置项默认值 */ + default?: any; + /** 配置项描述 */ + description?: string; + }; +}; + +export type ConfigurationNode = { + id?: string; + order?: number; + type?: string | string[]; + title?: string; + description?: string; + properties?: ConfigurationProperty; +}; diff --git a/src/workbench/browser/src/app/shared/components/setting/setting.component.html b/src/workbench/browser/src/app/shared/components/setting/setting.component.html index 391dcd73a..0501487e1 100644 --- a/src/workbench/browser/src/app/shared/components/setting/setting.component.html +++ b/src/workbench/browser/src/app/shared/components/setting/setting.component.html @@ -1,24 +1,82 @@ - - - + + + + + + +
+ + + + + {{ node.title }} + + + + + + + + + {{ node.title }} + + + + +
+
+

{{ module.title }}

+ + + + {{module.properties[field]?.label }} + + + +
+ {{module.properties[field]?.description }} +
+ + + + + + + + + + + + + + + {{module.properties[field]?.description}} + + + + + + - -
- - - - - {{ field.label }} - - - - - -
- -
+
+ + + + 暂无配置项 + + +
+ + + +
+
diff --git a/src/workbench/browser/src/app/shared/components/setting/setting.component.scss b/src/workbench/browser/src/app/shared/components/setting/setting.component.scss index 8c1064c52..c7f321615 100644 --- a/src/workbench/browser/src/app/shared/components/setting/setting.component.scss +++ b/src/workbench/browser/src/app/shared/components/setting/setting.component.scss @@ -2,15 +2,39 @@ font-size: 20px; position: absolute; padding: 15px; - bottom: 0; + cursor: pointer; + bottom: var(--FOOTER_HEIGHT, 0); } -.flex { - display: flex; - width: 100%; +.container { + display: grid; + grid-template-columns: minmax(200px, min-content) 25px auto; height: 60vh; + .divider { + height: 100%; + } } +.tree-view, .form { + overflow: auto; +} +.form { + padding-right: 10px; +} .footer { text-align: right; -} \ No newline at end of file +} + +.title { + font-size: 16px; + font-weight: 700; +} +.label { + padding: 0; + font-weight: 700; + color: rgba(0, 0, 0, 0.9); +} +.description { + margin-bottom: 3px; + color: rgb(102, 102, 102); +} diff --git a/src/workbench/browser/src/app/shared/components/setting/setting.component.ts b/src/workbench/browser/src/app/shared/components/setting/setting.component.ts index 1cb41d8fe..d761b16c9 100644 --- a/src/workbench/browser/src/app/shared/components/setting/setting.component.ts +++ b/src/workbench/browser/src/app/shared/components/setting/setting.component.ts @@ -1,6 +1,27 @@ import { Component, OnInit } from '@angular/core'; import { NzTabPosition } from 'ng-zorro-antd/tabs'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { SelectionModel } from '@angular/cdk/collections'; +import { FlatTreeControl } from '@angular/cdk/tree'; +import { NzTreeFlatDataSource, NzTreeFlattener } from 'ng-zorro-antd/tree-view'; +import { debounce, cloneDeep } from 'lodash'; +import { eoapiSettings } from './eoapi-settings/'; + +interface TreeNode { + name: string; + title: string; + moduleID?: string; + disabled?: boolean; + children?: TreeNode[]; + configuration?: any[]; +} + +interface FlatNode { + expandable: boolean; + name: string; + level: number; + disabled: boolean; +} @Component({ selector: 'eo-setting', @@ -8,94 +29,313 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; styleUrls: ['./setting.component.scss'], }) export class SettingComponent implements OnInit { + objectKeys = Object.keys; + private transformer = (node: TreeNode, level: number): FlatNode => ({ + ...node, + expandable: !!node.children && node.children.length > 0, + name: node.name, + level, + disabled: !!node.disabled, + }); + selectListSelection = new SelectionModel(); + + treeControl = new FlatTreeControl( + (node) => node.level, + (node) => node.expandable + ); + + treeFlattener = new NzTreeFlattener( + this.transformer, + (node) => node.level, + (node) => node.expandable, + (node) => node.children + ); + + dataSource = new NzTreeFlatDataSource(this.treeControl, this.treeFlattener); + + /** 当前配置项 */ + currentConfiguration = []; isVisible = false; - isModal = false; + _isShowModal = false; position: NzTabPosition = 'left'; - modules = []; + /** 所有配置 */ settings = {}; + /** 本地配置 */ + localSettings = { settings: {}, nestedSettings: {} }; + /** 深层嵌套的配置 */ + nestedSettings = {}; validateForm!: FormGroup; - constructor( - private fb: FormBuilder, - ) {} + + get isShowModal() { + return this._isShowModal; + } + + set isShowModal(val) { + this._isShowModal = val; + if (val) { + this.init(); + } + } + + constructor(private fb: FormBuilder) {} ngOnInit(): void { this.init(); + // this.parseSettings(); + } + + hasChild = (_: number, node: FlatNode): boolean => node.expandable; + + /** + * 设置数据 + * @param properties + */ + private setSettingsModel(properties, controls) { + // 平级配置对象 + Object.keys(properties).forEach((fieldKey) => { + const props = properties[fieldKey]; + this.settings[fieldKey] = this.localSettings?.settings?.[fieldKey] ?? props.default; + // 可扩展加入更多默认校验 + if (props.required) { + controls[fieldKey] = [null, [Validators.required]]; + } else { + controls[fieldKey] = [null]; + } + }); + // 深层嵌套的配置对象 + Object.keys(properties).forEach((fieldKey) => { + const keyArr = fieldKey.split('.'); + const keyArrL = keyArr.length - 1; + keyArr.reduce((p, k, i) => { + const isLast = i === keyArrL; + p[k] ??= isLast ? this.settings[fieldKey] : {}; + return p[k]; + }, this.nestedSettings); + // 当settings变化时,将值同步到nestedSettings + Object.defineProperty(this.settings, fieldKey, { + get: () => { + return this.getConfiguration(fieldKey); + }, + set: (newVal) => { + const target = keyArr.slice(0, -1).reduce((p, k) => p[k], this.nestedSettings); + target[keyArr[keyArrL]] = newVal; + }, + }); + }); } + /** + * 根据key路径获取对应的配置的值 + * @param key + * @returns + */ + getConfiguration(key: string) { + return key.split('.').reduce((p, k) => p[k], this.nestedSettings); + } + /** + * 获取模块的标题 + * @param module + * @returns + */ + getModuleTitle(module: any): string { + const title = module?.moduleName ?? module?.contributes?.title ?? module?.title; + return title; + } + + selectModule(node) { + // console.log('selectModule', node); + this.currentConfiguration = node.configuration || []; + // console.log('this.currentConfiguration', this.currentConfiguration); + this.selectListSelection.select(node); + } + + /** + * 解析所有模块的配置信息 + */ private init() { - if (window.eo && window.eo.getFeature) { - this.isVisible = true; - this.settings = window.eo.getSettings(); - const featureList = window.eo.getFeature('configuration'); - const controls = {}; - featureList?.forEach((feature: object, key: string) => { - if (!feature['title'] || !feature['properties'] || typeof feature['properties'] !== 'object') { - return true; - } - if (!this.settings[key] || typeof this.settings[key] !== 'object') { - this.settings[key] = {}; - } - const fields = []; - for (let field_key in feature['properties']) { - let field = feature['properties'][field_key]; - // 加入允许的type限制 - if (!field['type'] || !field['label']) { - continue; - } - if ('select' === field['type'] && !field['options']) { - continue; - } - const name = key + '_' + field_key; - field = Object.assign({ - name: name, - key: field_key, - required: false, - default: '', - description: '' - }, field); - fields.push(field); - if (!this.settings[key][field_key]) { - this.settings[key][field_key] = field['default']; - } - // 可扩展加入更多默认校验 - if (field.required) { - controls[name] = [null, [Validators.required]]; - } else { - controls[name] = [null]; - } - } - this.modules.push({ - key: key, - title: feature['title'], - fields: fields, + if (!window.eo && !window.eo.getFeature) return; + this.isVisible = true; + this.settings = {}; + this.nestedSettings = {}; + // 获取本地设置 + this.localSettings = window.eo.getSettings(); + // const featureList = window.eo.getFeature('configuration'); + const modules = window.eo.getModules(); + const extensitonConfigurations = [...modules.values()].filter((n) => n.contributes?.configuration); + console.log('localSettings', this.localSettings); + console.log('extensitonConfigurations', extensitonConfigurations); + const controls = {}; + // 所有设置 + const allSettings = cloneDeep([ + eoapiSettings['Eoapi-Common'], + eoapiSettings['Eoapi-theme'], + eoapiSettings['Eoapi-Extensions'], + // eoapiSettings['Eoapi-Features'], + ]); + // 所有配置 + const allConfiguration = allSettings.map((n) => { + const configuration = n.contributes.configuration; + if (!Array.isArray(configuration)) { + configuration.moduleID ??= n.moduleID; + } + return configuration; + }); + // 第三方扩展 + const extensionsModule = allSettings.find((n) => n.moduleID === 'Eoapi-Extensions'); + extensitonConfigurations.forEach((item) => { + const configuration = item?.contributes?.configuration; + if (configuration) { + configuration.title = item.moduleName ?? configuration.title; + configuration.moduleID = item.moduleID; + extensionsModule.contributes.configuration.push(configuration); + } + }); + // 给插件的属性前面追加模块ID + const appendModuleID = (properties, moduleID) => { + return Object.keys(properties).reduce((prev, key) => { + prev[`${moduleID}.${key}`] = properties[key]; + return prev; + }, {}); + }; + /** 根据configuration配置生成settings model */ + allConfiguration.forEach((item) => { + if (Array.isArray(item)) { + item.forEach((n) => { + n.properties = appendModuleID(n.properties, n.moduleID); + this.setSettingsModel(n.properties, controls); }); - }); - this.validateForm = this.fb.group(controls); - } + } else { + item.properties = appendModuleID(item.properties, item.moduleID); + this.setSettingsModel(item.properties, controls); + } + }); + type Configuration = typeof allConfiguration[number] | Array; + // 递归生成设置树 + const generateTreeData = (configurations: Configuration = []) => { + return [].concat(configurations).reduce((prev, curr) => { + if (Array.isArray(curr)) { + return prev.concat(generateTreeData(curr)); + } + const treeItem: TreeNode = { + name: curr.title, + title: curr.title, + configuration: [].concat(curr), + }; + return prev.concat(treeItem); + }, []); + }; + // 所有设置项 + const treeData = allSettings.reduce((prev, curr) => { + let treeItem: TreeNode; + const configuration = curr.contributes.configuration; + if (Array.isArray(configuration)) { + treeItem = { + name: curr.name, + moduleID: curr.moduleID, + title: curr.moduleName || curr.name, + children: generateTreeData(configuration), + configuration, + }; + } else { + treeItem = { + name: curr.name, + moduleID: curr.moduleID, + title: curr.moduleName || configuration.title || curr.name, + configuration: [configuration], + }; + } + return prev.concat(treeItem); + }, []); + console.log('treeData', treeData); + console.log('setings', this.settings); + console.log('nestedSettings', this.nestedSettings); + console.log('controls', controls); + + this.dataSource.setData(treeData); + this.treeControl.expandAll(); + this.validateForm = this.fb.group(controls); + this.validateForm.valueChanges.subscribe(debounce(this.handleSave.bind(this), 300)); + // 默认选中第一项 + this.selectModule(this.treeControl.dataNodes.at(0)); } + // private init() { + // if (window.eo && window.eo.getFeature) { + // this.isVisible = true; + // this.settings = window.eo.getSettings(); + // const featureList = window.eo.getFeature('configuration'); + // const controls = {}; + // featureList?.forEach((feature: object, key: string) => { + // if (!feature['title'] || !feature['properties'] || typeof feature['properties'] !== 'object') { + // return true; + // } + // if (!this.settings[key] || typeof this.settings[key] !== 'object') { + // this.settings[key] = {}; + // } + // const fields = []; + // for (let field_key in feature['properties']) { + // let field = feature['properties'][field_key]; + // // 加入允许的type限制 + // if (!field['type'] || !field['label']) { + // continue; + // } + // if ('select' === field['type'] && !field['options']) { + // continue; + // } + // const name = key + '_' + field_key; + // field = Object.assign( + // { + // name: name, + // key: field_key, + // required: false, + // default: '', + // description: '', + // }, + // field + // ); + // fields.push(field); + // if (!this.settings[key][field_key]) { + // this.settings[key][field_key] = field['default']; + // } + // // 可扩展加入更多默认校验 + // if (field.required) { + // controls[name] = [null, [Validators.required]]; + // } else { + // controls[name] = [null]; + // } + // } + // // this.modules.push({ + // // key: key, + // // title: feature['title'], + // // fields: fields, + // // }); + // }); + // this.validateForm = this.fb.group(controls); + // } + // } + handleShowModal() { - this.isModal = true; + this.isShowModal = true; } handleSave(): void { - for (const i in this.validateForm.controls) { - if (this.validateForm.controls.hasOwnProperty(i)) { - this.validateForm.controls[i].markAsDirty(); - this.validateForm.controls[i].updateValueAndValidity(); - } - } - if (this.validateForm.status === 'INVALID') { - return; - } + // for (const i in this.validateForm.controls) { + // if (this.validateForm.controls.hasOwnProperty(i)) { + // this.validateForm.controls[i].markAsDirty(); + // this.validateForm.controls[i].updateValueAndValidity(); + // } + // } + // if (this.validateForm.status === 'INVALID') { + // return; + // } // 加入根据返回显示提示消息 - const saved = window.eo.saveSettings(this.settings); + const saved = window.eo.saveSettings({ settings: this.settings, nestedSettings: this.nestedSettings }); if (saved) { - this.handleCancel(); + // this.handleCancel(); } } handleCancel(): void { - this.isModal = false; + this.isShowModal = false; } } diff --git a/src/workbench/browser/src/app/shared/components/setting/setting.module.ts b/src/workbench/browser/src/app/shared/components/setting/setting.module.ts index e06b5bf6e..9846a0066 100644 --- a/src/workbench/browser/src/app/shared/components/setting/setting.module.ts +++ b/src/workbench/browser/src/app/shared/components/setting/setting.module.ts @@ -7,11 +7,17 @@ import { NzButtonModule } from 'ng-zorro-antd/button'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzListModule } from 'ng-zorro-antd/list'; import { NzInputModule } from 'ng-zorro-antd/input'; +import { NzCheckboxModule } from 'ng-zorro-antd/checkbox'; +import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; import { NzFormModule } from 'ng-zorro-antd/form'; import { NzSelectModule } from 'ng-zorro-antd/select'; import { NzDividerModule } from 'ng-zorro-antd/divider'; +import { NzEmptyModule } from 'ng-zorro-antd/empty'; import { NzTabsModule } from 'ng-zorro-antd/tabs'; +import { NzTreeViewModule } from 'ng-zorro-antd/tree-view'; + import { ElectronService } from '../../../core/services'; +import { SharedModule } from '../../shared.module'; const ANTDMODULES = [ NzModalModule, @@ -23,10 +29,14 @@ const ANTDMODULES = [ NzSelectModule, NzDividerModule, NzTabsModule, + NzTreeViewModule, + NzCheckboxModule, + NzInputNumberModule, + NzEmptyModule, ]; @NgModule({ declarations: [SettingComponent], - imports: [FormsModule, ReactiveFormsModule, CommonModule, ...ANTDMODULES], + imports: [FormsModule, ReactiveFormsModule, SharedModule, CommonModule, ...ANTDMODULES], exports: [SettingComponent], providers: [ElectronService], }) diff --git a/src/workbench/browser/src/app/shared/components/setting/settingLayout.ts b/src/workbench/browser/src/app/shared/components/setting/settingLayout.ts new file mode 100644 index 000000000..ace6d10ca --- /dev/null +++ b/src/workbench/browser/src/app/shared/components/setting/settingLayout.ts @@ -0,0 +1,20 @@ +export type TOCEntry = { + id: string; + label: string; + order?: number; + children?: TOCEntry[]; + settings?: Array; +}; + +export const tocData: TOCEntry[] = [ + { + id: 'common', + label: '通用', + settings: ['common.*'], + }, + { + id: 'extension', + label: '扩展', + settings: ['extension.*'], + }, +]; diff --git a/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.ts b/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.ts index 9f502aae8..fefee90cd 100644 --- a/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.ts +++ b/src/workbench/browser/src/app/shared/components/sync-api/sync-api.component.ts @@ -28,14 +28,21 @@ export class SyncApiComponent implements OnInit { const feature = this.featureList.get(this.pushType); const action = feature.action || null; const module = window.eo.loadFeatureModule(this.pushType); + // TODO 临时取值方式需要修改 + const { + url, + token: secretKey, + projectId, + } = window.eo.getModuleSettings('eoapi-feature-push-eolink.eolink.remoteServer'); if (module && module[action] && typeof module[action] === 'function') { this.storage.run('projectExport', [], async (result: StorageHandleResult) => { if (result.status === StorageHandleStatus.success) { result.data.version = packageJson.version; try { const output = await module[action](result.data, { - projectId: 'ZXXhzTG756db7e34a8d7c803f001edf1a1c545bcbf27719', - SecretKey: 'SgAZ5Lk60f776c1a235a3c3e62543c3793c36d6cc511db9', + url, + projectId, + secretKey, }); console.log(output); } catch (e) { diff --git a/src/workbench/browser/src/app/shared/components/toolbar/select-theme/select-theme.component.html b/src/workbench/browser/src/app/shared/components/toolbar/select-theme/select-theme.component.html index 861458525..8b6c3448f 100644 --- a/src/workbench/browser/src/app/shared/components/toolbar/select-theme/select-theme.component.html +++ b/src/workbench/browser/src/app/shared/components/toolbar/select-theme/select-theme.component.html @@ -1,4 +1,4 @@ - - - -
-
{{ group.title }}
-
-
-
- -
- -
-
-
-