🗑️ Clean up frontend

This commit is contained in:
LittleSheep 2024-03-30 13:15:21 +08:00
parent 99f85a8b76
commit 23989f98b6
33 changed files with 148 additions and 546 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

18
ROADMAP.md Normal file
View File

@ -0,0 +1,18 @@
# Hydrogen.Messaging 规划
- [ ] 基本聊天功能
- [ ] 发送文字消息
- [ ] 发送带有附件的消息
- [ ] 回复消息
- [ ] 转发消息
- [ ] 发送语音消息(特殊的带有附件的消息)
- [ ] 发送位置信息(带有**元数据**的信息)
- [ ] WebSocket 网关(一个 WS 链接完成所有的频道发送、接受消息功能)
- [ ] 领域聊天功能(与 Hydrogen.Interactive 的联动)
- [ ] 双方 GRPC 通信
- [ ] 在广场的领域内置聊天频道显示
- [ ] 在聊天的领域内显示广场的帖子节选
- [ ] 双方共用一个领域系统,以广场的领域为准
- [ ] 实时通讯功能
- [ ] 支持实时通知
- [ ] 支持音视频通话

3
go.mod
View File

@ -6,6 +6,7 @@ require (
git.solsynth.dev/hydrogen/identity v0.0.0-20240320131628-6ac77f36957f
github.com/go-playground/validator/v10 v10.17.0
github.com/gofiber/fiber/v2 v2.52.0
github.com/gofiber/template/html/v2 v2.1.1
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/uuid v1.5.0
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
@ -27,6 +28,8 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect

6
go.sum
View File

@ -27,6 +27,12 @@ github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE=
github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
github.com/gofiber/template/html/v2 v2.1.1 h1:QEy3O3EBkvwDthy5bXVGUseOyO6ldJoiDxlF4+MJiV8=
github.com/gofiber/template/html/v2 v2.1.1/go.mod h1:2G0GHHOUx70C1LDncoBpe4T6maQbNa4x1CVNFW0wju0=
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=

6
pkg/embed.go Normal file
View File

@ -0,0 +1,6 @@
package pkg
import "embed"
//go:embed views/*
var FS embed.FS

View File

@ -1,17 +1,18 @@
package server
import (
"git.solsynth.dev/hydrogen/messaging/pkg"
"github.com/gofiber/fiber/v2/middleware/favicon"
"net/http"
"strings"
"time"
"git.solsynth.dev/hydrogen/messaging/pkg/views"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cache"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/filesystem"
"github.com/gofiber/fiber/v2/middleware/idempotency"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/template/html/v2"
jsoniter "github.com/json-iterator/go"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
@ -20,6 +21,8 @@ import (
var A *fiber.App
func NewServer() {
templates := html.NewFileSystem(http.FS(pkg.FS), ".gohtml")
A = fiber.New(fiber.Config{
DisableStartupMessage: true,
EnableIPValidation: true,
@ -30,6 +33,8 @@ func NewServer() {
JSONDecoder: jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal,
BodyLimit: 50 * 1024 * 1024,
EnablePrintRoutes: viper.GetBool("debug.print_routes"),
Views: templates,
ViewsLayout: "views/index",
})
A.Use(idempotency.New())
@ -83,15 +88,17 @@ func NewServer() {
}
}
A.Use("/", cache.New(cache.Config{
Expiration: 24 * time.Hour,
CacheControl: true,
}), filesystem.New(filesystem.Config{
Root: http.FS(views.FS),
PathPrefix: "dist",
Index: "index.html",
NotFoundFile: "dist/index.html",
A.Use(favicon.New(favicon.Config{
FileSystem: http.FS(pkg.FS),
File: "views/favicon.png",
URL: "/favicon.png",
}))
A.Get("/", func(c *fiber.Ctx) error {
return c.Render("views/open", fiber.Map{
"frontend": viper.GetString("frontend"),
})
})
}
func Listen() {

View File

@ -1,20 +0,0 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution")
module.exports = {
root: true,
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier/skip-formatting"
],
parserOptions: {
ecmaVersion: "latest"
},
rules: {
"vue/multi-word-component-names": "off",
"vue/valid-v-for": "off",
"vue/require-v-for-key": "off"
}
}

33
pkg/views/.gitignore vendored
View File

@ -1,33 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
*.lockb
*.lock

View File

@ -1,8 +0,0 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": false,
"printWidth": 120,
"trailingComma": "none"
}

View File

@ -1,8 +0,0 @@
{
"recommendations": [
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

View File

@ -1,46 +0,0 @@
# views
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## 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 [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```

View File

@ -1,6 +0,0 @@
package views
import "embed"
//go:embed all:dist
var FS embed.FS

1
pkg/views/env.d.ts vendored
View File

@ -1 +0,0 @@
/// <reference types="vite/client" />

BIN
pkg/views/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

28
pkg/views/index.gohtml Normal file
View File

@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="icon" type="image/png" href="favicon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet">
<title>Hydrogen.Interactive</title>
<style>
html, body {
padding: 0;
margin: 0;
font-family: Roboto Mono, monospace;
}
</style>
</head>
<body>
{{embed}}
</body>
</html>

View File

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/xml+svg" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Solarplaza</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

60
pkg/views/open.gohtml Normal file
View File

@ -0,0 +1,60 @@
<div class="container">
<div>
<img src="/favicon.png" width="128" height="128" alt="Icon"/>
<p class="caption text-blinking">Launching Solian... 🚀</p>
<p class="description">
Hold on a second... <br/>
We are redirecting you to our application...
</p>
</div>
</div>
<script>
function redirect() {
window.location.href = {{ .frontend }}
}
setTimeout(() => redirect(), 1850)
</script>
<style>
.container {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.caption {
margin-top: 4px;
font-weight: 600;
}
.text-blinking {
animation: text-blinking ease-in-out infinite 1.5s;
}
.description {
margin-top: 4px;
font-size: 0.85rem;
}
p {
margin: 0;
}
@keyframes text-blinking {
0% {
opacity: 100%;
}
50% {
opacity: 10%;
}
100% {
opacity: 100%;
}
}
</style>

View File

@ -1,48 +0,0 @@
{
"name": "views",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"@fontsource/roboto": "^5.0.8",
"@mdi/font": "^7.4.47",
"@unocss/reset": "^0.58.5",
"dompurify": "^3.0.9",
"marked": "^12.0.0",
"pinia": "^2.1.7",
"universal-cookie": "^7.1.0",
"unocss": "^0.58.5",
"vue": "^3.4.15",
"vue-easy-lightbox": "next",
"vue-router": "^4.2.5",
"vuetify": "^3.5.7"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.3",
"@tsconfig/node20": "^20.1.2",
"@types/dompurify": "^3.0.5",
"@types/node": "^20.11.10",
"@unocss/preset-typography": "^0.58.5",
"@vitejs/plugin-vue": "^5.0.3",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0",
"npm-run-all2": "^6.1.1",
"prettier": "^3.0.3",
"typescript": "~5.3.0",
"vite": "^5.0.11",
"vue-tsc": "^1.8.27"
}
}

View File

@ -1,15 +0,0 @@
html,
body,
#app,
.v-application {
overflow: auto !important;
font-family: "Roboto Sans", ui-sans-serif, system-ui, sans-serif;
}
.no-scrollbar {
scrollbar-width: none;
}
.no-scrollbar::-webkit-scrollbar {
width: 0;
}

View File

@ -1,5 +0,0 @@
<template>
<v-app>
<router-view />
</v-app>
</template>

View File

@ -1,108 +0,0 @@
<template>
<v-navigation-drawer v-model="drawerOpen" color="grey-lighten-5" floating>
<div class="flex flex-col h-full">
<v-list class="border-b border-opacity-15 h-[64px]" style="border-bottom-width: thin">
<v-list-item :subtitle="username" :title="nickname">
<template #prepend>
<v-avatar icon="mdi-account-circle" :image="id.userinfo.data?.avatar" />
</template>
<template #append>
<v-menu v-if="id.userinfo.isLoggedIn">
<template #activator="{ props }">
<v-btn v-bind="props" icon="mdi-menu-down" size="small" variant="text" />
</template>
<v-list density="compact">
<v-list-item
title="Solarpass"
prepend-icon="mdi-passport-biometric"
target="_blank"
:href="passportUrl"
/>
</v-list>
</v-menu>
<v-btn v-else icon="mdi-login-variant" size="small" variant="text" :href="signinUrl" />
</template>
</v-list-item>
</v-list>
<div class="flex-grow-1">
<!-- TODO Channel list -->
</div>
</div>
</v-navigation-drawer>
<v-app-bar height="64" color="primary" scroll-behavior="elevate" flat>
<div class="max-md:px-5 md:px-12 flex flex-grow-1 items-center">
<v-app-bar-nav-icon variant="text" @click.stop="toggleDrawer" />
<router-link :to="{ name: 'landing' }">
<h2 class="ml-2 text-lg font-500">Solarecho</h2>
</router-link>
<v-spacer />
<v-tooltip v-for="item in navigationMenu" :text="item.name" location="bottom">
<template #activator="{ props }">
<v-btn flat exact v-bind="props" :to="{ name: item.to }" size="small" :icon="item.icon" />
</template>
</v-tooltip>
</div>
</v-app-bar>
<v-main>
<router-view />
</v-main>
</template>
<script setup lang="ts">
import { computed, ref } from "vue"
import { useUserinfo } from "@/stores/userinfo"
import { useWellKnown } from "@/stores/wellKnown"
const id = useUserinfo()
const navigationMenu = [{ name: "Landing", icon: "mdi-home", to: "landing" }]
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()
const meta = useWellKnown()
const signinUrl = computed(() => {
return meta.wellKnown?.components?.identity + `/auth/sign-in?redirect_uri=${encodeURIComponent(location.href)}`
})
const passportUrl = computed(() => {
return meta.wellKnown?.components?.identity
})
meta.readWellKnown()
const drawerOpen = ref(true)
function toggleDrawer() {
drawerOpen.value = !drawerOpen.value
}
</script>
<style scoped>
.editor-fab {
position: fixed !important;
bottom: 16px;
right: 20px;
}
</style>

View File

@ -1,54 +0,0 @@
import "virtual:uno.css"
import "./assets/utils.css"
import { createApp } from "vue"
import { createPinia } from "pinia"
import "vuetify/styles"
import { createVuetify } from "vuetify"
import { md3 } from "vuetify/blueprints"
import * as components from "vuetify/components"
import * as labsComponents from "vuetify/labs/components"
import * as directives from "vuetify/directives"
import "@mdi/font/css/materialdesignicons.min.css"
import "@fontsource/roboto/latin.css"
import "@unocss/reset/tailwind.css"
import index from "./index.vue"
import router from "./router"
const app = createApp(index)
app.use(
createVuetify({
directives,
components: {
...components,
...labsComponents
},
blueprint: md3,
theme: {
defaultTheme: "original",
themes: {
original: {
colors: {
primary: "#4a5099",
secondary: "#2196f3",
accent: "#009688",
error: "#f44336",
warning: "#ff9800",
info: "#03a9f4",
success: "#4caf50"
}
}
}
}
})
)
app.use(createPinia())
app.use(router)
app.mount("#app")

View File

@ -1,21 +0,0 @@
import { createRouter, createWebHistory } from "vue-router"
import MasterLayout from "@/layouts/master.vue"
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
component: MasterLayout,
children: [
{
path: "/",
name: "landing",
component: () => import("@/views/landing.vue")
},
]
}
]
})
export default router

View File

@ -1,10 +0,0 @@
declare global {
interface Window {
__LAUNCHPAD_TARGET__?: string
}
}
export async function request(input: string, init?: RequestInit) {
const prefix = window.__LAUNCHPAD_TARGET__ ?? ""
return await fetch(prefix + input, init)
}

View File

@ -1,56 +0,0 @@
import Cookie from "universal-cookie"
import { defineStore } from "pinia"
import { ref } from "vue"
import { request } from "@/scripts/request"
export interface Userinfo {
isReady: boolean
isLoggedIn: boolean
displayName: string
data: any
}
const defaultUserinfo: Userinfo = {
isReady: false,
isLoggedIn: false,
displayName: "Citizen",
data: null
}
export function getAtk(): string {
return new Cookie().get("identity_auth_key")
}
export function checkLoggedIn(): boolean {
return new Cookie().get("identity_auth_key")
}
export const useUserinfo = defineStore("userinfo", () => {
const userinfo = ref(defaultUserinfo)
const isReady = ref(false)
async function readProfiles() {
if (!checkLoggedIn()) {
isReady.value = true
}
const res = await request("/api/users/me", {
headers: { Authorization: `Bearer ${getAtk()}` }
})
if (res.status !== 200) {
return
}
const data = await res.json()
userinfo.value = {
isReady: true,
isLoggedIn: true,
displayName: data["nick"],
data: data
}
}
return { userinfo, isReady, readProfiles }
})

View File

@ -1,14 +0,0 @@
import { request } from "@/scripts/request"
import { defineStore } from "pinia"
import { ref } from "vue"
export const useWellKnown = defineStore("well-known", () => {
const wellKnown = ref<any>(null)
async function readWellKnown() {
const res = await request("/.well-known")
wellKnown.value = await res.json()
}
return { wellKnown, readWellKnown }
})

View File

@ -1,3 +0,0 @@
<template>
<v-container>W.I.P</v-container>
</template>

View File

@ -1,16 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"allowJs": true,
"checkJs": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@ -1,11 +0,0 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

View File

@ -1,13 +0,0 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "nightwatch.conf.*", "playwright.config.*"],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

View File

@ -1,5 +0,0 @@
import { defineConfig, presetAttributify, presetTypography, presetUno } from "unocss"
export default defineConfig({
presets: [presetAttributify(), presetTypography(), presetUno({ preflight: false })]
})

View File

@ -1,22 +0,0 @@
import { fileURLToPath, URL } from "node:url"
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import vueJsx from "@vitejs/plugin-vue-jsx"
import unocss from "unocss/vite"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), vueJsx(), unocss()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url))
}
},
server: {
proxy: {
"/.well-known": "http://localhost:8447",
"/api": "http://localhost:8447"
}
}
})

View File

@ -1,6 +1,8 @@
name = "Solarplaza"
maintainer = "SmartSheep Studio"
frontend = "https://lian.solsynth.dev"
bind = "0.0.0.0:8447"
domain = "feed.smartsheep.studio"
secret = "LtTjzAGFLshwXhN4ZD4nG5KlMv1MWcsvfv03TSZYnT1VhiAnLIZFTnHUwR0XhGgi"