Skip to content

Commit

Permalink
add qr-share
Browse files Browse the repository at this point in the history
  • Loading branch information
totegamma committed Feb 12, 2025
1 parent a5b70bd commit 0690aa6
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 22 deletions.
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"react-icons": "^5.2.1",
"react-markdown": "^8.0.7",
"react-parallax-tilt": "^1.7.146",
"react-qr-code": "^2.0.12",
"react-qrcode-logo": "^3.0.0",
"react-router-dom": "^6.11.0",
"react-syntax-highlighter": "^15.5.0",
"react-use-websocket": "^4.3.1",
Expand Down
120 changes: 117 additions & 3 deletions app/src/components/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import { Box, Button, Typography, Link, Skeleton, useTheme, alpha } from '@mui/material'
import {
Box,
Button,
Typography,
Link,
Skeleton,
useTheme,
alpha,
Menu,
MenuItem,
ListItemIcon,
ListItemText,
Modal
} from '@mui/material'

import { CCAvatar } from '../components/ui/CCAvatar'
import { WatchButton } from '../components/WatchButton'
import { AckButton } from '../components/AckButton'
import { MarkdownRenderer } from '../components/ui/MarkdownRenderer'

import { Link as NavLink } from 'react-router-dom'

import Tilt from 'react-parallax-tilt'
import { useEffect, useMemo, useState } from 'react'
import { type User } from '@concrnt/worldlib'
import { type CCDocument, type Profile as TypeProfile } from '@concrnt/client'
Expand All @@ -24,6 +37,10 @@ import { CCIconButton } from './ui/CCIconButton'
import ReplayIcon from '@mui/icons-material/Replay'
import SearchIcon from '@mui/icons-material/Search'
import { useSearchDrawer } from '../context/SearchDrawer'
import ContentPasteIcon from '@mui/icons-material/ContentPaste'
import QrCodeIcon from '@mui/icons-material/QrCode'
import { ProfileQRCard } from './ui/ProfileQRCard'
import CancelIcon from '@mui/icons-material/Cancel'

