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 {
|
export interface AttributeTooltipProps {
|
||||||
position: { x: number; y: number };
|
position: { x: number; y: number };
|
||||||
attributes: Record<string, TrackerAttribute>;
|
attributes: Record<string, TrackerAttribute>;
|
||||||
|
classes: string[];
|
||||||
onUpdate: (attrName: string, attr: TrackerAttribute) => void;
|
onUpdate: (attrName: string, attr: TrackerAttribute) => void;
|
||||||
|
onClassesChange: (classes: string[]) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AttributeTooltip: Component<AttributeTooltipProps> = (props) => {
|
export const AttributeTooltip: Component<AttributeTooltipProps> = (props) => {
|
||||||
const [position, setPosition] = createSignal(props.position);
|
const [position, setPosition] = createSignal(props.position);
|
||||||
|
const [newClass, setNewClass] = createSignal("");
|
||||||
|
|
||||||
// 点击外部关闭
|
// 点击外部关闭
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const handleClickOutside = (e: MouseEvent) => {
|
const handleClickOutside = (e: MouseEvent) => {
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
if (!target.closest(".attribute-tooltip")) {
|
if (target.closest('body') && !target.closest(".attribute-tooltip")) {
|
||||||
props.onClose();
|
props.onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -29,6 +32,22 @@ export const AttributeTooltip: Component<AttributeTooltipProps> = (props) => {
|
||||||
props.onUpdate(attrName, { ...attr, value });
|
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) => {
|
const renderEditor = (attrName: string, attr: TrackerAttribute) => {
|
||||||
if (attr.type === "progress") {
|
if (attr.type === "progress") {
|
||||||
|
|
@ -125,6 +144,49 @@ export const AttributeTooltip: Component<AttributeTooltipProps> = (props) => {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-3">
|
<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)}>
|
<For each={Object.entries(props.attributes)}>
|
||||||
{([name, attr]) => (
|
{([name, attr]) => (
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { AttributeTooltip } from "./AttributeTooltip";
|
||||||
export interface TrackerViewProps {
|
export interface TrackerViewProps {
|
||||||
items: () => TrackerItem[];
|
items: () => TrackerItem[];
|
||||||
onEditAttribute?: (index: number, attrName: string, attr: TrackerAttribute) => void;
|
onEditAttribute?: (index: number, attrName: string, attr: TrackerAttribute) => void;
|
||||||
|
onClassesChange?: (index: number, classes: string[]) => void;
|
||||||
onRemoveClass?: (index: number, className: string) => void;
|
onRemoveClass?: (index: number, className: string) => void;
|
||||||
onMoveUp?: (index: number) => void;
|
onMoveUp?: (index: number) => void;
|
||||||
onMoveDown?: (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 (
|
return (
|
||||||
<div class="tracker-view flex-1 overflow-auto p-3 bg-white">
|
<div class="tracker-view flex-1 overflow-auto p-3 bg-white">
|
||||||
<Show
|
<Show
|
||||||
|
|
@ -132,7 +140,9 @@ export const TrackerView: Component<TrackerViewProps> = (props) => {
|
||||||
<AttributeTooltip
|
<AttributeTooltip
|
||||||
position={editingItem()!.position}
|
position={editingItem()!.position}
|
||||||
attributes={props.items()[editingItem()!.index]?.attributes || {}}
|
attributes={props.items()[editingItem()!.index]?.attributes || {}}
|
||||||
|
classes={props.items()[editingItem()!.index]?.classes || []}
|
||||||
onUpdate={handleUpdateAttribute}
|
onUpdate={handleUpdateAttribute}
|
||||||
|
onClassesChange={handleClassesChange}
|
||||||
onClose={() => setEditingItem(null)}
|
onClose={() => setEditingItem(null)}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
addTrackerItem as addTracker,
|
addTrackerItem as addTracker,
|
||||||
removeTrackerItem as removeTracker,
|
removeTrackerItem as removeTracker,
|
||||||
updateTrackerAttribute as updateTrackerAttr,
|
updateTrackerAttribute as updateTrackerAttr,
|
||||||
|
updateTrackerClasses as updateTrackerClassesStore,
|
||||||
moveTrackerItem as moveTracker,
|
moveTrackerItem as moveTracker,
|
||||||
removeTrackerClass as removeClassFromTracker,
|
removeTrackerClass as removeClassFromTracker,
|
||||||
getTrackerItems,
|
getTrackerItems,
|
||||||
|
|
@ -255,6 +256,7 @@ export interface UseCommanderReturn {
|
||||||
removeTrackerItemByIndex: (index: number) => void;
|
removeTrackerItemByIndex: (index: number) => void;
|
||||||
updateTrackerAttribute: (emmet: string, attrName: string, attr: TrackerAttribute) => boolean;
|
updateTrackerAttribute: (emmet: string, attrName: string, attr: TrackerAttribute) => boolean;
|
||||||
updateTrackerAttributeByIndex: (index: number, attrName: string, attr: TrackerAttribute) => void;
|
updateTrackerAttributeByIndex: (index: number, attrName: string, attr: TrackerAttribute) => void;
|
||||||
|
updateTrackerClassesByIndex: (index: number, classes: string[]) => void;
|
||||||
moveTrackerItem: (emmet: string, direction: 'up' | 'down') => boolean;
|
moveTrackerItem: (emmet: string, direction: 'up' | 'down') => boolean;
|
||||||
moveTrackerItemByIndex: (index: number, direction: 'up' | 'down') => void;
|
moveTrackerItemByIndex: (index: number, direction: 'up' | 'down') => void;
|
||||||
removeTrackerItemClass: (emmet: string, className: string) => boolean;
|
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') => {
|
const moveTrackerItem = (emmet: string, direction: 'up' | 'down') => {
|
||||||
return moveTracker(emmet, direction);
|
return moveTracker(emmet, direction);
|
||||||
};
|
};
|
||||||
|
|
@ -535,6 +546,7 @@ export function useCommander(
|
||||||
removeTrackerItemByIndex,
|
removeTrackerItemByIndex,
|
||||||
updateTrackerAttribute,
|
updateTrackerAttribute,
|
||||||
updateTrackerAttributeByIndex,
|
updateTrackerAttributeByIndex,
|
||||||
|
updateTrackerClassesByIndex,
|
||||||
moveTrackerItem,
|
moveTrackerItem,
|
||||||
moveTrackerItemByIndex,
|
moveTrackerItemByIndex,
|
||||||
removeTrackerItemClass,
|
removeTrackerItemClass,
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,9 @@ customElement<MdCommanderProps>(
|
||||||
onEditAttribute={(index, attrName, attr) =>
|
onEditAttribute={(index, attrName, attr) =>
|
||||||
commander.updateTrackerAttributeByIndex(index, attrName, attr)
|
commander.updateTrackerAttributeByIndex(index, attrName, attr)
|
||||||
}
|
}
|
||||||
|
onClassesChange={(index, classes) =>
|
||||||
|
commander.updateTrackerClassesByIndex(index, classes)
|
||||||
|
}
|
||||||
onRemoveClass={commander.removeTrackerItemClassByIndex}
|
onRemoveClass={commander.removeTrackerItemClassByIndex}
|
||||||
onMoveUp={(index) => commander.moveTrackerItemByIndex(index, "up")}
|
onMoveUp={(index) => commander.moveTrackerItemByIndex(index, "up")}
|
||||||
onMoveDown={(index) => commander.moveTrackerItemByIndex(index, "down")}
|
onMoveDown={(index) => commander.moveTrackerItemByIndex(index, "down")}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ export {
|
||||||
addTrackerItem,
|
addTrackerItem,
|
||||||
removeTrackerItem,
|
removeTrackerItem,
|
||||||
updateTrackerAttribute,
|
updateTrackerAttribute,
|
||||||
|
updateTrackerClasses,
|
||||||
moveTrackerItem,
|
moveTrackerItem,
|
||||||
removeTrackerClass,
|
removeTrackerClass,
|
||||||
getTrackerItems,
|
getTrackerItems,
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,29 @@ export function removeTrackerClass(emmet: string, className: string): boolean {
|
||||||
return true;
|
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[] {
|
export function getTrackerItems(): TrackerItem[] {
|
||||||
return tracker.items;
|
return tracker.items;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue