mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-19 06:43:51 +02:00
Allow users to reorder favorite friend groups in the sidebar
This commit is contained in:
130
package-lock.json
generated
130
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"node-api-dotnet": "^0.9.19"
|
"node-api-dotnet": "^0.9.19"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@dnd-kit/vue": "^0.3.2",
|
||||||
"@electron/rebuild": "^4.0.3",
|
"@electron/rebuild": "^4.0.3",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
@@ -741,6 +742,124 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@dnd-kit/abstract": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/abstract/-/abstract-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-uvPVK+SZYD6Viddn9M0K0JQdXknuVSxA/EbMlFRanve3P/XTc18oLa5zGftKSGjfQGmuzkZ34E26DSbly1zi3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@dnd-kit/geometry": "^0.3.2",
|
||||||
|
"@dnd-kit/state": "^0.3.2",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/abstract/node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/collision": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/collision/-/collision-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-pNmNSLCI8S9fNQ7QJ3fBCDjiT0sqBhUFcKgmyYaGvGCAU+kq0AP8OWlh0JSisc9k5mFyxmRpmFQcnJpILz/RPA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@dnd-kit/abstract": "^0.3.2",
|
||||||
|
"@dnd-kit/geometry": "^0.3.2",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/collision/node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/dom": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/dom/-/dom-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-cIUAVgt2szQyz6JRy7I+0r+xeyOAGH21Y15hb5bIyHoDEaZBvIDH+OOlD9eoLjCbsxDLN9WloU2CBi3OE6LYDg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@dnd-kit/abstract": "^0.3.2",
|
||||||
|
"@dnd-kit/collision": "^0.3.2",
|
||||||
|
"@dnd-kit/geometry": "^0.3.2",
|
||||||
|
"@dnd-kit/state": "^0.3.2",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/dom/node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/geometry": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/geometry/-/geometry-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-3UBPuIS7E3oGiHxOE8h810QA+0pnrnCtGxl4Os1z3yy5YkC/BEYGY+TxWPTQaY1/OMV7GCX7ZNMlama2QN3n3w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@dnd-kit/state": "^0.3.2",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/geometry/node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/state": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/state/-/state-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-dLUIkoYrIJhGXfF2wGLTfb46vUokEsO/OoE21TSfmahYrx7ysTmnwbePsznFaHlwgZhQEh6AlLvthLCeY21b1A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@preact/signals-core": "^1.10.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/state/node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/vue": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/vue/-/vue-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-tYX0YzylmkPyjTM8Kv//o90sWNVRCFBtzSu7/Ung2aFMJhfEEUKJoEYRL2V4Cz+fLkOPQFaFqNxSDpNKTeWNxQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@dnd-kit/abstract": "^0.3.2",
|
||||||
|
"@dnd-kit/dom": "^0.3.2",
|
||||||
|
"@dnd-kit/state": "^0.3.2",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/vue/node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/@electron/asar": {
|
"node_modules/@electron/asar": {
|
||||||
"version": "3.4.1",
|
"version": "3.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz",
|
||||||
@@ -2874,6 +2993,17 @@
|
|||||||
"url": "https://opencollective.com/pkgr"
|
"url": "https://opencollective.com/pkgr"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@preact/signals-core": {
|
||||||
|
"version": "1.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.13.0.tgz",
|
||||||
|
"integrity": "sha512-slT6XeTCAbdql61GVLlGU4x7XHI7kCZV5Um5uhE4zLX4ApgiiXc0UYFvVOKq06xcovzp7p+61l68oPi563ARKg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rolldown/pluginutils": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-rc.2",
|
"version": "1.0.0-rc.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/vrcx-team/VRCX#readme",
|
"homepage": "https://github.com/vrcx-team/VRCX#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@dnd-kit/vue": "^0.3.2",
|
||||||
"@electron/rebuild": "^4.0.3",
|
"@electron/rebuild": "^4.0.3",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"open": "Open",
|
"open": "Open",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"clear": "Clear"
|
"clear": "Clear",
|
||||||
|
"reset": "Reset"
|
||||||
},
|
},
|
||||||
"time_units": {
|
"time_units": {
|
||||||
"y": "y",
|
"y": "y",
|
||||||
@@ -93,7 +94,8 @@
|
|||||||
"sort_secondary": "Then by",
|
"sort_secondary": "Then by",
|
||||||
"sort_tertiary": "Then by",
|
"sort_tertiary": "Then by",
|
||||||
"favorite_groups": "Favorite Groups",
|
"favorite_groups": "Favorite Groups",
|
||||||
"favorite_groups_placeholder": "All Groups"
|
"favorite_groups_placeholder": "All Groups",
|
||||||
|
"edit_group_order": "Edit Group Order"
|
||||||
},
|
},
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"notification_center": {
|
"notification_center": {
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
const isHideFriendsInSameInstance = ref(false);
|
const isHideFriendsInSameInstance = ref(false);
|
||||||
const isSidebarDivideByFriendGroup = ref(false);
|
const isSidebarDivideByFriendGroup = ref(false);
|
||||||
const sidebarFavoriteGroups = ref([]);
|
const sidebarFavoriteGroups = ref([]);
|
||||||
|
const sidebarFavoriteGroupOrder = ref([]);
|
||||||
const hideUserNotes = ref(false);
|
const hideUserNotes = ref(false);
|
||||||
const hideUserMemos = ref(false);
|
const hideUserMemos = ref(false);
|
||||||
const hideUnfriends = ref(false);
|
const hideUnfriends = ref(false);
|
||||||
@@ -152,6 +153,7 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
isHideFriendsInSameInstanceConfig,
|
isHideFriendsInSameInstanceConfig,
|
||||||
isSidebarDivideByFriendGroupConfig,
|
isSidebarDivideByFriendGroupConfig,
|
||||||
sidebarFavoriteGroupsConfig,
|
sidebarFavoriteGroupsConfig,
|
||||||
|
sidebarFavoriteGroupOrderConfig,
|
||||||
hideUserNotesConfig,
|
hideUserNotesConfig,
|
||||||
hideUserMemosConfig,
|
hideUserMemosConfig,
|
||||||
hideUnfriendsConfig,
|
hideUnfriendsConfig,
|
||||||
@@ -208,6 +210,10 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
true
|
true
|
||||||
),
|
),
|
||||||
configRepository.getString('VRCX_sidebarFavoriteGroups', '[]'),
|
configRepository.getString('VRCX_sidebarFavoriteGroups', '[]'),
|
||||||
|
configRepository.getString(
|
||||||
|
'VRCX_sidebarFavoriteGroupOrder',
|
||||||
|
'[]'
|
||||||
|
),
|
||||||
configRepository.getBool('VRCX_hideUserNotes', false),
|
configRepository.getBool('VRCX_hideUserNotes', false),
|
||||||
configRepository.getBool('VRCX_hideUserMemos', false),
|
configRepository.getBool('VRCX_hideUserMemos', false),
|
||||||
configRepository.getBool('VRCX_hideUnfriends', false),
|
configRepository.getBool('VRCX_hideUnfriends', false),
|
||||||
@@ -299,6 +305,9 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
sidebarFavoriteGroups.value = JSON.parse(
|
sidebarFavoriteGroups.value = JSON.parse(
|
||||||
sidebarFavoriteGroupsConfig
|
sidebarFavoriteGroupsConfig
|
||||||
);
|
);
|
||||||
|
sidebarFavoriteGroupOrder.value = JSON.parse(
|
||||||
|
sidebarFavoriteGroupOrderConfig
|
||||||
|
);
|
||||||
hideUserNotes.value = hideUserNotesConfig;
|
hideUserNotes.value = hideUserNotesConfig;
|
||||||
hideUserMemos.value = hideUserMemosConfig;
|
hideUserMemos.value = hideUserMemosConfig;
|
||||||
hideUnfriends.value = hideUnfriendsConfig;
|
hideUnfriends.value = hideUnfriendsConfig;
|
||||||
@@ -717,6 +726,16 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
JSON.stringify(value)
|
JSON.stringify(value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @param {string[]} value
|
||||||
|
*/
|
||||||
|
function setSidebarFavoriteGroupOrder(value) {
|
||||||
|
sidebarFavoriteGroupOrder.value = value;
|
||||||
|
configRepository.setString(
|
||||||
|
'VRCX_sidebarFavoriteGroupOrder',
|
||||||
|
JSON.stringify(value)
|
||||||
|
);
|
||||||
|
}
|
||||||
function setHideUserNotes() {
|
function setHideUserNotes() {
|
||||||
hideUserNotes.value = !hideUserNotes.value;
|
hideUserNotes.value = !hideUserNotes.value;
|
||||||
configRepository.setBool('VRCX_hideUserNotes', hideUserNotes.value);
|
configRepository.setBool('VRCX_hideUserNotes', hideUserNotes.value);
|
||||||
@@ -975,6 +994,7 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
isHideFriendsInSameInstance,
|
isHideFriendsInSameInstance,
|
||||||
isSidebarDivideByFriendGroup,
|
isSidebarDivideByFriendGroup,
|
||||||
sidebarFavoriteGroups,
|
sidebarFavoriteGroups,
|
||||||
|
sidebarFavoriteGroupOrder,
|
||||||
hideUserNotes,
|
hideUserNotes,
|
||||||
hideUserMemos,
|
hideUserMemos,
|
||||||
hideUnfriends,
|
hideUnfriends,
|
||||||
@@ -1013,6 +1033,7 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
setIsHideFriendsInSameInstance,
|
setIsHideFriendsInSameInstance,
|
||||||
setIsSidebarDivideByFriendGroup,
|
setIsSidebarDivideByFriendGroup,
|
||||||
setSidebarFavoriteGroups,
|
setSidebarFavoriteGroups,
|
||||||
|
setSidebarFavoriteGroupOrder,
|
||||||
setHideUserNotes,
|
setHideUserNotes,
|
||||||
setHideUserMemos,
|
setHideUserMemos,
|
||||||
setHideUnfriends,
|
setHideUnfriends,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
class="absolute top-1 right-1.25 size-1.5 rounded-full bg-red-500" />
|
class="absolute top-1 right-1.25 size-1.5 rounded-full bg-red-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
<Popover>
|
<Popover v-model:open="isSettingsPopoverOpen">
|
||||||
<PopoverTrigger as-child>
|
<PopoverTrigger as-child>
|
||||||
<Button class="rounded-full" variant="ghost" size="icon-sm">
|
<Button class="rounded-full" variant="ghost" size="icon-sm">
|
||||||
<Settings />
|
<Settings />
|
||||||
@@ -112,6 +112,17 @@
|
|||||||
:model-value="isSidebarDivideByFriendGroup"
|
:model-value="isSidebarDivideByFriendGroup"
|
||||||
@update:modelValue="setIsSidebarDivideByFriendGroup" />
|
@update:modelValue="setIsSidebarDivideByFriendGroup" />
|
||||||
</div>
|
</div>
|
||||||
|
<Button
|
||||||
|
v-if="isSidebarDivideByFriendGroup"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="w-full text-xs"
|
||||||
|
@click="
|
||||||
|
isSettingsPopoverOpen = false;
|
||||||
|
isGroupOrderSheetOpen = true;
|
||||||
|
">
|
||||||
|
{{ t('side_panel.settings.edit_group_order') }}
|
||||||
|
</Button>
|
||||||
<div class="flex flex-col gap-1.5">
|
<div class="flex flex-col gap-1.5">
|
||||||
<span>{{ t('side_panel.settings.favorite_groups') }}</span>
|
<span>{{ t('side_panel.settings.favorite_groups') }}</span>
|
||||||
<Select
|
<Select
|
||||||
@@ -248,6 +259,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</TabsUnderline>
|
</TabsUnderline>
|
||||||
<NotificationCenterSheet />
|
<NotificationCenterSheet />
|
||||||
|
<GroupOrderSheet v-model:open="isGroupOrderSheetOpen" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -285,6 +297,7 @@
|
|||||||
import { debounce, userImage } from '../../shared/utils';
|
import { debounce, userImage } from '../../shared/utils';
|
||||||
|
|
||||||
import FriendsSidebar from './components/FriendsSidebar.vue';
|
import FriendsSidebar from './components/FriendsSidebar.vue';
|
||||||
|
import GroupOrderSheet from './components/GroupOrderSheet.vue';
|
||||||
import GroupsSidebar from './components/GroupsSidebar.vue';
|
import GroupsSidebar from './components/GroupsSidebar.vue';
|
||||||
import NotificationCenterSheet from './components/NotificationCenterSheet.vue';
|
import NotificationCenterSheet from './components/NotificationCenterSheet.vue';
|
||||||
|
|
||||||
@@ -357,6 +370,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const CLEAR_VALUE = '__clear__';
|
const CLEAR_VALUE = '__clear__';
|
||||||
|
const isGroupOrderSheetOpen = ref(false);
|
||||||
|
const isSettingsPopoverOpen = ref(false);
|
||||||
|
|
||||||
const sortOptions = computed(() => [
|
const sortOptions = computed(() => [
|
||||||
{ value: 'Sort Alphabetically', label: t('view.settings.appearance.side_panel.sorting.alphabetical') },
|
{ value: 'Sort Alphabetically', label: t('view.settings.appearance.side_panel.sorting.alphabetical') },
|
||||||
|
|||||||
@@ -120,7 +120,8 @@
|
|||||||
isSidebarGroupByInstance,
|
isSidebarGroupByInstance,
|
||||||
isHideFriendsInSameInstance,
|
isHideFriendsInSameInstance,
|
||||||
isSidebarDivideByFriendGroup,
|
isSidebarDivideByFriendGroup,
|
||||||
sidebarFavoriteGroups
|
sidebarFavoriteGroups,
|
||||||
|
sidebarFavoriteGroupOrder
|
||||||
} = storeToRefs(useAppearanceSettingsStore());
|
} = storeToRefs(useAppearanceSettingsStore());
|
||||||
const { gameLogDisabled } = storeToRefs(useAdvancedSettingsStore());
|
const { gameLogDisabled } = storeToRefs(useAdvancedSettingsStore());
|
||||||
const { showUserDialog } = useUserStore();
|
const { showUserDialog } = useUserStore();
|
||||||
@@ -228,7 +229,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.sort((a, b) => a[0].key.localeCompare(b[0].key));
|
const order = sidebarFavoriteGroupOrder.value;
|
||||||
|
return result.sort((a, b) => {
|
||||||
|
const idxA = order.indexOf(a[0]?.key);
|
||||||
|
const idxB = order.indexOf(b[0]?.key);
|
||||||
|
if (idxA !== -1 && idxB !== -1) return idxA - idxB;
|
||||||
|
if (idxA !== -1) return -1;
|
||||||
|
if (idxB !== -1) return 1;
|
||||||
|
return (a[0]?.key ?? '').localeCompare(b[0]?.key ?? '');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildToggleRow = ({
|
const buildToggleRow = ({
|
||||||
|
|||||||
132
src/views/Sidebar/components/GroupOrderSheet.vue
Normal file
132
src/views/Sidebar/components/GroupOrderSheet.vue
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<template>
|
||||||
|
<Sheet v-model:open="isOpen">
|
||||||
|
<SheetContent side="right" class="w-80 flex flex-col" @open-auto-focus.prevent>
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>{{ t('side_panel.settings.edit_group_order') }}</SheetTitle>
|
||||||
|
</SheetHeader>
|
||||||
|
<div class="flex-1 overflow-auto p-3">
|
||||||
|
<DragDropProvider @dragEnd="onDragEnd">
|
||||||
|
<div class="flex flex-col gap-1.5">
|
||||||
|
<SortableGroupItem
|
||||||
|
v-for="(item, index) in localOrder"
|
||||||
|
:key="item.key"
|
||||||
|
:id="item.key"
|
||||||
|
:index="index"
|
||||||
|
:label="item.displayName" />
|
||||||
|
</div>
|
||||||
|
</DragDropProvider>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end gap-2 border-t p-3">
|
||||||
|
<Button variant="secondary" size="sm" @click="resetOrder">
|
||||||
|
{{ t('common.actions.reset') }}
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" @click="confirmOrder">
|
||||||
|
{{ t('common.actions.confirm') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { DragDropProvider } from '@dnd-kit/vue';
|
||||||
|
import { isSortable } from '@dnd-kit/vue/sortable';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import { useAppearanceSettingsStore, useFavoriteStore } from '../../../stores';
|
||||||
|
|
||||||
|
import SortableGroupItem from './SortableGroupItem.vue';
|
||||||
|
|
||||||
|
const isOpen = defineModel('open', { type: Boolean, default: false });
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const appearanceStore = useAppearanceSettingsStore();
|
||||||
|
const { sidebarFavoriteGroups, sidebarFavoriteGroupOrder } = storeToRefs(appearanceStore);
|
||||||
|
const { setSidebarFavoriteGroupOrder } = appearanceStore;
|
||||||
|
|
||||||
|
const favoriteStore = useFavoriteStore();
|
||||||
|
const { favoriteFriendGroups, localFriendFavoriteGroups } = storeToRefs(favoriteStore);
|
||||||
|
|
||||||
|
const allGroupItems = computed(() => {
|
||||||
|
const items = [];
|
||||||
|
for (const group of favoriteFriendGroups.value) {
|
||||||
|
items.push({ key: group.key, displayName: group.displayName });
|
||||||
|
}
|
||||||
|
for (const name of localFriendFavoriteGroups.value) {
|
||||||
|
items.push({ key: `local:${name}`, displayName: name });
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedGroupKeys = computed(() => {
|
||||||
|
if (sidebarFavoriteGroups.value.length === 0) {
|
||||||
|
return allGroupItems.value.map((g) => g.key);
|
||||||
|
}
|
||||||
|
return sidebarFavoriteGroups.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const localOrder = ref([]);
|
||||||
|
|
||||||
|
function buildOrderedList() {
|
||||||
|
const selected = new Set(selectedGroupKeys.value);
|
||||||
|
const persistedOrder = sidebarFavoriteGroupOrder.value;
|
||||||
|
const itemMap = new Map(allGroupItems.value.map((g) => [g.key, g]));
|
||||||
|
|
||||||
|
const ordered = [];
|
||||||
|
for (const key of persistedOrder) {
|
||||||
|
if (selected.has(key) && itemMap.has(key)) {
|
||||||
|
ordered.push(itemMap.get(key));
|
||||||
|
selected.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key of selectedGroupKeys.value) {
|
||||||
|
if (selected.has(key) && itemMap.has(key)) {
|
||||||
|
ordered.push(itemMap.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(isOpen, (open) => {
|
||||||
|
if (open) {
|
||||||
|
localOrder.value = buildOrderedList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function onDragEnd(event) {
|
||||||
|
if (event.canceled) return;
|
||||||
|
const { source } = event.operation;
|
||||||
|
if (isSortable(source)) {
|
||||||
|
const { initialIndex, index } = source;
|
||||||
|
if (initialIndex !== index) {
|
||||||
|
const newOrder = [...localOrder.value];
|
||||||
|
const [removed] = newOrder.splice(initialIndex, 1);
|
||||||
|
newOrder.splice(index, 0, removed);
|
||||||
|
localOrder.value = newOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmOrder() {
|
||||||
|
const currentKeys = localOrder.value.map((g) => g.key);
|
||||||
|
const persistedOrder = sidebarFavoriteGroupOrder.value;
|
||||||
|
const merged = [...currentKeys];
|
||||||
|
for (const key of persistedOrder) {
|
||||||
|
if (!merged.includes(key)) {
|
||||||
|
merged.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSidebarFavoriteGroupOrder(merged);
|
||||||
|
isOpen.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetOrder() {
|
||||||
|
setSidebarFavoriteGroupOrder([]);
|
||||||
|
localOrder.value = buildOrderedList();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
29
src/views/Sidebar/components/SortableGroupItem.vue
Normal file
29
src/views/Sidebar/components/SortableGroupItem.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { GripVertical } from 'lucide-vue-next';
|
||||||
|
import { useSortable } from '@dnd-kit/vue/sortable';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: { type: String, required: true },
|
||||||
|
index: { type: Number, required: true },
|
||||||
|
label: { type: String, required: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
const element = ref(null);
|
||||||
|
|
||||||
|
const { isDragSource } = useSortable({
|
||||||
|
id: computed(() => props.id),
|
||||||
|
index: computed(() => props.index),
|
||||||
|
element
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="element"
|
||||||
|
class="flex items-center gap-2 rounded-md border bg-background px-3 py-2 text-sm select-none cursor-grab active:cursor-grabbing"
|
||||||
|
:class="{ 'opacity-50': isDragSource }">
|
||||||
|
<GripVertical class="size-4 shrink-0 text-muted-foreground" />
|
||||||
|
<span class="truncate">{{ label }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user