Compare commits

...

2 Commits

Author SHA1 Message Date
05e8782557 💄 Optimized attachments view 2024-03-30 20:24:26 +08:00
e986ff8c5f 💄 Better speed dial 2024-03-30 20:08:39 +08:00
6 changed files with 198 additions and 77 deletions

View File

@ -3,33 +3,53 @@
Attached {{ props.attachments.length }} attachment(s) Attached {{ props.attachments.length }} attachment(s)
</v-chip> </v-chip>
<v-card v-else variant="outlined" class="max-w-[540px] max-h-[720px]"> <v-responsive v-else :aspect-ratio="16 / 9" max-height="720">
<v-carousel hide-delimiter-background height="100%" :show-arrows="false"> <v-card variant="outlined" class="w-full h-full">
<v-carousel-item v-for="item in attachments"> <v-carousel
<img v-if="item.type === 1" :src="getUrl(item)" :alt="item.filename" class="cursor-zoom-in" hide-delimiter-background
@click="openLightbox" /> height="100%"
<video v-if="item.type === 2" controls class="w-full"> :hide-delimiters="props.attachments.length <= 1"
<source :src="getUrl(item)" /> :show-arrows="false"
</video> >
<div v-if="item.type === 3" class="w-full px-7 py-12"> <v-carousel-item v-for="(item, idx) in attachments">
<audio controls :src="getUrl(item)" class="mx-auto"></audio> <img
</div> v-if="item.type === 1"
</v-carousel-item> loading="lazy"
</v-carousel> decoding="async"
class="cursor-zoom-in content-visibility-auto"
:src="getUrl(item)"
:alt="item.filename"
@click="openLightbox(item, idx)"
/>
<video v-if="item.type === 2" controls class="w-full content-visibility-auto">
<source :src="getUrl(item)" />
</video>
<div v-if="item.type === 3" class="w-full px-7 py-12">
<audio controls :src="getUrl(item)" class="mx-auto"></audio>
</div>
</v-carousel-item>
</v-carousel>
<vue-easy-lightbox teleport="#app" :visible="lightbox" :imgs="[getUrl(current)]" @hide="lightbox = false"> <vue-easy-lightbox
<template v-slot:close-btn="{ close }"> teleport="#app"
<v-btn :visible="lightbox"
class="fixed left-2 top-2" :imgs="props.attachments.map((x) => getUrl(x))"
icon="mdi-close" v-model:index="currentIndex"
variant="text" @hide="lightbox = false"
color="white" >
:style="`margin-top: ${safeAreaTop}`" <template v-slot:close-btn="{ close }">
@click="close" <v-btn
/> class="fixed left-2 top-2"
</template> icon="mdi-close"
</vue-easy-lightbox> variant="text"
</v-card> color="white"
:style="`margin-top: ${safeAreaTop}`"
@click="close"
/>
</template>
</vue-easy-lightbox>
</v-card>
</v-responsive>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -43,23 +63,24 @@ const props = defineProps<{ attachments: any[]; overview?: boolean }>()
const ui = useUI() const ui = useUI()
const lightbox = ref(false) const lightbox = ref(false)
const focus = ref(0)
const current = computed(() => props.attachments[focus.value]) const current = ref<any>(null)
const canLightbox = computed(() => current.value.type === 1) const currentIndex = ref(0)
const safeAreaTop = computed(() => { const safeAreaTop = computed(() => {
return `${ui.safeArea.top}px` return `${ui.safeArea.top}px`
}) })
function getUrl(item: any) { function getUrl(item: any) {
return item.external_url ? item.external_url : buildRequestUrl("interactive", `/api/attachments/o/${item.file_id}`) return item.external_url
? item.external_url
: buildRequestUrl("interactive", `/api/attachments/o/${item.file_id}`)
} }
function openLightbox() { function openLightbox(item: any, idx: number) {
if (canLightbox.value) { current.value = item
lightbox.value = true currentIndex.value = idx
} lightbox.value = true
} }
</script> </script>

View File

