diff --git a/src/components/CommandEditor.vue b/src/components/CommandEditor.vue index 46af740..81e818b 100644 --- a/src/components/CommandEditor.vue +++ b/src/components/CommandEditor.vue @@ -4,9 +4,9 @@ ref="sidebar" :canCommandSave="canCommandSave" :quickcommandInfo="quickcommandInfo" + :allQuickCommandTags="allQuickCommandTags" class="absolute-left shadow-1" :style="{ - width: sideBarWidth + 'px', zIndex: 1, transform: isFullscreen ? 'translateX(-100%)' : 'translateX(0)', transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)', @@ -64,7 +64,11 @@ - + @@ -78,6 +82,7 @@ import CommandLanguageBar from "components/editor/CommandLanguageBar"; import EditorTools from "components/editor/EditorTools"; import CommandRunResult from "components/CommandRunResult"; import CommandComposer from "components/composer/CommandComposer.vue"; +import programs from "js/options/programs.js"; // 预加载 MonacoEditor const MonacoEditorPromise = import("components/editor/MonacoEditor"); @@ -109,13 +114,22 @@ export default { }, data() { return { - programLanguages: Object.keys(this.$root.programs), + programLanguages: Object.keys(programs), sideBarWidth: 200, languageBarHeight: 40, showComposer: false, isRunCodePage: this.action.type === "run", canCommandSave: this.action.type !== "run", showSidebar: this.action.type !== "run", + flows: [ + { + id: "main", + name: "main", + label: "主流程", + commands: [], + customVariables: [], + }, + ], quickcommandInfo: { program: "quickcommand", cmd: "", @@ -141,15 +155,9 @@ export default { required: true, }, allQuickCommandTags: Array, - isLeaving: { - type: Boolean, - default: false, - }, - }, - created() { - this.commandInit(); }, mounted() { + this.commandInit(); this.sidebarInit(); }, computed: { @@ -196,7 +204,7 @@ export default { // 匹配编程语言 matchLanguage() { if (!this.quickcommandInfo.customOptions.ext) return; - let language = Object.values(this.$root.programs).filter( + let language = Object.values(programs).filter( (program) => program.ext === this.quickcommandInfo.customOptions.ext ); if (language.length) { @@ -205,7 +213,7 @@ export default { }, // 设置编程语言 setLanguage(language) { - let highlight = this.$root.programs[language].highlight; + let highlight = programs[language].highlight; this.$refs.editor.setEditorLanguage(highlight ? highlight : language); }, insertText(text) { @@ -216,14 +224,16 @@ export default { this.$refs.editor.setEditorValue(text); this.$refs.editor.formatDocument(); }, - handleComposer({ type, code }) { - switch (type) { + handleComposerAction(actionType, actionData) { + switch (actionType) { case "run": - return this.runCurrentCommand(code); + return this.runCurrentCommand(actionData); case "insert": - return this.insertText(code); + return this.insertText(actionData); case "apply": - return this.replaceText(code); + return this.replaceText(actionData); + case "close": + return this.showComposer = false; } }, // 保存 diff --git a/src/components/CommandRunResult.vue b/src/components/CommandRunResult.vue index de82142..19f8428 100644 --- a/src/components/CommandRunResult.vue +++ b/src/components/CommandRunResult.vue @@ -75,6 +75,8 @@ import specialVars from "js/options/specialVars.js"; import commandTypes from "js/options/commandTypes.js"; import ResultArea from "components/ResultArea.vue"; import ResultMenu from "components/popup/ResultMenu.vue"; +import { generateFlowsCode } from "js/composer/generateCode"; +import { getValidCommand } from "js/commandManager"; export default { components: { ResultArea, ResultMenu }, @@ -127,11 +129,20 @@ export default { methods: { // 运行命令 async runCurrentCommand(currentCommand) { + let command = window.lodashM.cloneDeep(currentCommand); + // 如果是composer命令,则动态生成cmd + if (command.program === "quickcomposer") { + command.cmd = generateFlowsCode(command.flows); + } this.$root.isRunningCommand = true; - await this.getTempPayload(currentCommand); - if (currentCommand.cmd.includes("{{subinput")) - return this.setSubInput(currentCommand); - this.fire(currentCommand); + try { + await this.getTempPayload(command); + } catch (error) { + return quickcommand.showMessageBox(error.toString(), "error"); + } + // 如果命令包含子输入框,则设置子输入框 + if (command.cmd.includes("{{subinput")) return this.setSubInput(command); + this.fire(command); }, async fire(currentCommand) { currentCommand.cmd = this.assignSpecialVars(currentCommand.cmd); @@ -147,6 +158,7 @@ export default { let resultOpts = { outPlugin, action, earlyExit }; switch (currentCommand.program) { case "quickcommand": + case "quickcomposer": window.runCodeInSandbox( currentCommand.cmd, (stdout, stderr) => this.handleResult(stdout, stderr, resultOpts), @@ -278,11 +290,15 @@ export default { // payload 临时赋值 async getTempPayload(currentCommand) { if (!this.needTempPayload) return; - let type = - currentCommand.cmdType || currentCommand.features?.cmds[0].type; + currentCommand = getValidCommand(currentCommand); + const firstCmd = currentCommand.features.cmds[0]; + const type = firstCmd.type || "text"; this.$root.enterData = { - type: type || "text", - payload: await commandTypes[type]?.tempPayload?.(), + type, + payload: + type === "text" + ? firstCmd + : (await commandTypes[type]?.tempPayload?.()) || {}, }; }, handleResult(stdout, stderr, options) { diff --git a/src/components/ComposerEditor.vue b/src/components/ComposerEditor.vue index 19b7cdb..6cdfc8a 100644 --- a/src/components/ComposerEditor.vue +++ b/src/components/ComposerEditor.vue @@ -1,8 +1,9 @@ @@ -12,29 +13,131 @@ + + diff --git a/src/components/editor/MatchRuleEditor.vue b/src/components/editor/MatchRuleEditor.vue new file mode 100644 index 0000000..c1d97e7 --- /dev/null +++ b/src/components/editor/MatchRuleEditor.vue @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ commandTypes[rule.type].label }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 无需配置 + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/css/composer.css b/src/css/composer.css index e8b5c14..997e72f 100644 --- a/src/css/composer.css +++ b/src/css/composer.css @@ -25,6 +25,10 @@ font-size: 12px; } +.command-composer .q-field--filled.q-select--with-chips .q-field__control .q-chip { + margin: 0 4px; +} + /* 输入框图标大小 */ .command-composer .q-field--filled .q-field__control .q-icon { font-size: 18px; diff --git a/src/js/autoDetach.js b/src/js/autoDetach.js deleted file mode 100644 index 5ed5116..0000000 --- a/src/js/autoDetach.js +++ /dev/null @@ -1,66 +0,0 @@ -const winScpt = `Add-Type -TypeDefinition @" -using System; -using System.Runtime.InteropServices; -public class Win32 { - [StructLayout(LayoutKind.Sequential)] - public struct RECT { - public int Left; - public int Top; - public int Right; - public int Bottom; - } - [DllImport("user32.dll", SetLastError=true)] - public static extern IntPtr GetForegroundWindow(); - [DllImport("user32.dll", SetLastError=true)] - public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); -} -"@ -$foregroundWindow = [Win32]::GetForegroundWindow() -$windowRect = New-Object Win32+RECT -$_ = [Win32]::GetWindowRect($foregroundWindow, [ref]$windowRect) -$result = New-Object PSObject -$result | Add-Member -Type NoteProperty -Name Left -Value $windowRect.Left -$result | Add-Member -Type NoteProperty -Name Top -Value $windowRect.Top -$result | Add-Member -Type NoteProperty -Name Right -Value $windowRect.Right -$result | Add-Member -Type NoteProperty -Name Bottom -Value $windowRect.Bottom -$result | ConvertTo-Json`; - -const macScpt = `tell application "System Events" - set frontmostProcess to first application process where it is frontmost - set frontmostWindow to first window of frontmostProcess - set {windowLeft, windowTop} to position of frontmostWindow - set {windowWidth, windowHeight} to size of frontmostWindow - set windowRight to windowLeft + windowWidth - set windowBottom to windowTop + windowHeight -end tell -return "{ \\"Left\\": " & windowLeft & ", \\"Top\\": " & windowTop & ", \\"Right\\": " & windowRight & ", \\"Bottom\\": " &windowBottom & " }"`; - -const getForegroundWindowPos = async () => { - let foregroundWindowPos; - try { - if (window.utools.isWindows()) { - foregroundWindowPos = await window.quickcommand.runPowerShell(winScpt); - } else if (window.utools.isMacOS()) { - foregroundWindowPos = await window.quickcommand.runAppleScript(macScpt); - } - } catch (error) { - console.log(error); - } - if (!foregroundWindowPos) return; - return JSON.parse(foregroundWindowPos); -}; - -let autoDetach = async () => { - const foregroundWindowPos = await getForegroundWindowPos(); - console.log(foregroundWindowPos); - if (foregroundWindowPos) { - const { Left, Top, Right, Bottom } = foregroundWindowPos; - let { x, y } = window.utools.getCursorScreenPoint(); - window.utools.simulateMouseDoubleClick(Left + 200, Top + 30); - window.utools.simulateMouseMove(x, y); - } -}; - -export default { - autoDetach, -}; diff --git a/src/js/commandManager.js b/src/js/commandManager.js index 025df16..e2e4f5b 100644 --- a/src/js/commandManager.js +++ b/src/js/commandManager.js @@ -2,6 +2,8 @@ import { reactive } from "vue"; import quickcommandParser from "js/common/quickcommandParser.js"; import importAll from "js/common/importAll.js"; import utoolsFull from "js/utools.js"; +import { getUniqueId } from "js/common/uuid.js"; +import outputTypes from "js/options/outputTypes.js"; // 默认命令 const defaultCommands = importAll( @@ -16,6 +18,50 @@ const state = reactive({ activatedQuickPanels: [], }); +const getCmdType = (cmds) => { + const firstCmdType = cmds[0].type || "key"; + if (!cmds.find((x) => typeof x !== "string")) return "key"; + if (!cmds.find((x) => x.type !== firstCmdType)) return firstCmdType; + return "professional"; +}; + +const getFeatureCode = (cmds) => { + return `${getCmdType(cmds)}_${getUniqueId({ short: true })}`; +}; + +const getLabeledCmds = (cmds, explain) => { + return cmds.map((cmd) => { + if (typeof cmd === "string") { + return cmd || explain; + } + return { + ...cmd, + label: cmd.label || explain, + }; + }); +}; + +export const getValidCommand = (command) => { + const { cmds, explain } = command.features; + if (!explain) throw "名称不能为空"; + if (!Array.isArray(cmds)) throw "匹配规则格式错误"; + + // 未配置label或关键字时,直接使用名称 + command.features.cmds = getLabeledCmds(cmds, explain); + + // 不需要显示输入框的输入类型,添加mainHide属性 + if (outputTypes[command.output].outPlugin) { + command.features.mainHide = true; + } + + // 生成唯一code + if (!command.features.code) { + command.features.code = getFeatureCode(cmds); + } + + return window.lodashM.cloneDeep(command); +}; + // 使用函数工厂模式,确保每个组件获取自己的状态副本 export function useCommandManager() { // 获取已启用的命令 @@ -59,6 +105,11 @@ export function useCommandManager() { // 保存命令 const saveCommand = (command) => { + try { + command = getValidCommand(command); + } catch (e) { + return quickcommand.showMessageBox(e.toString(), "error"); + } const code = command.features.code; state.allQuickCommands[code] = command; @@ -70,7 +121,7 @@ export function useCommandManager() { utoolsFull.whole.setFeature(command.features); if (!isDefaultCommand(code)) { - utoolsFull.putDB(window.lodashM.cloneDeep(command), "qc_" + code); + utoolsFull.putDB(command, "qc_" + code); } getAllQuickCommandTags(); diff --git a/src/js/common/uuid.js b/src/js/common/uuid.js index 84ef06c..37ef469 100644 --- a/src/js/common/uuid.js +++ b/src/js/common/uuid.js @@ -1,7 +1,9 @@ -export const getUniqueId = () => { - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { +export const getUniqueId = (options = {}) => { + const { short = false } = options; + const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); + return short ? uuid.substring(0, 8) : uuid; }; diff --git a/src/js/composer/generateCode.js b/src/js/composer/generateCode.js index e3f73e5..b029292 100644 --- a/src/js/composer/generateCode.js +++ b/src/js/composer/generateCode.js @@ -133,3 +133,10 @@ export function generateCode(flow) { return finalCode; } + +export function generateFlowsCode(flows) { + const [mainFlow, ...subFlows] = flows; + return [...subFlows, mainFlow] + .map((flow) => generateCode(flow)) + .join("\n\n"); +} diff --git a/src/js/options/commandTypes.js b/src/js/options/commandTypes.js index 85efabc..ae8c8d1 100644 --- a/src/js/options/commandTypes.js +++ b/src/js/options/commandTypes.js @@ -3,7 +3,8 @@ */ const jsonSample = [ - "关键词", + "关键词1", + "关键词2", { type: "img", label: "图片匹配", @@ -41,12 +42,12 @@ const jsonSample = [ }, ]; -const commandTypes = { +export default { key: { name: "key", label: "关键词", icon: "font_download", - color: "teal", + color: "blue", matchLabel: "关键词", desc: "直接在主输入框输入对应关键字,最通用的一种模式,关键字可以设置多个", valueType: "array", @@ -57,7 +58,7 @@ const commandTypes = { }, regex: { name: "regex", - label: "正则/划词", + label: "正则", icon: "rule", color: "cyan", matchLabel: "正则", @@ -82,7 +83,7 @@ const commandTypes = { over: { name: "over", label: "所有文本", - matchLabel: "无需设置", + matchLabel: "无需配置", icon: "emergency", color: "light-green", desc: "匹配主输入框的所有文本,但只有在该文本未设置对应的插件或功能时才生效", @@ -104,7 +105,7 @@ const commandTypes = { }, window: { name: "window", - label: "窗口/进程", + label: "窗口", matchLabel: "进程名", icon: "widgets", color: "indigo", @@ -154,7 +155,7 @@ const commandTypes = { }, files: { name: "files", - label: "复制/选中文件", + label: "文件", matchLabel: "正则", icon: "description", color: "light-blue", @@ -200,5 +201,3 @@ const commandTypes = { jsonSample: jsonSample, }, }; - -export default commandTypes; diff --git a/src/pages/CommandPage.vue b/src/pages/CommandPage.vue index 3a8ecee..fc2de94 100644 --- a/src/pages/CommandPage.vue +++ b/src/pages/CommandPage.vue @@ -27,7 +27,7 @@ export default { }, methods: { runCurrentCommand(command) { - this.$refs.result.runCurrentCommand(window.lodashM.cloneDeep(command)); + this.$refs.result.runCurrentCommand(command); }, }, }; diff --git a/src/pages/ConfigurationPage.vue b/src/pages/ConfigurationPage.vue index 5e24085..b30d181 100644 --- a/src/pages/ConfigurationPage.vue +++ b/src/pages/ConfigurationPage.vue @@ -221,9 +221,7 @@ export default { } }, runCommand(code) { - this.$refs.result.runCurrentCommand( - window.lodashM.cloneDeep(this.allQuickCommands[code]) - ); + this.$refs.result.runCurrentCommand(this.allQuickCommands[code]); }, // 启用命令 enableCommand(code) { @@ -241,13 +239,16 @@ export default { } }, // 编辑命令 - editCommand(command) { + editCommand(commandOrCode) { // 即可传入 code,也可直接传入 command - if (typeof command === "string") command = this.allQuickCommands[command]; + const command = + typeof commandOrCode === "string" + ? this.allQuickCommands[commandOrCode] + : commandOrCode; this.commandEditorAction = { type: "edit", data: window.lodashM.cloneDeep(command), - component: "CommandEditor", + component: command.flows ? "ComposerEditor" : "CommandEditor", }; this.isEditorShow = true; }, @@ -349,6 +350,7 @@ export default { saveCommand(command) { const code = this.commandManager.saveCommand(command); this.locateToCommand(command.tags, code); + quickcommand.showMessageBox("保存成功!"); }, editorEvent(event) { switch (event.type) { @@ -394,7 +396,7 @@ export default { // 更新存储 this.commandManager.state.allQuickCommands = { - ...this.commandManager.state.allQuickCommands, + ...this.allQuickCommands, ...tagCommands, };