支持插件开发者模式

This commit is contained in:
muwoo 2021-12-09 11:46:06 +08:00
parent fc7e3e91bd
commit b3a00c88ad
19 changed files with 264 additions and 88 deletions

View File

@ -3,7 +3,7 @@
"pluginName": "rubick 系统菜单", "pluginName": "rubick 系统菜单",
"description": "rubick 系统菜单", "description": "rubick 系统菜单",
"main": "index.html", "main": "index.html",
"logo": "httpss://static.91jkys.com/upload/202112/08/8a1abbb051bf4b05bbc9bbf66ade63f2.png", "logo": "https://static.91jkys.com/upload/202112/08/8a1abbb051bf4b05bbc9bbf66ade63f2.png",
"version": "0.0.0", "version": "0.0.0",
"preload":"preload.js", "preload":"preload.js",
"pluginType": "ui", "pluginType": "ui",

View File

@ -26,6 +26,12 @@
</template> </template>
账户 账户
</a-menu-item> </a-menu-item>
<a-menu-item key="dev">
<template #icon>
<BugOutlined />
</template>
开发者
</a-menu-item>
</a-menu> </a-menu>
</div> </div>
<router-view /> <router-view />
@ -40,6 +46,7 @@ import {
UserOutlined, UserOutlined,
AppstoreOutlined, AppstoreOutlined,
SettingOutlined, SettingOutlined,
BugOutlined,
} from "@ant-design/icons-vue"; } from "@ant-design/icons-vue";
import { useStore } from "vuex"; import { useStore } from "vuex";
const router = useRouter(); const router = useRouter();

View File

@ -14,4 +14,11 @@ export default {
); );
return res.data; return res.data;
}, },
async getSystemDetail() {
const res = await axios.get(
"https://gitee.com/monkeyWang/rubick-database/raw/master/plugins/system.json"
);
return res.data;
},
}; };

View File

