1
This commit is contained in:
@ -3,12 +3,15 @@ import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue';
|
||||
import Muuri from 'muuri';
|
||||
import { useLayoutStore } from '@/store/useLayoutStore';
|
||||
import { useWidgetsStore } from '@/store/useWidgetsStore';
|
||||
import { useFoldersStore, type Folder, type FolderItem } from '@/store/useFoldersStore';
|
||||
import { useUIStore } from '@/store/useUIStore';
|
||||
import { useSettingsStore } from '@/store/useSettingsStore';
|
||||
import IconCard from '@/components/IconCard/index.vue';
|
||||
import WidgetCard from '@/components/WidgetCard/index.vue';
|
||||
import FolderCard from '@/components/FolderCard/index.vue';
|
||||
import FolderDialog from '@/components/FolderDialog/index.vue';
|
||||
|
||||
type GridItemType = 'icon' | 'widget';
|
||||
type GridItemType = 'icon' | 'widget' | 'folder';
|
||||
type GridItemSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';
|
||||
|
||||
interface Icon {
|
||||
@ -20,6 +23,7 @@ interface Icon {
|
||||
bgColor?: string;
|
||||
size?: GridItemSize;
|
||||
groupId?: string;
|
||||
folderId?: string;
|
||||
}
|
||||
|
||||
type WidgetSize = GridItemSize;
|
||||
@ -29,8 +33,13 @@ interface Widget {
|
||||
size: WidgetSize;
|
||||
data?: any;
|
||||
groupId?: string;
|
||||
folderId?: string;
|
||||
}
|
||||
|
||||
type FolderChild =
|
||||
| { type: 'icon'; id: string; icon: Icon }
|
||||
| { type: 'widget'; id: string; widget: Widget };
|
||||
|
||||
interface GridOrderEntry {
|
||||
id: string;
|
||||
type: GridItemType;
|
||||
@ -38,10 +47,12 @@ interface GridOrderEntry {
|
||||
|
||||
type OrderedGridItem =
|
||||
| { type: 'icon'; id: string; icon: Icon }
|
||||
| { type: 'widget'; id: string; widget: Widget };
|
||||
| { type: 'widget'; id: string; widget: Widget }
|
||||
| { type: 'folder'; id: string; folder: Folder; children: FolderChild[] };
|
||||
|
||||
const GRID_ORDER_STORAGE_KEY = 'itab_grid_order';
|
||||
const DEFAULT_GROUP_ID = 'home';
|
||||
const FOLDER_SIZE: GridItemSize = '2x2';
|
||||
|
||||
const props = defineProps<{
|
||||
activeGroupId?: string | null;
|
||||
@ -49,6 +60,7 @@ const props = defineProps<{
|
||||
|
||||
const layoutStore = useLayoutStore();
|
||||
const widgetsStore = useWidgetsStore();
|
||||
const foldersStore = useFoldersStore();
|
||||
const uiStore = useUIStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const gridRef = ref<HTMLElement | null>(null);
|
||||
@ -64,6 +76,12 @@ const iconSizeMap = new Map<string, GridItemSize>();
|
||||
const enteringKeys = ref(new Set<string>());
|
||||
const previousIconIds = ref(new Set<string>());
|
||||
const previousWidgetIds = ref(new Set<string>());
|
||||
const openFolderId = ref<string | null>(null);
|
||||
const hoverTargetEl = ref<HTMLElement | null>(null);
|
||||
const draggingMeta = ref<{ id: string | null; type: GridItemType | null }>({
|
||||
id: null,
|
||||
type: null,
|
||||
});
|
||||
const layoutAnimationMs = 240;
|
||||
const layoutEasing = 'cubic-bezier(0.4, 0, 0.2, 1)';
|
||||
const normalizeGroupId = (groupId?: string | null) => groupId ?? DEFAULT_GROUP_ID;
|
||||
@ -74,7 +92,7 @@ const normalizeGridOrder = (order: GridOrderEntry[]) => {
|
||||
const normalized: GridOrderEntry[] = [];
|
||||
for (const entry of order) {
|
||||
if (!entry || typeof entry.id !== 'string') continue;
|
||||
if (entry.type !== 'icon' && entry.type !== 'widget') continue;
|
||||
if (entry.type !== 'icon' && entry.type !== 'widget' && entry.type !== 'folder') continue;
|
||||
const key = `${entry.type}:${entry.id}`;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
@ -84,8 +102,9 @@ const normalizeGridOrder = (order: GridOrderEntry[]) => {
|
||||
};
|
||||
|
||||
const buildDefaultOrder = (): GridOrderEntry[] => [
|
||||
...widgetsStore.widgets.map(widget => ({ id: widget.id, type: 'widget' as const })),
|
||||
...layoutStore.icons.map(icon => ({ id: icon.id, type: 'icon' as const })),
|
||||
...widgetsStore.widgets.filter(widget => !widget.folderId).map(widget => ({ id: widget.id, type: 'widget' as const })),
|
||||
...layoutStore.icons.filter(icon => !icon.folderId).map(icon => ({ id: icon.id, type: 'icon' as const })),
|
||||
...foldersStore.folders.map(folder => ({ id: folder.id, type: 'folder' as const })),
|
||||
];
|
||||
|
||||
const applyGridOrder = (order: GridOrderEntry[], syncStoreOrder = true) => {
|
||||
@ -121,8 +140,9 @@ const loadGridOrder = () => {
|
||||
};
|
||||
|
||||
const ensureOrderConsistency = () => {
|
||||
const iconIds = new Set(layoutStore.icons.map(icon => icon.id));
|
||||
const widgetIds = new Set(widgetsStore.widgets.map(widget => widget.id));
|
||||
const iconIds = new Set(layoutStore.icons.filter(icon => !icon.folderId).map(icon => icon.id));
|
||||
const widgetIds = new Set(widgetsStore.widgets.filter(widget => !widget.folderId).map(widget => widget.id));
|
||||
const folderIds = new Set(foldersStore.folders.map(folder => folder.id));
|
||||
const seen = new Set<string>();
|
||||
const nextOrder: GridOrderEntry[] = [];
|
||||
|
||||
@ -140,22 +160,32 @@ const ensureOrderConsistency = () => {
|
||||
if (entry.type === 'widget' && widgetIds.has(entry.id)) {
|
||||
pushEntry(entry);
|
||||
}
|
||||
if (entry.type === 'folder' && folderIds.has(entry.id)) {
|
||||
pushEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
for (const widget of widgetsStore.widgets) {
|
||||
const key = `widget:${widget.id}`;
|
||||
if (!seen.has(key)) {
|
||||
if (!widget.folderId && !seen.has(key)) {
|
||||
pushEntry({ id: widget.id, type: 'widget' });
|
||||
}
|
||||
}
|
||||
|
||||
for (const icon of layoutStore.icons) {
|
||||
const key = `icon:${icon.id}`;
|
||||
if (!seen.has(key)) {
|
||||
if (!icon.folderId && !seen.has(key)) {
|
||||
pushEntry({ id: icon.id, type: 'icon' });
|
||||
}
|
||||
}
|
||||
|
||||
for (const folder of foldersStore.folders) {
|
||||
const key = `folder:${folder.id}`;
|
||||
if (!seen.has(key)) {
|
||||
pushEntry({ id: folder.id, type: 'folder' });
|
||||
}
|
||||
}
|
||||
|
||||
const orderChanged =
|
||||
nextOrder.length !== gridOrder.value.length ||
|
||||
nextOrder.some((entry, index) => {
|
||||
@ -176,42 +206,103 @@ const isInActiveGroup = (groupId?: string | null) =>
|
||||
const orderedItems = computed<OrderedGridItem[]>(() => {
|
||||
const iconsById = new Map(layoutStore.icons.map(icon => [icon.id, icon]));
|
||||
const widgetsById = new Map(widgetsStore.widgets.map(widget => [widget.id, widget]));
|
||||
const foldersById = new Map(foldersStore.folders.map(folder => [folder.id, folder]));
|
||||
const used = new Set<string>();
|
||||
const items: OrderedGridItem[] = [];
|
||||
|
||||
const buildChildren = (folder: Folder): FolderChild[] => {
|
||||
const children: FolderChild[] = [];
|
||||
for (const child of folder.items) {
|
||||
if (child.type === 'icon') {
|
||||
const icon = iconsById.get(child.id);
|
||||
if (icon) children.push({ type: 'icon', id: icon.id, icon });
|
||||
} else if (child.type === 'widget') {
|
||||
const widget = widgetsById.get(child.id);
|
||||
if (widget) children.push({ type: 'widget', id: widget.id, widget });
|
||||
}
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
for (const entry of gridOrder.value) {
|
||||
if (entry.type === 'icon') {
|
||||
const icon = iconsById.get(entry.id);
|
||||
if (icon && isInActiveGroup(icon.groupId)) {
|
||||
if (icon && !icon.folderId && isInActiveGroup(icon.groupId)) {
|
||||
items.push({ type: 'icon', id: icon.id, icon });
|
||||
used.add(`icon:${icon.id}`);
|
||||
}
|
||||
} else {
|
||||
} else if (entry.type === 'widget') {
|
||||
const widget = widgetsById.get(entry.id);
|
||||
if (widget && isInActiveGroup(widget.groupId)) {
|
||||
if (widget && !widget.folderId && isInActiveGroup(widget.groupId)) {
|
||||
items.push({ type: 'widget', id: widget.id, widget });
|
||||
used.add(`widget:${widget.id}`);
|
||||
}
|
||||
} else if (entry.type === 'folder') {
|
||||
const folder = foldersById.get(entry.id);
|
||||
if (folder && isInActiveGroup(folder.groupId)) {
|
||||
items.push({ type: 'folder', id: folder.id, folder, children: buildChildren(folder) });
|
||||
used.add(`folder:${folder.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const widget of widgetsStore.widgets) {
|
||||
const key = `widget:${widget.id}`;
|
||||
if (!used.has(key) && isInActiveGroup(widget.groupId)) {
|
||||
if (!widget.folderId && !used.has(key) && isInActiveGroup(widget.groupId)) {
|
||||
items.push({ type: 'widget', id: widget.id, widget });
|
||||
}
|
||||
}
|
||||
|
||||
for (const icon of layoutStore.icons) {
|
||||
const key = `icon:${icon.id}`;
|
||||
if (!used.has(key) && isInActiveGroup(icon.groupId)) {
|
||||
if (!icon.folderId && !used.has(key) && isInActiveGroup(icon.groupId)) {
|
||||
items.push({ type: 'icon', id: icon.id, icon });
|
||||
}
|
||||
}
|
||||
|
||||
for (const folder of foldersStore.folders) {
|
||||
const key = `folder:${folder.id}`;
|
||||
if (!used.has(key) && isInActiveGroup(folder.groupId)) {
|
||||
items.push({ type: 'folder', id: folder.id, folder, children: buildChildren(folder) });
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
const resolveFolderChildren = (folderId: string): FolderChild[] => {
|
||||
const folder = foldersStore.folders.find(f => f.id === folderId);
|
||||
if (!folder) return [];
|
||||
const iconsById = new Map(layoutStore.icons.map(icon => [icon.id, icon]));
|
||||
const widgetsById = new Map(widgetsStore.widgets.map(widget => [widget.id, widget]));
|
||||
const children: FolderChild[] = [];
|
||||
for (const child of folder.items) {
|
||||
if (child.type === 'icon') {
|
||||
const icon = iconsById.get(child.id);
|
||||
if (icon) children.push({ type: 'icon', id: icon.id, icon });
|
||||
} else if (child.type === 'widget') {
|
||||
const widget = widgetsById.get(child.id);
|
||||
if (widget) children.push({ type: 'widget', id: widget.id, widget });
|
||||
}
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
const sizeClassFor = (item: OrderedGridItem) => {
|
||||
if (item.type === 'widget') return `size-${item.widget.size}`;
|
||||
if (item.type === 'folder') return `size-${FOLDER_SIZE}`;
|
||||
return `size-${item.icon.size ?? '1x1'}`;
|
||||
};
|
||||
|
||||
const currentFolder = computed(() => {
|
||||
if (!openFolderId.value) return null;
|
||||
return foldersStore.folders.find(folder => folder.id === openFolderId.value) ?? null;
|
||||
});
|
||||
|
||||
const currentFolderChildren = computed(() =>
|
||||
currentFolder.value ? resolveFolderChildren(currentFolder.value.id) : []
|
||||
);
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (suppressClick.value || isDragging.value) return;
|
||||
const target = event.target as HTMLElement;
|
||||
@ -240,11 +331,163 @@ const handleContextMenu = (event: MouseEvent) => {
|
||||
}
|
||||
const id = itemEl.dataset.id;
|
||||
const type = itemEl.dataset.type as GridItemType | undefined;
|
||||
if (id && type) {
|
||||
if (id && (type === 'icon' || type === 'widget')) {
|
||||
uiStore.openContextMenu(event.clientX, event.clientY, id, type);
|
||||
} else {
|
||||
uiStore.openContextMenu(event.clientX, event.clientY, null, 'desktop');
|
||||
}
|
||||
};
|
||||
|
||||
const setHoverTarget = (el: HTMLElement | null) => {
|
||||
if (hoverTargetEl.value === el) return;
|
||||
if (hoverTargetEl.value) {
|
||||
hoverTargetEl.value.classList.remove('is-drop-target');
|
||||
}
|
||||
hoverTargetEl.value = el;
|
||||
if (el) {
|
||||
el.classList.add('is-drop-target');
|
||||
}
|
||||
};
|
||||
|
||||
const clearHoverTarget = () => setHoverTarget(null);
|
||||
|
||||
const findDropTarget = (x: number, y: number, ignoreEl?: HTMLElement | null) => {
|
||||
if (!gridRef.value) return null;
|
||||
const elements = Array.from(gridRef.value.querySelectorAll('.grid-item')) as HTMLElement[];
|
||||
for (const el of elements) {
|
||||
if (ignoreEl && el === ignoreEl) continue;
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const resolveGroupIdByItem = (item: { id: string; type: GridItemType }) => {
|
||||
if (item.type === 'icon') {
|
||||
return layoutStore.icons.find(icon => icon.id === item.id)?.groupId ?? activeGroupId.value;
|
||||
}
|
||||
if (item.type === 'widget') {
|
||||
return widgetsStore.widgets.find(widget => widget.id === item.id)?.groupId ?? activeGroupId.value;
|
||||
}
|
||||
if (item.type === 'folder') {
|
||||
return foldersStore.folders.find(folder => folder.id === item.id)?.groupId ?? activeGroupId.value;
|
||||
}
|
||||
return activeGroupId.value;
|
||||
};
|
||||
|
||||
const addItemToFolderState = (folderId: string, item: { id: string; type: 'icon' | 'widget' }) => {
|
||||
const folderGroup = foldersStore.folders.find(f => f.id === folderId)?.groupId ?? activeGroupId.value;
|
||||
if (item.type === 'icon') {
|
||||
layoutStore.updateIcon(item.id, { folderId, groupId: folderGroup });
|
||||
} else {
|
||||
widgetsStore.updateWidgetFolder(item.id, folderId, folderGroup);
|
||||
}
|
||||
foldersStore.addItem(folderId, { id: item.id, type: item.type });
|
||||
};
|
||||
|
||||
const removeItemFromFolderState = (folderId: string, item: { id: string; type: 'icon' | 'widget' }) => {
|
||||
if (item.type === 'icon') {
|
||||
layoutStore.updateIcon(item.id, { folderId: undefined });
|
||||
} else {
|
||||
widgetsStore.updateWidgetFolder(item.id, undefined);
|
||||
}
|
||||
foldersStore.removeItem(folderId, item.id, item.type);
|
||||
};
|
||||
|
||||
const removeEntryFromOrder = (order: GridOrderEntry[], item: { id: string; type: GridItemType }) =>
|
||||
order.filter(entry => !(entry.id === item.id && entry.type === item.type));
|
||||
|
||||
const createFolderFromItems = (
|
||||
source: { id: string; type: 'icon' | 'widget' },
|
||||
target: { id: string; type: 'icon' | 'widget' }
|
||||
) => {
|
||||
const groupId = resolveGroupIdByItem(target);
|
||||
const folder = foldersStore.addFolder({
|
||||
name: '组',
|
||||
groupId,
|
||||
items: [
|
||||
{ id: target.id, type: target.type },
|
||||
{ id: source.id, type: source.type },
|
||||
],
|
||||
});
|
||||
addItemToFolderState(folder.id, target);
|
||||
addItemToFolderState(folder.id, source);
|
||||
|
||||
const cleaned = removeEntryFromOrder(removeEntryFromOrder(gridOrder.value, source), target);
|
||||
const targetIndex = gridOrder.value.findIndex(
|
||||
entry => entry.id === target.id && entry.type === target.type
|
||||
);
|
||||
const insertIndex = targetIndex >= 0 ? targetIndex : cleaned.length;
|
||||
cleaned.splice(insertIndex, 0, { id: folder.id, type: 'folder' });
|
||||
applyGridOrder(cleaned, true);
|
||||
refreshLayout(true);
|
||||
};
|
||||
|
||||
const moveItemIntoFolder = (folderId: string, item: { id: string; type: 'icon' | 'widget' }) => {
|
||||
const folder = foldersStore.folders.find(f => f.id === folderId);
|
||||
if (!folder) return;
|
||||
addItemToFolderState(folderId, item);
|
||||
const nextOrder = removeEntryFromOrder(gridOrder.value, item);
|
||||
applyGridOrder(nextOrder, true);
|
||||
refreshLayout(true);
|
||||
};
|
||||
|
||||
const handleMergeDrop = () => {
|
||||
const dragId = draggingMeta.value.id;
|
||||
const dragType = draggingMeta.value.type;
|
||||
if (!dragId || !dragType) return;
|
||||
const targetEl = hoverTargetEl.value;
|
||||
clearHoverTarget();
|
||||
if (!targetEl) return;
|
||||
const targetId = targetEl.dataset.id;
|
||||
const targetType = targetEl.dataset.type as GridItemType | undefined;
|
||||
if (!targetId || !targetType) return;
|
||||
if (targetId === dragId && targetType === dragType) return;
|
||||
if (dragType === 'folder') return; // no nesting folders for now
|
||||
|
||||
if (targetType === 'folder') {
|
||||
moveItemIntoFolder(targetId, { id: dragId, type: dragType });
|
||||
return;
|
||||
}
|
||||
if (targetType === 'icon' || targetType === 'widget') {
|
||||
if (dragType === 'icon' || dragType === 'widget') {
|
||||
createFolderFromItems({ id: dragId, type: dragType }, { id: targetId, type: targetType });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const openFolder = (folderId: string) => {
|
||||
openFolderId.value = folderId;
|
||||
};
|
||||
|
||||
const handleRenameFolder = (payload: { id: string; name: string }) => {
|
||||
foldersStore.renameFolder(payload.id, payload.name);
|
||||
};
|
||||
|
||||
const handleOpenChild = (child: FolderChild) => {
|
||||
if (child.type === 'icon') {
|
||||
const targetMode = settingsStore.openInNewTab ? '_blank' : '_self';
|
||||
window.open(child.icon.url, targetMode);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFromFolder = (payload: { folderId: string; child: FolderChild }) => {
|
||||
const { folderId, child } = payload;
|
||||
removeItemFromFolderState(folderId, { id: child.id, type: child.type });
|
||||
let nextOrder = removeEntryFromOrder(gridOrder.value, { id: child.id, type: child.type });
|
||||
const folderExists = foldersStore.folders.some(f => f.id === folderId);
|
||||
if (!folderExists) {
|
||||
nextOrder = removeEntryFromOrder(nextOrder, { id: folderId, type: 'folder' });
|
||||
}
|
||||
const folderIndex = nextOrder.findIndex(entry => entry.id === folderId && entry.type === 'folder');
|
||||
const insertIndex = folderIndex >= 0 ? folderIndex + 1 : nextOrder.length;
|
||||
nextOrder.splice(insertIndex, 0, { id: child.id, type: child.type });
|
||||
applyGridOrder(nextOrder, true);
|
||||
refreshLayout(true);
|
||||
};
|
||||
|
||||
const persistOrderFromGrid = async () => {
|
||||
if (!grid.value) return;
|
||||
const items = grid.value.getItems();
|
||||
@ -253,7 +496,7 @@ const persistOrderFromGrid = async () => {
|
||||
const element = item.getElement() as HTMLElement;
|
||||
const id = element.dataset.id;
|
||||
const type = element.dataset.type as GridItemType | undefined;
|
||||
if (id && (type === 'icon' || type === 'widget')) {
|
||||
if (id && (type === 'icon' || type === 'widget' || type === 'folder')) {
|
||||
nextOrder.push({ id, type });
|
||||
}
|
||||
}
|
||||
@ -373,21 +616,36 @@ onMounted(async () => {
|
||||
layoutEasing,
|
||||
});
|
||||
|
||||
grid.value.on('dragStart', () => {
|
||||
grid.value.on('dragStart', (item: any) => {
|
||||
isDragging.value = true;
|
||||
suppressClick.value = true;
|
||||
const el = item.getElement() as HTMLElement;
|
||||
draggingMeta.value = {
|
||||
id: el?.dataset?.id ?? null,
|
||||
type: (el?.dataset?.type as GridItemType) ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
grid.value.on('dragMove', (item: any) => {
|
||||
const el = item.getElement() as HTMLElement;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const target = findDropTarget(rect.left + rect.width / 2, rect.top + rect.height / 2, el);
|
||||
setHoverTarget(target);
|
||||
});
|
||||
|
||||
grid.value.on('dragEnd', () => {
|
||||
isDragging.value = false;
|
||||
handleMergeDrop();
|
||||
clickBlockUntil.value = Date.now() + 180;
|
||||
window.setTimeout(() => {
|
||||
suppressClick.value = false;
|
||||
}, 0);
|
||||
draggingMeta.value = { id: null, type: null };
|
||||
});
|
||||
|
||||
grid.value.on('dragReleaseEnd', () => {
|
||||
persistOrderFromGrid();
|
||||
clearHoverTarget();
|
||||
});
|
||||
|
||||
grid.value.on('layoutStart', () => {
|
||||
@ -457,6 +715,15 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => foldersStore.folders.map(folder => ({ id: folder.id, items: folder.items.length, groupId: folder.groupId })),
|
||||
() => {
|
||||
ensureOrderConsistency();
|
||||
refreshLayout(true);
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.activeGroupId,
|
||||
() => {
|
||||
@ -498,10 +765,11 @@ onUnmounted(() => {
|
||||
:key="`${item.type}-${item.id}`"
|
||||
class="grid-item"
|
||||
:class="[
|
||||
`size-${item.type === 'widget' ? item.widget.size : (item.icon.size ?? '1x1')}`,
|
||||
sizeClassFor(item),
|
||||
{
|
||||
'is-resized': resizedKeys.has(`${item.type}:${item.id}`),
|
||||
'is-entering': enteringKeys.has(`${item.type}:${item.id}`)
|
||||
'is-entering': enteringKeys.has(`${item.type}:${item.id}`),
|
||||
'is-drop-target': hoverTargetEl && hoverTargetEl.dataset?.id === item.id
|
||||
}
|
||||
]"
|
||||
:data-id="item.id"
|
||||
@ -509,10 +777,25 @@ onUnmounted(() => {
|
||||
>
|
||||
<div class="grid-item-content">
|
||||
<WidgetCard v-if="item.type === 'widget'" :widget="item.widget" />
|
||||
<IconCard v-else :icon="item.icon" />
|
||||
<IconCard v-else-if="item.type === 'icon'" :icon="item.icon" />
|
||||
<FolderCard
|
||||
v-else
|
||||
:folder="item.folder"
|
||||
:children="item.children"
|
||||
@open="openFolder(item.folder.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FolderDialog
|
||||
:open="!!currentFolder"
|
||||
:folder="currentFolder"
|
||||
:children="currentFolderChildren"
|
||||
@close="openFolderId = null"
|
||||
@rename="handleRenameFolder"
|
||||
@remove="handleRemoveFromFolder"
|
||||
@open-item="handleOpenChild"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -592,4 +875,9 @@ onUnmounted(() => {
|
||||
cursor: grabbing;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.grid-item.is-drop-target .grid-item-content {
|
||||
outline: 2px dashed rgba(0, 122, 255, 0.7);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user