Compare commits
	
		
			2 commits
		
	
	
		
			develop
			...
			bring-back
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | bbbad9f6a1 | ||
|  | ddff409b76 | 
					 5 changed files with 308 additions and 0 deletions
				
			
		|  | @ -2,6 +2,7 @@ | ||||||
| ## 12.x.x (unreleased) | ## 12.x.x (unreleased) | ||||||
| 
 | 
 | ||||||
| ### Improvements | ### Improvements | ||||||
|  | - Add groups UI back @ThatOneCalculator | ||||||
| 
 | 
 | ||||||
| ### Bugfixes | ### Bugfixes | ||||||
| -  | -  | ||||||
|  |  | ||||||
|  | @ -86,6 +86,11 @@ export const navbarItemDef = reactive({ | ||||||
| 		icon: 'fas fa-icons', | 		icon: 'fas fa-icons', | ||||||
| 		to: '/gallery', | 		to: '/gallery', | ||||||
| 	}, | 	}, | ||||||
|  | 	groups: { | ||||||
|  | 		title: 'groups', | ||||||
|  | 		icon: 'fas fa-users', | ||||||
|  | 		to: '/my/groups', | ||||||
|  | 	}, | ||||||
| 	clips: { | 	clips: { | ||||||
| 		title: 'clip', | 		title: 'clip', | ||||||
| 		icon: 'fas fa-paperclip', | 		icon: 'fas fa-paperclip', | ||||||
|  |  | ||||||
							
								
								
									
										188
									
								
								packages/client/src/pages/my-groups/groups.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								packages/client/src/pages/my-groups/groups.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,188 @@ | ||||||
|  | <template> | ||||||
|  | <MkStickyContainer> | ||||||
|  | 	<template #header><MkPageHeader :actions="headerActions"/></template> | ||||||
|  | 	<div class="mk-group-page"> | ||||||
|  | 		<div class="_section members _gap"> | ||||||
|  | 			<div class="_content"> | ||||||
|  | 				<div class="users"> | ||||||
|  | 					<div v-for="user in users" :key="user.id" class="user _panel"> | ||||||
|  | 						<MkAvatar :user="user" class="avatar" :show-indicator="true" /> | ||||||
|  | 						<div class="body"> | ||||||
|  | 							<MkUserName :user="user" class="name" /> | ||||||
|  | 							<MkAcct :user="user" class="acct" /> | ||||||
|  | 						</div> | ||||||
|  | 						<div class="action"> | ||||||
|  | 							<button class="_button" @click="removeUser(user)"> | ||||||
|  | 								<i class="fas fa-times"></i> | ||||||
|  | 							</button> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </MkStickyContainer> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { computed, ref, watch } from "vue"; | ||||||
|  | import { definePageMetadata } from "@/scripts/page-metadata"; | ||||||
|  | import { i18n } from "@/i18n"; | ||||||
|  | import { useRouter } from "@/router"; | ||||||
|  | import * as os from "@/os"; | ||||||
|  | 
 | ||||||
|  | const props = defineProps<{ | ||||||
|  | 	groupId: { | ||||||
|  | 		type: string; | ||||||
|  | 		required: true; | ||||||
|  | 	}; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | const users = ref<any[]>([]); | ||||||
|  | const group = ref<any>(); | ||||||
|  | 
 | ||||||
|  | const router = useRouter(); | ||||||
|  | 
 | ||||||
|  | watch( | ||||||
|  | 	() => props.groupId, | ||||||
|  | 	() => { | ||||||
|  | 		fetch(); | ||||||
|  | 	} | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | async function fetch() { | ||||||
|  | 	os.api("users/groups/show", { | ||||||
|  | 		groupId: props.groupId, | ||||||
|  | 	}).then((gp) => { | ||||||
|  | 		group.value = gp; | ||||||
|  | 		os.api("users/show", { | ||||||
|  | 			userIds: group.value.userIds, | ||||||
|  | 		}).then((us) => { | ||||||
|  | 			users.value = us; | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fetch(); | ||||||
|  | 
 | ||||||
|  | function invite() { | ||||||
|  | 	os.selectUser().then((user) => { | ||||||
|  | 		os.apiWithDialog("users/groups/invite", { | ||||||
|  | 			groupId: group.value.id, | ||||||
|  | 			userId: user.id, | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function removeUser(user) { | ||||||
|  | 	os.api("users/groups/pull", { | ||||||
|  | 		groupId: group.value.id, | ||||||
|  | 		userId: user.id, | ||||||
|  | 	}).then(() => { | ||||||
|  | 		users.value = users.value.filter((x) => x.id !== user.id); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function renameGroup() { | ||||||
|  | 	const { canceled, result: name } = await os.inputText({ | ||||||
|  | 		title: i18n.ts.groupName, | ||||||
|  | 		default: group.value.name, | ||||||
|  | 	}); | ||||||
|  | 	if (canceled) return; | ||||||
|  | 
 | ||||||
|  | 	await os.api("users/groups/update", { | ||||||
|  | 		groupId: group.value.id, | ||||||
|  | 		name: name, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	group.value.name = name; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function transfer() { | ||||||
|  | 	os.selectUser().then((user) => { | ||||||
|  | 		os.apiWithDialog("users/groups/transfer", { | ||||||
|  | 			groupId: group.value.id, | ||||||
|  | 			userId: user.id, | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function deleteGroup() { | ||||||
|  | 	const { canceled } = await os.confirm({ | ||||||
|  | 		type: "warning", | ||||||
|  | 		text: i18n.t("removeAreYouSure", { x: group.value.name }), | ||||||
|  | 	}); | ||||||
|  | 	if (canceled) return; | ||||||
|  | 
 | ||||||
|  | 	await os.apiWithDialog("users/groups/delete", { | ||||||
|  | 		groupId: group.value.id, | ||||||
|  | 	}); | ||||||
|  | 	router.push("/my/groups"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | definePageMetadata( | ||||||
|  | 	computed(() => ({ | ||||||
|  | 		title: i18n.ts.members, | ||||||
|  | 		icon: "fas fa-users", | ||||||
|  | 	})), | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | const headerActions = $computed(() => [ | ||||||
|  | 	{ | ||||||
|  | 		icon: 'fas fa-plus', | ||||||
|  | 		text: i18n.ts.invite, | ||||||
|  | 		handler: invite, | ||||||
|  | 	}, { | ||||||
|  | 		icon: 'fas fa-i-cursor', | ||||||
|  | 		text: i18n.ts.rename, | ||||||
|  | 		handler: renameGroup, | ||||||
|  | 	}, { | ||||||
|  | 		icon: 'fas fa-right-left', | ||||||
|  | 		text: i18n.ts.transfer, | ||||||
|  | 		handler: transfer, | ||||||
|  | 	}, { | ||||||
|  | 		icon: 'fas fa-trash-alt', | ||||||
|  | 		text: i18n.ts.delete, | ||||||
|  | 		handler: deleteGroup, | ||||||
|  | 	}, | ||||||
|  | ]); | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .mk-group-page { | ||||||
|  | 	> .members { | ||||||
|  | 		> ._content { | ||||||
|  | 			> .users { | ||||||
|  | 				> ._panel { | ||||||
|  | 					margin: 1rem 2rem; | ||||||
|  | 				} | ||||||
|  | 				> .user { | ||||||
|  | 					display: flex; | ||||||
|  | 					align-items: center; | ||||||
|  | 					padding: 16px; | ||||||
|  | 
 | ||||||
|  | 					> .avatar { | ||||||
|  | 						width: 50px; | ||||||
|  | 						height: 50px; | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					> .body { | ||||||
|  | 						flex: 1; | ||||||
|  | 						padding: 8px; | ||||||
|  | 
 | ||||||
|  | 						> .name { | ||||||
|  | 							display: block; | ||||||
|  | 							font-weight: bold; | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						> .acct { | ||||||
|  | 							opacity: 0.5; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										106
									
								
								packages/client/src/pages/my-groups/index.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								packages/client/src/pages/my-groups/index.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,106 @@ | ||||||
|  | <template> | ||||||
|  | <MkStickyContainer> | ||||||
|  | 	<template #header><MkPageHeader :actions="headerActions"/></template> | ||||||
|  | 	<MkSpacer :content-max="800" :margin-min="20"> | ||||||
|  | 		<MkButton primary style="margin: 0 auto var(--margin) auto;" @click="create"><i class="fas fa-plus"></i> {{ i18n.ts.createGroup }}</MkButton> | ||||||
|  | 		<MkPagination v-slot="{items}" ref="owned" :pagination="ownedPagination"> | ||||||
|  | 			<div v-for="group in items" :key="group.id" class="_card"> | ||||||
|  | 				<div class="_title"><MkA :to="`/my/groups/${ group.id }`" class="_link">{{ group.name }}</MkA></div> | ||||||
|  | 				<div class="_content"> | ||||||
|  | 					<MkAvatars :user-ids="group.userIds"/> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 		</MkPagination> | ||||||
|  | 		<MkPagination v-slot="{items}" ref="joined" :pagination="joinedPagination"> | ||||||
|  | 			<div v-for="group in items" :key="group.id" class="_card"> | ||||||
|  | 				<div class="_title">{{ group.name }}</div> | ||||||
|  | 				<div class="_content"> | ||||||
|  | 					<MkAvatars :user-ids="group.userIds"/> | ||||||
|  | 				</div> | ||||||
|  | 				<div class="_footer"> | ||||||
|  | 					<MkButton danger @click="leave(group)">{{ i18n.ts.leaveGroup }}</MkButton> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 		</MkPagination> | ||||||
|  | 	</MkSpacer> | ||||||
|  | </MkStickyContainer> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { computed, ref } from 'vue'; | ||||||
|  | import MkPagination from '@/components/MkPagination.vue'; | ||||||
|  | import MkButton from '@/components/MkButton.vue'; | ||||||
|  | import MkAvatars from '@/components/MkAvatars.vue'; | ||||||
|  | import * as os from '@/os'; | ||||||
|  | import { definePageMetadata } from "@/scripts/page-metadata"; | ||||||
|  | import { i18n } from '@/i18n'; | ||||||
|  | import MkStickyContainer from '@/components/global/MkStickyContainer.vue'; | ||||||
|  | 
 | ||||||
|  | const owned = ref('owned'); | ||||||
|  | const joined = ref('joined'); | ||||||
|  | 
 | ||||||
|  | const ownedPagination = { | ||||||
|  | 	endpoint: 'users/groups/owned' as const, | ||||||
|  | 	limit: 10, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const joinedPagination = { | ||||||
|  | 	endpoint: 'users/groups/joined' as const, | ||||||
|  | 	limit: 10, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const headerActions = $computed(() => [ | ||||||
|  | 	{ | ||||||
|  | 		icon: 'fas fa-plus', | ||||||
|  | 		text: i18n.ts.createGroup, | ||||||
|  | 		handler: create, | ||||||
|  | 	}, | ||||||
|  | ]); | ||||||
|  | 
 | ||||||
|  | definePageMetadata( | ||||||
|  | 	computed(() => ({ | ||||||
|  | 		title: i18n.ts.groups, | ||||||
|  | 		icon: "fas fa-users", | ||||||
|  | 	})), | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | async function create() { | ||||||
|  | 	const { canceled, result: name } = await os.inputText({ | ||||||
|  | 		title: i18n.ts.groupName, | ||||||
|  | 	}); | ||||||
|  | 	if (canceled) return; | ||||||
|  | 	await os.api('users/groups/create', { name: name }); | ||||||
|  | 	owned.value.reload(); | ||||||
|  | 	os.success(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function leave(group) { | ||||||
|  | 	const { canceled } = await os.confirm({ | ||||||
|  | 		type: 'warning', | ||||||
|  | 		text: i18n.t('leaveGroupConfirm', { name: group.name }), | ||||||
|  | 	}); | ||||||
|  | 	if (canceled) return; | ||||||
|  | 	os.apiWithDialog('users/groups/leave', { | ||||||
|  | 		groupId: group.id, | ||||||
|  | 	}).then(() => { | ||||||
|  | 		joined.value.reload(); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | ._card { | ||||||
|  | 	margin-bottom: 1rem; | ||||||
|  | 	._title { | ||||||
|  | 		font-size: 1.2rem; | ||||||
|  | 		font-weight: bold; | ||||||
|  | 	} | ||||||
|  | 	._content { | ||||||
|  | 		margin: 1rem 0; | ||||||
|  | 	} | ||||||
|  | 	._footer { | ||||||
|  | 		display: flex; | ||||||
|  | 		justify-content: flex-end; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -425,6 +425,14 @@ export const routes = [{ | ||||||
| 	path: '/my/clips', | 	path: '/my/clips', | ||||||
| 	component: page(() => import('./pages/my-clips/index.vue')), | 	component: page(() => import('./pages/my-clips/index.vue')), | ||||||
| 	loginRequired: true, | 	loginRequired: true, | ||||||
|  | }, { | ||||||
|  | 	path: '/my/groups', | ||||||
|  | 	component: page(() => import('./pages/my-groups/index.vue')), | ||||||
|  | 	loginRequired: true, | ||||||
|  | }, { | ||||||
|  | 	path: '/my/groups/:groupId', | ||||||
|  | 	component: page(() => import('./pages/my-groups/group.vue')), | ||||||
|  | 	loginRequired: true, | ||||||
| }, { | }, { | ||||||
| 	path: '/my/antennas/create', | 	path: '/my/antennas/create', | ||||||
| 	component: page(() => import('./pages/my-antennas/create.vue')), | 	component: page(() => import('./pages/my-antennas/create.vue')), | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue