+
+
+
+```
+
+### 4. 通信
+因为 `proload.js` 是 `electron` 的 `renderer` 进程的,所以如果需要使用部分 `main` 进程的能力,则需要使用通信机制:
+```js
+// main process
+ipcMain.on('msg-trigger', async (event, arg) => {
+ const window = arg.winId ? BrowserWindow.fromId(arg.winId) : mainWindow
+ const operators = arg.type.split('.');
+ let fn = Api;
+ operators.forEach((op) => {
+ fn = fn[op];
+ });
+ const data = await fn(arg, window);
+ event.sender.send(`msg-back-${arg.type}`, data);
+});
+
+// renderer process
+ipcRenderer.send('msg-trigger', {
+ type: 'getPath',
+ name,
+});
+ipcRenderer.on(`msg-back-getPath`, (e, result) => {
+ console.log(result)
+});
+```
+
+## 插件加载原理
+### rubick 使用插件
+首先我们需要实现一个插件,必须要有个 `plugin.json`,这玩意就是用来告诉 `rubick` 插件的信息。
+```json
+{
+ "pluginName": "helloWorld",
+ "description": "我的第一个uTools插件",
+ "main": "index.html",
+ "version": "0.0.1",
+ "logo": "logo.png",
+ "features": [
+ {
+ "code": "hello",
+ "explain": "hello world",
+ "cmds":["hello", "你好"]
+ }
+ ]
+}
+```
+接下来是将写好的插件用 `rubick` 跑起来,复制 `plugin.json` 到 `rubick` 搜索框即可,所以当 `rubick` 检测到输入框内执行
+`ctrl/command + c` 时,读取剪切板内容,如果剪切板复制的是文件类型的 `plugin.json`,那么就将构造插件的 `pluginConfig` 配置文件,用于后续搜索
+时使用。
+
+```js
+// 监听 input change
+// 读取剪切板内容
+const fileUrl = clipboard.read('public.file-url').replace('file://', '');
+// 复制文件
+if (fileUrl && value === 'plugin.json') {
+ // 读取 plugin.json 配置
+ const config = JSON.parse(fs.readFileSync(fileUrl, 'utf-8'));
+ const pluginConfig = {
+ ...config,
+ // index.html 文件位置,用于webview加载
+ sourceFile: path.join(fileUrl, `../${config.main || 'index.html'}`),
+ id: uuidv4(),
+ type: 'dev',
+ icon: 'image://' + path.join(fileUrl, `../${config.logo}`),
+ subType: (() => {
+ if (config.main) {
+ return ''
+ }
+ return 'template';
+ })()
+ };
+}
+```
+实现效果如下:
+
+
+
+
+### rubick 内搜索插件原理
+
+接下来就是进行命令搜索插件:
+
+
+
+
+实现这个功能其实也就是对之前存储的`pluginConfig`的里面的 `features` 进行遍历,找到相应的 `cmd` 后进行下拉框展示即可。
+
+```js
+state.devPlugins.forEach((plugin) => {
+ // dev 插件未开启
+ if (plugin.type === 'dev' && !plugin.status) return;
+ const feature = plugin.features;
+ feature.forEach((fe) => {
+ // fe.cmds: 所有插件的命令; value: 当前输入框内搜索的名称
+ const cmds = searchKeyValues(fe.cmds, value);
+ options = [
+ ...options,
+ ...cmds.map((cmd) => ({
+ name: cmd,
+ value: 'plugin',
+ icon: plugin.sourceFile ? 'image://' + path.join(plugin.sourceFile, `../${plugin.logo}`) : plugin.logo,
+ desc: fe.explain,
+ type: plugin.type,
+ click: (router) => {
+ // 跳转到指定插件页面
+ actions.openPlugin({ commit }, { cmd, plugin, feature: fe, router });
+ }
+ }))
+ ];
+ });
+});
+```
+
+当点击 input 内插件时,需要跳转到插件 `webview` 加载页面:
+
+```js
+// actions.openPlugin
+router.push({
+ path: '/plugin',
+ query: {
+ ...plugin,
+ _modify: Date.now(),
+ detail: JSON.stringify(feature)
+ }
+});
+```
+
+
+
+本页写的插件demo已上传 [github](https://github.com/clouDr-f2e/rubick-plugin-demo)
diff --git a/src/main/common/listener.js b/src/main/common/listener.js
index 1ede511..a832123 100644
--- a/src/main/common/listener.js
+++ b/src/main/common/listener.js
@@ -215,6 +215,10 @@ class Listener {
});
const pos = this.getPos(robot.getMousePos());
win.setPosition(parseInt(pos.x), parseInt(pos.y));
+ win.setAlwaysOnTop(true);
+ win.setVisibleOnAllWorkspaces(true, {visibleOnFullScreen: true});
+ win.focus();
+ win.setVisibleOnAllWorkspaces(false, {visibleOnFullScreen: true});
win.show();
});
}
diff --git a/src/renderer/App.vue b/src/renderer/App.vue
index 3a83b14..4d8fc88 100644
--- a/src/renderer/App.vue
+++ b/src/renderer/App.vue
@@ -39,11 +39,15 @@
class="icon-more"
type="more"
/>
-
![]()
+
+
+
+
+
![]()
+
@@ -163,7 +167,18 @@ export default {
feature.forEach((fe) => {
const cmd = searchKeyValues(fe.cmds, args)[0];
const systemPlugin = fileLists.filter(
- (plugin) => plugin.name.indexOf(args) >= 0
+ (plugin) => {
+ let has = false;
+ plugin.keyWords.some(keyWord => {
+ if (keyWord.toLocaleUpperCase().indexOf(args.toLocaleUpperCase()) >= 0) {
+ has = keyWord;
+ plugin.name = keyWord;
+ return true;
+ }
+ return false;
+ });
+ return has;
+ }
)[0];
if (cmd) {
config = {
@@ -325,6 +340,7 @@ export default {
"searchValue",
"subPlaceHolder",
"pluginInfo",
+ "pluginLoading",
]),
showOptions() {
// 有选项值,且不在显示主页
@@ -449,5 +465,10 @@ export default {
font-weight: bold;
cursor: pointer;
}
+ .loading {
+ position:absolute;
+ top: 0;
+ left: 0;
+ }
}
diff --git a/src/renderer/pages/plugins/index.vue b/src/renderer/pages/plugins/index.vue
index 6f4f48e..04af1ef 100644
--- a/src/renderer/pages/plugins/index.vue
+++ b/src/renderer/pages/plugins/index.vue
@@ -1,6 +1,11 @@
-
+
@@ -29,6 +34,14 @@ export default {
this.webview.addEventListener('dom-ready', () => {
this.webview.send('onPluginReady', this.pluginInfo);
this.webview.send('onPluginEnter', this.pluginInfo);
+ this.commonUpdate({
+ pluginLoading: true,
+ });
+ });
+ this.webview.addEventListener('did-finish-load', () => {
+ this.commonUpdate({
+ pluginLoading: false,
+ });
});
this.setSubPlaceHolder('Hi, Rubick');
this.webview.addEventListener('ipc-message', (event) => {
@@ -83,10 +96,6 @@ export default {
methods: {
...mapMutations('main', ['setSubPlaceHolder', 'commonUpdate']),
},
- beforeRouteUpdate() {
- this.path = `File://${this.pluginInfo.sourceFile}`;
- this.webview.send('onPluginEnter', this.pluginInfo);
- },
beforeDestroy() {
const webview = document.querySelector('webview');
webview && webview.send('onPluginOut', this.pluginInfo)
@@ -97,10 +106,13 @@ export default {
return (this.devPlugins.filter(plugin => plugin.name === this.pluginInfo.name)[0] || {}).features
},
path() {
+ this.$nextTick(() => {
+ this.webview && this.webview.send('onPluginEnter', this.pluginInfo);
+ });
return `File://${this.pluginInfo.sourceFile}`
},
templatePath() {
- return `File://${path.join(__static, './plugins/tpl/index.html')}?code=${JSON.parse(this.pluginInfo.detail).code}&targetFile=${encodeURIComponent(this.pluginInfo.sourceFile)}&preloadPath=${this.pluginInfo.preload}`;
+ return `File://${path.join(__static, './plugins/tpl/index.html')}?code=${this.pluginInfo.detail.code}&targetFile=${encodeURIComponent(this.pluginInfo.sourceFile)}&preloadPath=${this.pluginInfo.preload}`;
}
}
}
diff --git a/src/renderer/store/modules/main.js b/src/renderer/store/modules/main.js
index 252ab97..c55eb81 100644
--- a/src/renderer/store/modules/main.js
+++ b/src/renderer/store/modules/main.js
@@ -22,9 +22,9 @@ const state = {
searchValue: '',
devPlugins: mergePlugins(sysFile.getUserPlugins() || []),
subPlaceHolder: '',
+ pluginLoading: true,
pluginInfo: (() => {
try {
- console.log(window.pluginInfo);
return window.pluginInfo || {};
} catch (e) {}
})()
diff --git a/static/plugins/superPanel/assets/logo.png b/static/plugins/superPanel/assets/logo.png
index 5373de3..65efaa3 100644
Binary files a/static/plugins/superPanel/assets/logo.png and b/static/plugins/superPanel/assets/logo.png differ
diff --git a/static/plugins/superPanel/index.html b/static/plugins/superPanel/index.html
index 535464a..3643071 100644
--- a/static/plugins/superPanel/index.html
+++ b/static/plugins/superPanel/index.html
@@ -40,8 +40,8 @@
background: #314659;
}
.top .img img {
- width: 22px;
- height: 22px;
+ width: 26px;
+ height: 26px;
}
.top .text {
color: #999;
diff --git a/static/plugins/superPanel/index.js b/static/plugins/superPanel/index.js
index c72243b..8b7148f 100644
--- a/static/plugins/superPanel/index.js
+++ b/static/plugins/superPanel/index.js
@@ -181,6 +181,8 @@ new Vue({
...JSON.parse(res),
src: msg,
});
+ }).catch(() => {
+ this.$set(this.selectData, 'translate', null);
}).finally(() => {
this.loading = false;
})
diff --git a/static/preload.js b/static/preload.js
index ffad797..b7d329b 100644
--- a/static/preload.js
+++ b/static/preload.js
@@ -252,6 +252,10 @@ window.rubick = {
isWindows() {
return os.type() === 'Windows_NT';
},
+
+ shellOpenPath(path) {
+ shell.openPath(path)
+ }
}
const preloadPath = getQueryVariable('preloadPath') || './preload.js';