✨ Basic Solar Network product page
This commit is contained in:
@@ -27,9 +27,9 @@
|
||||
<div class="flex flex-col" v-if="attachment?.metadata?.ratio">
|
||||
<span class="text-xs font-bold">Aspect Ratio</span>
|
||||
<span>
|
||||
{{ attachment?.metadata?.width }}x{{ attachment?.metadata?.height }}
|
||||
{{ attachment?.metadata?.ratio.toFixed(2) }}
|
||||
</span>
|
||||
{{ attachment?.metadata?.width }}x{{ attachment?.metadata?.height }}
|
||||
{{ attachment?.metadata?.ratio.toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col" v-if="attachment?.mimetype">
|
||||
<span class="text-xs font-bold">Mimetype</span>
|
||||
@@ -44,13 +44,19 @@
|
||||
|
||||
<div class="text-xs text-grey flex flex-col mx-[2.5ch]">
|
||||
<span>Solar Network Attachment Web Preview</span>
|
||||
<span>Powered by <a class="underline" target="_blank" href="https://git.solsynth.dev/Hydrogen/Paperclip">Hydrogen.Paperclip</a></span>
|
||||
<span
|
||||
>Powered by
|
||||
<a class="underline" target="_blank" href="https://git.solsynth.dev/Hydrogen/Paperclip"
|
||||
>Hydrogen.Paperclip</a
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatBytes } from "~/utils/format"
|
||||
import { useDisplay } from "vuetify"
|
||||
|
||||
const route = useRoute()
|
||||
@@ -61,7 +67,9 @@ const firstVideo = ref<string | null>()
|
||||
|
||||
const isMediumScreen = useDisplay().mdAndUp
|
||||
|
||||
const { data: attachment } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/uc/attachments/${route.params.id}/meta`)
|
||||
const { data: attachment } = await useFetch<any>(
|
||||
`${config.public.solarNetworkApi}/cgi/uc/attachments/${route.params.id}/meta`,
|
||||
)
|
||||
|
||||
definePageMeta({
|
||||
layout: "minimal",
|
||||
@@ -76,15 +84,19 @@ if (!attachment.value) {
|
||||
|
||||
const title = computed(() => `Attachment ${attachment.value?.id}`)
|
||||
|
||||
watch(attachment, (value) => {
|
||||
if (value.mimetype.split("/")[0] == "image") {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
|
||||
}
|
||||
watch(
|
||||
attachment,
|
||||
(value) => {
|
||||
if (value.mimetype.split("/")[0] == "image") {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
|
||||
}
|
||||
|
||||
if (value.mimetype.split("/")[0] == "video") {
|
||||
firstVideo.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
if (value.mimetype.split("/")[0] == "video") {
|
||||
firstVideo.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
|
||||
useHead({
|
||||
title: title.value,
|
||||
@@ -106,16 +118,4 @@ useSeoMeta({
|
||||
publisher: "Solar Network",
|
||||
ogSiteName: "Solsynth Capital",
|
||||
})
|
||||
|
||||
function formatBytes(bytes: number, decimals = 2) {
|
||||
if (!+bytes) return "0 Bytes"
|
||||
|
||||
const k = 1024
|
||||
const dm = decimals < 0 ? 0 : decimals
|
||||
const sizes = ["Bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
|
||||
}
|
||||
</script>
|
||||
|
@@ -93,7 +93,7 @@ onMounted(() => {
|
||||
|
||||
const poolOptions = [
|
||||
{ label: "Interactive", description: "Public indexable, no lifecycle.", value: "interactive" },
|
||||
{ label: "Messaging", description: "Has lifecycle, will delete after 14 days.", value: "messaging" },
|
||||
{ label: "Messaging", description: "Has lifecycle, will be deleted after 14 days.", value: "messaging" },
|
||||
{ label: "Sticker", description: "Public indexable, privilege required.", value: "sticker", disabled: true },
|
||||
{ label: "Dedicated Pool", description: "Your own configuration, coming soon.", value: "dedicated", disabled: true },
|
||||
]
|
||||
@@ -201,12 +201,12 @@ async function uploadSingleMultipart(chunkId: string) {
|
||||
const chunkIdx: number = multipartInfo.value["file_chunks"][chunkId]
|
||||
const chunk = content.value.slice(chunkIdx * multipartSize.value, (chunkIdx + 1) * multipartSize.value)
|
||||
|
||||
const data = new FormData()
|
||||
data.set("file", chunk)
|
||||
|
||||
const resp = await solarFetch(`/cgi/uc/attachments/multipart/${multipartInfo.value.rid}/${chunkId}`, {
|
||||
method: "POST",
|
||||
body: data,
|
||||
body: chunk,
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
},
|
||||
signal: AbortSignal.timeout(3 * 60 * 1000),
|
||||
})
|
||||
if (resp.status != 200) throw new Error(await resp.text())
|
||||
|
@@ -1,4 +1,6 @@
|
||||
<template>
|
||||
<canvas ref="canvasRef" class="fixed top-0 left-0 w-screen h-screen opacity-50"></canvas>
|
||||
|
||||
<v-container class="flex flex-col my-2 px-12 gap-[4rem]">
|
||||
<section class="content-section flex flex-col items-center justify-center text-center px-4">
|
||||
<img
|
||||
@@ -10,6 +12,7 @@
|
||||
enter: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: { duration: 0.8 }
|
||||
},
|
||||
}"
|
||||
:src="Logo"
|
||||
@@ -74,7 +77,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Logo from "../assets/logo-w-shadow.png"
|
||||
import Logo from "~/assets/logo-w-shadow.png"
|
||||
|
||||
import { getLocale } from "~/utils/locale"
|
||||
|
||||
@@ -100,6 +103,88 @@ const { data: products } = await useAsyncData("products", () => {
|
||||
.limit(5)
|
||||
.find()
|
||||
})
|
||||
|
||||
const canvasRef = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
const canvas: HTMLCanvasElement = canvasRef.value!
|
||||
const ctx = canvas.getContext("2d")!
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = window.innerWidth * dpr;
|
||||
canvas.height = window.innerHeight * dpr;
|
||||
|
||||
let particles: Particle[] = []
|
||||
const numParticles = 100
|
||||
|
||||
class Particle {
|
||||
x: number
|
||||
y: number
|
||||
vx: number
|
||||
vy: number
|
||||
size: number
|
||||
|
||||
constructor() {
|
||||
this.x = Math.random() * canvas.width
|
||||
this.y = Math.random() * canvas.height
|
||||
this.vx = (Math.random() - 0.5) * 1.5
|
||||
this.vy = (Math.random() - 0.5) * 1.5
|
||||
this.size = Math.random() * 3 + 1
|
||||
}
|
||||
|
||||
move() {
|
||||
this.x += this.vx
|
||||
this.y += this.vy
|
||||
if (this.x <= 0 || this.x >= canvas.width) this.vx *= -1
|
||||
if (this.y <= 0 || this.y >= canvas.height) this.vy *= -1
|
||||
}
|
||||
|
||||
draw() {
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x * dpr, this.y * dpr, this.size * dpr, 0, Math.PI * 2);
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
particles = []
|
||||
for (let i = 0; i < numParticles; i++) {
|
||||
particles.push(new Particle())
|
||||
}
|
||||
}
|
||||
|
||||
function drawLines() {
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
let dx = particles[i].x - particles[j].x;
|
||||
let dy = particles[i].y - particles[j].y;
|
||||
let distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 100) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particles[i].x * dpr, particles[i].y * dpr);
|
||||
ctx.lineTo(particles[j].x * dpr, particles[j].y * dpr);
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
|
||||
ctx.lineWidth = 0.5 * dpr;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function animate() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
particles.forEach((p) => {
|
||||
p.move()
|
||||
p.draw()
|
||||
})
|
||||
drawLines()
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
init()
|
||||
animate()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
185
pages/products/solar-network.vue
Normal file
185
pages/products/solar-network.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<v-container class="flex flex-col my-2 px-12 gap-[4rem]">
|
||||
<section class="content-section flex flex-col items-center justify-center text-center px-4" id="intro">
|
||||
<div class="pt-1/3 mb-4 w-full relative">
|
||||
<img :src="AlphaScreenshot" class="absolute bottom-2 left-0 right-0" />
|
||||
<img
|
||||
v-motion="{
|
||||
initial: {
|
||||
y: 100,
|
||||
opacity: 0,
|
||||
},
|
||||
enter: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: { duration: 0.8 },
|
||||
},
|
||||
}"
|
||||
:src="Icon"
|
||||
alt="Solar Network Logo"
|
||||
class="w-32 h-32 p-2 z-10 mx-auto icon-glow bg-white dark:bg-black shadow-2xl rounded-xl"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold">Solar Network</h1>
|
||||
<p class="mt-2 text-lg">{{ t("solarNetworkDescription") }}</p>
|
||||
<v-btn class="mt-4" color="primary" prepend-icon="mdi-arrow-down" href="#products">{{ t("learnMore") }}</v-btn>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-section flex flex-col items-center justify-center text-center px-4" id="downloads">
|
||||
<h1 class="text-3xl font-bold">{{ t("download") }}</h1>
|
||||
<p class="text-lg">
|
||||
File-hosting & versioning by
|
||||
<nuxt-link class="underline" to="https://github.com/Solsynth/HyperNet.Surface" target="_blank">GitHub</nuxt-link
|
||||
><sup>®</sup>
|
||||
</p>
|
||||
<v-btn
|
||||
v-if="hasPrerelease"
|
||||
slim
|
||||
density="compact"
|
||||
prepend-icon="mdi-beta"
|
||||
variant="text"
|
||||
style="text-transform: none"
|
||||
color="white"
|
||||
@click="showPrerelease = !showPrerelease"
|
||||
>
|
||||
{{ showPrerelease ? t("downloadSwitchRelease") : t("downloadSwitchPrerelease") }}
|
||||
</v-btn>
|
||||
<div class="max-h-[500px] w-full mt-4 text-left">
|
||||
<v-row dense>
|
||||
<v-col cols="12" md="6">
|
||||
<v-card
|
||||
prepend-icon="mdi-alert-decagram"
|
||||
:title="showPrerelease ? 'Latest pre-release' : 'Latest release'"
|
||||
density="comfortable"
|
||||
>
|
||||
<v-card-text v-if="currentRelease.status.value === 'success'">
|
||||
<p class="text-xs">
|
||||
<code>{{ currentRelease.data.value?.tag_name }}</code>
|
||||
</p>
|
||||
<p class="font-bold text-lg">{{ latestRelease.data.value?.name }}</p>
|
||||
<article class="prose prose-sm max-h-[360px] overflow-y-auto" style="max-width: unset">
|
||||
<m-d-c :value="currentRelease.data.value!.body!" />
|
||||
</article>
|
||||
</v-card-text>
|
||||
<div v-else>
|
||||
<v-progress-circular class="px-5 my-3" indeterminate />
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-card prepend-icon="mdi-download" title="Distributions" density="comfortable">
|
||||
<div v-if="currentRelease.status.value === 'success'">
|
||||
<v-list density="comfortable" slim>
|
||||
<v-list-item
|
||||
v-for="asset in currentRelease.data.value!.assets"
|
||||
:key="asset.id"
|
||||
:title="asset.label ?? asset.name"
|
||||
:subtitle="formatBytes(asset.size)"
|
||||
:href="asset.browser_download_url"
|
||||
target="_blank"
|
||||
/>
|
||||
</v-list>
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-progress-circular class="px-5 my-3" indeterminate />
|
||||
</div>
|
||||
<v-card-text>
|
||||
<p class="text-sm opacity-50 mb-2">{{ t('downloadForApple') }}</p>
|
||||
<div class="flex align-center gap-2.5">
|
||||
<nuxt-link
|
||||
to="https://apps.apple.com/us/app/solian/id6499032345?itscg=30200&itsct=apps_box_link&mttnsubad=6499032345"
|
||||
target="_blank"
|
||||
>
|
||||
<img :src="AppStoreDownload" />
|
||||
</nuxt-link>
|
||||
<div>
|
||||
<nuxt-link to="https://testflight.apple.com/join/YJ0lmN6O" target="_blank" class="underline">
|
||||
{{ t('downloadTestFlight') }}
|
||||
</nuxt-link>
|
||||
<p class="text-xs opacity-40">{{ t('downloadTestFlightDescription') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-sm opacity-50 mt-4">{{ t('downloadForDesktop') }}</p>
|
||||
<p class="text-sm">{{ t('downloadForDesktopDescription') }}</p>
|
||||
|
||||
<p class="text-sm opacity-50 mt-4">{{ t('downloadWithoutDownload') }}</p>
|
||||
<div class="text-sm flex gap-2 underline">
|
||||
<nuxt-link to="https://sn.solsynth.dev" target="_blank">{{ t('downloadWeb') }}</nuxt-link>
|
||||
<nuxt-link to="https://sn.solsynth.dev?cdn=cn" target="_blank"
|
||||
>{{ t('downloadWebChina') }}</nuxt-link
|
||||
>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</section>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Icon from "~/assets/products/solar-network/icon.png"
|
||||
import AlphaScreenshot from "~/assets/products/solar-network/alpha.webp"
|
||||
import AppStoreDownload from "~/assets/products/app-store-download.svg"
|
||||
|
||||
import { formatBytes } from "~/utils/format"
|
||||
import { Octokit } from "@octokit/rest"
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const latestRelease = useAsyncData("sn-latest-release", async () => {
|
||||
const octo = new Octokit({})
|
||||
const resp = await octo.repos.getLatestRelease({
|
||||
owner: "Solsynth",
|
||||
repo: "HyperNet.Surface",
|
||||
})
|
||||
return resp.data
|
||||
})
|
||||
const latestPrerelease = useAsyncData("sn-latest-prerelease", async () => {
|
||||
const octo = new Octokit({})
|
||||
const resp = await octo.repos.listReleases({
|
||||
owner: "Solsynth",
|
||||
repo: "HyperNet.Surface",
|
||||
per_page: 1,
|
||||
})
|
||||
return resp.data[0]
|
||||
})
|
||||
|
||||
const showPrerelease = ref(false)
|
||||
|
||||
const currentRelease = computed(() => (showPrerelease.value ? latestPrerelease : latestRelease))
|
||||
const hasPrerelease = computed<boolean>(
|
||||
() => latestPrerelease.data?.value?.tag_name != latestRelease.data?.value?.tag_name,
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content-section {
|
||||
min-height: calc(100vh - 80px);
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.icon-glow {
|
||||
-webkit-filter: drop-shadow(0 0 7px rgba(0, 0, 0, 0.5));
|
||||
filter: drop-shadow(0 0 7px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.icon-glow {
|
||||
-webkit-filter: invert() drop-shadow(0 0 7px rgba(255, 255, 255, 0.5));
|
||||
filter: invert() drop-shadow(0 0 7px rgba(255, 255, 255, 0.5));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
</style>
|
@@ -27,7 +27,7 @@
|
||||
</v-col>
|
||||
<v-col row="12" lg="4" order="first" order-lg="last">
|
||||
<div class="sticky top-0 h-fit">
|
||||
<v-card prepend-icon="mdi-identifier" title="About">
|
||||
<v-card prepend-icon="mdi-information-outline" title="About">
|
||||
<v-card-text>
|
||||
<p><b>Description</b></p>
|
||||
<p>{{ account.description }}</p>
|
||||
@@ -53,7 +53,7 @@ const config = useRuntimeConfig()
|
||||
|
||||
const tab = ref(1)
|
||||
|
||||
const { data: account } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/co/publisher/${route.params.name}`)
|
||||
const { data: account } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/co/publishers/${route.params.name}`)
|
||||
|
||||
if (account.value == null) {
|
||||
throw createError({
|
||||
|
@@ -12,16 +12,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-7">
|
||||
<v-card rounded="xl" class="mx-[-5px]">
|
||||
<v-tabs
|
||||
v-model="tab"
|
||||
align-tabs="start"
|
||||
color="primary"
|
||||
hide-slider
|
||||
>
|
||||
<v-tab :value="1">{{ t("userActivity") }}</v-tab>
|
||||
</v-tabs>
|
||||
</v-card>
|
||||
|
||||
</div>
|
||||
|
||||
<v-row>
|
||||
@@ -46,10 +37,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
alias: ["/@:name(.*)*"],
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
@@ -67,6 +54,4 @@ if (account.value == null) {
|
||||
|
||||
const urlOfAvatar = computed(() => account.value?.avatar ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.avatar}` : void 0)
|
||||
const urlOfBanner = computed(() => account.value?.banner ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.banner}` : void 0)
|
||||
|
||||
const externalOpenLink = computed(() => `${config.public.solianUrl}/accounts/view/${route.params.name}`)
|
||||
</script>
|
||||
|
Reference in New Issue
Block a user