refactor: impl
This commit is contained in:
parent
62780e3e56
commit
bda201ae81
|
|
@ -1,27 +1,17 @@
|
|||
import { type Component, For, Show, createEffect, on } from "solid-js";
|
||||
import { type Component, For, Show } from "solid-js";
|
||||
import type { CommanderEntry } from "./types";
|
||||
import { getResultClass } from "./hooks";
|
||||
|
||||
export interface CommanderEntriesProps {
|
||||
entries: () => CommanderEntry[];
|
||||
onCommandClick?: (command: string) => void;
|
||||
loading?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const CommanderEntries: Component<CommanderEntriesProps> = (props) => {
|
||||
let containerRef: HTMLDivElement | undefined;
|
||||
|
||||
// 当 entries 变化时自动滚动到底部
|
||||
createEffect(
|
||||
on(
|
||||
() => props.entries().length,
|
||||
() => {
|
||||
if (containerRef) {
|
||||
containerRef.scrollTop = containerRef.scrollHeight;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const handleCommandClick = (command: string) => {
|
||||
if (props.onCommandClick) {
|
||||
props.onCommandClick(command);
|
||||
|
|
@ -33,6 +23,12 @@ export const CommanderEntries: Component<CommanderEntriesProps> = (props) => {
|
|||
ref={containerRef}
|
||||
class="commander-entries flex-1 overflow-auto p-3 bg-white space-y-2"
|
||||
>
|
||||
<Show
|
||||
when={props.loading}
|
||||
fallback={
|
||||
<Show
|
||||
when={props.error}
|
||||
fallback={
|
||||
<Show
|
||||
when={props.entries().length > 0}
|
||||
fallback={
|
||||
|
|
@ -64,6 +60,19 @@ export const CommanderEntries: Component<CommanderEntriesProps> = (props) => {
|
|||
)}
|
||||
</For>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<div class="text-red-500 text-center py-8">{props.error}</div>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<div class="text-gray-500 flex items-center gap-2">
|
||||
<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-600"></div>
|
||||
<span>加载命令模板中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,20 +4,22 @@ import type {
|
|||
CompletionItem,
|
||||
TrackerItem,
|
||||
TrackerAttribute,
|
||||
TrackerCommand,
|
||||
TrackerViewMode,
|
||||
MdCommanderCommandMap,
|
||||
TrackerCommand,
|
||||
} from "../types";
|
||||
import { parseInput, getCompletions } from "./completions";
|
||||
import {
|
||||
addTrackerItem,
|
||||
getTrackerHistory,
|
||||
getTrackerItems,
|
||||
moveTrackerItem, removeTrackerClass,
|
||||
moveTrackerItem,
|
||||
removeTrackerClass,
|
||||
removeTrackerItem,
|
||||
updateTrackerAttribute,
|
||||
updateTrackerClasses
|
||||
updateTrackerClasses,
|
||||
} from "../stores";
|
||||
import { addEntry, getEntries, clearEntries } from "../stores/entriesStore";
|
||||
|
||||
// ==================== Commander Hook ====================
|
||||
|
||||
|
|
@ -29,7 +31,6 @@ export interface UseCommanderReturn {
|
|||
selectedCompletion: () => number;
|
||||
isFocused: () => boolean;
|
||||
setInputValue: (v: string) => void;
|
||||
setEntries: (updater: (prev: CommanderEntry[]) => CommanderEntry[]) => void;
|
||||
setShowCompletions: (v: boolean) => void;
|
||||
setSelectedCompletion: (v: number | ((prev: number) => number)) => void;
|
||||
setIsFocused: (v: boolean) => void;
|
||||
|
|
@ -62,7 +63,6 @@ export interface UseCommanderReturn {
|
|||
|
||||
export function useCommander(initialCommands?: MdCommanderCommandMap): UseCommanderReturn {
|
||||
const [inputValue, setInputValue] = createSignal("");
|
||||
const [entries, setEntries] = createSignal<CommanderEntry[]>([]);
|
||||
const [showCompletions, setShowCompletions] = createSignal(false);
|
||||
const [completions, setCompletions] = createSignal<CompletionItem[]>([]);
|
||||
const [selectedCompletion, setSelectedCompletionState] = createSignal(0);
|
||||
|
|
@ -72,6 +72,9 @@ export function useCommander(initialCommands?: MdCommanderCommandMap): UseComman
|
|||
const [viewMode, setViewMode] = createSignal<TrackerViewMode>("history");
|
||||
const [commands, setCommands] = createSignal<MdCommanderCommandMap>(initialCommands || {});
|
||||
|
||||
// 从 store 获取 entries
|
||||
const entries = getEntries;
|
||||
|
||||
// ==================== 命令执行 ====================
|
||||
|
||||
const handleCommand = () => {
|
||||
|
|
@ -108,14 +111,15 @@ export function useCommander(initialCommands?: MdCommanderCommandMap): UseComman
|
|||
timestamp: new Date(),
|
||||
};
|
||||
|
||||
setEntries((prev) => [...prev, newEntry]);
|
||||
// 使用 store 添加记录
|
||||
addEntry(newEntry);
|
||||
setCommandHistory((prev) => [...prev, input]);
|
||||
setHistoryIndex(-1);
|
||||
setInputValue("");
|
||||
setShowCompletions(false);
|
||||
|
||||
if (commandName === "clear") {
|
||||
setEntries([]);
|
||||
clearEntries();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -246,7 +250,6 @@ export function useCommander(initialCommands?: MdCommanderCommandMap): UseComman
|
|||
selectedCompletion,
|
||||
isFocused,
|
||||
setInputValue,
|
||||
setEntries,
|
||||
setShowCompletions,
|
||||
setSelectedCompletion,
|
||||
setIsFocused,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { customElement, noShadowDOM } from "solid-element";
|
||||
import { onMount, onCleanup, Show, createEffect, on } from "solid-js";
|
||||
import { onMount, onCleanup, Show, createResource } from "solid-js";
|
||||
import { useCommander } from "./hooks";
|
||||
import { CommanderInput } from "./CommanderInput";
|
||||
import { CommanderEntries } from "./CommanderEntries";
|
||||
|
|
@ -7,7 +7,7 @@ import { TrackerView } from "./TrackerView";
|
|||
import { TabBar } from "./TabBar";
|
||||
import type { MdCommanderProps } from "./types";
|
||||
import { loadElementSrc } from "../utils/path";
|
||||
import { initializeCommands, loadCommandTemplatesFromCSV } from "./stores";
|
||||
import { initializeCommands, loadCommandTemplatesFromCSV, getCommands, getCommandsLoading, getCommandsError } from "./stores";
|
||||
|
||||
customElement<MdCommanderProps>(
|
||||
"md-commander",
|
||||
|
|
@ -16,18 +16,28 @@ customElement<MdCommanderProps>(
|
|||
noShadowDOM();
|
||||
const { articlePath, rawSrc } = loadElementSrc(element as any);
|
||||
|
||||
// 初始化命令并注册到 store
|
||||
// 初始化命令
|
||||
const commands = initializeCommands(props.commands);
|
||||
const commander = useCommander(commands);
|
||||
|
||||
// 加载 CSV 模板
|
||||
createEffect( async () => {
|
||||
if (!rawSrc || !rawSrc) return;
|
||||
await loadCommandTemplatesFromCSV(rawSrc, articlePath);
|
||||
// 更新 commander 中的命令(从 store 获取)
|
||||
const {getCommands} = await import("./stores/commandsStore");
|
||||
commander.setCommands(getCommands());
|
||||
});
|
||||
// 使用 createResource 加载 CSV 模板
|
||||
const [templateData] = createResource(
|
||||
() => (rawSrc ? { path: rawSrc, articlePath } : null),
|
||||
async (paths) => {
|
||||
await loadCommandTemplatesFromCSV(paths.path, paths.articlePath);
|
||||
return getCommands();
|
||||
}
|
||||
);
|
||||
|
||||
// 当模板加载完成后更新 commander 中的命令
|
||||
createResource(
|
||||
() => templateData(),
|
||||
(loadedCommands) => {
|
||||
if (loadedCommands) {
|
||||
commander.setCommands(loadedCommands);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (commander.showCompletions() && commander.completions().length > 0) {
|
||||
|
|
@ -114,6 +124,8 @@ customElement<MdCommanderProps>(
|
|||
>
|
||||
<CommanderEntries
|
||||
entries={commander.entries}
|
||||
loading={getCommandsLoading()}
|
||||
error={getCommandsError()}
|
||||
onCommandClick={(cmd) => commander.setInputValue(cmd)}
|
||||
/>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -13,12 +13,17 @@ const defaultCommands: MdCommanderCommandMap = {
|
|||
list: listTrackCommand,
|
||||
};
|
||||
|
||||
const [commandsStore, setCommandsStore] = createStore<{
|
||||
export interface CommandsStoreState {
|
||||
commands: MdCommanderCommandMap;
|
||||
initialized: boolean;
|
||||
}>({
|
||||
loading: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const [commandsStore, setCommandsStore] = createStore<CommandsStoreState>({
|
||||
commands: { ...defaultCommands },
|
||||
initialized: false,
|
||||
loading: false,
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -36,7 +41,7 @@ export function initializeCommands(customCommands?: MdCommanderCommandMap): MdCo
|
|||
*/
|
||||
export function registerCommands(customCommands?: MdCommanderCommandMap): void {
|
||||
const commands = initializeCommands(customCommands);
|
||||
setCommandsStore({ commands, initialized: true });
|
||||
setCommandsStore({ commands, initialized: true, loading: false });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -60,6 +65,41 @@ export function getCommand(name: string): MdCommanderCommand | undefined {
|
|||
return commandsStore.commands[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加载状态
|
||||
*/
|
||||
export function getCommandsLoading(): boolean {
|
||||
return commandsStore.loading;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
*/
|
||||
export function getCommandsError(): string | undefined {
|
||||
return commandsStore.error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置加载状态
|
||||
*/
|
||||
export function setCommandsLoading(loading: boolean): void {
|
||||
setCommandsStore("loading", loading);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误信息
|
||||
*/
|
||||
export function setCommandsError(error?: string): void {
|
||||
setCommandsStore("error", error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新命令
|
||||
*/
|
||||
export function updateCommands(updater: (prev: MdCommanderCommandMap) => MdCommanderCommandMap): void {
|
||||
setCommandsStore("commands", (prev) => updater(prev));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 CSV 文件加载命令模板并更新命令定义
|
||||
*/
|
||||
|
|
@ -67,6 +107,9 @@ export async function loadCommandTemplatesFromCSV(
|
|||
path: string,
|
||||
articlePath: string
|
||||
): Promise<void> {
|
||||
setCommandsLoading(true);
|
||||
setCommandsError(undefined);
|
||||
|
||||
try {
|
||||
const csv = await loadCSV<CommandTemplateRow>(resolvePath(articlePath, path));
|
||||
|
||||
|
|
@ -82,7 +125,7 @@ export async function loadCommandTemplatesFromCSV(
|
|||
}
|
||||
|
||||
// 为每个命令添加模板
|
||||
setCommandsStore("commands", (prev) => {
|
||||
updateCommands((prev) => {
|
||||
const updated = { ...prev };
|
||||
for (const [commandName, rows] of templatesByCommand.entries()) {
|
||||
const cmd = updated[commandName];
|
||||
|
|
@ -105,8 +148,14 @@ export async function loadCommandTemplatesFromCSV(
|
|||
}
|
||||
return updated;
|
||||
});
|
||||
|
||||
setCommandsStore("initialized", true);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
setCommandsError(`加载命令模板失败:${errorMessage}`);
|
||||
console.warn(`Error loading command templates from ${path}:`, error);
|
||||
} finally {
|
||||
setCommandsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
import { createStore } from "solid-js/store";
|
||||
import type { CommanderEntry } from "../types";
|
||||
|
||||
export interface EntriesStore {
|
||||
entries: CommanderEntry[];
|
||||
}
|
||||
|
||||
const [entriesStore, setEntriesStore] = createStore<EntriesStore>({
|
||||
entries: [],
|
||||
});
|
||||
|
||||
/**
|
||||
* 添加命令执行记录
|
||||
*/
|
||||
export function addEntry(entry: CommanderEntry): void {
|
||||
setEntriesStore("entries", (prev) => [...prev, entry]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有记录
|
||||
*/
|
||||
export function clearEntries(): void {
|
||||
setEntriesStore("entries", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有记录
|
||||
*/
|
||||
export function getEntries(): CommanderEntry[] {
|
||||
return entriesStore.entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置记录
|
||||
*/
|
||||
export function setEntries(entries: CommanderEntry[]): void {
|
||||
setEntriesStore("entries", entries);
|
||||
}
|
||||
|
|
@ -20,4 +20,18 @@ export {
|
|||
isCommandsInitialized,
|
||||
getCommand,
|
||||
loadCommandTemplatesFromCSV,
|
||||
getCommandsLoading,
|
||||
getCommandsError,
|
||||
setCommandsLoading,
|
||||
setCommandsError,
|
||||
updateCommands,
|
||||
} from "./commandsStore";
|
||||
export type { CommandsStoreState } from "./commandsStore";
|
||||
|
||||
export {
|
||||
addEntry,
|
||||
clearEntries,
|
||||
getEntries,
|
||||
setEntries,
|
||||
} from "./entriesStore";
|
||||
export type { EntriesStore } from "./entriesStore";
|
||||
|
|
|
|||
Loading…
Reference in New Issue