feat: edit entry classes
This commit is contained in:
parent
80a6b4526c
commit
abaee79198
|
|
@ -4,18 +4,21 @@ import type { TrackerAttribute } from "./types";
|
|||
export interface AttributeTooltipProps {
|
||||
position: { x: number; y: number };
|
||||
attributes: Record<string, TrackerAttribute>;
|
||||
classes: string[];
|
||||
onUpdate: (attrName: string, attr: TrackerAttribute) => void;
|
||||
onClassesChange: (classes: string[]) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const AttributeTooltip: Component<AttributeTooltipProps> = (props) => {
|
||||
const [position, setPosition] = createSignal(props.position);
|
||||
const [newClass, setNewClass] = createSignal("");
|
||||
|
||||
// 点击外部关闭
|
||||
createEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!target.closest(".attribute-tooltip")) {
|
||||
if (target.closest('body') && !target.closest(".attribute-tooltip")) {
|
||||
props.onClose();
|
||||
}
|
||||
};
|
||||
|
|
@ -29,6 +32,22 @@ export const AttributeTooltip: Component<AttributeTooltipProps> = (props) => {
|
|||
props.onUpdate(attrName, { ...attr, value });
|
||||
};
|
||||
|
||||
// 添加 class
|
||||
const handleAddClass = () => {
|
||||
const className = newClass().trim();
|
||||
if (className && !props.classes.includes(className)) {
|
||||
props.onClassesChange([...props.classes, className]);
|
||||
setNewClass("");
|
||||
}
|
||||
};
|
||||
|
||||
// 移除 class
|
||||
const handleRemoveClass = (e: MouseEvent, className: string) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
props.onClassesChange(props.classes.filter((c) => c !== className));
|
||||
};
|
||||
|
||||
// 渲染不同类型的属性编辑器
|
||||
const renderEditor = (attrName: string, attr: TrackerAttribute) => {
|
||||
if (attr.type === "progress") {
|
||||
|
|
@ -125,6 +144,49 @@ export const AttributeTooltip: Component<AttributeTooltipProps> = (props) => {
|
|||
</button>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
{/* Class 列表编辑器 */}
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-xs text-gray-500 font-medium">Classes</label>
|
||||
<div class="flex flex-wrap gap-1 mb-1">
|
||||
<For each={props.classes}>
|
||||
{(className) => (
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs">
|
||||
.{className}
|
||||
<button
|
||||
class="text-blue-400 hover:text-blue-600 font-bold leading-none"
|
||||
onClick={(e) => handleRemoveClass(e, className)}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<input
|
||||
type="text"
|
||||
value={newClass()}
|
||||
onInput={(e) => setNewClass((e.target as HTMLInputElement).value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleAddClass();
|
||||
}
|
||||
}}
|
||||
placeholder="添加 class..."
|
||||
class="flex-1 px-2 py-1 border border-gray-300 rounded text-sm"
|
||||
/>
|
||||
<button
|
||||
class="px-2 py-1 bg-gray-200 hover:bg-gray-300 rounded text-sm font-medium transition-colors"
|
||||
onClick={handleAddClass}
|
||||
>
|
||||
添加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 属性编辑器 */}
|
||||
<For each={Object.entries(props.attributes)}>
|
||||
{([name, attr]) => (
|
||||
<div class="flex flex-col gap-1">
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { AttributeTooltip } from "./AttributeTooltip";
|
|||
export interface TrackerViewProps {
|
||||
items: () => TrackerItem[];
|
||||
onEditAttribute?: (index: number, attrName: string, attr: TrackerAttribute) => void;
|
||||
onClassesChange?: (index: number, classes: string[]) => void;
|
||||
onRemoveClass?: (index: number, className: string) => void;
|
||||
onMoveUp?: (index: number) => void;
|
||||
onMoveDown?: (index: number) => void;
|
||||
|
|
@ -53,6 +54,13 @@ export const TrackerView: Component<TrackerViewProps> = (props) => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleClassesChange = (classes: string[]) => {
|
||||
const data = editingItem();
|
||||
if (data) {
|
||||
props.onClassesChange?.(data.index, classes);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="tracker-view flex-1 overflow-auto p-3 bg-white">
|
||||
<Show
|
||||
|
|
@ -132,7 +140,9 @@ export const TrackerView: Component<TrackerViewProps> = (props) => {
|
|||
<AttributeTooltip
|
||||
position={editingItem()!.position}
|
||||
attributes={props.items()[editingItem()!.index]?.attributes || {}}
|
||||
classes={props.items()[editingItem()!.index]?.classes || []}
|
||||
onUpdate={handleUpdateAttribute}
|
||||
onClassesChange={handleClassesChange}
|
||||
onClose={() => setEditingItem(null)}
|
||||
/>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
addTrackerItem as addTracker,
|
||||
removeTrackerItem as removeTracker,
|
||||
updateTrackerAttribute as updateTrackerAttr,
|
||||
updateTrackerClasses as updateTrackerClassesStore,
|
||||
moveTrackerItem as moveTracker,
|
||||
removeTrackerClass as removeClassFromTracker,
|
||||
getTrackerItems,
|
||||
|
|
@ -255,6 +256,7 @@ export interface UseCommanderReturn {
|
|||
removeTrackerItemByIndex: (index: number) => void;
|
||||
updateTrackerAttribute: (emmet: string, attrName: string, attr: TrackerAttribute) => boolean;
|
||||
updateTrackerAttributeByIndex: (index: number, attrName: string, attr: TrackerAttribute) => void;
|
||||
updateTrackerClassesByIndex: (index: number, classes: string[]) => void;
|
||||
moveTrackerItem: (emmet: string, direction: 'up' | 'down') => boolean;
|
||||
moveTrackerItemByIndex: (index: number, direction: 'up' | 'down') => void;
|
||||
removeTrackerItemClass: (emmet: string, className: string) => boolean;
|
||||
|
|
@ -462,6 +464,15 @@ export function useCommander(
|
|||
}
|
||||
};
|
||||
|
||||
const updateTrackerClassesByIndex = (index: number, classes: string[]) => {
|
||||
const items = trackerItems();
|
||||
if (index >= 0 && index < items.length) {
|
||||
const item = items[index];
|
||||
const emmet = `${item.tag}${item.id ? '#' + item.id : ''}${item.classes.length > 0 ? '.' + item.classes.join('.') : ''}`;
|
||||
updateTrackerClassesStore(emmet, classes);
|
||||
}
|
||||
};
|
||||
|
||||
const moveTrackerItem = (emmet: string, direction: 'up' | 'down') => {
|
||||
return moveTracker(emmet, direction);
|
||||
};
|
||||
|
|
@ -535,6 +546,7 @@ export function useCommander(
|
|||
removeTrackerItemByIndex,
|
||||
updateTrackerAttribute,
|
||||
updateTrackerAttributeByIndex,
|
||||
updateTrackerClassesByIndex,
|
||||
moveTrackerItem,
|
||||
moveTrackerItemByIndex,
|
||||
removeTrackerItemClass,
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@ customElement<MdCommanderProps>(
|
|||
onEditAttribute={(index, attrName, attr) =>
|
||||
commander.updateTrackerAttributeByIndex(index, attrName, attr)
|
||||
}
|
||||
onClassesChange={(index, classes) =>
|
||||
commander.updateTrackerClassesByIndex(index, classes)
|
||||
}
|
||||
onRemoveClass={commander.removeTrackerItemClassByIndex}
|
||||
onMoveUp={(index) => commander.moveTrackerItemByIndex(index, "up")}
|
||||
onMoveDown={(index) => commander.moveTrackerItemByIndex(index, "down")}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export {
|
|||
addTrackerItem,
|
||||
removeTrackerItem,
|
||||
updateTrackerAttribute,
|
||||
updateTrackerClasses,
|
||||
moveTrackerItem,
|
||||
removeTrackerClass,
|
||||
getTrackerItems,
|
||||
|
|
|
|||
|
|
@ -161,6 +161,29 @@ export function removeTrackerClass(emmet: string, className: string): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
export function updateTrackerClasses(emmet: string, classes: string[]): boolean {
|
||||
const index = findTrackerIndex(emmet);
|
||||
if (index === -1) return false;
|
||||
|
||||
const item = tracker.items[index];
|
||||
setTracker("items", (prev) =>
|
||||
prev.map((i, idx) =>
|
||||
idx === index
|
||||
? { ...i, classes: [...classes] }
|
||||
: i
|
||||
)
|
||||
);
|
||||
setTracker("history", (prev) => [
|
||||
...prev,
|
||||
{
|
||||
type: "update",
|
||||
itemId: `${item.tag}${item.id ? '#' + item.id : ''}`,
|
||||
timestamp: new Date(),
|
||||
},
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getTrackerItems(): TrackerItem[] {
|
||||
return tracker.items;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue