fix depecrated deps

This commit is contained in:
SrGooglo 2025-02-11 16:13:13 +00:00
parent 79f64eaec2
commit 3cf055c72c
24 changed files with 2708 additions and 2638 deletions

View File

@ -14,4 +14,5 @@ export default {
"@classes": path.join(__dirname, "src/classes"),
"@models": path.join(__dirname, "../../", "comty.js/src/models"),
"comty.js": path.join(__dirname, "../../", "comty.js", "src"),
"@ragestudio/vessel": path.join(__dirname, "../../", "vessel", "src"),
}

View File

@ -3,6 +3,7 @@
"version": "1.25.0-a",
"license": "ComtyLicense",
"main": "electron/main",
"type": "module",
"author": "RageStudio",
"description": "A prototype of a social network.",
"scripts": {
@ -13,78 +14,61 @@
},
"dependencies": {
"@ant-design/icons": "^5.4.0",
"@capacitor/android": "^5.0.5",
"@capacitor/app": "^5.0.3",
"@capacitor/assets": "^2.0.4",
"@capacitor/cli": "^5.0.5",
"@capacitor/core": "^5.0.5",
"@capacitor/haptics": "1.1.4",
"@capacitor/splash-screen": "^5.0.4",
"@capacitor/status-bar": "^5.0.4",
"@capacitor/storage": "^1.2.5",
"@capgo/capacitor-updater": "^5.0.1",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@ffmpeg/ffmpeg": "^0.12.10",
"@ffmpeg/util": "^0.12.1",
"@loadable/component": "5.15.2",
"@mui/material": "^5.11.9",
"@ragestudio/cordova-nfc": "^1.2.0",
"@ragestudio/vessel": "^0.18.1",
"@ragestudio/vessel": "^0.19.0",
"@sentry/browser": "^7.64.0",
"@tauri-apps/api": "^1.5.4",
"@tsmx/human-readable": "^1.0.7",
"antd": "^5.20.6",
"axios": "^1.7.7",
"bear-react-carousel": "^4.0.10-alpha.0",
"capacitor-music-controls-plugin-v3": "^1.1.0",
"classnames": "2.3.1",
"dashjs": "^4.7.4",
"dompurify": "^3.0.0",
"fast-average-color": "^9.2.0",
"framer-motion": "^10.12.17",
"fuse.js": "6.5.3",
"hls.js": "^1.5.17",
"howler": "2.2.3",
"i18next": "21.6.6",
"js-cookie": "3.0.1",
"jsmediatags": "^3.9.7",
"less": "4.1.2",
"lottie-react": "^2.4.0",
"luxon": "^3.0.4",
"million": "^2.6.4",
"mime": "^3.0.0",
"moment": "2.29.4",
"motion": "^12.4.2",
"mpegts.js": "^1.6.10",
"nprogress": "^0.2.0",
"plyr": "^3.6.12",
"plyr-react": "^3.2.1",
"plyr": "^3.7.8",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react": "18.3.1",
"react-beautiful-dnd": "^13.1.1",
"react-color": "2.19.3",
"react-countup": "^6.4.1",
"react-dom": "18.2.0",
"react-dom": "18.3.1",
"react-fast-marquee": "^1.3.5",
"react-helmet": "6.1.0",
"react-i18next": "11.15.3",
"react-icons": "^4.8.0",
"react-icons": "^5.4.0",
"react-lazy-load-image-component": "^1.5.4",
"react-markdown": "^8.0.3",
"react-modal-image": "^2.6.0",
"react-motion": "0.5.2",
"react-rnd": "10.3.5",
"react-player": "^2.16.0",
"react-rnd": "^10.4.14",
"react-router-dom": "^6.26.2",
"react-transition-group": "^4.4.5",
"react-useanimations": "^2.10.0",
"realtime-bpm-analyzer": "^3.2.1",
"remark-gfm": "^3.0.1",
"rxjs": "^7.5.5",
"store": "^2.0.12",
"swapy": "^1.0.5",
"ua-parser-js": "^1.0.36",
"vaul": "^0.9.2",
"vaul": "^1.1.2",
"vite": "^5.4.4"
}
}

View File