export interface ProfileProps {
user?: User
Expand Down Expand Up @@ -51,6 +68,8 @@ export function Profile(props: ProfileProps): JSX.Element {
const [subProfile, setSubProfile] = useState<TypeProfile<any> | null>(null)

const { t } = useTranslation('', { keyPrefix: 'common' })
const [shareMenuAnchor, setShareMenuAnchor] = useState<null | HTMLElement>(null)
const [openQR, setOpenQR] = useState(false)

useEffect(() => {
let unmounted = false
Expand Down Expand Up @@ -139,12 +158,15 @@ export function Profile(props: ProfileProps): JSX.Element {
backgroundColor: alpha(theme.palette.primary.main, 0.7)
}
}}
onClick={() => {
onClick={(e) => {
setShareMenuAnchor(e.currentTarget)
/*
if (props.user) {
const id = props.user.alias ?? props.user.ccid
navigator.clipboard.writeText('https://concrnt.world/' + id)
enqueueSnackbar('リンクをコピーしました', { variant: 'success' })
}
*/
}}
>
<IosShareIcon
Expand Down Expand Up @@ -383,6 +405,98 @@ export function Profile(props: ProfileProps): JSX.Element {

{subProfile && <ProfileProperties showCreateLink character={subProfile} />}
</Box>
<Menu
anchorEl={shareMenuAnchor}
open={Boolean(shareMenuAnchor)}
onClose={() => {
setShareMenuAnchor(null)
}}
>
<MenuItem
onClick={() => {
if (props.user) {
const id = props.user.alias ?? props.user.ccid
navigator.clipboard.writeText('https://concrnt.world/' + id)
enqueueSnackbar('リンクをコピーしました', { variant: 'success' })
setShareMenuAnchor(null)
}
}}
>
<ListItemIcon>
<ContentPasteIcon
sx={{
color: 'text.primary'
}}
/>
</ListItemIcon>
<ListItemText>Copy Share Link</ListItemText>
</MenuItem>
<MenuItem
onClick={() => {
setOpenQR(true)
setShareMenuAnchor(null)
}}
>
<ListItemIcon>
<QrCodeIcon
sx={{
color: 'text.primary'
}}
/>
</ListItemIcon>
<ListItemText>Show QR Code</ListItemText>
</MenuItem>
</Menu>
<Modal
open={openQR}
onClose={() => {
setOpenQR(false)
}}
slotProps={{
backdrop: {
sx: {
backgroundColor: 'background.default'
}
}
}}
>
<Box
sx={{
width: '100vw',
height: '100dvh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}
onClick={(e) => {
if (e.target === e.currentTarget) setOpenQR(false)
}}
>
<Box maxWidth="90vw">
{props.user && (
<Tilt glareEnable={true} glareBorderRadius="1%">
<ProfileQRCard user={props.user} />
</Tilt>
)}
</Box>
<CCIconButton
sx={{
position: 'absolute',
top: '10px',
right: '10px'
}}
onClick={() => {
setOpenQR(false)
}}
>
<CancelIcon
sx={{
color: 'text.primary'
}}
/>
</CCIconButton>
</Box>
</Modal>

{props.user && (
<CCDrawer
Expand Down
9 changes: 5 additions & 4 deletions app/src/components/SubkeyInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Box, Divider, Paper, Typography } from '@mui/material'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import QRCode from 'react-qr-code'
import { QRCode } from 'react-qrcode-logo'
import { enqueueSnackbar } from 'notistack'
import { CCIconButton } from './ui/CCIconButton'
import ContentPasteIcon from '@mui/icons-material/ContentPaste'
Expand Down Expand Up @@ -73,8 +73,8 @@ export default function SubkeyInfo(props: { subkey: string }): JSX.Element {
<Divider orientation="vertical" />
<Box
style={{
width: '120px',
height: '120px',
width: '150px',
height: '150px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
Expand All @@ -85,7 +85,8 @@ export default function SubkeyInfo(props: { subkey: string }): JSX.Element {
>
<QRCode
value={props.subkey}
size={100}
size={1000}
ecLevel="L"
style={{
width: '100%',
height: '100%'
Expand Down
122 changes: 122 additions & 0 deletions app/src/components/ui/ProfileQRCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { alpha, Box, Paper, Typography, useTheme } from '@mui/material'
import { ConcrntLogo } from '../theming/ConcrntLogo'
import { CCAvatar } from './CCAvatar'
import { QRCode } from 'react-qrcode-logo'
import { ConcurrentTheme } from '../../model'
import { User } from '@concrnt/worldlib'

interface ProfileQRCardProps {
user: User
}

export const ProfileQRCard = (props: ProfileQRCardProps): JSX.Element => {
const theme = useTheme<ConcurrentTheme>()

return (
<Box
sx={{
width: '100%',
backgroundColor: 'primary.main',
maxWidth: '400px',
borderRadius: 2,
position: 'relative',
aspectRatio: '1/1.618',
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-start',
py: '20px',
alignItems: 'center',
overflow: 'hidden',
borderColor: 'divider'
}}
>
<Box
id="emblem"
sx={{
position: 'absolute',
opacity: '0.1',
left: '-30px',
bottom: '-30px',
width: '400px',
height: '400px',
display: 'block'
}}
>
<ConcrntLogo size="400px" color={theme.palette.primary.contrastText} />
</Box>

<Paper
sx={{
backgroundColor: 'primary.main.contrastText',
width: '90%',
aspectRatio: '1/1',
borderRadius: 2
}}
>
<QRCode
value={`https://concrnt.world/${props.user.ccid}?hint=${props.user.domain}`}
size={500}
ecLevel="L"
quietZone={50}
qrStyle="fluid"
eyeRadius={2}
style={{
width: '100%',
height: '100%'
}}
fgColor={theme.palette.primary.main}
bgColor={'transparent'}
/>
</Paper>

<Box
flexDirection="row"
display="flex"
alignItems="center"
gap="15px"
width="80%"
justifyContent="center"
flex={1}
>
<CCAvatar
circle
avatarURL={props.user.profile?.avatar}
sx={{
width: '100px',
height: '100px'
}}
/>
<Box>
<Typography fontSize="30px" color="primary.contrastText" fontWeight="bold">
{props.user?.profile?.username}
</Typography>
{props.user?.alias && (
<Typography fontSize="18px" color="primary.contrastText">
@{props.user.alias}
</Typography>
)}
</Box>
</Box>

<Box
sx={{
display: 'flex',
width: '90%',
color: alpha(theme.palette.primary.contrastText, 0.7),
flexDirection: 'column',
alignItems: 'flex-end',
fontFamily: 'SourceCodeProRoman-Regular, Source Code Pro'
}}
>
<Typography
sx={{
fontSize: { xs: '13px', sm: '16px', md: '16px' }
}}
>
{props.user.ccid}
</Typography>
<Typography>concrnt.world</Typography>
</Box>
</Box>
)
}
31 changes: 17 additions & 14 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0690aa6

Please sign in to comment.