💄 Better layout design
This commit is contained in:
		
							
								
								
									
										41
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,39 +1,6 @@ | |||||||
| # @hydrogen/solaragent | # SolarAgent | ||||||
|  |  | ||||||
| This template should help get you started developing with Vue 3 in Vite. | Hola! This is Project Hydrogen's universal frontend. | ||||||
|  | Integrated support for Identity, Interactive and Messaging! | ||||||
|  |  | ||||||
| ## Recommended IDE Setup | Also provide a mobile version that powered by capacitor! | ||||||
|  |  | ||||||
| [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). |  | ||||||
|  |  | ||||||
| ## Type Support for `.vue` Imports in TS |  | ||||||
|  |  | ||||||
| TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. |  | ||||||
|  |  | ||||||
| ## Customize configuration |  | ||||||
|  |  | ||||||
| See [Vite Configuration Reference](https://vitejs.dev/config/). |  | ||||||
|  |  | ||||||
| ## Project Setup |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| bun install |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Compile and Hot-Reload for Development |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| bun dev |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Type-Check, Compile and Minify for Production |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| bun build |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Lint with [ESLint](https://eslint.org/) |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| bun lint |  | ||||||
| ``` |  | ||||||
| @@ -20,6 +20,7 @@ | |||||||
|     "@capacitor/preferences": "^5.0.7", |     "@capacitor/preferences": "^5.0.7", | ||||||
|     "@fontsource/roboto": "^5.0.12", |     "@fontsource/roboto": "^5.0.12", | ||||||
|     "@mdi/font": "^7.4.47", |     "@mdi/font": "^7.4.47", | ||||||
|  |     "@vueuse/core": "^10.9.0", | ||||||
|     "dayjs": "^1.11.10", |     "dayjs": "^1.11.10", | ||||||
|     "dompurify": "^3.0.11", |     "dompurify": "^3.0.11", | ||||||
|     "marked": "^12.0.1", |     "marked": "^12.0.1", | ||||||
|   | |||||||
							
								
								
									
										152
									
								
								src/components/navigation/NavigationDrawer.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								src/components/navigation/NavigationDrawer.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | <template> | ||||||
|  |   <v-navigation-drawer | ||||||
|  |     v-model="ui.drawer.open" | ||||||
|  |     color="grey-lighten-5" | ||||||
|  |     width="320" | ||||||
|  |     :permanent="isLargeScreen" | ||||||
|  |     :rail="ui.drawer.mini" | ||||||
|  |     :rail-width="58" | ||||||
|  |     :order="0" | ||||||
|  |     floating | ||||||
|  |     @click="ui.drawer.mini = false" | ||||||
|  |   > | ||||||
|  |     <div class="flex flex-col h-full"> | ||||||
|  |       <v-toolbar | ||||||
|  |         class="flex items-center justify-between px-[14px] border-opacity-15" | ||||||
|  |         color="primary" | ||||||
|  |         height="64" | ||||||
|  |         :style="`padding-top: ${safeAreaTop}`" | ||||||
|  |       > | ||||||
|  |         <div class="flex items-center"> | ||||||
|  |           <img src="/favicon.png" alt="Logo" width="32" height="32" class="block" /> | ||||||
|  |           <div v-show="!ui.drawer.mini" class="ms-6 font-medium">Solar Network</div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <v-spacer /> | ||||||
|  |  | ||||||
|  |         <div> | ||||||
|  |           <v-btn | ||||||
|  |             v-if="isLargeScreen" | ||||||
|  |             v-show="!ui.drawer.mini" | ||||||
|  |             icon="mdi-arrow-collapse-left" | ||||||
|  |             size="small" | ||||||
|  |             variant="text" | ||||||
|  |             @click.stop="ui.drawer.mini = true" | ||||||
|  |           /> | ||||||
|  |         </div> | ||||||
|  |       </v-toolbar> | ||||||
|  |  | ||||||
|  |       <div class="flex-grow-1"> | ||||||
|  |         <v-list | ||||||
|  |           class="nav-list" | ||||||
|  |           density="compact" | ||||||
|  |           :opened="ui.drawer.mini ? [] : expanded.nav" | ||||||
|  |           @update:opened="(val) => expanded.nav = val" | ||||||
|  |         > | ||||||
|  |           <v-list-item | ||||||
|  |             exact | ||||||
|  |             title="Explore" | ||||||
|  |             prepend-icon="mdi-compass" | ||||||
|  |             :to="{ name: 'explore' }" | ||||||
|  |           /> | ||||||
|  |         </v-list> | ||||||
|  |  | ||||||
|  |         <v-divider class="border-opacity-75 my-2" /> | ||||||
|  |  | ||||||
|  |         <v-list | ||||||
|  |           class="resources-list" | ||||||
|  |           density="compact" | ||||||
|  |           :opened="ui.drawer.mini ? [] : expanded.resources" | ||||||
|  |           @update:opened="(val) => expanded.resources = val" | ||||||
|  |         > | ||||||
|  |           <channel-list /> | ||||||
|  |           <realm-list /> | ||||||
|  |         </v-list> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <!-- User info --> | ||||||
|  |       <v-list | ||||||
|  |         class="border-opacity-15 h-[64px]" | ||||||
|  |         style="border-top-width: thin" | ||||||
|  |         :style="`margin-bottom: ${safeAreaBottom}`" | ||||||
|  |       > | ||||||
|  |         <v-list-item :subtitle="username" :title="nickname"> | ||||||
|  |           <template #prepend> | ||||||
|  |             <v-avatar icon="mdi-account-circle" :image="id.userinfo.data?.picture" /> | ||||||
|  |           </template> | ||||||
|  |           <template #append> | ||||||
|  |             <div v-if="id.userinfo.isLoggedIn"> | ||||||
|  |               <notification-list /> | ||||||
|  |               <user-menu /> | ||||||
|  |             </div> | ||||||
|  |           </template> | ||||||
|  |         </v-list-item> | ||||||
|  |       </v-list> | ||||||
|  |     </div> | ||||||
|  |   </v-navigation-drawer> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { computed, onMounted, reactive, ref } from "vue" | ||||||
|  | import { useUserinfo } from "@/stores/userinfo" | ||||||
|  | import { useWellKnown } from "@/stores/wellKnown" | ||||||
|  | import { useUI } from "@/stores/ui" | ||||||
|  | import { useRealms } from "@/stores/realms" | ||||||
|  | import { useChannels } from "@/stores/channels" | ||||||
|  | import { useMediaQuery } from "@vueuse/core" | ||||||
|  | import PullToRefresh from "pulltorefreshjs" | ||||||
|  | import UserMenu from "@/components/users/UserMenu.vue" | ||||||
|  | import RealmList from "@/components/realms/RealmList.vue" | ||||||
|  | import ChannelList from "@/components/chat/channels/ChannelList.vue" | ||||||
|  | import NotificationList from "@/components/users/NotificationList.vue" | ||||||
|  |  | ||||||
|  | const ui = useUI() | ||||||
|  | const expanded = reactive<{ [id: string]: string[] }>({ | ||||||
|  |   nav: [], | ||||||
|  |   resources: ["channels", "realms"] | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const isLargeScreen = useMediaQuery("(min-width: 768px)") | ||||||
|  |  | ||||||
|  | const safeAreaTop = computed(() => { | ||||||
|  |   return `${ui.safeArea.top}px` | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const safeAreaBottom = computed(() => { | ||||||
|  |   return `${ui.safeArea.bottom}px` | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const id = useUserinfo() | ||||||
|  |  | ||||||
|  | const username = computed(() => { | ||||||
|  |   if (id.userinfo.isLoggedIn) { | ||||||
|  |     return "@" + id.userinfo.data?.name | ||||||
|  |   } else { | ||||||
|  |     return "@vistor" | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | const nickname = computed(() => { | ||||||
|  |   if (id.userinfo.isLoggedIn) { | ||||||
|  |     return id.userinfo.data?.nick | ||||||
|  |   } else { | ||||||
|  |     return "Anonymous" | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | id.readProfiles() | ||||||
|  |  | ||||||
|  | useWellKnown().readWellKnown() | ||||||
|  |  | ||||||
|  | onMounted(() => { | ||||||
|  |   PullToRefresh.init({ | ||||||
|  |     mainElement: ".resources-list", | ||||||
|  |     triggerElement: ".resources-list", | ||||||
|  |     onRefresh() { | ||||||
|  |       return Promise.all([ | ||||||
|  |         useRealms().list(), | ||||||
|  |         useChannels().list() | ||||||
|  |       ]) | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | }) | ||||||
|  | </script> | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| <template> | <template> | ||||||
|   <v-menu eager :close-on-content-click="false"> |   <v-menu location="top" :close-on-content-click="false"> | ||||||
|     <template #activator="{ props }"> |     <template #activator="{ props }"> | ||||||
|       <v-btn v-bind="props" icon size="small" variant="text" :loading="loading"> |       <v-btn v-bind="props" icon size="small" color="teal" variant="text" :loading="loading"> | ||||||
|         <v-badge v-if="notify.total > 0" color="error" :content="notify.total"> |         <v-badge v-if="notify.total > 0" color="error" :content="notify.total"> | ||||||
|           <v-icon icon="mdi-bell" /> |           <v-icon icon="mdi-bell" /> | ||||||
|         </v-badge> |         </v-badge> | ||||||
|   | |||||||
| @@ -4,12 +4,12 @@ | |||||||
|       <v-btn icon="mdi-menu-up" size="small" variant="text" v-bind="props" /> |       <v-btn icon="mdi-menu-up" size="small" variant="text" v-bind="props" /> | ||||||
|     </template> |     </template> | ||||||
|  |  | ||||||
|     <v-list density="compact" v-if="!id.userinfo.isLoggedIn"> |     <v-list class="w-[280px]" density="compact" v-if="!id.userinfo.isLoggedIn"> | ||||||
|       <v-list-item title="Sign in" prepend-icon="mdi-login-variant" :to="{ name: 'auth.sign-in' }" /> |       <v-list-item title="Sign in" prepend-icon="mdi-login-variant" :to="{ name: 'auth.sign-in' }" /> | ||||||
|       <v-list-item title="Create account" prepend-icon="mdi-account-plus" :to="{ name: 'auth.sign-up' }" /> |       <v-list-item title="Create account" prepend-icon="mdi-account-plus" :to="{ name: 'auth.sign-up' }" /> | ||||||
|     </v-list> |     </v-list> | ||||||
|  |  | ||||||
|     <v-list density="compact" v-else> |     <v-list class="w-[280px]" density="compact" v-else> | ||||||
|       <v-list-item title="Settings" prepend-icon="mdi-cog" exact :to="{ name: 'settings' }" /> |       <v-list-item title="Settings" prepend-icon="mdi-cog" exact :to="{ name: 'settings' }" /> | ||||||
|  |  | ||||||
|       <v-divider class="border-opacity-50 my-2" /> |       <v-divider class="border-opacity-50 my-2" /> | ||||||
|   | |||||||
| @@ -1,83 +1,15 @@ | |||||||
| <template> | <template> | ||||||
|   <v-navigation-drawer |   <NavigationDrawer /> | ||||||
|     v-model="drawerOpen" |  | ||||||
|     color="grey-lighten-5" |  | ||||||
|     width="320" |  | ||||||
|     :rail="drawerMini" |  | ||||||
|     :rail-width="58" |  | ||||||
|     :order="0" |  | ||||||
|     floating |  | ||||||
|     @click="drawerMini = false" |  | ||||||
|   > |  | ||||||
|     <div class="flex flex-col h-full"> |  | ||||||
|       <v-toolbar |  | ||||||
|         class="flex items-center justify-between px-[14px] border-opacity-15" |  | ||||||
|         color="primary" |  | ||||||
|         height="64" |  | ||||||
|         :style="`padding-top: ${safeAreaTop}`" |  | ||||||
|       > |  | ||||||
|         <div class="flex items-center"> |  | ||||||
|           <img src="/favicon.png" alt="Logo" width="32" height="32" class="block" /> |  | ||||||
|           <div v-show="!drawerMini" class="ms-6 font-medium">Solar Network</div> |  | ||||||
|         </div> |  | ||||||
|  |  | ||||||
|         <v-spacer /> |   <v-app-bar v-if="!isLargeScreen" height="64" color="primary" scroll-behavior="hide" :order="2" flat> | ||||||
|  |  | ||||||
|         <div> |  | ||||||
|           <v-btn |  | ||||||
|             v-show="!drawerMini" |  | ||||||
|             icon="mdi-arrow-collapse-left" |  | ||||||
|             size="small" |  | ||||||
|             variant="text" |  | ||||||
|             @click.stop="drawerMini = true" |  | ||||||
|           /> |  | ||||||
|         </div> |  | ||||||
|       </v-toolbar> |  | ||||||
|  |  | ||||||
|       <div class="flex-grow-1"> |  | ||||||
|         <v-list |  | ||||||
|           class="resources-list" |  | ||||||
|           density="compact" |  | ||||||
|           :opened="drawerMini ? [] : expanded" |  | ||||||
|           @update:opened="(val) => expanded = val" |  | ||||||
|         > |  | ||||||
|           <channel-list /> |  | ||||||
|           <v-divider class="border-opacity-75 my-2" /> |  | ||||||
|           <realm-list /> |  | ||||||
|         </v-list> |  | ||||||
|       </div> |  | ||||||
|  |  | ||||||
|       <!-- User info --> |  | ||||||
|       <v-list |  | ||||||
|         class="border-opacity-15 h-[64px]" |  | ||||||
|         style="border-top-width: thin" |  | ||||||
|         :style="`margin-bottom: ${safeAreaBottom}`" |  | ||||||
|       > |  | ||||||
|         <v-list-item :subtitle="username" :title="nickname"> |  | ||||||
|           <template #prepend> |  | ||||||
|             <v-avatar icon="mdi-account-circle" :image="id.userinfo.data?.picture" /> |  | ||||||
|           </template> |  | ||||||
|           <template #append> |  | ||||||
|             <user-menu /> |  | ||||||
|           </template> |  | ||||||
|         </v-list-item> |  | ||||||
|       </v-list> |  | ||||||
|     </div> |  | ||||||
|   </v-navigation-drawer> |  | ||||||
|  |  | ||||||
|   <v-app-bar height="64" color="primary" scroll-behavior="hide" :order="2" flat> |  | ||||||
|     <div class="max-md:px-5 md:px-12 flex flex-grow-1 items-center"> |     <div class="max-md:px-5 md:px-12 flex flex-grow-1 items-center"> | ||||||
|       <v-app-bar-nav-icon variant="text" @click.stop="drawerOpen = !drawerOpen" /> |       <v-app-bar-nav-icon variant="text" @click.stop="ui.drawer.open = !ui.drawer.open" /> | ||||||
|  |  | ||||||
|       <router-link :to="{ name: 'explore' }"> |       <router-link :to="{ name: 'explore' }"> | ||||||
|         <h2 class="ml-2 text-lg font-500">Solian</h2> |         <h2 class="ml-2 text-lg font-500">Solian</h2> | ||||||
|       </router-link> |       </router-link> | ||||||
|  |  | ||||||
|       <v-spacer /> |       <v-spacer /> | ||||||
|  |  | ||||||
|       <div v-if="id.userinfo.isLoggedIn"> |  | ||||||
|         <notification-list /> |  | ||||||
|       </div> |  | ||||||
|     </div> |     </div> | ||||||
|   </v-app-bar> |   </v-app-bar> | ||||||
|  |  | ||||||
| @@ -87,64 +19,17 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { computed, onMounted, ref } from "vue" |  | ||||||
| import { useUserinfo } from "@/stores/userinfo" | import { useUserinfo } from "@/stores/userinfo" | ||||||
| import { useWellKnown } from "@/stores/wellKnown" | import { useWellKnown } from "@/stores/wellKnown" | ||||||
|  | import { useMediaQuery } from "@vueuse/core" | ||||||
| import { useUI } from "@/stores/ui" | import { useUI } from "@/stores/ui" | ||||||
| import { useRealms } from "@/stores/realms" | import NavigationDrawer from "@/components/navigation/NavigationDrawer.vue" | ||||||
| import { useChannels } from "@/stores/channels" |  | ||||||
| import RealmList from "@/components/realms/RealmList.vue" |  | ||||||
| import NotificationList from "@/components/users/NotificationList.vue" |  | ||||||
| import ChannelList from "@/components/chat/channels/ChannelList.vue" |  | ||||||
| import UserMenu from "@/components/users/UserMenu.vue" |  | ||||||
| import PullToRefresh from "pulltorefreshjs" |  | ||||||
|  |  | ||||||
| const ui = useUI() | const ui = useUI() | ||||||
| const expanded = ref<string[]>(["channels"]) |  | ||||||
|  |  | ||||||
| const safeAreaTop = computed(() => { | const isLargeScreen = useMediaQuery("(min-width: 768px)") | ||||||
|   return `${ui.safeArea.top}px` |  | ||||||
| }) |  | ||||||
|  |  | ||||||
| const safeAreaBottom = computed(() => { |  | ||||||
|   return `${ui.safeArea.bottom}px` |  | ||||||
| }) |  | ||||||
|  |  | ||||||
| const id = useUserinfo() |  | ||||||
|  |  | ||||||
| const username = computed(() => { |  | ||||||
|   if (id.userinfo.isLoggedIn) { |  | ||||||
|     return "@" + id.userinfo.data?.name |  | ||||||
|   } else { |  | ||||||
|     return "@vistor" |  | ||||||
|   } |  | ||||||
| }) |  | ||||||
| const nickname = computed(() => { |  | ||||||
|   if (id.userinfo.isLoggedIn) { |  | ||||||
|     return id.userinfo.data?.nick |  | ||||||
|   } else { |  | ||||||
|     return "Anonymous" |  | ||||||
|   } |  | ||||||
| }) |  | ||||||
|  |  | ||||||
| id.readProfiles() |  | ||||||
|  |  | ||||||
|  | useUserinfo().readProfiles() | ||||||
| useWellKnown().readWellKnown() | useWellKnown().readWellKnown() | ||||||
|  |  | ||||||
| const drawerOpen = ref(true) |  | ||||||
| const drawerMini = ref(false) |  | ||||||
|  |  | ||||||
| onMounted(() => { |  | ||||||
|   PullToRefresh.init({ |  | ||||||
|     mainElement: ".resources-list", |  | ||||||
|     triggerElement: ".resources-list", |  | ||||||
|     onRefresh() { |  | ||||||
|       return Promise.all([ |  | ||||||
|         useRealms().list(), |  | ||||||
|         useChannels().list() |  | ||||||
|       ]) |  | ||||||
|     } |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,11 @@ export const useUI = defineStore("ui", () => { | |||||||
|     messages: false, |     messages: false, | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|  |   const drawer = reactive({ | ||||||
|  |     open: true, | ||||||
|  |     mini: false, | ||||||
|  |   }) | ||||||
|  |  | ||||||
|   const safeArea = reactive({ |   const safeArea = reactive({ | ||||||
|     top: 0, |     top: 0, | ||||||
|     bottom: 0 |     bottom: 0 | ||||||
| @@ -27,5 +32,5 @@ export const useUI = defineStore("ui", () => { | |||||||
|    }, 5000) |    }, 5000) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return { safeArea, snackbar, reconnection, showSnackbar, showErrorSnackbar } |   return { safeArea, snackbar, drawer, reconnection, showSnackbar, showErrorSnackbar } | ||||||
| }) | }) | ||||||
|   | |||||||
| @@ -45,8 +45,10 @@ import { computed, onUnmounted, reactive, ref, watch } from "vue" | |||||||
| import { useRoute } from "vue-router" | import { useRoute } from "vue-router" | ||||||
| import ChatList from "@/components/chat/ChatList.vue" | import ChatList from "@/components/chat/ChatList.vue" | ||||||
| import ChatEditor from "@/components/chat/ChatEditor.vue" | import ChatEditor from "@/components/chat/ChatEditor.vue" | ||||||
|  | import { useUserinfo } from "@/stores/userinfo" | ||||||
|  |  | ||||||
| const ui = useUI() | const ui = useUI() | ||||||
|  | const id = useUserinfo() | ||||||
| const route = useRoute() | const route = useRoute() | ||||||
| const channels = useChannels() | const channels = useChannels() | ||||||
|  |  | ||||||
| @@ -154,17 +156,28 @@ let mounted = false | |||||||
| let jitsiInstance: any | let jitsiInstance: any | ||||||
|  |  | ||||||
| async function mountJitsi() { | async function mountJitsi() { | ||||||
|   joining.value = true |  | ||||||
|   if (mounted) return false |   if (mounted) return false | ||||||
|   if (!channels.call) return |   if (!channels.call) return | ||||||
|  |  | ||||||
|  |   joining.value = true | ||||||
|   const tk = await channels.exchangeCallToken() |   const tk = await channels.exchangeCallToken() | ||||||
|  |   joining.value = false | ||||||
|   if (!tk) return |   if (!tk) return | ||||||
|  |  | ||||||
|   const domain = tk.endpoint.replace("http://", "").replace("https://", "") |   const domain = tk.endpoint.replace("http://", "").replace("https://", "") | ||||||
|   const options = { |   const options = { | ||||||
|     roomName: channels.call.external_id, |     roomName: channels.call.external_id, | ||||||
|     parentNode: document.querySelector("#call"), |     parentNode: document.querySelector("#call"), | ||||||
|     onload: () => joining.value = false, |  | ||||||
|     jwt: tk.token, |     jwt: tk.token, | ||||||
|  |     userInfo: { | ||||||
|  |       avatar: id.userinfo.data?.picture, | ||||||
|  |       displayName: id.userinfo.displayName, | ||||||
|  |     }, | ||||||
|  |     configOverwrite: { | ||||||
|  |       prejoinPageEnabled: true, | ||||||
|  |       startWithAudioMuted: false, | ||||||
|  |       startWithVideoMuted: true | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   // This class imported by the script tag in index.html |   // This class imported by the script tag in index.html | ||||||
|   // @ts-ignore |   // @ts-ignore | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user