Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,11 @@ dependencies {

// authentication
implementation("at.favre.lib:bcrypt:0.10.2")
implementation("com.j256.two-factor-auth:two-factor-auth:1.3")

// misc
implementation("org.jetbrains.kotlin:kotlin-reflect:2.2.21")
implementation("io.trbl:blurhash:1.0.0")
implementation("org.jetbrains.kotlin:kotlin-reflect:2.3.0")
implementation("com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20260101.1")
implementation("site.remlit:aidx4j:1.0.0")
implementation("site.remlit:effekt:0.2.1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ data class DriveFile(
val type: String,
val src: String,
val alt: String?,
val blurHash: String?,

val sensitive: Boolean,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ data class User(

val avatar: String? = null,
val avatarAlt: String? = null,
val avatarBlurHash: String? = null,

val banner: String? = null,
val bannerAlt: String? = null,
val bannerBlurHash: String? = null,

val locked: Boolean = false,
val suspended: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by common-generators.
// This file should not be edited.
// Generated on 2025-12-30T18:15:44.439522
// Generated on 2026-01-09T17:41:19.235923
package site.remlit.aster.common.model.generated

import kotlin.Boolean
Expand All @@ -22,6 +22,7 @@ import site.remlit.aster.common.model.type.PolicyType
@Serializable
public data class PartialDriveFile(
public val alt: String?,
public val blurHash: String?,
public val createdAt: Instant?,
public val id: String?,
public val sensitive: Boolean?,
Expand Down Expand Up @@ -78,8 +79,10 @@ public data class PartialUser(
public val automated: Boolean?,
public val avatar: String?,
public val avatarAlt: String?,
public val avatarBlurHash: String?,
public val banner: String?,
public val bannerAlt: String?,
public val bannerBlurHash: String?,
public val bio: String?,
public val birthday: String?,
public val createdAt: Instant?,
Expand Down
9 changes: 5 additions & 4 deletions frontend/packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@
},
"dependencies": {
"@floating-ui/react-dom": "^2.1.6",
"@preact/compat": "^18.3.1",
"@preact/signals": "^2.5.1",
"@tabler/icons-react": "^3.33.0",
"@tanstack/react-form": "^1.23.0",
"@tanstack/react-query": "^5.90.2",
"@tanstack/react-router": "^1.132.0",
"@tanstack/react-store": "^0.7.0",
"@tanstack/router-plugin": "^1.132.0",
"aster-common": "link:../common/build/dist/js/productionLibrary",
"fast-blurhash": "^1.1.4",
"mfm-js": "^0.25.0",
"preact": "^10.27.2",
"@preact/compat": "^18.3.1",
"@preact/signals": "^2.5.1",
"aster-common": "link:../common/build/dist/js/productionLibrary"
"preact": "^10.27.2"
},
"devDependencies": {
"@eslint/js": "^9.37.0",
Expand Down
45 changes: 43 additions & 2 deletions frontend/packages/app/src/lib/components/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,64 @@ import * as Common from 'aster-common'
import './Avatar.scss'
import {useNavigate} from "@tanstack/react-router";
import localstore from "../utils/localstore.ts";
import {decodeBlurHash, getBlurHashAverageColor} from "fast-blurhash";
import {createRef} from "preact";
import {useEffect, useRef, useState} from "react";

function Avatar(
{user, size}:
{ user: any, size?: undefined | 'xl' | 'lg' | 'md' | 'sm' }
) {
const navigate = useNavigate();
const [useFallback, setUseFallback] = useState(false);

let fallback = "/assets/img/avatar.png"

let sizePx = 45;
switch (size) {
case 'xl': sizePx = 55; break;
case 'lg': sizePx = 50; break;
case 'md': sizePx = 35; break;
case 'sm': sizePx = 25; break;
}

const canvasRef = createRef<HTMLCanvasElement>()

useEffect(() => {
if (!user.avatarBlurHash) return

try {
const decoded = decodeBlurHash(user.avatarBlurHash, sizePx, sizePx)
const ctx = canvasRef.current?.getContext('2d');
const imageData = ctx?.createImageData(sizePx, sizePx);
imageData.data.set(decoded);
ctx.putImageData(imageData, 0, 0);
} catch (_) {}
})

function render() {
if (useFallback) {
return (
<canvas ref={canvasRef} width={sizePx} height={sizePx} />
)
} else {
return (
<img
src={user?.avatar ?? fallback}
alt={user?.avatarAlt ?? `${user.username}'s avatar`}
onError={() => setUseFallback(true)}
/>
)
}
}

return (
<div className={`avatarCtn`}>
<div
className={`avatar ${size ?? ""} highlightable${localstore.getParsed("rounded_avatars") ? " rounded" : ""}`}
onClick={() => navigate({to: `/${Common.renderHandle(user)}`})}
>
<img src={user?.avatar ?? fallback} alt={user?.avatarAlt ?? `${user.username}'s avatar`}
onError={e => e.currentTarget.src = fallback}/>
{render()}
</div>
</div>
)
Expand Down
57 changes: 29 additions & 28 deletions frontend/packages/app/src/lib/components/DriveFile.scss
Original file line number Diff line number Diff line change
@@ -1,36 +1,37 @@
.driveFile {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 12px;
gap: 6px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 12px;
gap: 6px;
box-sizing: border-box;

border-radius: var(--br-md);
border-radius: var(--br-md);

&:hover {
background-color: var(--bg-3-50);
}
&:hover {
background-color: var(--bg-3-50);
}

img, .preview {
display: flex;
align-items: center;
justify-content: center;
img, .preview {
display: flex;
align-items: center;
justify-content: center;

height: 115px;
width: 115px;
object-fit: cover;
height: 115px;
width: 115px;
object-fit: cover;
overflow: hidden;

border-radius: var(--br-sm);
background-color: var(--bg-3);
color: var(--tx-3);
}
border-radius: var(--br-sm);
background-color: var(--bg-3);
color: var(--tx-3);
}

.name {
max-width: 110px;
text-wrap: wrap;
word-break: break-all;
text-align: center;
}
.name {
max-width: 110px;
text-wrap: wrap;
word-break: break-all;
text-align: center;
}
}
29 changes: 28 additions & 1 deletion frontend/packages/app/src/lib/components/DriveFile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,41 @@ import {
IconVideo
} from "@tabler/icons-react";
import * as React from "react";
import {useEffect, useState} from "react";
import {decodeBlurHash} from "fast-blurhash";
import {createRef} from "preact";

function DriveFile({data}: { data: common.DriveFile }) {
const [hidden, setHidden] = React.useState(false);

const [useFallback, setUseFallback] = useState(false);
const canvasRef = createRef<HTMLCanvasElement>()

useEffect(() => {
if (!data.blurHash) return

try {
const decoded = decodeBlurHash(data.blurHash, 115, 115)
const ctx = canvasRef.current?.getContext('2d');
const imageData = ctx?.createImageData(115, 115);
imageData.data.set(decoded);
ctx.putImageData(imageData, 0, 0);
} catch (_) {}
})

function renderPreview() {
const type = data.type
if (type.startsWith("image")) {
return <img src={data.src} alt={data.alt}/>
return (
<div className={"preview"}>
{useFallback ? (
<canvas ref={canvasRef} width={115} height={115} />
) : (
<img src={data.src} alt={data.alt}
onError={() => setUseFallback(true)}/>
)}
</div>
)
} else if (type.startsWith("video")) {
return <div className={"preview"}>
<IconVideo size={30}/>
Expand Down
73 changes: 37 additions & 36 deletions frontend/packages/app/src/lib/components/page/UserPage.scss
Original file line number Diff line number Diff line change
@@ -1,49 +1,50 @@
.userPage {
.userHeader {
--th: 12px;
--nth: calc(var(--th) * -1);
.userHeader {
--th: 12px;
--nth: calc(var(--th) * -1);

--w: calc(100% + (var(--th) * 2));
--w: calc(100% + (var(--th) * 2));

box-sizing: border-box;
box-sizing: border-box;

margin: var(--nth) var(--nth) 0 var(--nth);
margin: var(--nth) var(--nth) 0 var(--nth);

width: var(--w);
height: 250px;
width: var(--w);
height: 250px;

background: var(--bg-3);
background-position: center;
background-size: cover;

box-shadow: inset 0 0 65px 5px var(--bg-2);
}
background: var(--bg-3);
background-position: center;
background-size: cover;

.userIdentity {
margin-top: calc(-1 * (55px + (2 * 12px)));
}
box-shadow: inset 0 0 65px 5px var(--bg-2);
}

--username-shadow: 0 0 5px #00000080, 0 0 10px #00000050, 0 0 15px #000000;
.userIdentity {
margin-top: calc(-1 * (55px + (2 * 12px)));
}

.displayName {
font-size: var(--fs-xxl);
font-weight: 600;
text-shadow: var(--username-shadow);
}
--username-shadow: 0 0 5px #00000080, 0 0 10px #00000050, 0 0 15px #000000;

.username {
font-weight: 400;
text-shadow: var(--username-shadow);
}
.displayName {
color: var(--tx-1);
font-size: var(--fs-xxl);
font-weight: 600;
text-shadow: var(--username-shadow);
}

.underHeader {
margin: 20px 0 20px 0;
.username {
font-weight: 400;
text-shadow: var(--username-shadow);
}

.bio {
&.none {
color: var(--tx-3);
font-style: italic;
}
}
}
.underHeader {
margin: 20px 0 20px 0;

.bio {
&.none {
color: var(--tx-3);
font-style: italic;
}
}
}
}
7 changes: 5 additions & 2 deletions frontend/packages/app/src/lib/components/page/UserPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {useQuery} from "@tanstack/react-query";
import Loading from "../Loading.tsx";
import Error from "../Error.tsx";
import Avatar from "../Avatar.tsx";
import {useState} from "react";
import {useEffect, useState} from "react";
import Container from "../Container.tsx";
import Button from "../Button.tsx";
import Mfm from "../Mfm.tsx";
Expand All @@ -31,7 +31,10 @@ function UserPage(
return (
<>
<Container gap={"lg"}>
<div className={"userHeader"} style={{backgroundImage: `url(${data?.banner})`}}></div>
<div
className={"userHeader"}
style={{ backgroundImage: `url(${data?.banner})` }}
/>
<div className={"userIdentity"}>
<Container gap={"xl"} align={"horizontal"}>
<Container align={"horizontal"} gap={"md"}>
Expand Down
Loading