@ -3,6 +3,7 @@ import Market from "../views/market/index.vue";
import Installed from "../views/installed/index.vue"; import Installed from "../views/installed/index.vue";
import Account from "../views/account/index.vue"; import Account from "../views/account/index.vue";
import Settings from "../views/settings/index.vue"; import Settings from "../views/settings/index.vue";
import Dev from "../views/dev/index.vue";
const routes: Array<RouteRecordRaw> = [ const routes: Array<RouteRecordRaw> = [
{ {
@ -25,6 +26,11 @@ const routes: Array<RouteRecordRaw> = [
name: "settings", name: "settings",
component: Settings, component: Settings,
}, },
{
path: "/dev",
name: "dev",
component: Dev,
},
{ {
path: "/:catchAll(.*)", path: "/:catchAll(.*)",
name: "market", name: "market",

View File

@ -0,0 +1,64 @@
<template>
<div class="dev">
<a-alert style="margin-bottom: 40px;" message="rubick 插件系统依托于 npm 管理,本地调试需要先在本地插件当前目录执行 npm link" type="warning" />
<a-form
ref="formRef"
:model="formState"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-item label="插件名称" name="name">
<a-input v-model:value="formState.name" />
</a-form-item>
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button :loading="loading" type="primary" @click="onSubmit">安装</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { reactive, ref } from "vue";
const formRef = ref();
const formState = reactive({
name: undefined,
});
const rules = {
name: {
required: true,
message: "Please input name",
},
};
const onSubmit = () => {
formRef.value.validate().then(() => {
downloadPlugin(formState.name);
});
};
const loading = ref(false);
const downloadPlugin = async (pluginName) => {
loading.value = true;
await window.market.downloadPlugin({
name: pluginName,
isDev: true,
});
loading.value = false;
};
const labelCol = { span: 4 };
const wrapperCol = { span: 14 };
</script>
<style lang="less">
.dev {
box-sizing: border-box;
width: 100%;
overflow-x: hidden;
background: #fff;
height: calc(~"100vh - 46px");
padding: 20px;
}
</style>

View File

@ -1,57 +1,61 @@
<template> <template>
<div class="installed"> <div class="installed">
<div class="installed-list"> <div v-if="!localPlugins.length">
<div <a-result status="404" title="暂无任何插件" sub-title="去插件市场选择安装合适的插件吧" />
:class="currentSelect === index ? 'item active' : 'item'"
:key="index"
v-for="(plugin, index) in localPlugins"
>
<img :src="plugin.logo" />
<div class="info">
<div class="title">
{{ plugin.pluginName }}
<span class="desc">v{{ plugin.version }}</span>
</div>
<div class="desc">{{ plugin.description }}</div>
</div>
</div>
</div> </div>
<div class="plugin-detail"> <div class="container" v-else>
<div class="plugin-top"> <div class="installed-list">
<div class="left"> <div
<div class="title"> :class="currentSelect === index ? 'item active' : 'item'"
{{ pluginDetail.pluginName }} :key="index"
<a-tag>{{ pluginDetail.version }}</a-tag> v-for="(plugin, index) in localPlugins"
>
<img :src="plugin.logo" />
<div class="info">
<div class="title">
{{ plugin.pluginName }}
<span class="desc">v{{ plugin.version }}</span>
</div>
<div class="desc">{{ plugin.description }}</div>
</div> </div>
<div class="desc">
开发者{{ `${pluginDetail.author || "未知"}` }}
</div>
<div class="desc">
{{ pluginDetail.description }}
</div>
</div>
<div class="right">
<a-button
type="danger"
size="small"
shape="round"
@click="deletePlugin(pluginDetail)"
>移除</a-button
>
</div> </div>
</div> </div>
<a-tabs default-active-key="1"> <div class="plugin-detail">
<a-tab-pane key="1" tab="功能关键字"> <div class="plugin-top">
<div class="feature-container"> <div class="left">
<div <div class="title">
class="desc-item" {{ pluginDetail.pluginName }}
:key="index" <a-tag>{{ pluginDetail.version }}</a-tag>
v-for="(item, index) in pluginDetail.features" </div>
<div class="desc">
开发者{{ `${pluginDetail.author || "未知"}` }}
</div>
<div class="desc">
{{ pluginDetail.description }}
</div>
</div>
<div class="right">
<a-button
type="danger"
size="small"
shape="round"
@click="deletePlugin(pluginDetail)"
>移除</a-button
> >
<div>{{ item.explain }}</div> </div>
<a-tag </div>
:key="cmd" <a-tabs default-active-key="1">
@click=" <a-tab-pane key="1" tab="功能关键字">
<div class="feature-container">
<div
class="desc-item"
:key="index"
v-for="(item, index) in pluginDetail.features"
>
<div>{{ item.explain }}</div>
<a-tag
:key="cmd"
@click="
openPlugin({ openPlugin({
cmd, cmd,
plugin: pluginDetail, plugin: pluginDetail,
@ -59,18 +63,20 @@
router: $router, router: $router,
}) })
" "
v-for="cmd in item.cmds" v-for="cmd in item.cmds"
> >
{{ cmd }} {{ cmd }}
</a-tag> </a-tag>
</div>
</div> </div>
</div> </a-tab-pane>
</a-tab-pane> <a-tab-pane key="2" tab="详情介绍">
<a-tab-pane key="2" tab="详情介绍"> <div class="detail-container" v-html="readme"></div>
<div class="detail-container" v-html="readme"></div> </a-tab-pane>
</a-tab-pane> </a-tabs>
</a-tabs> </div>
</div> </div>
</div> </div>
</template> </template>
@ -88,7 +94,11 @@ const appPath = remote.app.getPath("cache");
const baseDir = path.join(appPath, "./rubick-plugins"); const baseDir = path.join(appPath, "./rubick-plugins");
const store = useStore(); const store = useStore();
const localPlugins = computed(() => store.state.localPlugins); const localPlugins = computed(() =>
store.state.localPlugins.filter(
(plugin) => plugin.name !== "rubick-system-feature"
)
);
const updateLocalPlugin = () => store.dispatch("updateLocalPlugin"); const updateLocalPlugin = () => store.dispatch("updateLocalPlugin");
const currentSelect = ref(0); const currentSelect = ref(0);
@ -125,7 +135,14 @@ const deletePlugin = async (plugin) => {
overflow: hidden; overflow: hidden;
background: #f3efef; background: #f3efef;
height: calc(~"100vh - 46px"); height: calc(~"100vh - 46px");
display: flex; .container {
box-sizing: border-box;
width: 100%;
overflow: hidden;
background: #f3efef;
height: 100%;
display: flex;
}
.installed-list { .installed-list {
width: 40%; width: 40%;
background: #fff; background: #fff;

View File

@ -45,7 +45,6 @@ const totalPlugins = computed(() => store.state.totalPlugins);
const data = ref([]); const data = ref([]);
onBeforeMount(async () => { onBeforeMount(async () => {
console.log(12312);
data.value = await request.getFinderDetail(); data.value = await request.getFinderDetail();
}); });

View File

@ -0,0 +1,49 @@
<template>
<div class="system">
<PluginList
v-if="system && !!system.length"
@downloadSuccess="downloadSuccess"
title="系统插件"
:list="system"
/>
</div>
</template>
<script setup>
import { ref, computed, onBeforeMount } from "vue";
import request from "../../../assets/request/index";
import PluginList from "./plugin-list.vue";
import { useStore } from "vuex";
const store = useStore();
const totalPlugins = computed(() => store.state.totalPlugins);
const data = ref([]);
onBeforeMount(async () => {
data.value = await request.getSystemDetail();
});
const system = computed(() => {
const defaultData = data.value || [];
if (!defaultData.length) return [];
return defaultData.map((plugin) => {
let searchInfo = null;
totalPlugins.value.forEach((t) => {
if (t.name === plugin) {
searchInfo = t;
}
});
return searchInfo;
});
});
</script>
<style lang="less">
.system {
width: 100%;
height: 100vh;
overflow-x: hidden;
box-sizing: border-box;
}
</style>

View File

@ -67,9 +67,11 @@ import {
import { reactive, toRefs, computed } from "vue"; import { reactive, toRefs, computed } from "vue";
import { useStore } from "vuex"; import { useStore } from "vuex";
import Finder from "./components/finder.vue"; import Finder from "./components/finder.vue";
import System from "./components/system.vue";
const Components = { const Components = {
finder: Finder, finder: Finder,
system: System,
}; };
const state = reactive({ const state = reactive({

View File

@ -1,5 +0,0 @@
const APP_FINDER_PATH = process.platform === 'darwin' ? ['/System/Applications', '/Applications', '/System/Library/PreferencePanes'] : []
export {
APP_FINDER_PATH
}

View File

@ -0,0 +1,8 @@
import { app } from "electron";
import path from "path";
const appPath = app.getPath("cache");
const PLUGIN_INSTALL_DIR = path.join(appPath, "./rubick-plugins");
export { PLUGIN_INSTALL_DIR };

View File

@ -0,0 +1,8 @@
import { remote } from "electron";
import path from "path";
const appPath = remote.app.getPath("cache");
const PLUGIN_INSTALL_DIR = path.join(appPath, "./rubick-plugins");
export { PLUGIN_INSTALL_DIR };

View File

@ -1,21 +1,36 @@
import path from "path"; import path from "path";
import fs from "fs"; import fs from "fs";
import getLocalDataFile from "./getLocalDataFile"; import getLocalDataFile from "./getLocalDataFile";
import { app } from "electron";
import { PluginHandler } from "@/core"; import { PluginHandler } from "@/core";
import { PLUGIN_INSTALL_DIR as baseDir } from "@/common/constans/main";
const configPath = path.join(getLocalDataFile(), "./rubick-local-plugin.json"); const configPath = path.join(getLocalDataFile(), "./rubick-local-plugin.json");
const appPath = app.getPath("cache");
const baseDir = path.join(appPath, "./rubick-plugins");
const pluginInstance = new PluginHandler({ const pluginInstance = new PluginHandler({
baseDir: baseDir, baseDir,
}); });
global.LOCAL_PLUGINS = { global.LOCAL_PLUGINS = {
PLUGINS: [], PLUGINS: [],
async downloadPlugin(plugin) { async downloadPlugin(plugin) {
await pluginInstance.install([plugin.name]); console.log(plugin);
await pluginInstance.install([plugin.name], { isDev: plugin.isDev });
if (plugin.isDev) {
// 获取 dev 插件信息
const pluginPath = path.resolve(
baseDir,
"node_modules",
plugin.name
);
const pluginInfo = JSON.parse(
fs.readFileSync(path.join(pluginPath, "./package.json"), "utf8")
);
plugin = {
...plugin,
...pluginInfo,
};
}
console.log(plugin);
global.LOCAL_PLUGINS.addPlugin(plugin); global.LOCAL_PLUGINS.addPlugin(plugin);
return global.LOCAL_PLUGINS.PLUGINS; return global.LOCAL_PLUGINS.PLUGINS;
}, },
@ -60,7 +75,7 @@ global.LOCAL_PLUGINS = {
fs.writeFileSync(configPath, JSON.stringify(global.LOCAL_PLUGINS.PLUGINS)); fs.writeFileSync(configPath, JSON.stringify(global.LOCAL_PLUGINS.PLUGINS));
}, },
async deletePlugin(plugin) { async deletePlugin(plugin) {
await pluginInstance.uninstall([plugin.name]); await pluginInstance.uninstall([plugin.name], { isDev: plugin.isDev });
global.LOCAL_PLUGINS.PLUGINS = global.LOCAL_PLUGINS.PLUGINS.filter((p) => plugin.name !== p.name); global.LOCAL_PLUGINS.PLUGINS = global.LOCAL_PLUGINS.PLUGINS.filter((p) => plugin.name !== p.name);
fs.writeFileSync(configPath, JSON.stringify(global.LOCAL_PLUGINS.PLUGINS)); fs.writeFileSync(configPath, JSON.stringify(global.LOCAL_PLUGINS.PLUGINS));
return global.LOCAL_PLUGINS.PLUGINS; return global.LOCAL_PLUGINS.PLUGINS;

View File

@ -68,8 +68,8 @@ class AdapterHandler {
} }
// 安装并启动插件 // 安装并启动插件
async install(adapters: Array<string>) { async install(adapters: Array<string>, options: { isDev: boolean }) {
const installCmd = "install"; const installCmd = options.isDev ? "link" : "install";
// 安装 // 安装
await this.execCommand(installCmd, adapters); await this.execCommand(installCmd, adapters);
} }
@ -110,11 +110,13 @@ class AdapterHandler {
/** /**
* *
* @param {...string[]} adapters * @param {...string[]} adapters
* @param options
* @memberof AdapterHandler * @memberof AdapterHandler
*/ */
async uninstall(adapters: string[]) { async uninstall(adapters: string[], options: { isDev: boolean }) {
const installCmd = options.isDev ? "unlink" : "uninstall";
// 卸载插件 // 卸载插件
await this.execCommand("uninstall", adapters); await this.execCommand(installCmd, adapters);
} }
/** /**

View File

@ -22,11 +22,13 @@ const API: any = {
currentPlugin: null, currentPlugin: null,
DBKEY: "RUBICK_DB_DEFAULT", DBKEY: "RUBICK_DB_DEFAULT",
openPlugin({ plugin }, window) { openPlugin({ plugin }, window) {
if (API.currentPlugin && API.currentPlugin.name === plugin.name) return;
runnerInstance.removeView(window); runnerInstance.removeView(window);
runnerInstance.init(plugin, window); runnerInstance.init(plugin, window);
API.currentPlugin = plugin; API.currentPlugin = plugin;
}, },
removePlugin(e, window) { removePlugin(e, window) {
API.currentPlugin = null;
runnerInstance.removeView(window); runnerInstance.removeView(window);
}, },
hideMainWindow(arg, window) { hideMainWindow(arg, window) {

View File

@ -1,19 +1,15 @@
/* eslint-disable */ /* eslint-disable */
import path from "path"; import path from "path";
import {app} from "electron";
import fs from "fs"; import fs from "fs";
import { PLUGIN_INSTALL_DIR } from "@/common/constans/main";
const appPath = app.getPath("cache");
export default () => { export default () => {
// 读取所有插件 // 读取所有插件
const totalPlugins = global.LOCAL_PLUGINS.getLocalPlugins(); const totalPlugins = global.LOCAL_PLUGINS.getLocalPlugins();
let systemPlugins = totalPlugins.filter((plugin) => plugin.pluginType === "system"); let systemPlugins = totalPlugins.filter((plugin) => plugin.pluginType === "system");
const baseDir = path.join(appPath, "./rubick-plugins");
systemPlugins = systemPlugins.map((plugin) => { systemPlugins = systemPlugins.map((plugin) => {
const pluginPath = path.resolve( const pluginPath = path.resolve(
baseDir, PLUGIN_INSTALL_DIR,
"node_modules", "node_modules",
plugin.name plugin.name
); );

View File

@ -48,6 +48,7 @@ const props = defineProps({
}); });
const changeValue = (e) => { const changeValue = (e) => {
if (props.currentPlugin.name === "rubick-system-feature") return;
emit("onSearch", e); emit("onSearch", e);
}; };

View File

@ -6,13 +6,11 @@ import commonConst from "@/common/utils/commonConst";
import { execSync } from "child_process"; import { execSync } from "child_process";
import searchManager from "./search"; import searchManager from "./search";
import optionsManager from "./options"; import optionsManager from "./options";
import { PLUGIN_INSTALL_DIR as baseDir } from "@/common/constans/renderer";
const appPath = remote.app.getPath("cache");
const createPluginManager = (): any => { const createPluginManager = (): any => {
const baseDir = path.join(appPath, "./rubick-plugins");
const pluginInstance = new PluginHandler({ const pluginInstance = new PluginHandler({
baseDir: baseDir, baseDir,
}); });
const state: any = reactive({ const state: any = reactive({
@ -54,7 +52,6 @@ const createPluginManager = (): any => {
const { searchValue, onSearch, setSearchValue, placeholder } = searchManager(); const { searchValue, onSearch, setSearchValue, placeholder } = searchManager();
const { options } = optionsManager({ const { options } = optionsManager({
searchValue, searchValue,
baseDir,
appList, appList,
openPlugin, openPlugin,
currentPlugin: toRefs(state).currentPlugin, currentPlugin: toRefs(state).currentPlugin,

View File

@ -2,6 +2,7 @@ import { ref, toRaw, toRefs, watch } from "vue";
import throttle from "lodash.throttle"; import throttle from "lodash.throttle";
import { remote } from "electron"; import { remote } from "electron";
import path from "path"; import path from "path";
import { PLUGIN_INSTALL_DIR as baseDir } from "@/common/constans/renderer";
function searchKeyValues(lists, value) { function searchKeyValues(lists, value) {
return lists.filter((item) => { return lists.filter((item) => {
@ -12,7 +13,7 @@ function searchKeyValues(lists, value) {
}); });
} }
const optionsManager = ({ searchValue, baseDir, appList, openPlugin, currentPlugin }) => { const optionsManager = ({ searchValue, appList, openPlugin, currentPlugin }) => {
const optionsRef = ref([]); const optionsRef = ref([]);
watch(searchValue, () => search(searchValue.value)); watch(searchValue, () => search(searchValue.value));