2026-02-28 16:28:07 +08:00
|
|
|
import { customElement, noShadowDOM } from "solid-element";
|
2026-03-01 09:36:09 +08:00
|
|
|
import { onMount, onCleanup, createSignal, Show } from "solid-js";
|
2026-02-28 17:44:38 +08:00
|
|
|
import { useCommander } from "./hooks";
|
2026-02-28 16:28:07 +08:00
|
|
|
import { CommanderInput } from "./CommanderInput";
|
|
|
|
|
import { CommanderEntries } from "./CommanderEntries";
|
2026-03-01 09:36:09 +08:00
|
|
|
import { TrackerView } from "./TrackerView";
|
|
|
|
|
import { TabBar } from "./TabBar";
|
|
|
|
|
import { AttributeEditor } from "./AttributeEditor";
|
|
|
|
|
import type { MdCommanderProps, TrackerAttribute } from "./types";
|
2026-02-28 16:28:07 +08:00
|
|
|
|
|
|
|
|
customElement<MdCommanderProps>(
|
|
|
|
|
"md-commander",
|
|
|
|
|
{ placeholder: "", class: "", height: "" },
|
|
|
|
|
(props, { element }) => {
|
|
|
|
|
noShadowDOM();
|
|
|
|
|
|
2026-03-01 09:49:01 +08:00
|
|
|
const commander = useCommander(props.commands);
|
2026-03-01 09:36:09 +08:00
|
|
|
const [editingAttr, setEditingAttr] = createSignal<{
|
2026-03-01 10:01:53 +08:00
|
|
|
index: number;
|
2026-03-01 09:36:09 +08:00
|
|
|
attrName: string;
|
|
|
|
|
attr: TrackerAttribute;
|
|
|
|
|
} | null>(null);
|
2026-02-28 16:28:07 +08:00
|
|
|
|
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
|
|
|
if (commander.showCompletions() && commander.completions().length > 0) {
|
|
|
|
|
if (e.key === "ArrowDown") {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
commander.setSelectedCompletion(
|
|
|
|
|
(prev) => (prev + 1) % commander.completions().length,
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (e.key === "ArrowUp") {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
commander.setSelectedCompletion(
|
|
|
|
|
(prev) => (prev - 1 + commander.completions().length) % commander.completions().length,
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (e.key === "Tab") {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
commander.acceptCompletion();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (e.key === "Escape") {
|
2026-03-01 09:36:09 +08:00
|
|
|
if (editingAttr()) {
|
|
|
|
|
setEditingAttr(null);
|
|
|
|
|
} else {
|
|
|
|
|
commander.setShowCompletions(false);
|
|
|
|
|
}
|
2026-02-28 16:28:07 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 17:15:41 +08:00
|
|
|
// 补全未打开时,使用上下键浏览历史
|
|
|
|
|
if (e.key === "ArrowUp" && !commander.showCompletions()) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
commander.navigateHistory("up");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (e.key === "ArrowDown" && !commander.showCompletions()) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
commander.navigateHistory("down");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 16:28:07 +08:00
|
|
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
commander.handleCommand();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const heightStyle = () => props.height || "400px";
|
|
|
|
|
|
|
|
|
|
onMount(() => {
|
|
|
|
|
const handleClickOutside = (e: MouseEvent) => {
|
|
|
|
|
if (!element?.contains(e.target as Node)) {
|
|
|
|
|
commander.setShowCompletions(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
document.addEventListener("click", handleClickOutside);
|
|
|
|
|
onCleanup(() => document.removeEventListener("click", handleClickOutside));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
2026-02-28 16:49:02 +08:00
|
|
|
class={`md-commander flex flex-col border border-gray-300 rounded-lg overflow-hidden ${props.class || ""}`}
|
2026-02-28 16:28:07 +08:00
|
|
|
style={{ height: heightStyle() }}
|
|
|
|
|
>
|
2026-03-01 09:36:09 +08:00
|
|
|
{/* 标签页导航 */}
|
|
|
|
|
<TabBar
|
|
|
|
|
mode={commander.viewMode}
|
|
|
|
|
onModeChange={commander.setViewMode}
|
2026-02-28 16:49:02 +08:00
|
|
|
/>
|
2026-02-28 16:30:58 +08:00
|
|
|
|
2026-03-01 09:36:09 +08:00
|
|
|
{/* 内容区域:历史或追踪 */}
|
|
|
|
|
<Show
|
|
|
|
|
when={commander.viewMode() === "history"}
|
|
|
|
|
fallback={
|
|
|
|
|
<TrackerView
|
|
|
|
|
items={commander.trackerItems}
|
2026-03-01 10:01:53 +08:00
|
|
|
onEditAttribute={(index, attrName, attr) =>
|
|
|
|
|
setEditingAttr({ index, attrName, attr })
|
2026-03-01 09:36:09 +08:00
|
|
|
}
|
2026-03-01 10:01:53 +08:00
|
|
|
onRemoveClass={commander.removeTrackerItemClassByIndex}
|
|
|
|
|
onMoveUp={(index) => commander.moveTrackerItemByIndex(index, "up")}
|
|
|
|
|
onMoveDown={(index) => commander.moveTrackerItemByIndex(index, "down")}
|
|
|
|
|
onRemove={(index) => commander.removeTrackerItemByIndex(index)}
|
2026-03-01 09:36:09 +08:00
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<CommanderEntries
|
|
|
|
|
entries={commander.entries}
|
|
|
|
|
onCommandClick={(cmd) => commander.setInputValue(cmd)}
|
|
|
|
|
/>
|
|
|
|
|
</Show>
|
|
|
|
|
|
2026-02-28 16:28:07 +08:00
|
|
|
{/* 命令输入框 */}
|
2026-02-28 16:49:02 +08:00
|
|
|
<div class="relative border-t border-gray-300">
|
2026-02-28 16:28:07 +08:00
|
|
|
<CommanderInput
|
|
|
|
|
placeholder={props.placeholder}
|
|
|
|
|
inputValue={commander.inputValue}
|
|
|
|
|
onInput={(e) => {
|
|
|
|
|
commander.setInputValue((e.target as HTMLInputElement).value);
|
|
|
|
|
commander.updateCompletions();
|
|
|
|
|
}}
|
|
|
|
|
onKeyDown={handleKeyDown}
|
|
|
|
|
onFocus={() => {
|
|
|
|
|
commander.setIsFocused(true);
|
|
|
|
|
commander.updateCompletions();
|
|
|
|
|
}}
|
|
|
|
|
onBlur={() => commander.setIsFocused(false)}
|
|
|
|
|
onSubmit={commander.handleCommand}
|
|
|
|
|
showCompletions={commander.showCompletions}
|
|
|
|
|
completions={commander.completions}
|
|
|
|
|
selectedCompletion={commander.selectedCompletion}
|
|
|
|
|
onSelectCompletion={commander.setSelectedCompletion}
|
|
|
|
|
onAcceptCompletion={commander.acceptCompletion}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-03-01 09:36:09 +08:00
|
|
|
|
|
|
|
|
{/* 属性编辑器弹窗 */}
|
|
|
|
|
<Show when={editingAttr()}>
|
|
|
|
|
<AttributeEditor
|
|
|
|
|
attribute={() => editingAttr()!.attr}
|
|
|
|
|
onUpdate={(attr) => {
|
|
|
|
|
const data = editingAttr();
|
|
|
|
|
if (data) {
|
2026-03-01 10:01:53 +08:00
|
|
|
commander.updateTrackerAttributeByIndex(data.index, data.attrName, attr);
|
2026-03-01 09:36:09 +08:00
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
onClose={() => setEditingAttr(null)}
|
|
|
|
|
/>
|
|
|
|
|
</Show>
|
2026-02-28 16:28:07 +08:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|