@ -11,13 +11,13 @@
> >
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">
<v-toolbar <v-toolbar
class="flex items-center justify-between px-[12px] border-opacity-15" class="flex items-center justify-between px-[14px] border-opacity-15"
color="primary" color="primary"
height="64" height="64"
:style="`padding-top: ${safeAreaTop}`" :style="`padding-top: ${safeAreaTop}`"
> >
<div class="flex items-center"> <div class="flex items-center">
<img src="/favicon.png" alt="Logo" width="36" height="36" class="block" /> <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-show="!drawerMini" class="ms-6 font-medium">Solar Network</div>
</div> </div>
@ -83,22 +83,6 @@
<v-main id="main"> <v-main id="main">
<router-view /> <router-view />
</v-main> </v-main>
<v-menu open-on-hover open-on-click :open-delay="0" :close-delay="0" location="top"
transition="scroll-y-reverse-transition">
<template v-slot:activator="{ props }">
<v-fab v-bind="props" appear class="editor-fab" icon="mdi-pencil" color="primary" size="64"
:active="id.userinfo.isLoggedIn" />
</template>
<div class="flex flex-col items-center gap-4 mb-4">
<v-btn variant="elevated" color="secondary" icon="mdi-newspaper-variant" @click="editor.show.article = true" />
<v-btn variant="elevated" color="accent" icon="mdi-camera-iris" @click="editor.show.moment = true" />
</div>
</v-menu>
<post-tools />
<realm-tools />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -123,7 +107,6 @@ const safeAreaBottom = computed(() => {
}) })
const id = useUserinfo() const id = useUserinfo()
const editor = useEditor()
const username = computed(() => { const username = computed(() => {
if (id.userinfo.isLoggedIn) { if (id.userinfo.isLoggedIn) {
@ -154,10 +137,3 @@ const drawerOpen = ref(true)
const drawerMini = ref(false) const drawerMini = ref(false)
</script> </script>
<style scoped>
.editor-fab {
position: fixed !important;
bottom: 16px;
right: 20px;
}
</style>

65
src/layouts/plaza.vue Normal file
View File

@ -0,0 +1,65 @@
<template>
<router-view />
<v-fab
appear
class="editor-fab"
color="primary"
size="64"
:active="id.userinfo.isLoggedIn"
>
<v-icon icon="mdi-pencil" />
<v-speed-dial
target=".editor-fab"
activator="parent"
location="top center"
class="editor-speed-dial"
transition="slide-y-reverse-transition"
open-on-hover
open-on-click
>
<v-btn
key="article"
variant="elevated"
color="secondary"
icon="mdi-newspaper-variant"
@click="editor.show.article = true"
/>
<v-btn
key="moment"
variant="elevated"
color="accent"
icon="mdi-camera-iris"
@click="editor.show.moment = true"
/>
</v-speed-dial>
</v-fab>
<post-tools />
<realm-tools />
</template>
<script setup lang="ts">
import PostTools from "@/components/publish/PostTools.vue"
import RealmTools from "@/components/realms/RealmTools.vue"
import { useEditor } from "@/stores/editor"
import { useUserinfo } from "@/stores/userinfo"
const id = useUserinfo()
const editor = useEditor()
</script>
<style scoped>
.editor-fab {
position: fixed !important;
bottom: 16px;
right: 20px;
}
.editor-speed-dial {
position: fixed !important;
bottom: 16px;
right: 20px;
}
</style>

View File

@ -10,25 +10,42 @@ const router = createRouter({
children: [ children: [
{ {
path: "/", path: "/",
name: "explore", component: () => import("@/layouts/plaza.vue"),
component: () => import("@/views/explore.vue") children: [
{
path: "/",
name: "explore",
component: () => import("@/views/explore.vue")
},
{
path: "/p/moments/:alias",
name: "posts.details.moments",
component: () => import("@/views/posts/moments.vue")
},
{
path: "/p/articles/:alias",
name: "posts.details.articles",
component: () => import("@/views/posts/articles.vue")
},
{
path: "/realms/:realmId",
name: "realms.page",
component: () => import("@/views/realms/page.vue")
}
]
}, },
{ {
path: "/p/moments/:alias", path: "/chat",
name: "posts.details.moments", children: [
component: () => import("@/views/posts/moments.vue") {
}, path: ":channel",
{ name: "chat.channel",
path: "/p/articles/:alias", component: () => import("@/views/chat/channel.vue")
name: "posts.details.articles", }
component: () => import("@/views/posts/articles.vue") ]
},
{
path: "/realms/:realmId",
name: "realms.page",
component: () => import("@/views/realms/page.vue")
}, },
{ {

View File

@ -3,7 +3,8 @@ import { Preferences } from "@capacitor/preferences"
const serviceMap: { [id: string]: string } = { const serviceMap: { [id: string]: string } = {
interactive: "https://co.solsynth.dev", interactive: "https://co.solsynth.dev",
identity: "https://id.solsynth.dev" identity: "https://id.solsynth.dev",
messaging: "http://localhost:8447",
} }
export async function request(service: string, input: string, init?: RequestInit, noRetry?: boolean) { export async function request(service: string, input: string, init?: RequestInit, noRetry?: boolean) {

View File

@ -0,0 +1,41 @@
<template>
<v-app-bar :order="5" color="grey-lighten-3">
<div class="max-md:px-5 md:px-12 flex flex-grow-1 items-center">
<v-app-bar-nav-icon icon="mdi-chat" :loading="loading" />
<h2 class="ml-2 text-lg font-500">{{ metadata?.name }}</h2>
<p class="ml-3 text-xs opacity-80">{{ metadata?.description }}</p>
</div>
</v-app-bar>
<!-- @vue-ignore -->
<v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar>
</template>
<script setup lang="ts">
import { request } from "@/scripts/request"
import { useRoute } from "vue-router"
import { ref } from "vue"
const route = useRoute()
const error = ref<string | null>(null)
const loading = ref(false)
const metadata = ref<any>(null)
async function readMetadata() {
loading.value = true
const res = await request("messaging", `/api/channels/${route.params.channel}`)
if (res.status !== 200) {
error.value = await res.text()
} else {
error.value = null
metadata.value = await res.json()
}
loading.value = false
}
readMetadata()
</script>