(() => {
for (const widget of widgetsStore.widgets) {
const key = `widget:${widget.id}`;
- if (!used.has(key)) {
+ if (!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)) {
+ if (!used.has(key) && isInActiveGroup(icon.groupId)) {
items.push({ type: 'icon', id: icon.id, icon });
}
}
@@ -222,7 +258,36 @@ const persistOrderFromGrid = async () => {
}
}
if (nextOrder.length) {
- applyGridOrder(nextOrder, true);
+ if (!gridOrder.value.length) {
+ applyGridOrder(nextOrder, true);
+ } else {
+ const entryKey = (entry: GridOrderEntry) => `${entry.type}:${entry.id}`;
+ const visibleKeys = new Set(nextOrder.map(entryKey));
+ const visibleQueue = [...nextOrder];
+ const merged: GridOrderEntry[] = [];
+
+ for (const entry of gridOrder.value) {
+ if (visibleKeys.has(entryKey(entry))) {
+ const nextEntry = visibleQueue.shift();
+ if (nextEntry) {
+ merged.push(nextEntry);
+ }
+ } else {
+ merged.push(entry);
+ }
+ }
+
+ const mergedKeys = new Set(merged.map(entryKey));
+ for (const entry of visibleQueue) {
+ const key = entryKey(entry);
+ if (!mergedKeys.has(key)) {
+ merged.push(entry);
+ mergedKeys.add(key);
+ }
+ }
+
+ applyGridOrder(merged, true);
+ }
}
await nextTick();
grid.value?.synchronize();
@@ -247,7 +312,11 @@ const syncGridItems = () => {
const refreshLayout = async (instant = false) => {
await nextTick();
syncGridItems();
+ grid.value?.synchronize();
grid.value?.refreshItems();
+ if (grid.value?._settings?.layout) {
+ grid.value._settings.layout.fillGaps = true;
+ }
if (instant) {
grid.value?.layout(true);
} else {
@@ -283,6 +352,7 @@ const syncSizeMap = (
onMounted(async () => {
loadGridOrder();
+ ensureOrderConsistency();
previousIconIds.value = new Set(layoutStore.icons.map(icon => icon.id));
previousWidgetIds.value = new Set(widgetsStore.widgets.map(widget => widget.id));
await nextTick();
@@ -296,7 +366,7 @@ onMounted(async () => {
},
dragSort: true,
layout: {
- fillGaps: settingsStore.autoAlign,
+ fillGaps: true,
rounding: true,
},
layoutDuration: layoutAnimationMs,
@@ -388,12 +458,10 @@ watch(
);
watch(
- () => settingsStore.autoAlign,
- value => {
+ () => props.activeGroupId,
+ () => {
if (!grid.value) return;
- // Muuri has no public setter for fillGaps; update internal setting and relayout.
- grid.value._settings.layout.fillGaps = value;
- grid.value.layout(true);
+ refreshLayout(true);
}
);
@@ -475,8 +543,7 @@ onUnmounted(() => {
}
.grid-item.is-resized {
- overflow: hidden;
- border-radius: $border-radius-small;
+ overflow: visible;
}
.grid-item.is-entering {
diff --git a/app/src/components/IconCard/index.vue b/app/src/components/IconCard/index.vue
index 640cd4a..ae6ce95 100644
--- a/app/src/components/IconCard/index.vue
+++ b/app/src/components/IconCard/index.vue
@@ -10,7 +10,6 @@
{{ props.icon.text?.trim() || props.icon.name.charAt(0) }}
-
{{ props.icon.name }}
@@ -38,9 +37,8 @@ const props = defineProps<{
@import '@/styles/tokens.scss';
.icon-card-wrapper {
- display: grid;
- grid-template-rows: minmax(0, 1fr) var(--icon-label-margin-top) auto;
- align-items: stretch;
+ position: relative;
+ display: block;
width: 100%;
height: 100%;
text-decoration: none;
@@ -83,27 +81,21 @@ const props = defineProps<{
}
}
-.card-spacer {
- height: 100%;
- pointer-events: none;
-}
-
-.icon-card-wrapper.size-1x1 .icon-card {
- width: auto;
- height: 100%;
- aspect-ratio: 1 / 1;
- justify-self: center;
-}
-
.label {
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-top: var(--icon-label-margin-top);
+ transform: translateX(-50%);
font-size: var(--icon-label-font-size);
color: $color-text-primary;
text-align: center;
width: 100%;
- display: var(--icon-label-display, block);
- white-space: normal;
- word-break: break-word;
+ max-width: 100%;
+ visibility: var(--icon-label-visibility, visible);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
line-height: 1.2;
- pointer-events: none;
}
diff --git a/app/src/components/MainContent/index.vue b/app/src/components/MainContent/index.vue
index 66f0b0a..71ea6a6 100644
--- a/app/src/components/MainContent/index.vue
+++ b/app/src/components/MainContent/index.vue
@@ -1,14 +1,16 @@
-
-
-
+
+
@@ -24,6 +26,10 @@ import GridCanvas from '@/components/GridCanvas/index.vue';
import { useSettingsStore } from '@/store/useSettingsStore';
import { storeToRefs } from 'pinia';
+defineProps<{
+ activeGroupId?: string | null;
+}>();
+
const uiStore = useUIStore();
const settingsStore = useSettingsStore();
const { showSearch, showClock, showDate, showSeconds, use24Hour } = storeToRefs(settingsStore);
@@ -49,6 +55,13 @@ const handleDesktopContextMenu = (event: MouseEvent) => {
justify-content: center;
align-items: flex-start; // 内容顶部对齐
overflow-y: auto;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+
+.main-content::-webkit-scrollbar {
+ width: 0;
+ height: 0;
}
.main-content-container {
@@ -59,4 +72,15 @@ const handleDesktopContextMenu = (event: MouseEvent) => {
flex-direction: column;
align-items: center;
}
+
+.content-header {
+ position: sticky;
+ top: var(--content-padding);
+ z-index: 2;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ align-self: stretch;
+}
diff --git a/app/src/components/SettingsPanel/index.vue b/app/src/components/SettingsPanel/index.vue
index a9a3aee..d38e5b8 100644
--- a/app/src/components/SettingsPanel/index.vue
+++ b/app/src/components/SettingsPanel/index.vue
@@ -179,12 +179,12 @@
用网格密度统一控制图标与组件大小。
@@ -362,6 +362,7 @@ import { useSettingsStore } from '@/store/useSettingsStore';
import {
iconDensityRange,
iconRadiusRange,
+ layoutDensityRange,
searchProviders,
themePresets,
} from '@/config/settingsPresets';
@@ -441,9 +442,13 @@ const themeId = computed({
set: value => settingsStore.setSetting('themeId', value as typeof settingsStore.themeId),
});
-const layoutDensity = computed({
- get: () => settingsStore.layoutDensity,
- set: value => settingsStore.setSetting('layoutDensity', value),
+const invertLayoutDensity = (value: number) =>
+ layoutDensityRange.max + layoutDensityRange.min -
+ Math.min(Math.max(value, layoutDensityRange.min), layoutDensityRange.max);
+
+const iconSizeDensity = computed({
+ get: () => invertLayoutDensity(settingsStore.layoutDensity),
+ set: value => settingsStore.setSetting('layoutDensity', invertLayoutDensity(value)),
});
const compactSidebar = computed({
@@ -656,7 +661,6 @@ const clampRange = (value: number, min: number, max: number) =>
const applyImportedSettings = (settings: Partial) => {
const providerIds = new Set(searchProviders.map(item => item.id));
const themeIds = new Set(themePresets.map(item => item.id));
- const layoutDensityValues = new Set([1, 2, 3, 4]);
if (typeof settings.showSearch === 'boolean') {
settingsStore.setSetting('showSearch', settings.showSearch);
@@ -709,8 +713,11 @@ const applyImportedSettings = (settings: Partial) =
clampRange(settings.iconDensity, iconDensityRange.min, iconDensityRange.max)
);
}
- if (typeof settings.layoutDensity === 'number' && layoutDensityValues.has(settings.layoutDensity)) {
- settingsStore.setSetting('layoutDensity', settings.layoutDensity as typeof settingsStore.layoutDensity);
+ if (typeof settings.layoutDensity === 'number') {
+ settingsStore.setSetting(
+ 'layoutDensity',
+ clampRange(settings.layoutDensity, layoutDensityRange.min, layoutDensityRange.max)
+ );
}
};
diff --git a/app/src/components/TheSidebar/index.vue b/app/src/components/TheSidebar/index.vue
index d503f68..1d5e1ea 100644
--- a/app/src/components/TheSidebar/index.vue
+++ b/app/src/components/TheSidebar/index.vue
@@ -169,6 +169,16 @@ const setActive = (id: string) => {
localStorage.setItem(STORAGE_KEY, id);
};
+watch(
+ () => activeId.value,
+ id => {
+ if (selectableItems.value.some(item => item.id === id)) {
+ emit('select', id);
+ }
+ },
+ { immediate: true }
+);
+
const handleItemClick = (item: NavItem) => {
if (item.action === 'add') {
emit('add');
diff --git a/app/src/components/WidgetCard/index.vue b/app/src/components/WidgetCard/index.vue
index 7685d98..112500f 100644
--- a/app/src/components/WidgetCard/index.vue
+++ b/app/src/components/WidgetCard/index.vue
@@ -5,7 +5,6 @@
-
{{ label }}
@@ -41,29 +40,22 @@ const label = computed(() => {
@import '@/styles/tokens.scss';
.widget-card-wrapper {
+ position: relative;
width: 100%;
height: 100%;
- display: grid;
- grid-template-rows: minmax(0, 1fr) var(--icon-label-margin-top) auto;
padding: var(--widget-card-padding, var(--icon-card-padding));
-
- // 鏍规嵁灏哄绫诲悕鎺у埗灏哄
- &.size-1x1 { grid-column: span 1; grid-row: span 1; }
- &.size-1x2 { grid-column: span 1; grid-row: span 2; }
- &.size-2x1 { grid-column: span 2; grid-row: span 1; }
- &.size-2x2 { grid-column: span 2; grid-row: span 2; }
- &.size-2x4 { grid-column: span 2; grid-row: span 4; }
-
+ box-sizing: border-box;
}
.widget-card {
+ box-sizing: border-box;
width: 100%;
height: 100%;
min-height: 0;
background-color: $color-surface-1;
backdrop-filter: blur($backdrop-filter-blur) saturate($backdrop-filter-saturation);
border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: $border-radius-small;
+ border-radius: var(--icon-radius, #{$border-radius-small});
box-shadow: $shadow-md;
display: flex;
flex-direction: column;
@@ -72,31 +64,26 @@ const label = computed(() => {
transform-origin: center;
}
-.card-spacer {
- height: 100%;
- pointer-events: none;
-}
-
-.widget-card-wrapper.size-1x1 .widget-card {
- width: auto;
- height: 100%;
- aspect-ratio: 1 / 1;
- justify-self: center;
-}
-
.widget-content {
flex-grow: 1;
padding: var(--widget-content-padding);
}
.label {
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-top: var(--icon-label-margin-top);
+ transform: translateX(-50%);
font-size: var(--widget-label-font-size);
color: $color-text-primary;
text-align: center;
width: 100%;
- display: var(--widget-label-display, block);
- white-space: normal;
- word-break: break-word;
+ max-width: 100%;
+ visibility: var(--widget-label-visibility, visible);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
line-height: 1.2;
flex-shrink: 0;
}
diff --git a/app/src/composables/useSettingsSync.ts b/app/src/composables/useSettingsSync.ts
index f543a2c..378182f 100644
--- a/app/src/composables/useSettingsSync.ts
+++ b/app/src/composables/useSettingsSync.ts
@@ -53,8 +53,9 @@ export const useSettingsSync = () => {
const baseGrid = layoutConfig.grid;
const sizeScale = layoutPreset.cellSize / baseGrid.cellSize;
const labelScale = clampRange(sizeScale, 0.7, 1);
+ const densityScale = getIconDensityScale(settingsStore.iconDensity);
const iconPadding = Math.max(0, baseIcon.padding * sizeScale);
- const widgetScale = sizeScale;
+ const widgetScale = 1;
const widgetPadding = Math.max(0, baseIcon.padding * sizeScale);
const iconFontSize = Math.max(14, Math.round(baseIcon.fontSize * sizeScale));
const iconLabelFontSize = Math.max(10, Math.round(baseIcon.labelFontSize * labelScale));
@@ -67,11 +68,7 @@ export const useSettingsSync = () => {
4,
Math.round(layoutConfig.widget.labelPaddingY * labelScale)
);
- const densityScale = getIconDensityScale(settingsStore.iconDensity);
- const baseVisualGap = (layoutPreset.gap + iconPadding * 2) * densityScale;
- // Keep density purely controlling spacing; allow deeper negative gap for extra-tight layouts.
- const minGap = -layoutPreset.cellSize * 0.75;
- const gridGap = Math.max(minGap, baseVisualGap - iconPadding * 2);
+ const gridGap = Math.max(0, Math.round(baseGrid.gap * densityScale));
setPx(root, '--grid-cell-size', layoutPreset.cellSize);
setPx(root, '--grid-gap', gridGap);
@@ -82,16 +79,13 @@ export const useSettingsSync = () => {
setPx(root, '--icon-font-size', iconFontSize);
setPx(root, '--icon-label-font-size', iconLabelFontSize);
setPx(root, '--icon-radius', iconRadius);
- setPx(
- root,
- '--icon-label-margin-top',
- settingsStore.showIconLabels ? iconLabelMarginTop : 0
- );
+ setPx(root, '--icon-label-margin-top', iconLabelMarginTop);
setPx(root, '--widget-label-font-size', widgetLabelFontSize);
setPx(root, '--widget-label-padding-y', widgetLabelPaddingY);
- setVar(root, '--icon-label-display', settingsStore.showIconLabels ? 'block' : 'none');
- setVar(root, '--widget-label-display', settingsStore.showIconLabels ? 'block' : 'none');
+ const labelVisibility = settingsStore.showIconLabels ? 'visible' : 'hidden';
+ setVar(root, '--icon-label-visibility', labelVisibility);
+ setVar(root, '--widget-label-visibility', labelVisibility);
const sidebarVars = getSidebarVars(settingsStore.compactSidebar);
setPx(root, '--sidebar-width', sidebarVars.width);
diff --git a/app/src/config/layout.ts b/app/src/config/layout.ts
index dfe6f18..566a208 100644
--- a/app/src/config/layout.ts
+++ b/app/src/config/layout.ts
@@ -54,7 +54,7 @@ export const layoutConfig = {
// 图标首字母字体大小(像素)。
fontSize: 28,
// 卡片内边距(像素)。
- padding: 8,
+ padding: 0,
// 图标卡片圆角(像素)。
radius: 12,
// 标签字体大小(像素)。
diff --git a/app/src/config/settingsPresets.ts b/app/src/config/settingsPresets.ts
index 4e11102..767016c 100644
--- a/app/src/config/settingsPresets.ts
+++ b/app/src/config/settingsPresets.ts
@@ -83,7 +83,7 @@ const clampRange = (value: number, min: number, max: number) =>
Math.min(Math.max(value, min), max);
export type LayoutDensityPreset = {
- value: 1 | 2 | 3 | 4;
+ value: number;
label: string;
cellSize: number;
gap: number;
@@ -119,19 +119,46 @@ export const layoutDensityPresets: LayoutDensityPreset[] = [
},
];
-export const getLayoutDensityPreset = (value?: number) =>
- layoutDensityPresets.find(item => item.value === value) ?? layoutDensityPresets[1];
+export const layoutDensityRange = {
+ min: 1,
+ max: 4,
+ step: 0.1,
+ default: 2,
+};
+
+export const getLayoutDensityPreset = (value?: number) => {
+ const normalized = clampRange(
+ typeof value === 'number' ? value : layoutDensityRange.default,
+ layoutDensityRange.min,
+ layoutDensityRange.max
+ );
+ const lower = Math.floor(normalized);
+ const upper = Math.ceil(normalized);
+ if (lower === upper) {
+ return layoutDensityPresets[lower - 1] ?? layoutDensityPresets[1];
+ }
+ const lowerPreset = layoutDensityPresets[lower - 1] ?? layoutDensityPresets[0];
+ const upperPreset = layoutDensityPresets[upper - 1] ??
+ layoutDensityPresets[layoutDensityPresets.length - 1];
+ const t = (normalized - lower) / (upper - lower);
+ return {
+ value: normalized,
+ label: t < 0.5 ? lowerPreset.label : upperPreset.label,
+ cellSize: Math.round(lowerPreset.cellSize + (upperPreset.cellSize - lowerPreset.cellSize) * t),
+ gap: Math.round(lowerPreset.gap + (upperPreset.gap - lowerPreset.gap) * t),
+ };
+};
export const iconDensityRange = {
min: 0,
- max: 10,
+ max: 50,
step: 1,
- default: 5,
+ default: 25,
};
const iconDensityScaleRange = {
- loose: 1.3,
- dense: 0.05,
+ loose: 2,
+ dense: 0,
};
export const getIconDensityScale = (value?: number) => {
@@ -140,6 +167,7 @@ export const getIconDensityScale = (value?: number) => {
iconDensityRange.min,
iconDensityRange.max
);
+ const ease = (t: number) => Math.pow(t, 0.5);
if (normalized === iconDensityRange.default) {
return 1;
}
@@ -147,11 +175,12 @@ export const getIconDensityScale = (value?: number) => {
// Smaller slider value = denser布局
const t = (iconDensityRange.default - normalized) /
(iconDensityRange.default - iconDensityRange.min);
- return 1 - t * (1 - iconDensityScaleRange.dense);
+ return 1 - ease(t) * (1 - iconDensityScaleRange.dense);
}
+
const t = (normalized - iconDensityRange.default) /
(iconDensityRange.max - iconDensityRange.default);
- return 1 + t * (iconDensityScaleRange.loose - 1);
+ return 1 + ease(t) * (iconDensityScaleRange.loose - 1);
};
export const iconSizeRange = {
diff --git a/app/src/store/useLayoutStore.ts b/app/src/store/useLayoutStore.ts
index bb4f834..75e55cc 100644
--- a/app/src/store/useLayoutStore.ts
+++ b/app/src/store/useLayoutStore.ts
@@ -1,6 +1,7 @@
import { defineStore, acceptHMRUpdate } from 'pinia';
type IconSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';
+const DEFAULT_GROUP_ID = 'home';
// 模拟数据(来自截图参考)
interface Icon {
@@ -11,6 +12,7 @@ interface Icon {
img?: string; // 可选:用于图片图标(如徽标)
text?: string; // 可选:用于文字图标
bgColor?: string; // 可选:纯色背景
+ groupId?: string;
}
interface DragState {
@@ -47,10 +49,33 @@ const defaultIcons: Icon[] = [
{ id: '16', name: '书签管理', url: '#', bgColor: '#f1c40f' },
];
+const defaultGroupById: Record = {
+ '1': 'product',
+ '2': 'product',
+ '3': 'home',
+ '4': 'product',
+ '5': 'fun',
+ '6': 'home',
+ '7': 'fun',
+ '8': 'fun',
+ '9': 'ai',
+ '10': 'fun',
+ '11': 'design',
+ '12': 'design',
+ '13': 'ai',
+ '14': 'code',
+ '15': 'home',
+ '16': 'home',
+};
+
const savedIcons = localStorage.getItem('itab_icons');
const normalizeIcons = (icons: Icon[]): Icon[] =>
- icons.map(icon => ({ ...icon, size: icon.size ?? '1x1' }));
+ icons.map(icon => ({
+ ...icon,
+ size: icon.size ?? '1x1',
+ groupId: icon.groupId ?? defaultGroupById[icon.id] ?? DEFAULT_GROUP_ID,
+ }));
const loadIcons = (): Icon[] => {
if (!savedIcons) return normalizeIcons(defaultIcons);
@@ -77,7 +102,7 @@ export const useLayoutStore = defineStore('layout', {
actions: {
addIcon(icon: Omit) {
const nextId = `custom-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
- this.icons.push({ ...icon, id: nextId });
+ this.icons.push({ ...icon, id: nextId, groupId: icon.groupId ?? DEFAULT_GROUP_ID });
localStorage.setItem('itab_icons', JSON.stringify(this.icons));
},
updateIcon(iconId: string, updates: Partial>) {
diff --git a/app/src/store/useSettingsStore.ts b/app/src/store/useSettingsStore.ts
index 77ef221..d5b84c8 100644
--- a/app/src/store/useSettingsStore.ts
+++ b/app/src/store/useSettingsStore.ts
@@ -4,7 +4,7 @@ import {
getThemePreset,
iconDensityRange,
iconRadiusRange,
- layoutDensityPresets,
+ layoutDensityRange,
searchProviders,
themePresets,
type SearchProviderId,
@@ -12,7 +12,7 @@ import {
} from '@/config/settingsPresets';
type IconDensity = number;
-type LayoutDensity = 1 | 2 | 3 | 4;
+type LayoutDensity = number;
type SettingsState = {
showSearch: boolean;
@@ -54,7 +54,6 @@ const defaultSettings: SettingsState = {
const searchProviderIds = new Set(searchProviders.map(item => item.id));
const themePresetIds = new Set(themePresets.map(item => item.id));
-const layoutDensityValues = new Set(layoutDensityPresets.map(item => item.value));
const clampRange = (value: number, min: number, max: number) =>
Math.min(Math.max(value, min), max);
@@ -92,10 +91,10 @@ const loadSettings = (): SettingsState => {
? parsed.themeId
: defaultSettings.themeId,
layoutDensity:
- typeof parsed.layoutDensity === 'number' && layoutDensityValues.has(parsed.layoutDensity)
- ? parsed.layoutDensity
+ typeof parsed.layoutDensity === 'number'
+ ? clampRange(parsed.layoutDensity, layoutDensityRange.min, layoutDensityRange.max)
: defaultSettings.layoutDensity,
- autoAlign: typeof parsed.autoAlign === 'boolean' ? parsed.autoAlign : defaultSettings.autoAlign,
+ autoAlign: defaultSettings.autoAlign,
compactSidebar:
typeof parsed.compactSidebar === 'boolean' ? parsed.compactSidebar : defaultSettings.compactSidebar,
showGroupLabels:
diff --git a/app/src/store/useWidgetsStore.ts b/app/src/store/useWidgetsStore.ts
index 91a881b..1b38a10 100644
--- a/app/src/store/useWidgetsStore.ts
+++ b/app/src/store/useWidgetsStore.ts
@@ -2,12 +2,14 @@ import { defineStore, acceptHMRUpdate } from 'pinia';
// 组件数据结构
type WidgetSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';
+const DEFAULT_GROUP_ID = 'home';
interface Widget {
id: string;
component: string; // 渲染的组件名
size: WidgetSize; // 组件尺寸
gridPosition: { x: number; y: number }; // 网格位置
data?: any; // 组件数据
+ groupId?: string;
}
interface WidgetsState {
@@ -46,13 +48,34 @@ const defaultWidgets: Widget[] = [
const savedWidgets = localStorage.getItem('itab_widgets');
+const defaultGroupById: Record = {
+ 'widget-1': 'home',
+ 'widget-2': 'fun',
+ 'widget-3': 'product',
+};
+
+const normalizeWidgets = (widgets: Widget[]): Widget[] =>
+ widgets.map(widget => ({
+ ...widget,
+ groupId: widget.groupId ?? defaultGroupById[widget.id] ?? DEFAULT_GROUP_ID,
+ }));
+
+const loadWidgets = (): Widget[] => {
+ if (!savedWidgets) return normalizeWidgets(defaultWidgets);
+ try {
+ return normalizeWidgets(JSON.parse(savedWidgets) as Widget[]);
+ } catch {
+ return normalizeWidgets(defaultWidgets);
+ }
+};
+
// 模拟数据(来自截图参考)
export const useWidgetsStore = defineStore('widgets', {
state: (): WidgetsState => ({
- widgets: savedWidgets ? JSON.parse(savedWidgets) : defaultWidgets,
+ widgets: loadWidgets(),
}),
actions: {
- addWidget(payload: { component: string; size?: WidgetSize; data?: any }) {
+ addWidget(payload: { component: string; size?: WidgetSize; data?: any; groupId?: string }) {
const nextId = `widget-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
const nextWidget: Widget = {
id: nextId,
@@ -60,6 +83,7 @@ export const useWidgetsStore = defineStore('widgets', {
size: payload.size ?? '1x1',
gridPosition: { x: 0, y: 0 },
data: payload.data,
+ groupId: payload.groupId ?? DEFAULT_GROUP_ID,
};
this.widgets.push(nextWidget);
localStorage.setItem('itab_widgets', JSON.stringify(this.widgets));