@ -2,6 +2,7 @@ import "./patches"
import config from "@config"
import React from "react"
import { Runtime } from "@ragestudio/vessel"
import { Helmet } from "react-helmet"
import { Translation } from "react-i18next"
@ -10,10 +11,6 @@ import { invoke } from "@tauri-apps/api/tauri"
import { Lightbox } from "react-modal-image"
import * as antd from "antd"
import { StatusBar, Style } from "@capacitor/status-bar"
import { App as CapacitorApp } from "@capacitor/app"
import { CapacitorUpdater } from "@capgo/capacitor-updater"
import AppsMenu from "@components/AppMenu"
import AuthModel from "@models/auth"
@ -41,9 +38,9 @@ import Splash from "./splash"
import "@styles/index.less"
if (IS_MOBILE_HOST) {
CapacitorUpdater.notifyAppReady()
}
// if (IS_MOBILE_HOST) {
// CapacitorUpdater.notifyAppReady()
// }
class ComtyApp extends React.Component {
constructor(props) {
@ -66,8 +63,12 @@ class ComtyApp extends React.Component {
window.app.message = antd.message
window.app.isCapacitor = IS_MOBILE_HOST
if (window.app.version !== window.localStorage.getItem("last_version")) {
app.message.info(`Comty has been updated to version ${window.app.version}!`)
if (
window.app.version !== window.localStorage.getItem("last_version")
) {
app.message.info(
`Comty has been updated to version ${window.app.version}!`,
)
window.localStorage.setItem("last_version", window.app.version)
}
@ -113,7 +114,7 @@ class ComtyApp extends React.Component {
sessionController: this.sessionController,
onDone: () => {
app.layout.draggable.destroy("login")
}
},
},
})
},
@ -129,19 +130,23 @@ class ComtyApp extends React.Component {
props: {
bodyStyle: {
height: "100%",
}
},
},
})
},
// Opens the notification window and sets up the UI for the notification to be displayed
openNotifications: () => {
window.app.layout.drawer.open("notifications", NotificationsCenter, {
window.app.layout.drawer.open(
"notifications",
NotificationsCenter,
{
props: {
width: "fit-content",
},
allowMultiples: false,
escClosable: true,
})
},
)
},
openSearcher: (options) => {
if (app.isMobile) {
@ -150,34 +155,44 @@ class ComtyApp extends React.Component {
componentProps: {
renderResults: true,
autoFocus: true,
}
},
})
}
return app.layout.modal.open("searcher", (props) => <Searcher autoFocus renderResults {...props} />, {
framed: false
})
return app.layout.modal.open(
"searcher",
(props) => <Searcher autoFocus renderResults {...props} />,
{
framed: false,
},
)
},
openMessages: () => {
app.location.push("/messages")
},
openFullImageViewer: (src) => {
app.cores.window_mng.render("image_lightbox", <Lightbox
app.cores.window_mng.render(
"image_lightbox",
<Lightbox
small={src}
large={src}
onClose={() => app.cores.window_mng.close("image_lightbox")}
onClose={() =>
app.cores.window_mng.close("image_lightbox")
}
hideDownload
showRotate
/>)
/>,
)
},
openPostCreator: (params) => {
app.layout.modal.open("post_creator", (props) => <PostCreator
{...props}
{...params}
/>, {
framed: false
})
}
app.layout.modal.open(
"post_creator",
(props) => <PostCreator {...props} {...params} />,
{
framed: false,
},
)
},
},
navigation: {
reload: () => {
@ -198,14 +213,16 @@ class ComtyApp extends React.Component {
goToSettings: (setting_id) => {
return app.location.push(`/settings`, {
query: {
setting: setting_id
}
setting: setting_id,
},
})
},
goToAccount: (username) => {
if (!username) {
if (!app.userData) {
console.error("Cannot go to account, no username provided and no user logged in")
console.error(
"Cannot go to account, no username provided and no user logged in",
)
return false
}
@ -219,27 +236,33 @@ class ComtyApp extends React.Component {
},
goToPlaylist: (playlist_id) => {
return app.location.push(`/play/${playlist_id}`)
}
},
},
capacitor: {
isAppCapacitor: () => window.navigator.userAgent === "capacitor",
setStatusBarStyleDark: async () => {
if (!window.app.capacitor.isAppCapacitor()) {
console.warn("[App] setStatusBarStyleDark is only available on capacitor")
console.warn(
"[App] setStatusBarStyleDark is only available on capacitor",
)
return false
}
return await StatusBar.setStyle({ style: Style.Dark })
},
setStatusBarStyleLight: async () => {
if (!window.app.capacitor.isAppCapacitor()) {
console.warn("[App] setStatusBarStyleLight is not supported on this platform")
console.warn(
"[App] setStatusBarStyleLight is not supported on this platform",
)
return false
}
return await StatusBar.setStyle({ style: Style.Light })
},
hideStatusBar: async () => {
if (!window.app.capacitor.isAppCapacitor()) {
console.warn("[App] hideStatusBar is not supported on this platform")
console.warn(
"[App] hideStatusBar is not supported on this platform",
)
return false
}
@ -247,7 +270,9 @@ class ComtyApp extends React.Component {
},
showStatusBar: async () => {
if (!window.app.capacitor.isAppCapacitor()) {
console.warn("[App] showStatusBar is not supported on this platform")
console.warn(
"[App] showStatusBar is not supported on this platform",
)
return false
}
return await StatusBar.show()
@ -257,13 +282,14 @@ class ComtyApp extends React.Component {
clearInternalStorage: async () => {
antd.Modal.confirm({
title: "Clear internal storage",
content: "Are you sure you want to clear all internal storage? This will remove all your data from the app, including your session.",
content:
"Are you sure you want to clear all internal storage? This will remove all your data from the app, including your session.",
onOk: async () => {
Utils.deleteInternalStorage()
}
},
})
},
}
},
}
static staticRenders = {
@ -309,12 +335,10 @@ class ComtyApp extends React.Component {
app.navigation.goAuth()
antd.notification.open({
message: <Translation>
{(t) => t("Invalid Session")}
</Translation>,
description: <Translation>
{(t) => t(error)}
</Translation>,
message: (
<Translation>{(t) => t("Invalid Session")}</Translation>
),
description: <Translation>{(t) => t(error)}</Translation>,
icon: <Icons.MdOutlineAccessTimeFilled />,
})
},
@ -339,7 +363,7 @@ class ComtyApp extends React.Component {
"auth:disabled_account": async () => {
await SessionModel.removeToken()
app.navigation.goAuth()
}
},
}
flushState = async () => {
@ -355,13 +379,13 @@ class ComtyApp extends React.Component {
StatusBar.setOverlaysWebView({ overlay: false })
CapacitorApp.addListener("backButton", ({ canGoBack }) => {
if (!canGoBack) {
CapacitorApp.exitApp()
} else {
app.location.back()
}
})
// CapacitorApp.addListener("backButton", ({ canGoBack }) => {
// if (!canGoBack) {
// CapacitorApp.exitApp()
// } else {
// app.location.back()
// }
// })
}
await this.initialization()
@ -389,7 +413,10 @@ class ComtyApp extends React.Component {
try {
await this.__SessionInit()
} catch (error) {
console.error(`[App] Error while initializing session`, error)
console.error(
`[App] Error while initializing session`,
error,
)
throw {
cause: "Cannot initialize session",
@ -436,17 +463,19 @@ class ComtyApp extends React.Component {
}
render() {
return <React.Fragment>
return (
<React.Fragment>
<Helmet>
<title>{config.app.siteName}</title>
<meta name="og:description" content={config.app.siteDescription} />
<meta
name="og:description"
content={config.app.siteDescription}
/>
<meta property="og:title" content={config.app.siteName} />
</Helmet>
<Router.InternalRouter>
<ThemeProvider>
{
window.__TAURI__ && <DesktopTopBar />
}
{window.__TAURI__ && <DesktopTopBar />}
<Layout
user={this.state.user}
staticRenders={ComtyApp.staticRenders}
@ -454,13 +483,12 @@ class ComtyApp extends React.Component {
user: this.state.user,
}}
>
{
this.state.initialized && <Router.PageRender />
}
{this.state.initialized && <Router.PageRender />}
</Layout>
</ThemeProvider>
</Router.InternalRouter>
</React.Fragment>
)
}
}

View File

@ -1,8 +1,9 @@
import React from "react"
import { motion, AnimatePresence } from "framer-motion"
import { motion, AnimatePresence } from "motion/react"
const PageTransition = (props) => {
return <AnimatePresence>
return (
<AnimatePresence>
<motion.div
layout
initial={{ y: 10, opacity: 0 }}
@ -14,6 +15,7 @@ const PageTransition = (props) => {
{props.children}
</motion.div>
</AnimatePresence>
)
}
export default PageTransition

View File

@ -2,7 +2,7 @@ import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import { Icons, createIconRender } from "@components/Icons"
import { motion } from "framer-motion"
import { motion } from "motion/react"
import useWsEvents from "@hooks/useWsEvents"
@ -17,44 +17,36 @@ const PollOption = (props) => {
}
}
return <div
className={classnames(
"poll-option",
{
return (
<div
className={classnames("poll-option", {
["checked"]: props.checked,
}
)}
})}
style={{
"--percentage": `${props.percentage}%`
"--percentage": `${props.percentage}%`,
}}
onClick={onClick}
>
{
props.checked && <motion.div
{props.checked && (
<motion.div
className="percentage-indicator"
animate={{ width: `${props.percentage}%` }}
initial={{ width: 0 }}
transition={{ ease: "easeOut" }}
/>
}
)}
<div className="poll-option-content">
{
props.checked && createIconRender("FaCheck")
}
{props.checked && createIconRender("FaCheck")}
{
props.showPercentage && <span>
{Math.floor(props.percentage)}%
</span>
}
{props.showPercentage && (
<span>{Math.floor(props.percentage)}%</span>
)}
<span>
{props.option.label}
</span>
<span>{props.option.label}</span>
</div>
</div>
)
}
const Poll = (props) => {
@ -64,7 +56,8 @@ const Poll = (props) => {
const [hasVoted, setHasVoted] = React.useState(false)
const [totalVotes, setTotalVotes] = React.useState(0)
useWsEvents({
useWsEvents(
{
"post.poll.vote": (data) => {
const { post_id, option_id, user_id, previous_option_id } = data
@ -91,15 +84,20 @@ const Poll = (props) => {
}
if (previous_option_id) {
const previousOptionIndex = prev.findIndex((option) => option.id === previous_option_id)
const previousOptionIndex = prev.findIndex(
(option) => option.id === previous_option_id,
)
if (previousOptionIndex !== -1) {
prev[previousOptionIndex].count = prev[previousOptionIndex].count - 1
prev[previousOptionIndex].count =
prev[previousOptionIndex].count - 1
}
}
if (option_id) {
const newOptionIndex = prev.findIndex((option) => option.id === option_id)
const newOptionIndex = prev.findIndex(
(option) => option.id === option_id,
)
if (newOptionIndex !== -1) {
prev[newOptionIndex].count += 1
@ -108,10 +106,12 @@ const Poll = (props) => {
return prev
})
}
}, {
socketName: "posts"
})
},
},
{
socketName: "posts",
},
)
async function onVote(id) {
console.debug(`Voting poll option`, {
@ -141,12 +141,15 @@ const Poll = (props) => {
}
}, [options])
return <div className="poll">
{
!editMode && options.map((option, index) => {
const percentage = totalVotes > 0 ? (option.count / totalVotes) * 100 : 0
return (
<div className="poll">
{!editMode &&
options.map((option, index) => {
const percentage =
totalVotes > 0 ? (option.count / totalVotes) * 100 : 0
return <PollOption
return (
<PollOption
key={index}
option={option}
onClick={onVote}
@ -154,26 +157,25 @@ const Poll = (props) => {
percentage={percentage}
showPercentage={hasVoted}
/>
})
}
)
})}
{
editMode && <antd.Form
{editMode && (
<antd.Form
name="post-poll"
className="post-poll-edit"
ref={formRef}
initialValues={{
options: options
options: options,
}}
>
<antd.Form.List
name="options"
>
<antd.Form.List name="options">
{(fields, { add, remove }) => {
return <>
{
fields.map((field, index) => {
return <div
return (
<>
{fields.map((field, index) => {
return (
<div
key={field.key}
className="post-poll-edit-option"
>
@ -181,20 +183,22 @@ const Poll = (props) => {
{...field}
name={[field.name, "label"]}
>
<antd.Input
placeholder="Type a option"
/>
<antd.Input placeholder="Type a option" />
</antd.Form.Item>
{
fields.length > 1 && <antd.Button
onClick={() => remove(field.name)}
icon={createIconRender("MdRemove")}
{fields.length > 1 && (
<antd.Button
onClick={() =>
remove(field.name)
}
icon={createIconRender(
"MdRemove",
)}
/>
}
)}
</div>
})
}
)
})}
<antd.Button
onClick={() => add()}
@ -203,13 +207,14 @@ const Poll = (props) => {
Add Option
</antd.Button>
</>
)
}}
</antd.Form.List>
</antd.Form>
}
)}
{
editMode && <div className="poll-edit-actions">
{editMode && (
<div className="poll-edit-actions">
<antd.Button
onClick={onClose}
icon={createIconRender("CloseOutlined")}
@ -217,8 +222,9 @@ const Poll = (props) => {
type="text"
/>
</div>
}
)}
</div>
)
}
export default Poll

View File

@ -1,6 +1,5 @@
import React from "react"
import { Skeleton, Button } from "antd"
import Plyr from "plyr-react"
import mimetypes from "mime"
import BearCarousel from "bear-react-carousel"
@ -9,7 +8,6 @@ import ImageViewer from "@components/ImageViewer"
import ContentFailed from "../contentFailed"
import "bear-react-carousel/dist/index.css"
import "plyr-react/dist/plyr.css"
import "./index.less"
const renderDebug = localStorage.getItem("render_debug") === "true"
@ -75,19 +73,21 @@ const Attachment = React.memo((props) => {
return <ImageViewer src={url} />
}
case "video": {
return <video controls>
return (
<video controls>
<source src={url} type={mimeType} />
</video>
)
}
case "audio": {
return <audio controls>
return (
<audio controls>
<source src={url} type={mimeType} />
</audio>
)
}
default: {
return <h4>
Unsupported media type [{mimeType}]
</h4>
return <h4>Unsupported media type [{mimeType}]</h4>
}
}
}
@ -104,7 +104,8 @@ const Attachment = React.memo((props) => {
return <ContentFailed />
}
return <div
return (
<div
key={props.index}
id={id}
className="attachment"
@ -112,6 +113,7 @@ const Attachment = React.memo((props) => {
>
{renderMedia()}
</div>
)
} catch (error) {
console.error(error)
@ -126,29 +128,29 @@ export default React.memo((props) => {
React.useEffect(() => {
// get attachment index from query string
const attachmentIndex = parseInt(new URLSearchParams(window.location.search).get("attachment"))
const attachmentIndex = parseInt(
new URLSearchParams(window.location.search).get("attachment"),
)
if (attachmentIndex) {
controller?.slideToPage(attachmentIndex)
}
}, [])
return <div className="post_attachments">
{
props.flags && props.flags.includes("nsfw") && !nsfwAccepted &&
return (
<div className="post_attachments">
{props.flags && props.flags.includes("nsfw") && !nsfwAccepted && (
<div className="nsfw_alert">
<h2>
This post may contain sensitive content.
</h2>
<h2>This post may contain sensitive content.</h2>
<Button onClick={() => setNsfwAccepted(true)}>
Show anyways
</Button>
</div>
}
)}
{
props.attachments?.length > 0 && <BearCarousel
{props.attachments?.length > 0 && (
<BearCarousel
data={props.attachments.map((attachment, index) => {
if (typeof attachment !== "object") {
attachment = {
@ -158,9 +160,14 @@ export default React.memo((props) => {
return {
key: index,
children: <React.Fragment key={index}>
<Attachment index={index} attachment={attachment} />
children: (
<React.Fragment key={index}>
<Attachment
index={index}
attachment={attachment}
/>
</React.Fragment>
),
}
})}
isEnableNavButton
@ -170,6 +177,7 @@ export default React.memo((props) => {
onSlideChange={setCarouselState}
isDebug={renderDebug}
/>
}
)}
</div>
)
})

View File

@ -1,7 +1,7 @@
import React from "react"
import classnames from "classnames"
import Plyr from "plyr-react"
import { motion } from "framer-motion"
import ReactPlayer from "react-player/lazy"
import { motion } from "motion/react"
import Poll from "@components/Poll"
import { Icons } from "@components/Icons"
@ -16,42 +16,65 @@ import "./index.less"
const articleAnimationProps = {
layout: true,
initial: { y: -100, opacity: 0 },
animate: { y: 0, opacity: 1, },
animate: { y: 0, opacity: 1 },
exit: { scale: 0, opacity: 0 },
transition: { duration: 0.1, },
transition: { duration: 0.1 },
}
const messageRegexs = [
{
regex: /https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})(&[a-zA-Z0-9_-]+=[a-zA-Z0-9_-]+)*/g,
fn: (key, result) => {
return <Plyr source={{
type: "video",
sources: [{
src: result[1],
provider: "youtube",
}],
}} />
}
return <ReactPlayer url={result[1]} controls />
},
},
{
regex: /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi,
fn: (key, result) => {
return <a key={key} href={result[1]} target="_blank" rel="noopener noreferrer">{result[1]}</a>
}
return (
<a
key={key}
href={result[1]}
target="_blank"
rel="noopener noreferrer"
>
{result[1]}
</a>
)
},
},
{
regex: /(@[a-zA-Z0-9_]+)/gi,
fn: (key, result) => {
return <a key={key} onClick={() => window.app.location.push(`/@${result[1].substr(1)}`)}>{result[1]}</a>
return (
<a
key={key}
onClick={() =>
window.app.location.push(`/@${result[1].substr(1)}`)
}
>
{result[1]}
</a>
)
},
},
{
regex: /#[a-zA-Z0-9_]+/gi,
fn: (key, result) => {
return <a key={key} onClick={() => window.app.location.push(`/trending/${result[0].substr(1)}`)}>{result[0]}</a>
}
return (
<a
key={key}
onClick={() =>
window.app.location.push(
`/trending/${result[0].substr(1)}`,
)
}
>
{result[0]}
</a>
)
},
},
]
export default class PostCard extends React.PureComponent {
@ -101,7 +124,9 @@ export default class PostCard extends React.PureComponent {
return
}
const actionResult = await this.props.events.onClickLike(this.state.data)
const actionResult = await this.props.events.onClickLike(
this.state.data,
)
if (actionResult) {
this.setState({
@ -119,7 +144,9 @@ export default class PostCard extends React.PureComponent {
return
}
const actionResult = await this.props.events.onClickSave(this.state.data)
const actionResult = await this.props.events.onClickSave(
this.state.data,
)
if (actionResult) {
this.setState({
@ -159,33 +186,42 @@ export default class PostCard extends React.PureComponent {
componentDidCatch = (error, info) => {
console.error(error)
return <div className="postCard error">
return (
<div className="postCard error">
<h1>
<Icons.FiAlertTriangle />
<span>Cannot render this post</span>
<span>
Maybe this version of the app is outdated or is not supported yet
Maybe this version of the app is outdated or is not
supported yet
</span>
</h1>
</div>
)
}
componentDidMount = () => {
app.cores.api.listenEvent(`post.update.${this.state.data._id}`, this.handleDataUpdate, "posts")
app.cores.api.listenEvent(
`post.update.${this.state.data._id}`,
this.handleDataUpdate,
"posts",
)
}
componentWillUnmount = () => {
app.cores.api.unlistenEvent(`post.update.${this.state.data._id}`, this.handleDataUpdate, "posts")
app.cores.api.unlistenEvent(
`post.update.${this.state.data._id}`,
this.handleDataUpdate,
"posts",
)
}
render() {
return <motion.article
className={classnames(
"post_card",
{
return (
<motion.article
className={classnames("post_card", {
["open"]: this.state.open,
}
)}
})}
id={this.state.data._id}
style={this.props.style}
{...articleAnimationProps}
@ -203,41 +239,39 @@ export default class PostCard extends React.PureComponent {
<div
id="post_content"
className={classnames(
"post_content",
)}
className={classnames("post_content")}
>
<div className="message">
{
processString(messageRegexs)(this.state.data.message ?? "")
}
{processString(messageRegexs)(
this.state.data.message ?? "",
)}
</div>
{
!this.props.disableAttachments && this.state.data.attachments && this.state.data.attachments.length > 0 && <PostAttachments
{!this.props.disableAttachments &&
this.state.data.attachments &&
this.state.data.attachments.length > 0 && (
<PostAttachments
attachments={this.state.data.attachments}
flags={this.state.data.flags}
/>
}
)}
{
this.state.data.poll_options && <Poll
{this.state.data.poll_options && (
<Poll
post_id={this.state.data._id}
options={this.state.data.poll_options}
/>
}
)}
</div>
<PostActions
post={this.state.data}
user_id={this.state.data.user_id}
likesCount={this.state.countLikes}
repliesCount={this.state.countReplies}
defaultLiked={this.state.hasLiked}
defaultSaved={this.state.hasSaved}
PP
PP
actions={{
onClickLike: this.onClickLike,
onClickEdit: this.onClickEdit,
@ -247,16 +281,21 @@ PP
}}
/>
{
!this.props.disableHasReplies && !!this.state.hasReplies && <div
{!this.props.disableHasReplies &&
!!this.state.hasReplies && (
<div
className="post-card-has_replies"
onClick={() => app.navigation.goToPost(this.state.data._id)}
>
<span>View {this.state.hasReplies} replies</span>
</div>
onClick={() =>
app.navigation.goToPost(this.state.data._id)
}
>
<span>
View {this.state.hasReplies} replies
</span>
</div>
)}
</div>
</motion.article>
)
}
}

View File

@ -1,6 +1,6 @@
import React from "react"
import * as antd from "antd"
import { AnimatePresence } from "framer-motion"
import { AnimatePresence } from "motion/react"
import { Icons } from "@components/Icons"
import PostCard from "@components/PostCard"
@ -11,34 +11,40 @@ import PostModel from "@models/post"
import "./index.less"
const LoadingComponent = () => {
return <div className="post_card">
return (
<div className="post_card">
<antd.Skeleton
avatar
style={{
width: "100%"
width: "100%",
}}
/>
</div>
)
}
const NoResultComponent = () => {
return <antd.Empty
return (
<antd.Empty
description="No more post here"
style={{
width: "100%"
width: "100%",
}}
/>
)
}
const typeToComponent = {
"post": (args) => <PostCard {...args} />,
post: (args) => <PostCard {...args} />,
//"playlist": (args) => <PlaylistTimelineEntry {...args} />,
}
const Entry = React.memo((props) => {
const { data } = props
return React.createElement(typeToComponent[data.type ?? "post"] ?? PostCard, {
return React.createElement(
typeToComponent[data.type ?? "post"] ?? PostCard,
{
key: data._id,
data: data,
disableReplyTag: props.disableReplyTag,
@ -51,11 +57,13 @@ const Entry = React.memo((props) => {
onClickReply: props.onReplyPost,
onDoubleClick: props.onDoubleClick,
},
})
},
)
})
const PostList = React.forwardRef((props, ref) => {
return <LoadMore
return (
<LoadMore
ref={ref}
className="post-list"
loadingComponent={LoadingComponent}
@ -64,8 +72,8 @@ const PostList = React.forwardRef((props, ref) => {
fetching={props.loading}
onBottom={props.onLoadMore}
>
{
!props.realtimeUpdates && !app.isMobile && <div className="resume_btn_wrapper">
{!props.realtimeUpdates && !app.isMobile && (
<div className="resume_btn_wrapper">
<antd.Button
type="primary"
shape="round"
@ -76,21 +84,15 @@ const PostList = React.forwardRef((props, ref) => {
Resume
</antd.Button>
</div>
}
)}
<AnimatePresence>
{
props.list.map((data) => {
return <Entry
key={data._id}
data={data}
{...props}
/>
})
}
{props.list.map((data) => {
return <Entry key={data._id} data={data} {...props} />
})}
</AnimatePresence>
</LoadMore>
)
})
export class PostsListsComponent extends React.Component {
@ -144,7 +146,7 @@ export class PostsListsComponent extends React.Component {
return post._id !== id
}),
})
}
},
}
handleLoad = async (fn, params = {}) => {
@ -228,7 +230,7 @@ export class PostsListsComponent extends React.Component {
_id: randomId,
username: "random user",
avatar: `https://api.dicebear.com/7.x/thumbs/svg?seed=${randomId}`,
}
},
})
},
listRef: this.listRef,
@ -278,7 +280,11 @@ export class PostsListsComponent extends React.Component {
}
}
if (!this.props.realtime || this.state.resumingLoading || this.state.scrollingToTop) {
if (
!this.props.realtime ||
this.state.resumingLoading ||
this.state.scrollingToTop
) {
return null
}
@ -317,10 +323,16 @@ export class PostsListsComponent extends React.Component {
} else {
this.props.watchTimeline.forEach((event) => {
if (typeof this.timelineWsEvents[event] !== "function") {
console.error(`The event "${event}" is not defined in the timelineWsEvents object`)
console.error(
`The event "${event}" is not defined in the timelineWsEvents object`,
)
}
app.cores.api.listenEvent(event, this.timelineWsEvents[event], "posts")
app.cores.api.listenEvent(
event,
this.timelineWsEvents[event],
"posts",
)
})
}
}
@ -339,16 +351,25 @@ export class PostsListsComponent extends React.Component {
} else {
this.props.watchTimeline.forEach((event) => {
if (typeof this.timelineWsEvents[event] !== "function") {
console.error(`The event "${event}" is not defined in the timelineWsEvents object`)
console.error(
`The event "${event}" is not defined in the timelineWsEvents object`,
)
}
app.cores.api.unlistenEvent(event, this.timelineWsEvents[event], "posts")
app.cores.api.unlistenEvent(
event,
this.timelineWsEvents[event],
"posts",
)
})
}
}
if (this.listRef && this.listRef.current) {
this.listRef.current.removeEventListener("scroll", this.onScrollList)
this.listRef.current.removeEventListener(
"scroll",
this.onScrollList,
)
}
window._hacks = null
@ -363,21 +384,25 @@ export class PostsListsComponent extends React.Component {
}
onLikePost = async (data) => {
let result = await PostModel.toggleLike({ post_id: data._id }).catch(() => {
let result = await PostModel.toggleLike({ post_id: data._id }).catch(
() => {
antd.message.error("Failed to like post")
return false
})
},
)
return result
}
onSavePost = async (data) => {
let result = await PostModel.toggleSave({ post_id: data._id }).catch(() => {
let result = await PostModel.toggleSave({ post_id: data._id }).catch(
() => {
antd.message.error("Failed to save post")
return false
})
},
)
return result
}
@ -437,10 +462,12 @@ export class PostsListsComponent extends React.Component {
return React.createElement(this.props.emptyListRender)
}
return <div className="no_more_posts">
return (
<div className="no_more_posts">
<antd.Empty />
<h1>Whoa, nothing on here...</h1>
</div>
)
}
const PostListProps = {
@ -466,19 +493,17 @@ export class PostsListsComponent extends React.Component {
}
if (app.isMobile) {
return <PostList
ref={this.listRef}
{...PostListProps}
/>
return <PostList ref={this.listRef} {...PostListProps} />
}
return <div className="post-list_wrapper">
<PostList
ref={this.listRef}
{...PostListProps}
/>
return (
<div className="post-list_wrapper">
<PostList ref={this.listRef} {...PostListProps} />
</div>
)
}
}
export default React.forwardRef((props, ref) => <PostsListsComponent innerRef={ref} {...props} />)
export default React.forwardRef((props, ref) => (
<PostsListsComponent innerRef={ref} {...props} />
))

View File

@ -1,7 +1,7 @@
import React from "react"
import { createIconRender } from "@components/Icons"
import { AnimatePresence, motion } from "framer-motion"
import { AnimatePresence, motion } from "motion/react"
import "./index.less"
@ -26,9 +26,11 @@ const ContextMenu = (props) => {
const renderItems = () => {
if (items.length === 0) {
return <div>
return (
<div>
<p>No items</p>
</div>
)
}
return items.map((item, index) => {
@ -36,31 +38,28 @@ const ContextMenu = (props) => {
return <div key={index} className="context-menu-separator" />
}
return <div
return (
<div
key={index}
onClick={() => handleItemClick(item)}
className="item"
>
<p className="label">
{item.label}
</p>
<p className="label">{item.label}</p>
{
item.description && <p className="description">
{item.description}
</p>
}
{item.description && (
<p className="description">{item.description}</p>
)}
{
createIconRender(item.icon)
}
{createIconRender(item.icon)}
</div>
)
})
}
return <AnimatePresence>
{
visible && <div
return (
<AnimatePresence>
{visible && (
<div
className="context-menu-wrapper"
style={{
top: cords.y,
@ -74,13 +73,12 @@ const ContextMenu = (props) => {
exit={{ opacity: 0, scale: 0.3 }}
transition={{ duration: 0.05, ease: "easeInOut" }}
>
{
renderItems()
}
{renderItems()}
</motion.div>
</div>
}
)}
</AnimatePresence>
)
}
export default ContextMenu

View File

@ -1,5 +1,5 @@
import { Core } from "@ragestudio/vessel"
import { Haptics } from "@capacitor/haptics"
// import { Haptics } from "@capacitor/haptics"
const vibrationPatterns = {
light: [10],
@ -11,9 +11,7 @@ const vibrationPatterns = {
export default class HapticsCore extends Core {
static namespace = "haptics"
static dependencies = [
"settings"
]
static dependencies = ["settings"]
static isGlobalDisabled() {
return app.cores.settings.is("haptics:enabled", false)
@ -28,24 +26,25 @@ export default class HapticsCore extends Core {
}
public = {
isGlobalDisabled: HapticsCore.isGlobalDisabled,
//isGlobalDisabled: HapticsCore.isGlobalDisabled,
vibrate: this.vibrate.bind(this),
}
nativeVibrate = (pattern) => {
if (!Array.isArray(pattern)) {
pattern = [pattern]
}
// nativeVibrate = (pattern) => {
// if (!Array.isArray(pattern)) {
// pattern = [pattern]
// }
for (let i = 0; i < pattern.length; i++) {
Haptics.vibrate({
duration: pattern[i],
})
}
}
// for (let i = 0; i < pattern.length; i++) {
// Haptics.vibrate({
// duration: pattern[i],
// })
// }
// }
handleClickEvent = (event) => {
const button = event.target.closest("button") || event.target.closest(".ant-btn")
const button =
event.target.closest("button") || event.target.closest(".ant-btn")
if (button) {
this.vibrate("light")

View File

@ -1,4 +1,4 @@
import { Haptics } from "@capacitor/haptics"
//import { Haptics } from "@capacitor/haptics"
const NotfTypeToAudio = {
info: "notification",
@ -9,21 +9,28 @@ const NotfTypeToAudio = {
class NotificationFeedback {
static getSoundVolume = () => {
return (window.app.cores.settings.get("sfx:notifications_volume") ?? 50) / 100
return (
(window.app.cores.settings.get("sfx:notifications_volume") ?? 50) /
100
)
}
static playHaptic = async () => {
if (app.cores.settings.get("haptics:notifications_feedback")) {
await Haptics.vibrate()
//await Haptics.vibrate()
//use navigator.vibrate
}
}
static playAudio = (type) => {
if (app.cores.settings.get("sfx:notifications_feedback")) {
if (typeof window.app.cores.sfx?.play === "function") {
window.app.cores.sfx.play(NotfTypeToAudio[type] ?? "notification", {
window.app.cores.sfx.play(
NotfTypeToAudio[type] ?? "notification",
{
volume: NotificationFeedback.getSoundVolume(),
})
},
)
}
}
}

View File

@ -1,131 +0,0 @@
import { CapacitorMusicControls } from "capacitor-music-controls-plugin-v3"
export default class MediaSession {
initialize() {
CapacitorMusicControls.addListener("controlsNotification", (info) => {
console.log(info)
this.handleControlsEvent(info)
})
document.addEventListener("controlsNotification", (event) => {
console.log(event)
const info = { message: event.message, position: 0 }
this.handleControlsEvent(info)
})
}
update(manifest) {
try {
if ("mediaSession" in navigator) {
return navigator.mediaSession.metadata = new MediaMetadata({
title: manifest.title,
artist: manifest.artist,
album: manifest.album,
artwork: [
{
src: manifest.cover ?? manifest.thumbnail,
sizes: "512x512",
type: "image/jpeg",
}
],
})
}
return CapacitorMusicControls.create({
track: manifest.title,
artist: manifest.artist,
album: manifest.album,
cover: manifest.cover,
hasPrev: false,
hasNext: false,
hasClose: true,
isPlaying: true,
dismissable: false,
playIcon: "media_play",
pauseIcon: "media_pause",
prevIcon: "media_prev",
nextIcon: "media_next",
closeIcon: "media_close",
notificationIcon: "notification"
})
} catch (error) {
console.error(error)
}
}
updateIsPlaying(to, timeElapsed = 0) {
try {
if ("mediaSession" in navigator) {
return navigator.mediaSession.playbackState = to ? "playing" : "paused"
}
return CapacitorMusicControls.updateIsPlaying({
isPlaying: to,
elapsed: timeElapsed,
})
} catch {
console.error(error)
}
}
destroy() {
if ("mediaSession" in navigator) {
navigator.mediaSession.playbackState = "none"
}
this.active = false
return CapacitorMusicControls.destroy()
}
handleControlsEvent(action) {
try {
const message = action.message
switch (message) {
case "music-controls-next": {
return app.cores.player.playback.next()
}
case "music-controls-previous": {
return app.cores.player.playback.previous()
}
case "music-controls-pause": {
return app.cores.player.playback.pause()
}
case "music-controls-play": {
return app.cores.player.playback.play()
}
case "music-controls-destroy": {
return app.cores.player.playback.stop()
}
// External controls (iOS only)
case "music-controls-toggle-play-pause": {
return app.cores.player.playback.toggle()
}
// Headset events (Android only)
// All media button events are listed below
case "music-controls-media-button": {
return app.cores.player.playback.toggle()
}
case "music-controls-headset-unplugged": {
return app.cores.player.playback.pause()
}
case "music-controls-headset-plugged": {
return app.cores.player.playback.play()
}
default:
break;
}
} catch (error) {
console.error(error)
}
}
}

View File

@ -1,11 +1,8 @@
import React from "react"
import progressBar from "nprogress"
import Layouts from "@layouts"
export default class Layout extends React.PureComponent {
progressBar = progressBar.configure({ parent: "html", showSpinner: false })
state = {
layoutType: "default",
renderError: null,
@ -17,14 +14,18 @@ export default class Layout extends React.PureComponent {
},
"layout.animations.fadeOut": () => {
if (app.cores.settings.get("reduceAnimations")) {
console.warn("Skipping fadeIn animation due to `reduceAnimations` setting")
console.warn(
"Skipping fadeIn animation due to `reduceAnimations` setting",
)
return false
}
const transitionLayer = document.getElementById("transitionLayer")
if (!transitionLayer) {
console.warn("transitionLayer not found, no animation will be played")
console.warn(
"transitionLayer not found, no animation will be played",
)
return false
}
@ -32,19 +33,23 @@ export default class Layout extends React.PureComponent {
},
"layout.animations.fadeIn": () => {
if (app.cores.settings.get("reduceAnimations")) {
console.warn("Skipping fadeOut animation due to `reduceAnimations` setting")
console.warn(
"Skipping fadeOut animation due to `reduceAnimations` setting",
)
return false
}
const transitionLayer = document.getElementById("transitionLayer")
if (!transitionLayer) {
console.warn("transitionLayer not found, no animation will be played")
console.warn(
"transitionLayer not found, no animation will be played",
)
return false
}
transitionLayer.classList.remove("fade-opacity-leave")
}
},
}
componentDidMount() {
@ -58,7 +63,10 @@ export default class Layout extends React.PureComponent {
}
if (app.cores.settings.get("reduceAnimations")) {
this.layoutInterface.toggleRootContainerClassname("reduce-animations", true)
this.layoutInterface.toggleRootContainerClassname(
"reduce-animations",
true,
)
}
this.layoutInterface.toggleCenteredContent(true)
@ -75,7 +83,7 @@ export default class Layout extends React.PureComponent {
this.setState({ renderError: { info, stack } })
}
layoutInterface = window.app.layout = {
layoutInterface = (window.app.layout = {
set: (layout) => {
if (typeof Layouts[layout] !== "function") {
return console.error("Layout not found")
@ -88,28 +96,52 @@ export default class Layout extends React.PureComponent {
})
},
toggleCenteredContent: (to) => {
return this.layoutInterface.toggleRootContainerClassname("centered-content", to)
return this.layoutInterface.toggleRootContainerClassname(
"centered-content",
to,
)
},
toggleRootScaleEffect: (to) => {
return this.layoutInterface.toggleRootContainerClassname("root-scale-effect", to)
return this.layoutInterface.toggleRootContainerClassname(
"root-scale-effect",
to,
)
},
toggleMobileStyle: (to) => {
return this.layoutInterface.toggleRootContainerClassname("mobile", to)
return this.layoutInterface.toggleRootContainerClassname(
"mobile",
to,
)
},
toggleReducedAnimations: (to) => {
return this.layoutInterface.toggleRootContainerClassname("reduce-animations", to)
return this.layoutInterface.toggleRootContainerClassname(
"reduce-animations",
to,
)
},
toggleTopBarSpacer: (to) => {
return this.layoutInterface.toggleRootContainerClassname("top-bar-spacer", to)
return this.layoutInterface.toggleRootContainerClassname(
"top-bar-spacer",
to,
)
},
toggleDisableTopLayoutPadding: (to) => {
return this.layoutInterface.toggleRootContainerClassname("disable-top-layout-padding", to)
return this.layoutInterface.toggleRootContainerClassname(
"disable-top-layout-padding",
to,
)
},
togglePagePanelSpacer: (to) => {
return this.layoutInterface.toggleRootContainerClassname("page-panel-spacer", to)
return this.layoutInterface.toggleRootContainerClassname(
"page-panel-spacer",
to,
)
},
toggleCompactMode: (to) => {
return this.layoutInterface.toggleRootContainerClassname("compact-mode", to)
return this.layoutInterface.toggleRootContainerClassname(
"compact-mode",
to,
)
},
toggleRootContainerClassname: (classname, to) => {
const root = document.documentElement
@ -119,7 +151,10 @@ export default class Layout extends React.PureComponent {
return false
}
to = typeof to === "boolean" ? to : !root.classList.contains(classname)
to =
typeof to === "boolean"
? to
: !root.classList.contains(classname)
if (root.classList.contains(classname) === to) {
// ignore
@ -154,8 +189,8 @@ export default class Layout extends React.PureComponent {
...to,
behavior: "smooth",
})
}
}
},
})
render() {
let layoutType = this.state.layoutType
@ -167,7 +202,10 @@ export default class Layout extends React.PureComponent {
if (this.state.renderError) {
if (this.props.staticRenders?.RenderError) {
return React.createElement(this.props.staticRenders?.RenderError, { error: this.state.renderError })
return React.createElement(
this.props.staticRenders?.RenderError,
{ error: this.state.renderError },
)
}
return JSON.stringify(this.state.renderError)
@ -176,11 +214,12 @@ export default class Layout extends React.PureComponent {
const Layout = Layouts[layoutType]
if (!Layout) {
return app.eventBus.emit("runtime.crash", new Error(`Layout type [${layoutType}] not found`))
return app.eventBus.emit(
"runtime.crash",
new Error(`Layout type [${layoutType}] not found`),
)
}
return <Layout {...layoutComponentProps}>
{this.props.children}
</Layout>
return <Layout {...layoutComponentProps}>{this.props.children}</Layout>
}
}

View File

@ -1,13 +1,16 @@
import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import { Motion, spring } from "react-motion"
import { motion, AnimatePresence } from "motion/react"
import { Icons, createIconRender } from "@components/Icons"
import { WithPlayerContext, Context } from "@contexts/WithPlayerContext"
import { QuickNavMenuItems, QuickNavMenu } from "@layouts/components/@mobile/quickNav"
import {
QuickNavMenuItems,
QuickNavMenu,
} from "@layouts/components/@mobile/quickNav"
import PlayerView from "@pages/@mobile-views/player"
import CreatorView from "@pages/@mobile-views/creator"
@ -23,10 +26,11 @@ const tourSteps = [
},
{
title: "Account button",
description: "Tap & hold on the account icon to open miscellaneous options.",
description:
"Tap & hold on the account icon to open miscellaneous options.",
placement: "top",
refName: "accountBtnRef",
}
},
]
const openPlayerView = () => {
@ -41,23 +45,26 @@ const PlayerButton = (props) => {
openPlayerView()
}, [])
return <div
className={classnames(
"player_btn",
{
"bounce": props.playback === "playing"
}
)}
return (
<div
className={classnames("player_btn", {
bounce: props.playback === "playing",
})}
style={{
"--average-color": props.colorAnalysis?.rgba,
"--color": props.colorAnalysis?.isDark ? "var(--text-color-white)" : "var(--text-color-black)",
"--color": props.colorAnalysis?.isDark
? "var(--text-color-white)"
: "var(--text-color-black)",
}}
onClick={openPlayerView}
>
{
props.playback === "playing" ? <Icons.MdMusicNote /> : <Icons.MdPause />
}
{props.playback === "playing" ? (
<Icons.MdMusicNote />
) : (
<Icons.MdPause />
)}
</div>
)
}
const AccountButton = React.forwardRef((props, ref) => {
@ -98,12 +105,13 @@ const AccountButton = React.forwardRef((props, ref) => {
onClick: () => {
app.eventBus.emit("app.logout_request")
},
}
]
},
],
})
}
return <div
return (
<div
key="account"
id="account"
className="item"
@ -113,11 +121,14 @@ const AccountButton = React.forwardRef((props, ref) => {
context-menu="ignore"
>
<div className="icon">
{
user ? <antd.Avatar shape="square" src={app.userData.avatar} /> : createIconRender("FiLogin")
}
{user ? (
<antd.Avatar shape="square" src={app.userData.avatar} />
) : (
createIconRender("FiLogin")
)}
</div>
</div>
)
})
export class BottomBar extends React.Component {
@ -133,7 +144,7 @@ export class BottomBar extends React.Component {
busEvents = {
"runtime.crash": () => {
this.toggleVisibility(false)
}
},
}
navBtnRef = React.createRef()
@ -308,50 +319,63 @@ export class BottomBar extends React.Component {
}
this.setState({
tourOpen: to
tourOpen: to,
})
}
render() {
if (this.state.render) {
return <div className="bottomBar">
{this.state.render}
</div>
return <div className="bottomBar">{this.state.render}</div>
}
const heightValue = this.state.visible ? Number(app.cores.style.getDefaultVar("bottom-bar-height").replace("px", "")) : 0
const heightValue = Number(
app.cores.style
.getDefaultVar("bottom-bar-height")
.replace("px", ""),
)
return <>
{
this.state.tourOpen && <antd.Tour
return (
<>
{this.state.tourOpen && (
<antd.Tour
open
steps={this.getTourSteps()}
onClose={() => this.setState({ tourOpen: false })}
/>
}
<QuickNavMenu
visible={this.state.quickNavVisible}
/>
)}
<QuickNavMenu visible={this.state.quickNavVisible} />
<Motion style={{
y: spring(this.state.visible ? 0 : 300),
height: spring(heightValue)
}}>
{({ y, height }) =>
<div className="bottomBar_wrapper">
<div
className="bottomBar"
style={{
WebkitTransform: `translateY(${y}px)`,
transform: `translateY(${y}px)`,
height: `${height}px`,
<AnimatePresence>
{this.state.visible && (
<motion.div
className="bottomBar_wrapper"
initial={{
height: 0,
y: 300,
}}
animate={{
height: heightValue,
y: 0,
}}
exit={{
height: 0,
y: 300,
}}
transition={{
type: "spring",
stiffness: 100,
damping: 20,
}}
>
<div className="bottomBar">
<div className="items">
<div
key="creator"
id="creator"
className={classnames("item", "primary")}
className={classnames(
"item",
"primary",
)}
onClick={openCreator}
>
<div className="icon">
@ -359,17 +383,22 @@ export class BottomBar extends React.Component {
</div>
</div>
{
this.context.track_manifest && <div
className="item"
>
{this.context.track_manifest && (
<div className="item">
<PlayerButton
manifest={this.context.track_manifest}
playback={this.context.playback_status}
colorAnalysis={this.context.track_manifest?.cover_analysis}
manifest={
this.context.track_manifest
}
playback={
this.context.playback_status
}
colorAnalysis={
this.context.track_manifest
?.cover_analysis
}
/>
</div>
}
)}
<div
key="navigator"
@ -381,7 +410,9 @@ export class BottomBar extends React.Component {
onTouchStart={this.handleNavTouchStart}
onTouchEnd={this.handleNavTouchEnd}
onTouchCancel={() => {
this.setState({ quickNavVisible: false })
this.setState({
quickNavVisible: false,
})
}}
>
<div className="icon">
@ -400,22 +431,21 @@ export class BottomBar extends React.Component {
</div>
</div>
<AccountButton
ref={this.accountBtnRef}
/>
<AccountButton ref={this.accountBtnRef} />
</div>
</div>
</div>
}
</Motion>
</motion.div>
)}
</AnimatePresence>
</>
)
}
}
export default (props) => {
return <WithPlayerContext>
<BottomBar
{...props}
/>
return (
<WithPlayerContext>
<BottomBar {...props} />
</WithPlayerContext>
)
}

View File

@ -1,6 +1,6 @@
import React from "react"
import classnames from "classnames"
import { Motion, spring } from "react-motion"
import { motion, AnimatePresence } from "motion/react"
import useLayoutInterface from "@hooks/useLayoutInterface"
import useDefaultVisibility from "@hooks/useDefaultVisibility"
@ -9,7 +9,8 @@ import "./index.less"
export default (props) => {
const [visible, setVisible] = useDefaultVisibility()
const [shouldUseTopBarSpacer, setShouldUseTopBarSpacer] = React.useState(true)
const [shouldUseTopBarSpacer, setShouldUseTopBarSpacer] =
React.useState(true)
const [render, setRender] = React.useState(null)
useLayoutInterface("top_bar", {
@ -31,7 +32,7 @@ export default (props) => {
shouldUseTopBarSpacer: (to) => {
app.layout.toggleTopBarSpacer(to)
setShouldUseTopBarSpacer(to)
}
},
})
const handleUpdateRender = (...args) => {
@ -47,7 +48,7 @@ export default (props) => {
const updateRender = (component, options = {}) => {
setRender({
component,
options
options,
})
}
@ -85,20 +86,31 @@ export default (props) => {
}
}, [render])
const heightValue = visible ? Number(app.cores.style.getDefaultVar("top-bar-height").replace("px", "")) : 0
const heightValue = Number(
app.cores.style.getDefaultVar("top-bar-height").replace("px", ""),
)
return <Motion style={{
y: spring(visible ? 0 : 300,),
height: spring(heightValue,),
}}>
{({ y, height }) => {
return <>
<div
return (
<AnimatePresence>
{visible && (
<motion.div
className="top-bar_wrapper"
style={{
WebkitTransform: `translateY(-${y}px)`,
transform: `translateY(-${y}px)`,
height: `${height}px`,
animate={{
y: 0,
height: heightValue,
}}
initial={{
y: -300,
height: 0,
}}
exit={{
y: -300,
height: 0,
}}
transition={{
type: "spring",
stiffness: 100,
damping: 20,
}}
>
<div
@ -107,12 +119,14 @@ export default (props) => {
render?.options?.className,
)}
>
{
render?.component && React.cloneElement(render?.component, render?.options?.props ?? {})
}
{render?.component &&
React.cloneElement(
render?.component,
render?.options?.props ?? {},
)}
</div>
</div>
</>
}}
</Motion>
</motion.div>
)}
</AnimatePresence>
)
}

View File

@ -1,7 +1,6 @@
import React from "react"
import classnames from "classnames"
import * as antd from "antd"
import { AnimatePresence, motion } from "framer-motion"
import { AnimatePresence, motion } from "motion/react"
import "./index.less"
@ -30,7 +29,7 @@ export class Drawer extends React.Component {
this.toggleVisibility(false)
this.props.controller.close(this.props.id, {
transition: 150
transition: 150,
})
}
@ -65,9 +64,10 @@ export class Drawer extends React.Component {
handleDone: this.handleDone,
handleFail: this.handleFail,
}
return <AnimatePresence>
{
this.state.visible && <motion.div
return (
<AnimatePresence>
{this.state.visible && (
<motion.div
key={this.props.id}
id={this.props.id}
className="drawer"
@ -84,12 +84,14 @@ export class Drawer extends React.Component {
opacity: [1, 0],
}}
>
{
React.createElement(this.props.children, componentProps)
}
{React.createElement(
this.props.children,
componentProps,
)}
</motion.div>
}
)}
</AnimatePresence>
)
}
}
@ -171,10 +173,12 @@ export default class DrawerController extends React.Component {
if (lastDrawer) {
if (app.layout.modal && lastDrawer.options.confirmOnOutsideClick) {
return app.layout.modal.confirm({
descriptionText: lastDrawer.options.confirmOnOutsideClickText ?? "Are you sure you want to close this drawer?",
descriptionText:
lastDrawer.options.confirmOnOutsideClickText ??
"Are you sure you want to close this drawer?",
onConfirm: () => {
lastDrawer.close()
}
},
})
}
@ -202,18 +206,12 @@ export default class DrawerController extends React.Component {
}
if (typeof addresses[id] === "undefined") {
drawers.push(<Drawer
key={id}
{...instance}
/>)
drawers.push(<Drawer key={id} {...instance} />)
addresses[id] = drawers.length - 1
refs[id] = instance.ref
} else {
drawers[addresses[id]] = <Drawer
key={id}
{...instance}
/>
drawers[addresses[id]] = <Drawer key={id} {...instance} />
refs[id] = instance.ref
}
@ -267,10 +265,11 @@ export default class DrawerController extends React.Component {
}
render() {
return <>
return (
<>
<AnimatePresence>
{
this.state.maskVisible && <motion.div
{this.state.maskVisible && (
<motion.div
className="drawers-mask"
onClick={() => this.closeLastDrawer()}
initial={{
@ -282,22 +281,23 @@ export default class DrawerController extends React.Component {
exit={{
opacity: 0,
}}
transition={{
type: "spring",
stiffness: 100,
damping: 20,
}}
/>
}
)}
</AnimatePresence>
<div
className={classnames(
"drawers-wrapper",
{
className={classnames("drawers-wrapper", {
["hidden"]: !this.state.drawers.length,
}
)}
})}
>
<AnimatePresence>
{this.state.drawers}
</AnimatePresence>
<AnimatePresence>{this.state.drawers}</AnimatePresence>
</div>
</>
)
}
}

View File

@ -1,6 +1,6 @@
import React from "react"
import classnames from "classnames"
import { Motion, spring } from "react-motion"
import { motion, AnimatePresence } from "motion/react"
import useLayoutInterface from "@hooks/useLayoutInterface"
@ -17,7 +17,7 @@ export default (props) => {
return setRender({
component,
options
options,
})
},
})
@ -30,35 +30,35 @@ export default (props) => {
}
}, [render])
return <Motion
style={{
y: spring(render ? 0 : 100,),
return (
<AnimatePresence>
{render && (
<motion.div
className="page_header_wrapper"
animate={{
y: 0,
}}
initial={{
y: -100,
}}
exit={{
y: -100,
}}
transition={{
type: "spring",
stiffness: 100,
damping: 20,
}}
>
{({ y, height }) => {
return <div
className={classnames(
"page_header_wrapper",
{
["hidden"]: !render,
}
)}
style={{
WebkitTransform: `translateY(-${y}px)`,
transform: `translateY(-${y}px)`,
}}
>
<div
className="page_header"
>
{
render?.component && React.cloneElement(
<div className="page_header">
{render?.component &&
React.cloneElement(
render?.component,
render?.options?.props ?? {}
render?.options?.props ?? {},
)}
</div>
</motion.div>
)}
</AnimatePresence>
)
}
</div>
</div>
}}
</Motion>
}

View File

@ -2,7 +2,7 @@ import React from "react"
import config from "@config"
import classnames from "classnames"
import { Translation } from "react-i18next"
import { motion, AnimatePresence } from "framer-motion"
import { motion, AnimatePresence } from "motion/react"
import { Menu, Avatar, Dropdown, Tag } from "antd"
import Drawer from "@layouts/components/drawer"

View File

@ -1,6 +1,6 @@
import React from "react"
import classnames from "classnames"
import { Motion, spring } from "react-motion"
import { motion, AnimatePresence } from "motion/react"
import WidgetsWrapper from "@components/WidgetsWrapper"
@ -51,69 +51,83 @@ export default class ToolsBar extends React.Component {
detachRender: (id) => {
this.setState({
renders: {
top: this.state.renders.top.filter((render) => render.id !== id),
bottom: this.state.renders.bottom.filter((render) => render.id !== id),
top: this.state.renders.top.filter(
(render) => render.id !== id,
),
bottom: this.state.renders.bottom.filter(
(render) => render.id !== id,
),
},
})
return true
}
},
}
render() {
const hasAnyRenders = this.state.renders.top.length > 0 || this.state.renders.bottom.length > 0
const hasAnyRenders =
this.state.renders.top.length > 0 ||
this.state.renders.bottom.length > 0
const isVisible = hasAnyRenders && this.state.visible
return <Motion
style={{
x: spring(isVisible ? 0 : 100),
width: spring(isVisible ? 100 : 0),
return (
<AnimatePresence>
{isVisible && (
<motion.div
className={classnames("tools-bar-wrapper", {
["visible"]: isVisible,
})}
animate={{
x: 0,
width: "100%",
}}
initial={{
x: 100,
width: "0%",
}}
exit={{
x: 100,
width: "0%",
}}
transition={{
type: "spring",
stiffness: 100,
damping: 20,
}}
>
{({ x, width }) => {
return <div
style={{
width: `${width}%`,
transform: `translateX(${x}%)`,
}}
className={classnames(
"tools-bar-wrapper",
{
visible: isVisible,
}
)}
>
<div
id="tools_bar"
className="tools-bar"
>
<div id="tools_bar" className="tools-bar">
<div className="attached_renders top">
{this.state.renders.top.map((render, index) => {
return React.createElement(
render.component,
{
this.state.renders.top.map((render, index) => {
return React.createElement(render.component, {
...render.props,
key: index,
})
})
}
},
)
})}
</div>
<WidgetsWrapper />
<div className="attached_renders bottom">
{this.state.renders.bottom.map(
(render, index) => {
return React.createElement(
render.component,
{
this.state.renders.bottom.map((render, index) => {
return React.createElement(render.component, {
...render.props,
key: index,
})
})
}
},
)
},
)}
</div>
</div>
</div>
}}
</Motion>
</motion.div>
)}
</AnimatePresence>
)
}
}

View File

@ -21,7 +21,6 @@
&:not(.visible) {
min-width: 0;
padding: 0;
}
}

View File

@ -1,7 +1,7 @@
import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import { motion, AnimatePresence } from "framer-motion"
import { motion, AnimatePresence } from "motion/react"
import { Icons } from "@components/Icons"
import FollowButton from "@components/FollowButton"
@ -21,10 +21,10 @@ import FollowersTab from "./tabs/followers"
import "./index.less"
const TabsComponent = {
"posts": PostsTab,
"followers": FollowersTab,
"details": DetailsTab,
"music": MusicTab,
posts: PostsTab,
followers: FollowersTab,
details: DetailsTab,
music: MusicTab,
}
export default class Account extends React.Component {
@ -60,7 +60,7 @@ export default class Account extends React.Component {
}
user = await UserModel.data({
username: requestedUser
username: requestedUser,
}).catch((error) => {
console.error(error)
@ -77,7 +77,9 @@ export default class Account extends React.Component {
console.log(`Loaded User [${user.username}] >`, user)
const followersResult = await FollowsModel.getFollowers(user._id).catch(() => false)
const followersResult = await FollowsModel.getFollowers(
user._id,
).catch(() => false)
if (followersResult) {
followersCount = followersResult.count
@ -118,7 +120,10 @@ export default class Account extends React.Component {
handlePageTransition = (key) => {
if (typeof key !== "string") {
console.error("Cannot handle page transition. Invalid key, only valid passing string", key)
console.error(
"Cannot handle page transition. Invalid key, only valid passing string",
key,
)
return
}
@ -129,7 +134,7 @@ export default class Account extends React.Component {
}
this.setState({
tabActiveKey: key
tabActiveKey: key,
})
}
@ -137,43 +142,39 @@ export default class Account extends React.Component {
const user = this.state.user
if (this.state.isNotExistent) {
return <antd.Result
return (
<antd.Result
status="404"
title="This user does not exist, yet..."
>
</antd.Result>
></antd.Result>
)
}
if (!user) {
return <antd.Skeleton active />
}
return <div
return (
<div
id="profile"
className={classnames(
"account-profile",
{
className={classnames("account-profile", {
["withCover"]: user.cover,
}
)}
})}
>
{
user.cover && <div
{user.cover && (
<div
className={classnames("cover", {
["expanded"]: this.state.coverExpanded
["expanded"]: this.state.coverExpanded,
})}
style={{ backgroundImage: `url("${user.cover}")` }}
onClick={() => this.toggleCoverExpanded()}
id="profile-cover"
/>
}
)}
<div className="panels">
<div className="left-panel">
<UserCard
user={user}
/>
<UserCard user={user} />
<div className="actions">
<FollowButton
@ -183,19 +184,20 @@ export default class Account extends React.Component {
self={this.state.isSelf}
/>
{
!this.state.isSelf && <antd.Button
{!this.state.isSelf && (
<antd.Button
icon={<Icons.MdMessage />}
onClick={() => app.location.push(`/messages/${user._id}`)}
/>
onClick={() =>
app.location.push(
`/messages/${user._id}`,
)
}
/>
)}
</div>
</div>
<div
className="center-panel"
ref={this.contentRef}
>
<div className="center-panel" ref={this.contentRef}>
<AnimatePresence mode="wait">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
@ -209,12 +211,14 @@ export default class Account extends React.Component {
width: "100%",
}}
>
{React.createElement(
TabsComponent[this.state.tabActiveKey],
{
React.createElement(TabsComponent[this.state.tabActiveKey], {
onTopVisibility: this.onPostListTopVisibility,
state: this.state
})
}
onTopVisibility:
this.onPostListTopVisibility,
state: this.state,
},
)}
</motion.div>
</AnimatePresence>
</div>
@ -245,11 +249,12 @@ export default class Account extends React.Component {
id: "details",
label: "Details",
icon: "FiInfo",
}
},
])}
/>
</div>
</div>
</div>
)
}
}

View File

@ -1,6 +1,6 @@
import React from "react"
import classnames from "classnames"
import { Motion, spring } from "react-motion"
import { motion, AnimatePresence } from "motion/react"
import { usePlayerStateContext } from "@contexts/WithPlayerContext"
@ -17,7 +17,10 @@ const LyricsText = React.forwardRef((props, textRef) => {
const currentTrackTime = app.cores.player.controls.seek() * 1000
const lineIndex = lyrics.synced_lyrics.findIndex((line) => {
return currentTrackTime >= line.startTimeMs && currentTrackTime <= line.endTimeMs
return (
currentTrackTime >= line.startTimeMs &&
currentTrackTime <= line.endTimeMs
)
})
if (lineIndex === -1) {
@ -72,7 +75,9 @@ const LyricsText = React.forwardRef((props, textRef) => {
setVisible(true)
// find line element by id
const lineElement = textRef.current.querySelector(`#lyrics-line-${currentLineIndex}`)
const lineElement = textRef.current.querySelector(
`#lyrics-line-${currentLineIndex}`,
)
// center scroll to current line
if (lineElement) {
@ -112,42 +117,46 @@ const LyricsText = React.forwardRef((props, textRef) => {
return null
}
return <div
className="lyrics-text-wrapper"
>
<Motion
style={{
opacity: spring(visible ? 1 : 0),
}}
>
{({ opacity }) => {
return <div
return (
<div className="lyrics-text-wrapper">
<AnimatePresence>
{visible && (
<motion.div
ref={textRef}
className="lyrics-text"
style={{
opacity
animate={{
opacity: 1,
}}
initial={{
opacity: 0,
}}
exit={{
opacity: 0,
}}
transition={{
type: "spring",
stiffness: 100,
damping: 20,
}}
>
{
lyrics.synced_lyrics.map((line, index) => {
return <p
{lyrics.synced_lyrics.map((line, index) => {
return (
<p
key={index}
id={`lyrics-line-${index}`}
className={classnames(
"line",
{
["current"]: currentLineIndex === index
}
)}
className={classnames("line", {
["current"]: currentLineIndex === index,
})}
>
{line.text}
</p>
})
}
</div>
}}
</Motion>
)
})}
</motion.div>
)}
</AnimatePresence>
</div>
)
})
export default LyricsText

View File

@ -11,8 +11,10 @@ import "./index.less"
const connectionsTooltipStrings = {
secure: "This connection is secure",
insecure: "This connection is insecure, cause it's not using HTTPS protocol and the server cannot be verified on the trusted certificate authority.",
warning: "This connection is secure but the server cannot be verified on the trusted certificate authority.",
insecure:
"This connection is insecure, cause it's not using HTTPS protocol and the server cannot be verified on the trusted certificate authority.",
warning:
"This connection is secure but the server cannot be verified on the trusted certificate authority.",
}
export default {
@ -29,12 +31,14 @@ export default {
const [capInfo, setCapInfo] = React.useState(null)
const setCapacitorInfo = async () => {
if (Capacitor) {
if (Capacitor.Plugins.App) {
const info = await Capacitor.Plugins.App.getInfo()
setCapInfo(info)
}
}
}
const checkServerVersion = async () => {
const serverManifest = await app.cores.api.customRequest()
@ -61,14 +65,12 @@ export default {
setCapacitorInfo()
}, [])
return <div className="about_app">
return (
<div className="about_app">
<div className="header">
<div className="branding">
<div className="logo">
<img
src={config.logo.alt}
alt="Logo"
/>
<img src={config.logo.alt} alt="Logo" />
</div>
<div className="texts">
<div className="sitename-text">
@ -76,13 +78,23 @@ export default {
<antd.Tag>Beta</antd.Tag>
</div>
<span>{config.author}</span>
<span>Licensed with {config.package?.license ?? "unlicensed"} </span>
<span>
Licensed with{" "}
{config.package?.license ?? "unlicensed"}{" "}
</span>
</div>
</div>
<div className="versions">
<antd.Tag><Icons.FiTag />v{window.app.version ?? "experimental"}</antd.Tag>
<antd.Tag>
<Icons.FiTag />v
{window.app.version ?? "experimental"}
</antd.Tag>
<antd.Tag color={isProduction ? "green" : "magenta"}>
{isProduction ? <Icons.FiCheckCircle /> : <Icons.FiTriangle />}
{isProduction ? (
<Icons.FiCheckCircle />
) : (
<Icons.FiTriangle />
)}
{String(import.meta.env.MODE)}
</antd.Tag>
</div>
@ -90,23 +102,38 @@ export default {
<div className="group">
<div className="group_header">
<h3><Icons.FiInfo />Server information</h3>
<h3>
<Icons.FiInfo />
Server information
</h3>
</div>
<div className="field">
<div className="field_header">
<h3><Icons.MdOutlineStream /> Origin</h3>
<h3>
<Icons.MdOutlineStream /> Origin
</h3>
<antd.Tooltip
title={secureConnection ? connectionsTooltipStrings.secure : connectionsTooltipStrings.insecure}
title={
secureConnection
? connectionsTooltipStrings.secure
: connectionsTooltipStrings.insecure
}
>
<antd.Tag
color={secureConnection ? "green" : "red"}
icon={secureConnection ? <Icons.MdHttps /> : <Icons.MdWarning />}
>
{
secureConnection ? " Secure connection" : "Insecure connection"
icon={
secureConnection ? (
<Icons.MdHttps />
) : (
<Icons.MdWarning />
)
}
>
{secureConnection
? " Secure connection"
: "Insecure connection"}
</antd.Tag>
</antd.Tooltip>
</div>
@ -118,7 +145,9 @@ export default {
<div className="field">
<div className="field_header">
<h3><Icons.MdOutlineMemory /> Instance Performance</h3>
<h3>
<Icons.MdOutlineMemory /> Instance Performance
</h3>
</div>
<div className="field_value">
@ -132,13 +161,9 @@ export default {
width: "100%",
}}
>
<LatencyIndicator
type="http"
/>
<LatencyIndicator type="http" />
<LatencyIndicator
type="ws"
/>
<LatencyIndicator type="ws" />
</div>
</div>
</div>
@ -173,9 +198,7 @@ export default {
<p>Platform</p>
</div>
<div className="field_value">
{Capacitor.platform}
</div>
<div className="field_value">Web App</div>
</div>
<div className="inline_field">
@ -220,8 +243,8 @@ export default {
</div>
</div>
{
capInfo && <div className="inline_field">
{capInfo && (
<div className="inline_field">
<div className="field_header">
<div className="field_icon">
<Icons.MdInfo />
@ -230,14 +253,12 @@ export default {
<p>App ID</p>
</div>
<div className="field_value">
{capInfo.id}
<div className="field_value">{capInfo.id}</div>
</div>
</div>
}
)}
{
capInfo && <div className="inline_field">
{capInfo && (
<div className="inline_field">
<div className="field_header">
<div className="field_icon">
<Icons.MdInfo />
@ -246,14 +267,12 @@ export default {
<p>App Build</p>
</div>
<div className="field_value">
{capInfo.build}
<div className="field_value">{capInfo.build}</div>
</div>
</div>
}
)}
{
capInfo && <div className="inline_field">
{capInfo && (
<div className="inline_field">
<div className="field_header">
<div className="field_icon">
<Icons.MdInfo />
@ -262,11 +281,9 @@ export default {
<p>App Version</p>
</div>
<div className="field_value">
{capInfo.version}
<div className="field_value">{capInfo.version}</div>
</div>
</div>
}
)}
<div className="inline_field">
<div className="field_header">
@ -286,5 +303,6 @@ export default {
</div>
</div>
</div>
}
)
},
}

View File

@ -11,9 +11,7 @@ const oneYearInSeconds = 60 * 60 * 24 * 365
const sslDirPath = path.join(__dirname, ".ssl")
const config = {
plugins: [
react(),
],
plugins: [react()],
resolve: {
alias: aliases,
},
@ -24,7 +22,7 @@ const config = {
allow: ["..", "../../"],
},
headers: {
"Strict-Transport-Security": `max-age=${oneYearInSeconds}`
"Strict-Transport-Security": `max-age=${oneYearInSeconds}`,
},
proxy: {
"/api": {
@ -36,38 +34,16 @@ const config = {
ws: true,
toProxy: true,
secure: false,
}
}
},
},
},
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
}
}
},
optimizeDeps: {
esbuildOptions: {
target: "esnext"
}
},
build: {
target: "esnext",
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("node_modules")) {
return id.toString().split("node_modules/")[1].split("/")[0].toString()
}
}
}
}
},
esbuild: {
supported: {
"top-level-await": true
},
}
}
if (fs.existsSync(sslDirPath)) {