diff --git a/packages/app/aliases.js b/packages/app/aliases.js index 19d1f1be..b9661f84 100644 --- a/packages/app/aliases.js +++ b/packages/app/aliases.js @@ -1,17 +1,18 @@ import path from "path" export default { - "@": path.join(__dirname, "src"), - "@config": path.join(__dirname, "config"), - "@cores": path.join(__dirname, "src/cores"), - "@pages": path.join(__dirname, "src/pages"), - "@styles": path.join(__dirname, "src/styles"), - "@components": path.join(__dirname, "src/components"), - "@contexts": path.join(__dirname, "src/contexts"), - "@utils": path.join(__dirname, "src/utils"), - "@layouts": path.join(__dirname, "src/layouts"), - "@hooks": path.join(__dirname, "src/hooks"), - "@classes": path.join(__dirname, "src/classes"), - "@models": path.join(__dirname, "../../", "comty.js/src/models"), - "comty.js": path.join(__dirname, "../../", "comty.js", "src"), -} \ No newline at end of file + "@": path.join(__dirname, "src"), + "@config": path.join(__dirname, "config"), + "@cores": path.join(__dirname, "src/cores"), + "@pages": path.join(__dirname, "src/pages"), + "@styles": path.join(__dirname, "src/styles"), + "@components": path.join(__dirname, "src/components"), + "@contexts": path.join(__dirname, "src/contexts"), + "@utils": path.join(__dirname, "src/utils"), + "@layouts": path.join(__dirname, "src/layouts"), + "@hooks": path.join(__dirname, "src/hooks"), + "@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"), +} diff --git a/packages/app/package.json b/packages/app/package.json index d595898b..d3144633 100755 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -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" } } diff --git a/packages/app/src/App.jsx b/packages/app/src/App.jsx index f1a5c089..8ae52064 100755 --- a/packages/app/src/App.jsx +++ b/packages/app/src/App.jsx @@ -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, { - props: { - width: "fit-content", + window.app.layout.drawer.open( + "notifications", + NotificationsCenter, + { + props: { + width: "fit-content", + }, + allowMultiples: false, + escClosable: true, }, - 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) => , { - framed: false - }) + return app.layout.modal.open( + "searcher", + (props) => , + { + framed: false, + }, + ) }, openMessages: () => { app.location.push("/messages") }, openFullImageViewer: (src) => { - app.cores.window_mng.render("image_lightbox", app.cores.window_mng.close("image_lightbox")} - hideDownload - showRotate - />) + app.cores.window_mng.render( + "image_lightbox", + + app.cores.window_mng.close("image_lightbox") + } + hideDownload + showRotate + />, + ) }, openPostCreator: (params) => { - app.layout.modal.open("post_creator", (props) => , { - framed: false - }) - } + app.layout.modal.open( + "post_creator", + (props) => , + { + 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: - {(t) => t("Invalid Session")} - , - description: - {(t) => t(error)} - , + message: ( + {(t) => t("Invalid Session")} + ), + description: {(t) => t(error)}, icon: , }) }, @@ -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,32 +463,33 @@ class ComtyApp extends React.Component { } render() { - return - - {config.app.siteName} - - - - - - { - window.__TAURI__ && - } - - { - this.state.initialized && - } - - - - + return ( + + + {config.app.siteName} + + + + + + {window.__TAURI__ && } + + {this.state.initialized && } + + + + + ) } } -export default new Runtime(ComtyApp) \ No newline at end of file +export default new Runtime(ComtyApp) diff --git a/packages/app/src/components/PageTransition/index.jsx b/packages/app/src/components/PageTransition/index.jsx index 404bcf62..50819684 100644 --- a/packages/app/src/components/PageTransition/index.jsx +++ b/packages/app/src/components/PageTransition/index.jsx @@ -1,19 +1,21 @@ import React from "react" -import { motion, AnimatePresence } from "framer-motion" +import { motion, AnimatePresence } from "motion/react" const PageTransition = (props) => { - return - - {props.children} - - + return ( + + + {props.children} + + + ) } -export default PageTransition \ No newline at end of file +export default PageTransition diff --git a/packages/app/src/components/Poll/index.jsx b/packages/app/src/components/Poll/index.jsx index df32e245..b9654f47 100644 --- a/packages/app/src/components/Poll/index.jsx +++ b/packages/app/src/components/Poll/index.jsx @@ -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" @@ -11,214 +11,220 @@ import PostModel from "@models/post" import "./index.less" const PollOption = (props) => { - async function onClick() { - if (typeof props.onClick === "function") { - await props.onClick(props.option.id) - } - } + async function onClick() { + if (typeof props.onClick === "function") { + await props.onClick(props.option.id) + } + } - return
+ {props.checked && ( + + )} - } - )} - style={{ - "--percentage": `${props.percentage}%` - }} - onClick={onClick} - > - { - props.checked && - } +
+ {props.checked && createIconRender("FaCheck")} -
- { - props.checked && createIconRender("FaCheck") - } + {props.showPercentage && ( + {Math.floor(props.percentage)}% + )} - { - props.showPercentage && - {Math.floor(props.percentage)}% - - } - - - {props.option.label} - -
-
+ {props.option.label} +
+ + ) } const Poll = (props) => { - const { editMode, onClose, formRef } = props + const { editMode, onClose, formRef } = props - const [options, setOptions] = React.useState(props.options ?? []) - const [hasVoted, setHasVoted] = React.useState(false) - const [totalVotes, setTotalVotes] = React.useState(0) + const [options, setOptions] = React.useState(props.options ?? []) + const [hasVoted, setHasVoted] = React.useState(false) + const [totalVotes, setTotalVotes] = React.useState(0) - useWsEvents({ - "post.poll.vote": (data) => { - const { post_id, option_id, user_id, previous_option_id } = data + useWsEvents( + { + "post.poll.vote": (data) => { + const { post_id, option_id, user_id, previous_option_id } = data - if (post_id !== props.post_id) { - return false - } + if (post_id !== props.post_id) { + return false + } - console.debug(`U[${user_id}] vote to option [${option_id}]`) + console.debug(`U[${user_id}] vote to option [${option_id}]`) - setOptions((prev) => { - prev = prev.map((option) => { - return option - }) + setOptions((prev) => { + prev = prev.map((option) => { + return option + }) - if (user_id === app.userData._id) { - // remove all `voted` properties - prev = prev.map((option) => { - delete option.voted + if (user_id === app.userData._id) { + // remove all `voted` properties + prev = prev.map((option) => { + delete option.voted - option.voted = option.id === option_id + option.voted = option.id === option_id - return option - }) - } + return option + }) + } - if (previous_option_id) { - const previousOptionIndex = prev.findIndex((option) => option.id === previous_option_id) + if (previous_option_id) { + const previousOptionIndex = prev.findIndex( + (option) => option.id === previous_option_id, + ) - if (previousOptionIndex !== -1) { - prev[previousOptionIndex].count = prev[previousOptionIndex].count - 1 - } - } + if (previousOptionIndex !== -1) { + prev[previousOptionIndex].count = + prev[previousOptionIndex].count - 1 + } + } - if (option_id) { - const newOptionIndex = prev.findIndex((option) => option.id === option_id) + if (option_id) { + const newOptionIndex = prev.findIndex( + (option) => option.id === option_id, + ) - if (newOptionIndex !== -1) { - prev[newOptionIndex].count += 1 - } - } + if (newOptionIndex !== -1) { + prev[newOptionIndex].count += 1 + } + } - return prev - }) - } - }, { - socketName: "posts" - }) + return prev + }) + }, + }, + { + socketName: "posts", + }, + ) - async function onVote(id) { - console.debug(`Voting poll option`, { - option_id: id, - post_id: props.post_id, - }) + async function onVote(id) { + console.debug(`Voting poll option`, { + option_id: id, + post_id: props.post_id, + }) - await PostModel.votePoll({ - post_id: props.post_id, - option_id: id, - }) - } + await PostModel.votePoll({ + post_id: props.post_id, + option_id: id, + }) + } - React.useEffect(() => { - if (options) { - const totalVotes = options.reduce((sum, option) => { - return sum + option.count - }, 0) + React.useEffect(() => { + if (options) { + const totalVotes = options.reduce((sum, option) => { + return sum + option.count + }, 0) - setTotalVotes(totalVotes) + setTotalVotes(totalVotes) - const hasVoted = options.some((option) => { - return option.voted - }) + const hasVoted = options.some((option) => { + return option.voted + }) - setHasVoted(hasVoted) - } - }, [options]) + setHasVoted(hasVoted) + } + }, [options]) - return
- { - !editMode && options.map((option, index) => { - const percentage = totalVotes > 0 ? (option.count / totalVotes) * 100 : 0 + return ( +
+ {!editMode && + options.map((option, index) => { + const percentage = + totalVotes > 0 ? (option.count / totalVotes) * 100 : 0 - return - }) - } + return ( + + ) + })} - { - editMode && - - {(fields, { add, remove }) => { - return <> - { - fields.map((field, index) => { - return
- - - + {editMode && ( + + + {(fields, { add, remove }) => { + return ( + <> + {fields.map((field, index) => { + return ( +
+ + + - { - fields.length > 1 && remove(field.name)} - icon={createIconRender("MdRemove")} - /> - } -
- }) - } + {fields.length > 1 && ( + + remove(field.name) + } + icon={createIconRender( + "MdRemove", + )} + /> + )} +
+ ) + })} - add()} - icon={createIconRender("PlusOutlined")} - > - Add Option - - - }} -
-
- } + add()} + icon={createIconRender("PlusOutlined")} + > + Add Option + + + ) + }} + + + )} - { - editMode &&
- -
- } -
+ {editMode && ( +
+ +
+ )} +
+ ) } -export default Poll \ No newline at end of file +export default Poll diff --git a/packages/app/src/components/PostCard/components/attachments/index.jsx b/packages/app/src/components/PostCard/components/attachments/index.jsx index 6796301d..89ab8ebd 100755 --- a/packages/app/src/components/PostCard/components/attachments/index.jsx +++ b/packages/app/src/components/PostCard/components/attachments/index.jsx @@ -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,167 +8,176 @@ 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" const Attachment = React.memo((props) => { - const [loaded, setLoaded] = React.useState(false) + const [loaded, setLoaded] = React.useState(false) - const [mimeType, setMimeType] = React.useState(null) + const [mimeType, setMimeType] = React.useState(null) - try { - const { url, id } = props.attachment + try { + const { url, id } = props.attachment - const onDoubleClickAttachment = (e) => { - if (mimeType.split("/")[0] === "image") { - e.preventDefault() - e.stopPropagation() + const onDoubleClickAttachment = (e) => { + if (mimeType.split("/")[0] === "image") { + e.preventDefault() + e.stopPropagation() - app.controls.openFullImageViewer(url) - } - } + app.controls.openFullImageViewer(url) + } + } - const getMediaType = async () => { - let extension = null + const getMediaType = async () => { + let extension = null - // get media type by parsing the url - const mediaExtname = /\.([a-zA-Z0-9]+)$/.exec(url) + // get media type by parsing the url + const mediaExtname = /\.([a-zA-Z0-9]+)$/.exec(url) - if (mediaExtname) { - extension = mediaExtname[1] - } else { - // try to get media by creating requesting the url - const response = await fetch(url, { - method: "HEAD", - }) + if (mediaExtname) { + extension = mediaExtname[1] + } else { + // try to get media by creating requesting the url + const response = await fetch(url, { + method: "HEAD", + }) - extension = response.headers.get("content-type").split("/")[1] - } + extension = response.headers.get("content-type").split("/")[1] + } - extension = extension.toLowerCase() + extension = extension.toLowerCase() - if (!extension) { - setLoaded(true) + if (!extension) { + setLoaded(true) - console.error("Failed to get media type", url, extension) + console.error("Failed to get media type", url, extension) - return - } + return + } - const mimeType = mimetypes.getType(extension) + const mimeType = mimetypes.getType(extension) - setMimeType(mimeType) + setMimeType(mimeType) - setLoaded(true) - } + setLoaded(true) + } - const renderMedia = () => { - if (!mimeType) { - return null - } + const renderMedia = () => { + if (!mimeType) { + return null + } - switch (mimeType.split("/")[0]) { - case "image": { - return - } - case "video": { - return - } - case "audio": { - return - } - default: { - return

- Unsupported media type [{mimeType}] -

- } - } - } + switch (mimeType.split("/")[0]) { + case "image": { + return + } + case "video": { + return ( + + ) + } + case "audio": { + return ( + + ) + } + default: { + return

Unsupported media type [{mimeType}]

+ } + } + } - React.useEffect(() => { - getMediaType() - }, []) + React.useEffect(() => { + getMediaType() + }, []) - if (!loaded) { - return - } + if (!loaded) { + return + } - if (loaded && !mimeType) { - return - } + if (loaded && !mimeType) { + return + } - return
- {renderMedia()} -
- } catch (error) { - console.error(error) + return ( +
+ {renderMedia()} +
+ ) + } catch (error) { + console.error(error) - return - } + return + } }) export default React.memo((props) => { - const [controller, setController] = React.useState() - const [carouselState, setCarouselState] = React.useState() - const [nsfwAccepted, setNsfwAccepted] = React.useState(false) + const [controller, setController] = React.useState() + const [carouselState, setCarouselState] = React.useState() + const [nsfwAccepted, setNsfwAccepted] = React.useState(false) - React.useEffect(() => { - // get attachment index from query string - const attachmentIndex = parseInt(new URLSearchParams(window.location.search).get("attachment")) + React.useEffect(() => { + // get attachment index from query string + const attachmentIndex = parseInt( + new URLSearchParams(window.location.search).get("attachment"), + ) - if (attachmentIndex) { - controller?.slideToPage(attachmentIndex) - } - }, []) + if (attachmentIndex) { + controller?.slideToPage(attachmentIndex) + } + }, []) - return
- { - props.flags && props.flags.includes("nsfw") && !nsfwAccepted && -
-

- This post may contain sensitive content. -

+ return ( +
+ {props.flags && props.flags.includes("nsfw") && !nsfwAccepted && ( +
+

This post may contain sensitive content.

- -
- } + +
+ )} - { - props.attachments?.length > 0 && { - if (typeof attachment !== "object") { - attachment = { - url: attachment, - } - } + {props.attachments?.length > 0 && ( + { + if (typeof attachment !== "object") { + attachment = { + url: attachment, + } + } - return { - key: index, - children: - - - } - })} - isEnableNavButton - isEnableMouseMove - isEnablePagination - setController={setController} - onSlideChange={setCarouselState} - isDebug={renderDebug} - /> - } -
-}) \ No newline at end of file + return { + key: index, + children: ( + + + + ), + } + })} + isEnableNavButton + isEnableMouseMove + isEnablePagination + setController={setController} + onSlideChange={setCarouselState} + isDebug={renderDebug} + /> + )} +
+ ) +}) diff --git a/packages/app/src/components/PostCard/index.jsx b/packages/app/src/components/PostCard/index.jsx index 79b5a2b2..cf231885 100755 --- a/packages/app/src/components/PostCard/index.jsx +++ b/packages/app/src/components/PostCard/index.jsx @@ -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" @@ -14,249 +14,288 @@ import PostAttachments from "./components/attachments" import "./index.less" const articleAnimationProps = { - layout: true, - initial: { y: -100, opacity: 0 }, - animate: { y: 0, opacity: 1, }, - exit: { scale: 0, opacity: 0 }, - transition: { duration: 0.1, }, + layout: true, + initial: { y: -100, opacity: 0 }, + animate: { y: 0, opacity: 1 }, + exit: { scale: 0, opacity: 0 }, + 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 - } - }, - { - 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 {result[1]} - } - }, - { - regex: /(@[a-zA-Z0-9_]+)/gi, - fn: (key, result) => { - return window.app.location.push(`/@${result[1].substr(1)}`)}>{result[1]} - } - }, - { - regex: /#[a-zA-Z0-9_]+/gi, - fn: (key, result) => { - return window.app.location.push(`/trending/${result[0].substr(1)}`)}>{result[0]} - } - } + { + 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 + }, + }, + { + 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 ( + + {result[1]} + + ) + }, + }, + { + regex: /(@[a-zA-Z0-9_]+)/gi, + fn: (key, result) => { + return ( + + window.app.location.push(`/@${result[1].substr(1)}`) + } + > + {result[1]} + + ) + }, + }, + { + regex: /#[a-zA-Z0-9_]+/gi, + fn: (key, result) => { + return ( + + window.app.location.push( + `/trending/${result[0].substr(1)}`, + ) + } + > + {result[0]} + + ) + }, + }, ] export default class PostCard extends React.PureComponent { - state = { - data: this.props.data, + state = { + data: this.props.data, - countLikes: this.props.data.countLikes ?? 0, - countReplies: this.props.data.countComments ?? 0, + countLikes: this.props.data.countLikes ?? 0, + countReplies: this.props.data.countComments ?? 0, - hasLiked: this.props.data.isLiked ?? false, - hasSaved: this.props.data.isSaved ?? false, - hasReplies: this.props.data.hasReplies ?? false, + hasLiked: this.props.data.isLiked ?? false, + hasSaved: this.props.data.isSaved ?? false, + hasReplies: this.props.data.hasReplies ?? false, - open: this.props.defaultOpened ?? false, + open: this.props.defaultOpened ?? false, - isNsfw: this.props.data.flags?.includes("nsfw") ?? false, - nsfwAccepted: false, - } + isNsfw: this.props.data.flags?.includes("nsfw") ?? false, + nsfwAccepted: false, + } - handleDataUpdate = (data) => { - this.setState({ - data: data, - }) - } + handleDataUpdate = (data) => { + this.setState({ + data: data, + }) + } - onDoubleClick = async () => { - if (typeof this.props.events.onDoubleClick !== "function") { - console.warn("onDoubleClick event is not a function") - return - } + onDoubleClick = async () => { + if (typeof this.props.events.onDoubleClick !== "function") { + console.warn("onDoubleClick event is not a function") + return + } - return await this.props.events.onDoubleClick(this.state.data) - } + return await this.props.events.onDoubleClick(this.state.data) + } - onClickDelete = async () => { - if (typeof this.props.events.onClickDelete !== "function") { - console.warn("onClickDelete event is not a function") - return - } + onClickDelete = async () => { + if (typeof this.props.events.onClickDelete !== "function") { + console.warn("onClickDelete event is not a function") + return + } - return await this.props.events.onClickDelete(this.state.data) - } + return await this.props.events.onClickDelete(this.state.data) + } - onClickLike = async () => { - if (typeof this.props.events.onClickLike !== "function") { - console.warn("onClickLike event is not a function") - return - } + onClickLike = async () => { + if (typeof this.props.events.onClickLike !== "function") { + console.warn("onClickLike event is not a function") + 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({ - hasLiked: actionResult.liked, - countLikes: actionResult.count, - }) - } + if (actionResult) { + this.setState({ + hasLiked: actionResult.liked, + countLikes: actionResult.count, + }) + } - return actionResult - } + return actionResult + } - onClickSave = async () => { - if (typeof this.props.events.onClickSave !== "function") { - console.warn("onClickSave event is not a function") - return - } + onClickSave = async () => { + if (typeof this.props.events.onClickSave !== "function") { + console.warn("onClickSave event is not a function") + 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({ - hasSaved: actionResult.saved, - }) - } + if (actionResult) { + this.setState({ + hasSaved: actionResult.saved, + }) + } - return actionResult - } + return actionResult + } - onClickEdit = async () => { - if (typeof this.props.events.onClickEdit !== "function") { - console.warn("onClickEdit event is not a function") - return - } + onClickEdit = async () => { + if (typeof this.props.events.onClickEdit !== "function") { + console.warn("onClickEdit event is not a function") + return + } - return await this.props.events.onClickEdit(this.state.data) - } + return await this.props.events.onClickEdit(this.state.data) + } - onClickReply = async () => { - if (typeof this.props.events.onClickReply !== "function") { - console.warn("onClickReply event is not a function") - return - } + onClickReply = async () => { + if (typeof this.props.events.onClickReply !== "function") { + console.warn("onClickReply event is not a function") + return + } - return await this.props.events.onClickReply(this.state.data) - } + return await this.props.events.onClickReply(this.state.data) + } - componentDidUpdate = (prevProps) => { - if (prevProps.data !== this.props.data) { - this.setState({ - data: this.props.data, - }) - } - } + componentDidUpdate = (prevProps) => { + if (prevProps.data !== this.props.data) { + this.setState({ + data: this.props.data, + }) + } + } - componentDidCatch = (error, info) => { - console.error(error) + componentDidCatch = (error, info) => { + console.error(error) - return
-

- - Cannot render this post - - Maybe this version of the app is outdated or is not supported yet - -

-
- } + return ( +
+

+ + Cannot render this post + + Maybe this version of the app is outdated or is not + supported yet + +

+
+ ) + } - componentDidMount = () => { - app.cores.api.listenEvent(`post.update.${this.state.data._id}`, this.handleDataUpdate, "posts") - } + componentDidMount = () => { + 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") - } + componentWillUnmount = () => { + app.cores.api.unlistenEvent( + `post.update.${this.state.data._id}`, + this.handleDataUpdate, + "posts", + ) + } - render() { - return -
- + render() { + return ( + +
+ -
-
- { - processString(messageRegexs)(this.state.data.message ?? "") - } -
+
+
+ {processString(messageRegexs)( + this.state.data.message ?? "", + )} +
- { - !this.props.disableAttachments && this.state.data.attachments && this.state.data.attachments.length > 0 && - } + {!this.props.disableAttachments && + this.state.data.attachments && + this.state.data.attachments.length > 0 && ( + + )} - { - this.state.data.poll_options && - } -
+ {this.state.data.poll_options && ( + + )} +
- - likesCount={this.state.countLikes} - repliesCount={this.state.countReplies} - - defaultLiked={this.state.hasLiked} - defaultSaved={this.state.hasSaved} -PP - actions={{ - onClickLike: this.onClickLike, - onClickEdit: this.onClickEdit, - onClickDelete: this.onClickDelete, - onClickSave: this.onClickSave, - onClickReply: this.onClickReply, - }} - /> - - { - !this.props.disableHasReplies && !!this.state.hasReplies &&
app.navigation.goToPost(this.state.data._id)} - > - View {this.state.hasReplies} replies -
- } -
- -
- } -} \ No newline at end of file + {!this.props.disableHasReplies && + !!this.state.hasReplies && ( +
+ app.navigation.goToPost(this.state.data._id) + } + > + + View {this.state.hasReplies} replies + +
+ )} +
+
+ ) + } +} diff --git a/packages/app/src/components/PostsList/index.jsx b/packages/app/src/components/PostsList/index.jsx index b18dd04f..4a651d36 100755 --- a/packages/app/src/components/PostsList/index.jsx +++ b/packages/app/src/components/PostsList/index.jsx @@ -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,474 +11,499 @@ import PostModel from "@models/post" import "./index.less" const LoadingComponent = () => { - return
- -
+ return ( +
+ +
+ ) } const NoResultComponent = () => { - return + return ( + + ) } const typeToComponent = { - "post": (args) => , - //"playlist": (args) => , + post: (args) => , + //"playlist": (args) => , } const Entry = React.memo((props) => { - const { data } = props + const { data } = props - return React.createElement(typeToComponent[data.type ?? "post"] ?? PostCard, { - key: data._id, - data: data, - disableReplyTag: props.disableReplyTag, - disableHasReplies: props.disableHasReplies, - events: { - onClickLike: props.onLikePost, - onClickSave: props.onSavePost, - onClickDelete: props.onDeletePost, - onClickEdit: props.onEditPost, - onClickReply: props.onReplyPost, - onDoubleClick: props.onDoubleClick, - }, - }) + return React.createElement( + typeToComponent[data.type ?? "post"] ?? PostCard, + { + key: data._id, + data: data, + disableReplyTag: props.disableReplyTag, + disableHasReplies: props.disableHasReplies, + events: { + onClickLike: props.onLikePost, + onClickSave: props.onSavePost, + onClickDelete: props.onDeletePost, + onClickEdit: props.onEditPost, + onClickReply: props.onReplyPost, + onDoubleClick: props.onDoubleClick, + }, + }, + ) }) const PostList = React.forwardRef((props, ref) => { - return - { - !props.realtimeUpdates && !app.isMobile &&
- } - > - Resume - -
- } - - - { - props.list.map((data) => { - return - }) - } - -
+ return ( + + {!props.realtimeUpdates && !app.isMobile && ( +
+ } + > + Resume + +
+ )} + + {props.list.map((data) => { + return + })} + +
+ ) }) export class PostsListsComponent extends React.Component { - state = { - openPost: null, - - loading: false, - resumingLoading: false, - initialLoading: true, - scrollingToTop: false, - - topVisible: true, - - realtimeUpdates: true, - - hasMore: true, - list: this.props.list ?? [], - } - - parentRef = this.props.innerRef - listRef = React.createRef() - - timelineWsEvents = { - "feed.new": (data) => { - console.log("New feed => ", data) - - if (!this.state.realtimeUpdates) { - return - } - - this.setState({ - list: [data, ...this.state.list], - }) - }, - "post.new": (data) => { - console.log("New post => ", data) - - if (!this.state.realtimeUpdates) { - return - } - - this.setState({ - list: [data, ...this.state.list], - }) - }, - "post.delete": (id) => { - console.log("Deleted post => ", id) - - this.setState({ - list: this.state.list.filter((post) => { - return post._id !== id - }), - }) - } - } - - handleLoad = async (fn, params = {}) => { - this.setState({ - loading: true, - }) - - let payload = { - trim: this.state.list.length, - limit: app.cores.settings.get("feed_max_fetch"), - } - - if (this.props.loadFromModelProps) { - payload = { - ...payload, - ...this.props.loadFromModelProps, - } - } - - if (params.replace) { - payload.trim = 0 - } - - const result = await fn(payload).catch((err) => { - console.error(err) - - app.message.error("Failed to load more posts") - - return null - }) - - console.log("Loaded posts => ", result) - - if (result) { - if (result.length === 0) { - return this.setState({ - hasMore: false, - }) - } - - if (params.replace) { - this.setState({ - list: result, - }) - } else { - this.setState({ - list: [...this.state.list, ...result], - }) - } - } - - this.setState({ - loading: false, - }) - } - - addPost = (post) => { - this.setState({ - list: [post, ...this.state.list], - }) - } - - removePost = (id) => { - this.setState({ - list: this.state.list.filter((post) => { - return post._id !== id - }), - }) - } - - _hacks = { - addPost: this.addPost, - removePost: this.removePost, - addRandomPost: () => { - const randomId = Math.random().toString(36).substring(7) - - this.addPost({ - _id: randomId, - message: `Random post ${randomId}`, - user: { - _id: randomId, - username: "random user", - avatar: `https://api.dicebear.com/7.x/thumbs/svg?seed=${randomId}`, - } - }) - }, - listRef: this.listRef, - } - - onResumeRealtimeUpdates = async () => { - console.log("Resuming realtime updates") - - this.setState({ - resumingLoading: true, - scrollingToTop: true, - }) - - this.listRef.current.scrollTo({ - top: 0, - behavior: "smooth", - }) - - // reload posts - await this.handleLoad(this.props.loadFromModel, { - replace: true, - }) - - this.setState({ - realtimeUpdates: true, - resumingLoading: false, - }) - } - - onScrollList = (e) => { - const { scrollTop } = e.target - - if (this.state.scrollingToTop && scrollTop === 0) { - this.setState({ - scrollingToTop: false, - }) - } - - if (scrollTop > 200) { - if (this.state.topVisible) { - this.setState({ - topVisible: false, - }) - - if (typeof this.props.onTopVisibility === "function") { - this.props.onTopVisibility(false) - } - } - - if (!this.props.realtime || this.state.resumingLoading || this.state.scrollingToTop) { - return null - } - - this.setState({ - realtimeUpdates: false, - }) - } else { - if (!this.state.topVisible) { - this.setState({ - topVisible: true, - }) - - if (typeof this.props.onTopVisibility === "function") { - this.props.onTopVisibility(true) - } - - // if (this.props.realtime || !this.state.realtimeUpdates && !this.state.resumingLoading && scrollTop < 5) { - // this.onResumeRealtimeUpdates() - // } - } - } - } - - componentDidMount = async () => { - if (typeof this.props.loadFromModel === "function") { - await this.handleLoad(this.props.loadFromModel) - } - - this.setState({ - initialLoading: false, - }) - - if (this.props.watchTimeline) { - if (!Array.isArray(this.props.watchTimeline)) { - console.error("watchTimeline prop must be an array") - } else { - this.props.watchTimeline.forEach((event) => { - if (typeof this.timelineWsEvents[event] !== "function") { - console.error(`The event "${event}" is not defined in the timelineWsEvents object`) - } - - app.cores.api.listenEvent(event, this.timelineWsEvents[event], "posts") - }) - } - } - - if (this.listRef && this.listRef.current) { - this.listRef.current.addEventListener("scroll", this.onScrollList) - } - - window._hacks = this._hacks - } - - componentWillUnmount = async () => { - if (this.props.watchTimeline) { - if (!Array.isArray(this.props.watchTimeline)) { - console.error("watchTimeline prop must be an array") - } else { - this.props.watchTimeline.forEach((event) => { - if (typeof this.timelineWsEvents[event] !== "function") { - console.error(`The event "${event}" is not defined in the timelineWsEvents object`) - } - - app.cores.api.unlistenEvent(event, this.timelineWsEvents[event], "posts") - }) - } - } - - if (this.listRef && this.listRef.current) { - this.listRef.current.removeEventListener("scroll", this.onScrollList) - } - - window._hacks = null - } - - componentDidUpdate = async (prevProps, prevState) => { - if (prevProps.list !== this.props.list) { - this.setState({ - list: this.props.list, - }) - } - } - - onLikePost = async (data) => { - 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(() => { - antd.message.error("Failed to save post") - - return false - }) - - return result - } - - onEditPost = (data) => { - app.controls.openPostCreator({ - edit_post: data._id, - }) - } - - onReplyPost = (data) => { - app.controls.openPostCreator({ - reply_to: data._id, - }) - } - - onDoubleClickPost = (data) => { - app.navigation.goToPost(data._id) - } - - onDeletePost = async (data) => { - antd.Modal.confirm({ - title: "Are you sure you want to delete this post?", - content: "This action is irreversible", - okText: "Yes", - okType: "danger", - cancelText: "No", - onOk: async () => { - await PostModel.deletePost({ post_id: data._id }).catch(() => { - antd.message.error("Failed to delete post") - }) - }, - }) - } - - ontoggleOpen = (to, data) => { - if (typeof this.props.onOpenPost === "function") { - this.props.onOpenPost(to, data) - } - } - - onLoadMore = async () => { - if (typeof this.props.onLoadMore === "function") { - return this.handleLoad(this.props.onLoadMore) - } else if (this.props.loadFromModel) { - return this.handleLoad(this.props.loadFromModel) - } - } - - render() { - if (this.state.initialLoading) { - return - } - - if (this.state.list.length === 0) { - if (typeof this.props.emptyListRender === "function") { - return React.createElement(this.props.emptyListRender) - } - - return
- -

Whoa, nothing on here...

-
- } - - const PostListProps = { - list: this.state.list, - - disableReplyTag: this.props.disableReplyTag, - disableHasReplies: this.props.disableHasReplies, - - onLikePost: this.onLikePost, - onSavePost: this.onSavePost, - onDeletePost: this.onDeletePost, - onEditPost: this.onEditPost, - onReplyPost: this.onReplyPost, - onDoubleClick: this.onDoubleClickPost, - - onLoadMore: this.onLoadMore, - hasMore: this.state.hasMore, - loading: this.state.loading, - - realtimeUpdates: this.state.realtimeUpdates, - resumingLoading: this.state.resumingLoading, - onResumeRealtimeUpdates: this.onResumeRealtimeUpdates, - } - - if (app.isMobile) { - return - } - - return
- -
- } + state = { + openPost: null, + + loading: false, + resumingLoading: false, + initialLoading: true, + scrollingToTop: false, + + topVisible: true, + + realtimeUpdates: true, + + hasMore: true, + list: this.props.list ?? [], + } + + parentRef = this.props.innerRef + listRef = React.createRef() + + timelineWsEvents = { + "feed.new": (data) => { + console.log("New feed => ", data) + + if (!this.state.realtimeUpdates) { + return + } + + this.setState({ + list: [data, ...this.state.list], + }) + }, + "post.new": (data) => { + console.log("New post => ", data) + + if (!this.state.realtimeUpdates) { + return + } + + this.setState({ + list: [data, ...this.state.list], + }) + }, + "post.delete": (id) => { + console.log("Deleted post => ", id) + + this.setState({ + list: this.state.list.filter((post) => { + return post._id !== id + }), + }) + }, + } + + handleLoad = async (fn, params = {}) => { + this.setState({ + loading: true, + }) + + let payload = { + trim: this.state.list.length, + limit: app.cores.settings.get("feed_max_fetch"), + } + + if (this.props.loadFromModelProps) { + payload = { + ...payload, + ...this.props.loadFromModelProps, + } + } + + if (params.replace) { + payload.trim = 0 + } + + const result = await fn(payload).catch((err) => { + console.error(err) + + app.message.error("Failed to load more posts") + + return null + }) + + console.log("Loaded posts => ", result) + + if (result) { + if (result.length === 0) { + return this.setState({ + hasMore: false, + }) + } + + if (params.replace) { + this.setState({ + list: result, + }) + } else { + this.setState({ + list: [...this.state.list, ...result], + }) + } + } + + this.setState({ + loading: false, + }) + } + + addPost = (post) => { + this.setState({ + list: [post, ...this.state.list], + }) + } + + removePost = (id) => { + this.setState({ + list: this.state.list.filter((post) => { + return post._id !== id + }), + }) + } + + _hacks = { + addPost: this.addPost, + removePost: this.removePost, + addRandomPost: () => { + const randomId = Math.random().toString(36).substring(7) + + this.addPost({ + _id: randomId, + message: `Random post ${randomId}`, + user: { + _id: randomId, + username: "random user", + avatar: `https://api.dicebear.com/7.x/thumbs/svg?seed=${randomId}`, + }, + }) + }, + listRef: this.listRef, + } + + onResumeRealtimeUpdates = async () => { + console.log("Resuming realtime updates") + + this.setState({ + resumingLoading: true, + scrollingToTop: true, + }) + + this.listRef.current.scrollTo({ + top: 0, + behavior: "smooth", + }) + + // reload posts + await this.handleLoad(this.props.loadFromModel, { + replace: true, + }) + + this.setState({ + realtimeUpdates: true, + resumingLoading: false, + }) + } + + onScrollList = (e) => { + const { scrollTop } = e.target + + if (this.state.scrollingToTop && scrollTop === 0) { + this.setState({ + scrollingToTop: false, + }) + } + + if (scrollTop > 200) { + if (this.state.topVisible) { + this.setState({ + topVisible: false, + }) + + if (typeof this.props.onTopVisibility === "function") { + this.props.onTopVisibility(false) + } + } + + if ( + !this.props.realtime || + this.state.resumingLoading || + this.state.scrollingToTop + ) { + return null + } + + this.setState({ + realtimeUpdates: false, + }) + } else { + if (!this.state.topVisible) { + this.setState({ + topVisible: true, + }) + + if (typeof this.props.onTopVisibility === "function") { + this.props.onTopVisibility(true) + } + + // if (this.props.realtime || !this.state.realtimeUpdates && !this.state.resumingLoading && scrollTop < 5) { + // this.onResumeRealtimeUpdates() + // } + } + } + } + + componentDidMount = async () => { + if (typeof this.props.loadFromModel === "function") { + await this.handleLoad(this.props.loadFromModel) + } + + this.setState({ + initialLoading: false, + }) + + if (this.props.watchTimeline) { + if (!Array.isArray(this.props.watchTimeline)) { + console.error("watchTimeline prop must be an array") + } else { + this.props.watchTimeline.forEach((event) => { + if (typeof this.timelineWsEvents[event] !== "function") { + console.error( + `The event "${event}" is not defined in the timelineWsEvents object`, + ) + } + + app.cores.api.listenEvent( + event, + this.timelineWsEvents[event], + "posts", + ) + }) + } + } + + if (this.listRef && this.listRef.current) { + this.listRef.current.addEventListener("scroll", this.onScrollList) + } + + window._hacks = this._hacks + } + + componentWillUnmount = async () => { + if (this.props.watchTimeline) { + if (!Array.isArray(this.props.watchTimeline)) { + console.error("watchTimeline prop must be an array") + } else { + this.props.watchTimeline.forEach((event) => { + if (typeof this.timelineWsEvents[event] !== "function") { + console.error( + `The event "${event}" is not defined in the timelineWsEvents object`, + ) + } + + app.cores.api.unlistenEvent( + event, + this.timelineWsEvents[event], + "posts", + ) + }) + } + } + + if (this.listRef && this.listRef.current) { + this.listRef.current.removeEventListener( + "scroll", + this.onScrollList, + ) + } + + window._hacks = null + } + + componentDidUpdate = async (prevProps, prevState) => { + if (prevProps.list !== this.props.list) { + this.setState({ + list: this.props.list, + }) + } + } + + onLikePost = async (data) => { + 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( + () => { + antd.message.error("Failed to save post") + + return false + }, + ) + + return result + } + + onEditPost = (data) => { + app.controls.openPostCreator({ + edit_post: data._id, + }) + } + + onReplyPost = (data) => { + app.controls.openPostCreator({ + reply_to: data._id, + }) + } + + onDoubleClickPost = (data) => { + app.navigation.goToPost(data._id) + } + + onDeletePost = async (data) => { + antd.Modal.confirm({ + title: "Are you sure you want to delete this post?", + content: "This action is irreversible", + okText: "Yes", + okType: "danger", + cancelText: "No", + onOk: async () => { + await PostModel.deletePost({ post_id: data._id }).catch(() => { + antd.message.error("Failed to delete post") + }) + }, + }) + } + + ontoggleOpen = (to, data) => { + if (typeof this.props.onOpenPost === "function") { + this.props.onOpenPost(to, data) + } + } + + onLoadMore = async () => { + if (typeof this.props.onLoadMore === "function") { + return this.handleLoad(this.props.onLoadMore) + } else if (this.props.loadFromModel) { + return this.handleLoad(this.props.loadFromModel) + } + } + + render() { + if (this.state.initialLoading) { + return + } + + if (this.state.list.length === 0) { + if (typeof this.props.emptyListRender === "function") { + return React.createElement(this.props.emptyListRender) + } + + return ( +
+ +

Whoa, nothing on here...

+
+ ) + } + + const PostListProps = { + list: this.state.list, + + disableReplyTag: this.props.disableReplyTag, + disableHasReplies: this.props.disableHasReplies, + + onLikePost: this.onLikePost, + onSavePost: this.onSavePost, + onDeletePost: this.onDeletePost, + onEditPost: this.onEditPost, + onReplyPost: this.onReplyPost, + onDoubleClick: this.onDoubleClickPost, + + onLoadMore: this.onLoadMore, + hasMore: this.state.hasMore, + loading: this.state.loading, + + realtimeUpdates: this.state.realtimeUpdates, + resumingLoading: this.state.resumingLoading, + onResumeRealtimeUpdates: this.onResumeRealtimeUpdates, + } + + if (app.isMobile) { + return + } + + return ( +
+ +
+ ) + } } -export default React.forwardRef((props, ref) => ) \ No newline at end of file +export default React.forwardRef((props, ref) => ( + +)) diff --git a/packages/app/src/cores/contextMenu/components/contextMenu/index.jsx b/packages/app/src/cores/contextMenu/components/contextMenu/index.jsx index 6c99f413..1c756a56 100755 --- a/packages/app/src/cores/contextMenu/components/contextMenu/index.jsx +++ b/packages/app/src/cores/contextMenu/components/contextMenu/index.jsx @@ -1,86 +1,84 @@ import React from "react" import { createIconRender } from "@components/Icons" -import { AnimatePresence, motion } from "framer-motion" +import { AnimatePresence, motion } from "motion/react" import "./index.less" const ContextMenu = (props) => { - const [visible, setVisible] = React.useState(true) - const { items = [], cords, clickedComponent, ctx } = props + const [visible, setVisible] = React.useState(true) + const { items = [], cords, clickedComponent, ctx } = props - async function onClose() { - setVisible(false) - props.unregisterOnClose(onClose) - } + async function onClose() { + setVisible(false) + props.unregisterOnClose(onClose) + } - React.useEffect(() => { - props.registerOnClose(onClose) - }, []) + React.useEffect(() => { + props.registerOnClose(onClose) + }, []) - const handleItemClick = async (item) => { - if (typeof item.action === "function") { - await item.action(clickedComponent, ctx) - } - } + const handleItemClick = async (item) => { + if (typeof item.action === "function") { + await item.action(clickedComponent, ctx) + } + } - const renderItems = () => { - if (items.length === 0) { - return
-

No items

-
- } + const renderItems = () => { + if (items.length === 0) { + return ( +
+

No items

+
+ ) + } - return items.map((item, index) => { - if (item.type === "separator") { - return
- } + return items.map((item, index) => { + if (item.type === "separator") { + return
+ } - return
handleItemClick(item)} - className="item" - > -

- {item.label} -

+ return ( +
handleItemClick(item)} + className="item" + > +

{item.label}

- { - item.description &&

- {item.description} -

- } + {item.description && ( +

{item.description}

+ )} - { - createIconRender(item.icon) - } -
- }) - } + {createIconRender(item.icon)} +
+ ) + }) + } - return - { - visible &&
- - { - renderItems() - } - -
- } -
+ return ( + + {visible && ( +
+ + {renderItems()} + +
+ )} +
+ ) } -export default ContextMenu \ No newline at end of file +export default ContextMenu diff --git a/packages/app/src/cores/haptics/haptics.core.js b/packages/app/src/cores/haptics/haptics.core.js index 913892dc..ce2f54ed 100755 --- a/packages/app/src/cores/haptics/haptics.core.js +++ b/packages/app/src/cores/haptics/haptics.core.js @@ -1,68 +1,67 @@ import { Core } from "@ragestudio/vessel" -import { Haptics } from "@capacitor/haptics" +// import { Haptics } from "@capacitor/haptics" const vibrationPatterns = { - light: [10], - medium: [50], - heavy: [80], - error: [100, 30, 100, 30, 100], + light: [10], + medium: [50], + heavy: [80], + error: [100, 30, 100, 30, 100], } export default class HapticsCore extends Core { - static namespace = "haptics" + static namespace = "haptics" - static dependencies = [ - "settings" - ] + static dependencies = ["settings"] - static isGlobalDisabled() { - return app.cores.settings.is("haptics:enabled", false) - } + static isGlobalDisabled() { + return app.cores.settings.is("haptics:enabled", false) + } - async onInitialize() { - if (window.navigator.userAgent === "capacitor") { - navigator.vibrate = this.nativeVibrate - } + async onInitialize() { + if (window.navigator.userAgent === "capacitor") { + navigator.vibrate = this.nativeVibrate + } - document.addEventListener("click", this.handleClickEvent) - } + document.addEventListener("click", this.handleClickEvent) + } - public = { - isGlobalDisabled: HapticsCore.isGlobalDisabled, - vibrate: this.vibrate.bind(this), - } + public = { + //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") + handleClickEvent = (event) => { + const button = + event.target.closest("button") || event.target.closest(".ant-btn") - if (button) { - this.vibrate("light") - } - } + if (button) { + this.vibrate("light") + } + } - vibrate(pattern = "light") { - const disabled = HapticsCore.isGlobalDisabled() + vibrate(pattern = "light") { + const disabled = HapticsCore.isGlobalDisabled() - if (disabled) { - return false - } + if (disabled) { + return false + } - if (typeof pattern === "string") { - pattern = vibrationPatterns[pattern] - } + if (typeof pattern === "string") { + pattern = vibrationPatterns[pattern] + } - return navigator.vibrate(pattern) - } -} \ No newline at end of file + return navigator.vibrate(pattern) + } +} diff --git a/packages/app/src/cores/notifications/feedback.js b/packages/app/src/cores/notifications/feedback.js index aaab127b..af3241bf 100644 --- a/packages/app/src/cores/notifications/feedback.js +++ b/packages/app/src/cores/notifications/feedback.js @@ -1,41 +1,48 @@ -import { Haptics } from "@capacitor/haptics" +//import { Haptics } from "@capacitor/haptics" const NotfTypeToAudio = { - info: "notification", - success: "notification", - warning: "warn", - error: "error", + info: "notification", + success: "notification", + warning: "warn", + error: "error", } class NotificationFeedback { - static getSoundVolume = () => { - return (window.app.cores.settings.get("sfx:notifications_volume") ?? 50) / 100 - } + static getSoundVolume = () => { + 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() - } - } + static playHaptic = async () => { + if (app.cores.settings.get("haptics:notifications_feedback")) { + //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", { - volume: NotificationFeedback.getSoundVolume(), - }) - } - } - } + 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", + { + volume: NotificationFeedback.getSoundVolume(), + }, + ) + } + } + } - static async feedback({ type = "notification", feedback = true }) { - if (!feedback) { - return false - } + static async feedback({ type = "notification", feedback = true }) { + if (!feedback) { + return false + } - NotificationFeedback.playHaptic(type) - NotificationFeedback.playAudio(type) - } + NotificationFeedback.playHaptic(type) + NotificationFeedback.playAudio(type) + } } -export default NotificationFeedback \ No newline at end of file +export default NotificationFeedback diff --git a/packages/app/src/cores/player/classes/MediaSession.js b/packages/app/src/cores/player/classes/MediaSession.js deleted file mode 100755 index 6a3ca2d0..00000000 --- a/packages/app/src/cores/player/classes/MediaSession.js +++ /dev/null @@ -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) - } - } -} \ No newline at end of file diff --git a/packages/app/src/layout.jsx b/packages/app/src/layout.jsx index 25541ef3..99e0ffc9 100755 --- a/packages/app/src/layout.jsx +++ b/packages/app/src/layout.jsx @@ -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 - {this.props.children} - + return {this.props.children} } -} \ No newline at end of file +} diff --git a/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx b/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx index a2f10c02..d27f0785 100755 --- a/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx +++ b/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx @@ -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" @@ -15,407 +18,434 @@ import CreatorView from "@pages/@mobile-views/creator" import "./index.less" const tourSteps = [ - { - title: "Quick nav", - description: "Tap & hold on the icon to open the navigation menu.", - placement: "top", - refName: "navBtnRef", - }, - { - title: "Account button", - description: "Tap & hold on the account icon to open miscellaneous options.", - placement: "top", - refName: "accountBtnRef", - } + { + title: "Quick nav", + description: "Tap & hold on the icon to open the navigation menu.", + placement: "top", + refName: "navBtnRef", + }, + { + title: "Account button", + description: + "Tap & hold on the account icon to open miscellaneous options.", + placement: "top", + refName: "accountBtnRef", + }, ] const openPlayerView = () => { - app.layout.draggable.open("player", PlayerView) + app.layout.draggable.open("player", PlayerView) } const openCreator = () => { - app.layout.draggable.open("creator", CreatorView) + app.layout.draggable.open("creator", CreatorView) } const PlayerButton = (props) => { - React.useEffect(() => { - openPlayerView() - }, []) + React.useEffect(() => { + openPlayerView() + }, []) - return
- { - props.playback === "playing" ? : - } -
+ return ( +
+ {props.playback === "playing" ? ( + + ) : ( + + )} +
+ ) } const AccountButton = React.forwardRef((props, ref) => { - const user = app.userData + const user = app.userData - const handleClick = () => { - if (!user) { - return app.navigation.goAuth() - } + const handleClick = () => { + if (!user) { + return app.navigation.goAuth() + } - return app.navigation.goToAccount() - } + return app.navigation.goToAccount() + } - const handleHold = () => { - app.layout.draggable.actions({ - list: [ - { - key: "settings", - icon: "FiSettings", - label: "Settings", - onClick: () => { - app.navigation.goToSettings() - }, - }, - { - key: "account", - icon: "FiUser", - label: "Account", - onClick: () => { - app.navigation.goToAccount() - }, - }, - { - key: "logout", - icon: "FiLogOut", - label: "Logout", - danger: true, - onClick: () => { - app.eventBus.emit("app.logout_request") - }, - } - ] - }) - } + const handleHold = () => { + app.layout.draggable.actions({ + list: [ + { + key: "settings", + icon: "FiSettings", + label: "Settings", + onClick: () => { + app.navigation.goToSettings() + }, + }, + { + key: "account", + icon: "FiUser", + label: "Account", + onClick: () => { + app.navigation.goToAccount() + }, + }, + { + key: "logout", + icon: "FiLogOut", + label: "Logout", + danger: true, + onClick: () => { + app.eventBus.emit("app.logout_request") + }, + }, + ], + }) + } - return
-
- { - user ? : createIconRender("FiLogin") - } -
-
+ return ( +
+
+ {user ? ( + + ) : ( + createIconRender("FiLogin") + )} +
+
+ ) }) export class BottomBar extends React.Component { - static contextType = Context + static contextType = Context - state = { - visible: false, - quickNavVisible: false, - render: null, - tourOpen: false, - } + state = { + visible: false, + quickNavVisible: false, + render: null, + tourOpen: false, + } - busEvents = { - "runtime.crash": () => { - this.toggleVisibility(false) - } - } + busEvents = { + "runtime.crash": () => { + this.toggleVisibility(false) + }, + } - navBtnRef = React.createRef() - accountBtnRef = React.createRef() + navBtnRef = React.createRef() + accountBtnRef = React.createRef() - componentDidMount = () => { - this.interface = app.layout.bottom_bar = { - toggleVisible: this.toggleVisibility, - isVisible: () => this.state.visible, - render: (fragment) => { - this.setState({ render: fragment }) - }, - clear: () => { - this.setState({ render: null }) - }, - toggleTour: () => { - this.setState({ tourOpen: !this.state.tourOpen }) - }, - } + componentDidMount = () => { + this.interface = app.layout.bottom_bar = { + toggleVisible: this.toggleVisibility, + isVisible: () => this.state.visible, + render: (fragment) => { + this.setState({ render: fragment }) + }, + clear: () => { + this.setState({ render: null }) + }, + toggleTour: () => { + this.setState({ tourOpen: !this.state.tourOpen }) + }, + } - setTimeout(() => { - this.setState({ visible: true }) - }, 10) + setTimeout(() => { + this.setState({ visible: true }) + }, 10) - // Register bus events - Object.keys(this.busEvents).forEach((key) => { - app.eventBus.on(key, this.busEvents[key]) - }) + // Register bus events + Object.keys(this.busEvents).forEach((key) => { + app.eventBus.on(key, this.busEvents[key]) + }) - setTimeout(() => { - const isTourFinished = localStorage.getItem("mobile_tour") + setTimeout(() => { + const isTourFinished = localStorage.getItem("mobile_tour") - if (!isTourFinished) { - this.toggleTour(true) + if (!isTourFinished) { + this.toggleTour(true) - localStorage.setItem("mobile_tour", true) - } - }, 500) - } + localStorage.setItem("mobile_tour", true) + } + }, 500) + } - componentWillUnmount = () => { - delete window.app.layout.bottom_bar + componentWillUnmount = () => { + delete window.app.layout.bottom_bar - // Unregister bus events - Object.keys(this.busEvents).forEach((key) => { - app.eventBus.off(key, this.busEvents[key]) - }) - } + // Unregister bus events + Object.keys(this.busEvents).forEach((key) => { + app.eventBus.off(key, this.busEvents[key]) + }) + } - getTourSteps = () => { - return tourSteps.map((step) => { - step.target = () => this[step.refName].current + getTourSteps = () => { + return tourSteps.map((step) => { + step.target = () => this[step.refName].current - return step - }) - } + return step + }) + } - toggleVisibility = (to) => { - if (typeof to === "undefined") { - to = !this.state.visible - } + toggleVisibility = (to) => { + if (typeof to === "undefined") { + to = !this.state.visible + } - this.setState({ visible: to }) - } + this.setState({ visible: to }) + } - handleItemClick = (item) => { - if (item.dispatchEvent) { - app.eventBus.emit(item.dispatchEvent) - } else if (item.location) { - app.location.push(item.location) - } - } + handleItemClick = (item) => { + if (item.dispatchEvent) { + app.eventBus.emit(item.dispatchEvent) + } else if (item.location) { + app.location.push(item.location) + } + } - handleNavTouchStart = (e) => { - this._navTouchStart = setTimeout(() => { - this.setState({ quickNavVisible: true }) + handleNavTouchStart = (e) => { + this._navTouchStart = setTimeout(() => { + this.setState({ quickNavVisible: true }) - if (app.cores.haptics?.vibrate) { - app.cores.haptics.vibrate(80) - } + if (app.cores.haptics?.vibrate) { + app.cores.haptics.vibrate(80) + } - // remove the timeout - this._navTouchStart = null - }, 400) - } + // remove the timeout + this._navTouchStart = null + }, 400) + } - handleNavTouchEnd = (event) => { - if (this._lastHovered) { - this._lastHovered.classList.remove("hover") - } + handleNavTouchEnd = (event) => { + if (this._lastHovered) { + this._lastHovered.classList.remove("hover") + } - if (this._navTouchStart) { - clearTimeout(this._navTouchStart) + if (this._navTouchStart) { + clearTimeout(this._navTouchStart) - this._navTouchStart = null + this._navTouchStart = null - return false - } + return false + } - this.setState({ quickNavVisible: false }) + this.setState({ quickNavVisible: false }) - // get cords of the touch - const x = event.changedTouches[0].clientX - const y = event.changedTouches[0].clientY + // get cords of the touch + const x = event.changedTouches[0].clientX + const y = event.changedTouches[0].clientY - // get the element at the touch - const element = document.elementFromPoint(x, y) + // get the element at the touch + const element = document.elementFromPoint(x, y) - // get the closest element with the attribute - const closest = element.closest(".quick-nav_item") + // get the closest element with the attribute + const closest = element.closest(".quick-nav_item") - if (!closest) { - return false - } + if (!closest) { + return false + } - const item = QuickNavMenuItems.find((item) => { - return item.id === closest.getAttribute("quicknav-item") - }) + const item = QuickNavMenuItems.find((item) => { + return item.id === closest.getAttribute("quicknav-item") + }) - if (!item) { - return false - } + if (!item) { + return false + } - if (item.location) { - app.location.push(item.location) + if (item.location) { + app.location.push(item.location) - if (app.cores.haptics?.vibrate) { - app.cores.haptics.vibrate([40, 80]) - } - } - } + if (app.cores.haptics?.vibrate) { + app.cores.haptics.vibrate([40, 80]) + } + } + } - handleNavTouchMove = (event) => { - // check if the touch is hovering a quicknav item - const x = event.changedTouches[0].clientX - const y = event.changedTouches[0].clientY + handleNavTouchMove = (event) => { + // check if the touch is hovering a quicknav item + const x = event.changedTouches[0].clientX + const y = event.changedTouches[0].clientY - // get the element at the touch - const element = document.elementFromPoint(x, y) + // get the element at the touch + const element = document.elementFromPoint(x, y) - // get the closest element with the attribute - const closest = element.closest("[quicknav-item]") + // get the closest element with the attribute + const closest = element.closest("[quicknav-item]") - if (!closest) { - if (this._lastHovered) { - this._lastHovered.classList.remove("hover") - } + if (!closest) { + if (this._lastHovered) { + this._lastHovered.classList.remove("hover") + } - this._lastHovered = null + this._lastHovered = null - return false - } + return false + } - if (this._lastHovered !== closest) { - if (this._lastHovered) { - this._lastHovered.classList.remove("hover") - } + if (this._lastHovered !== closest) { + if (this._lastHovered) { + this._lastHovered.classList.remove("hover") + } - this._lastHovered = closest + this._lastHovered = closest - closest.classList.add("hover") + closest.classList.add("hover") - if (app.cores.haptics?.vibrate) { - app.cores.haptics.vibrate(40) - } - } - } + if (app.cores.haptics?.vibrate) { + app.cores.haptics.vibrate(40) + } + } + } - toggleTour = (to) => { - if (typeof to === "undefined") { - to = !this.state.tourOpen - } + toggleTour = (to) => { + if (typeof to === "undefined") { + to = !this.state.tourOpen + } - this.setState({ - tourOpen: to - }) - } + this.setState({ + tourOpen: to, + }) + } - render() { - if (this.state.render) { - return
- {this.state.render} -
- } + render() { + if (this.state.render) { + return
{this.state.render}
+ } - 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 && this.setState({ tourOpen: false })} - /> - } - + return ( + <> + {this.state.tourOpen && ( + this.setState({ tourOpen: false })} + /> + )} + - - {({ y, height }) => -
-
-
-
-
- {createIconRender("FiPlusCircle")} -
-
+ + {this.state.visible && ( + +
+
+
+
+ {createIconRender("FiPlusCircle")} +
+
- { - this.context.track_manifest &&
- -
- } + {this.context.track_manifest && ( +
+ +
+ )} - + -
-
- {createIconRender("FiSearch")} -
-
+
+
+ {createIconRender("FiSearch")} +
+
- -
-
-
- } - - - } + +
+
+ + )} + + + ) + } } export default (props) => { - return - - -} \ No newline at end of file + return ( + + + + ) +} diff --git a/packages/app/src/layouts/components/@mobile/topBar/index.jsx b/packages/app/src/layouts/components/@mobile/topBar/index.jsx index 0700e132..7ff79110 100755 --- a/packages/app/src/layouts/components/@mobile/topBar/index.jsx +++ b/packages/app/src/layouts/components/@mobile/topBar/index.jsx @@ -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" @@ -8,111 +8,125 @@ import useDefaultVisibility from "@hooks/useDefaultVisibility" import "./index.less" export default (props) => { - const [visible, setVisible] = useDefaultVisibility() - const [shouldUseTopBarSpacer, setShouldUseTopBarSpacer] = React.useState(true) - const [render, setRender] = React.useState(null) + const [visible, setVisible] = useDefaultVisibility() + const [shouldUseTopBarSpacer, setShouldUseTopBarSpacer] = + React.useState(true) + const [render, setRender] = React.useState(null) - useLayoutInterface("top_bar", { - toggleVisibility: (to) => { - setVisible((prev) => { - if (typeof to === undefined) { - to = !prev - } + useLayoutInterface("top_bar", { + toggleVisibility: (to) => { + setVisible((prev) => { + if (typeof to === undefined) { + to = !prev + } - return to - }) - }, - render: (component, options) => { - handleUpdateRender(component, options) - }, - renderDefault: () => { - setRender(null) - }, - shouldUseTopBarSpacer: (to) => { - app.layout.toggleTopBarSpacer(to) - setShouldUseTopBarSpacer(to) - } - }) + return to + }) + }, + render: (component, options) => { + handleUpdateRender(component, options) + }, + renderDefault: () => { + setRender(null) + }, + shouldUseTopBarSpacer: (to) => { + app.layout.toggleTopBarSpacer(to) + setShouldUseTopBarSpacer(to) + }, + }) - const handleUpdateRender = (...args) => { - if (document.startViewTransition) { - return document.startViewTransition(() => { - updateRender(...args) - }) - } + const handleUpdateRender = (...args) => { + if (document.startViewTransition) { + return document.startViewTransition(() => { + updateRender(...args) + }) + } - return updateRender(...args) - } + return updateRender(...args) + } - const updateRender = (component, options = {}) => { - setRender({ - component, - options - }) - } + const updateRender = (component, options = {}) => { + setRender({ + component, + options, + }) + } - React.useEffect(() => { - if (!shouldUseTopBarSpacer) { - app.layout.togglePagePanelSpacer(true) - } else { - app.layout.togglePagePanelSpacer(false) - } - }, [shouldUseTopBarSpacer]) + React.useEffect(() => { + if (!shouldUseTopBarSpacer) { + app.layout.togglePagePanelSpacer(true) + } else { + app.layout.togglePagePanelSpacer(false) + } + }, [shouldUseTopBarSpacer]) - React.useEffect(() => { - if (shouldUseTopBarSpacer) { - if (visible) { - app.layout.toggleTopBarSpacer(true) - } else { - app.layout.toggleTopBarSpacer(false) - } - } else { - if (visible) { - app.layout.togglePagePanelSpacer(true) - } else { - app.layout.togglePagePanelSpacer(false) - } + React.useEffect(() => { + if (shouldUseTopBarSpacer) { + if (visible) { + app.layout.toggleTopBarSpacer(true) + } else { + app.layout.toggleTopBarSpacer(false) + } + } else { + if (visible) { + app.layout.togglePagePanelSpacer(true) + } else { + app.layout.togglePagePanelSpacer(false) + } - app.layout.toggleTopBarSpacer(false) - } - }, [visible]) + app.layout.toggleTopBarSpacer(false) + } + }, [visible]) - React.useEffect(() => { - if (render) { - setVisible(true) - } else { - setVisible(false) - } - }, [render]) + React.useEffect(() => { + if (render) { + setVisible(true) + } else { + setVisible(false) + } + }, [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 - {({ y, height }) => { - return <> -
-
- { - render?.component && React.cloneElement(render?.component, render?.options?.props ?? {}) - } -
-
- - }} -
-} \ No newline at end of file + return ( + + {visible && ( + +
+ {render?.component && + React.cloneElement( + render?.component, + render?.options?.props ?? {}, + )} +
+
+ )} +
+ ) +} diff --git a/packages/app/src/layouts/components/drawer/index.jsx b/packages/app/src/layouts/components/drawer/index.jsx index 3005fa24..9d767ec7 100755 --- a/packages/app/src/layouts/components/drawer/index.jsx +++ b/packages/app/src/layouts/components/drawer/index.jsx @@ -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,31 +64,34 @@ export class Drawer extends React.Component { handleDone: this.handleDone, handleFail: this.handleFail, } - return - { - this.state.visible && - { - React.createElement(this.props.children, componentProps) - } - - } - + return ( + + {this.state.visible && ( + + {React.createElement( + this.props.children, + componentProps, + )} + + )} + + ) } } @@ -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() + drawers.push() addresses[id] = drawers.length - 1 refs[id] = instance.ref } else { - drawers[addresses[id]] = + drawers[addresses[id]] = refs[id] = instance.ref } @@ -267,37 +265,39 @@ export default class DrawerController extends React.Component { } render() { - return <> - - { - this.state.maskVisible && this.closeLastDrawer()} - initial={{ - opacity: 0, - }} - animate={{ - opacity: 1, - }} - exit={{ - opacity: 0, - }} - /> - } - - -
+ return ( + <> - {this.state.drawers} + {this.state.maskVisible && ( + this.closeLastDrawer()} + initial={{ + opacity: 0, + }} + animate={{ + opacity: 1, + }} + exit={{ + opacity: 0, + }} + transition={{ + type: "spring", + stiffness: 100, + damping: 20, + }} + /> + )} -
- + +
+ {this.state.drawers} +
+ + ) } -} \ No newline at end of file +} diff --git a/packages/app/src/layouts/components/header/index.jsx b/packages/app/src/layouts/components/header/index.jsx index d0c5f013..b1787d93 100755 --- a/packages/app/src/layouts/components/header/index.jsx +++ b/packages/app/src/layouts/components/header/index.jsx @@ -1,64 +1,64 @@ 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 "./index.less" export default (props) => { - const [render, setRender] = React.useState(null) + const [render, setRender] = React.useState(null) - useLayoutInterface("header", { - render: (component, options) => { - if (component === null) { - return setRender(null) - } + useLayoutInterface("header", { + render: (component, options) => { + if (component === null) { + return setRender(null) + } - return setRender({ - component, - options - }) - }, - }) + return setRender({ + component, + options, + }) + }, + }) - React.useEffect(() => { - if (render) { - app.layout.toggleDisableTopLayoutPadding(true) - } else { - app.layout.toggleDisableTopLayoutPadding(false) - } - }, [render]) + React.useEffect(() => { + if (render) { + app.layout.toggleDisableTopLayoutPadding(true) + } else { + app.layout.toggleDisableTopLayoutPadding(false) + } + }, [render]) - return - {({ y, height }) => { - return
-
- { - render?.component && React.cloneElement( - render?.component, - render?.options?.props ?? {} - ) - } -
-
- }} -
+ return ( + + {render && ( + +
+ {render?.component && + React.cloneElement( + render?.component, + render?.options?.props ?? {}, + )} +
+
+ )} +
+ ) } diff --git a/packages/app/src/layouts/components/sidebar/index.jsx b/packages/app/src/layouts/components/sidebar/index.jsx index 3f4fa159..25ccd9ce 100755 --- a/packages/app/src/layouts/components/sidebar/index.jsx +++ b/packages/app/src/layouts/components/sidebar/index.jsx @@ -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" diff --git a/packages/app/src/layouts/components/toolsBar/index.jsx b/packages/app/src/layouts/components/toolsBar/index.jsx index 557a493b..f61ed41c 100755 --- a/packages/app/src/layouts/components/toolsBar/index.jsx +++ b/packages/app/src/layouts/components/toolsBar/index.jsx @@ -1,119 +1,133 @@ 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" import "./index.less" export default class ToolsBar extends React.Component { - state = { - visible: false, - renders: { - top: [], - bottom: [], - }, - } + state = { + visible: false, + renders: { + top: [], + bottom: [], + }, + } - componentDidMount() { - app.layout.tools_bar = this.interface + componentDidMount() { + app.layout.tools_bar = this.interface - setTimeout(() => { - this.setState({ - visible: true, - }) - }, 10) - } + setTimeout(() => { + this.setState({ + visible: true, + }) + }, 10) + } - componentWillUnmount() { - delete app.layout.tools_bar - } + componentWillUnmount() { + delete app.layout.tools_bar + } - interface = { - toggleVisibility: (to) => { - this.setState({ - visible: to ?? !this.state.visible, - }) - }, - attachRender: (id, component, props, { position = "bottom" } = {}) => { - this.setState((prev) => { - prev.renders[position].push({ - id: id, - component: component, - props: props, - }) + interface = { + toggleVisibility: (to) => { + this.setState({ + visible: to ?? !this.state.visible, + }) + }, + attachRender: (id, component, props, { position = "bottom" } = {}) => { + this.setState((prev) => { + prev.renders[position].push({ + id: id, + component: component, + props: props, + }) - return prev - }) + return prev + }) - return 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), - }, - }) + return 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, + ), + }, + }) - return true - } - } + return true + }, + } - render() { - const hasAnyRenders = this.state.renders.top.length > 0 || this.state.renders.bottom.length > 0 + render() { + const hasAnyRenders = + this.state.renders.top.length > 0 || + this.state.renders.bottom.length > 0 - const isVisible = hasAnyRenders && this.state.visible + const isVisible = hasAnyRenders && this.state.visible - return - {({ x, width }) => { - return
-
-
- { - this.state.renders.top.map((render, index) => { - return React.createElement(render.component, { - ...render.props, - key: index, - }) - }) - } -
+ return ( + + {isVisible && ( + +
+
+ {this.state.renders.top.map((render, index) => { + return React.createElement( + render.component, + { + ...render.props, + key: index, + }, + ) + })} +
- + -
- { - this.state.renders.bottom.map((render, index) => { - return React.createElement(render.component, { - ...render.props, - key: index, - }) - }) - } -
-
-
- }} - - } -} \ No newline at end of file +
+ {this.state.renders.bottom.map( + (render, index) => { + return React.createElement( + render.component, + { + ...render.props, + key: index, + }, + ) + }, + )} +
+
+ + )} + + ) + } +} diff --git a/packages/app/src/layouts/components/toolsBar/index.less b/packages/app/src/layouts/components/toolsBar/index.less index e8c1f7ad..0aa0e088 100755 --- a/packages/app/src/layouts/components/toolsBar/index.less +++ b/packages/app/src/layouts/components/toolsBar/index.less @@ -1,79 +1,78 @@ @import "@styles/vars.less"; .tools-bar-wrapper { - position: sticky; + position: sticky; - top: 0; - right: 0; + top: 0; + right: 0; - z-index: 150; + z-index: 150; - height: 100vh; - height: 100dvh; - max-width: 420px; + height: 100vh; + height: 100dvh; + max-width: 420px; - padding: 10px; + padding: 10px; - .visible { - min-width: 320px; - } + .visible { + min-width: 320px; + } - &:not(.visible) { - min-width: 0; - padding: 0; - - } + &:not(.visible) { + min-width: 0; + padding: 0; + } } .tools-bar { - position: relative; + position: relative; - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; - width: 100%; - height: 100%; + width: 100%; + height: 100%; - border-radius: 12px; + border-radius: 12px; - border-radius: @sidebar_borderRadius; - box-shadow: @card-shadow; + border-radius: @sidebar_borderRadius; + box-shadow: @card-shadow; - padding: 10px; + padding: 10px; - background-color: var(--background-color-accent); + background-color: var(--background-color-accent); - gap: 20px; + gap: 20px; - flex: 0; + flex: 0; - .attached_renders { - position: relative; + .attached_renders { + position: relative; - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; - align-items: center; + align-items: center; - width: 100%; - height: fit-content; + width: 100%; + height: fit-content; - gap: 10px; + gap: 10px; - &.bottom { - position: absolute; + &.bottom { + position: absolute; - bottom: 0; - left: 0; + bottom: 0; + left: 0; - padding: 10px; - } + padding: 10px; + } - .card { - width: 100%; - height: fit-content; + .card { + width: 100%; + height: fit-content; - background-color: var(--background-color-primary); - } - } -} \ No newline at end of file + background-color: var(--background-color-primary); + } + } +} diff --git a/packages/app/src/pages/account/index.jsx b/packages/app/src/pages/account/index.jsx index 4ce83003..fce56175 100755 --- a/packages/app/src/pages/account/index.jsx +++ b/packages/app/src/pages/account/index.jsx @@ -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,119 +142,119 @@ export default class Account extends React.Component { const user = this.state.user if (this.state.isNotExistent) { - return - - + return ( + + ) } if (!user) { return } - return
- { - user.cover &&
this.toggleCoverExpanded()} - id="profile-cover" - /> - } - -
-
- + {user.cover && ( +
this.toggleCoverExpanded()} + id="profile-cover" /> + )} -
- +
+
+ - { - !this.state.isSelf && } - onClick={() => app.location.push(`/messages/${user._id}`)} +
+ - } + + {!this.state.isSelf && ( + } + onClick={() => + app.location.push( + `/messages/${user._id}`, + ) + } + /> + )} +
+
+ +
+ + + {React.createElement( + TabsComponent[this.state.tabActiveKey], + { + onTopVisibility: + this.onPostListTopVisibility, + state: this.state, + }, + )} + + +
+ +
+ this.handlePageTransition(e.key)} + items={GenerateMenuItems([ + { + id: "posts", + label: "Posts", + icon: "FiBookOpen", + }, + { + id: "music", + label: "Music", + icon: "MdAlbum", + }, + { + id: "followers", + label: "Followers", + icon: "FiUsers", + }, + { + id: "details", + label: "Details", + icon: "FiInfo", + }, + ])} + />
- -
- - - { - React.createElement(TabsComponent[this.state.tabActiveKey], { - onTopVisibility: this.onPostListTopVisibility, - state: this.state - }) - } - - -
- -
- this.handlePageTransition(e.key)} - items={GenerateMenuItems([ - { - id: "posts", - label: "Posts", - icon: "FiBookOpen", - }, - { - id: "music", - label: "Music", - icon: "MdAlbum", - }, - { - id: "followers", - label: "Followers", - icon: "FiUsers", - }, - { - id: "details", - label: "Details", - icon: "FiInfo", - } - ])} - /> -
-
+ ) } -} \ No newline at end of file +} diff --git a/packages/app/src/pages/lyrics/components/text/index.jsx b/packages/app/src/pages/lyrics/components/text/index.jsx index c5b8449c..e784df86 100644 --- a/packages/app/src/pages/lyrics/components/text/index.jsx +++ b/packages/app/src/pages/lyrics/components/text/index.jsx @@ -1,153 +1,162 @@ 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" const LyricsText = React.forwardRef((props, textRef) => { - const [playerState] = usePlayerStateContext() + const [playerState] = usePlayerStateContext() - const { lyrics } = props + const { lyrics } = props - const [syncInterval, setSyncInterval] = React.useState(null) - const [currentLineIndex, setCurrentLineIndex] = React.useState(0) - const [visible, setVisible] = React.useState(false) + const [syncInterval, setSyncInterval] = React.useState(null) + const [currentLineIndex, setCurrentLineIndex] = React.useState(0) + const [visible, setVisible] = React.useState(false) - function syncPlayback() { - const currentTrackTime = app.cores.player.controls.seek() * 1000 + function syncPlayback() { + const currentTrackTime = app.cores.player.controls.seek() * 1000 - const lineIndex = lyrics.synced_lyrics.findIndex((line) => { - return currentTrackTime >= line.startTimeMs && currentTrackTime <= line.endTimeMs - }) + const lineIndex = lyrics.synced_lyrics.findIndex((line) => { + return ( + currentTrackTime >= line.startTimeMs && + currentTrackTime <= line.endTimeMs + ) + }) - if (lineIndex === -1) { - if (!visible) { - setVisible(false) - } + if (lineIndex === -1) { + if (!visible) { + setVisible(false) + } - return false - } + return false + } - const line = lyrics.synced_lyrics[lineIndex] + const line = lyrics.synced_lyrics[lineIndex] - setCurrentLineIndex(lineIndex) + setCurrentLineIndex(lineIndex) - if (line.break) { - return setVisible(false) - } + if (line.break) { + return setVisible(false) + } - if (line.text) { - return setVisible(true) - } - } + if (line.text) { + return setVisible(true) + } + } - function startSyncInterval() { - if (!lyrics || !lyrics.synced_lyrics) { - stopSyncInterval() - return false - } + function startSyncInterval() { + if (!lyrics || !lyrics.synced_lyrics) { + stopSyncInterval() + return false + } - if (playerState.playback_status !== "playing") { - stopSyncInterval() - return false - } + if (playerState.playback_status !== "playing") { + stopSyncInterval() + return false + } - if (syncInterval) { - stopSyncInterval() - } + if (syncInterval) { + stopSyncInterval() + } - setSyncInterval(setInterval(syncPlayback, 100)) - } + setSyncInterval(setInterval(syncPlayback, 100)) + } - function stopSyncInterval() { - clearInterval(syncInterval) - setSyncInterval(null) - } + function stopSyncInterval() { + clearInterval(syncInterval) + setSyncInterval(null) + } - //* Handle when current line index change - React.useEffect(() => { - if (currentLineIndex === 0) { - setVisible(false) - } else { - setVisible(true) + //* Handle when current line index change + React.useEffect(() => { + if (currentLineIndex === 0) { + setVisible(false) + } else { + setVisible(true) - // find line element by id - const lineElement = textRef.current.querySelector(`#lyrics-line-${currentLineIndex}`) + // find line element by id + const lineElement = textRef.current.querySelector( + `#lyrics-line-${currentLineIndex}`, + ) - // center scroll to current line - if (lineElement) { - lineElement.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - } else { - // scroll to top - textRef.current.scrollTop = 0 - } - } - }, [currentLineIndex]) + // center scroll to current line + if (lineElement) { + lineElement.scrollIntoView({ + behavior: "smooth", + block: "center", + }) + } else { + // scroll to top + textRef.current.scrollTop = 0 + } + } + }, [currentLineIndex]) - //* Handle when playback status change - React.useEffect(() => { - startSyncInterval() - }, [playerState.playback_status]) + //* Handle when playback status change + React.useEffect(() => { + startSyncInterval() + }, [playerState.playback_status]) - //* Handle when manifest object change, reset... - React.useEffect(() => { - setVisible(false) - setCurrentLineIndex(0) - }, [playerState.track_manifest]) + //* Handle when manifest object change, reset... + React.useEffect(() => { + setVisible(false) + setCurrentLineIndex(0) + }, [playerState.track_manifest]) - React.useEffect(() => { - startSyncInterval() - }, [lyrics]) + React.useEffect(() => { + startSyncInterval() + }, [lyrics]) - React.useEffect(() => { - return () => { - clearInterval(syncInterval) - } - }, []) + React.useEffect(() => { + return () => { + clearInterval(syncInterval) + } + }, []) - if (!lyrics?.synced_lyrics) { - return null - } + if (!lyrics?.synced_lyrics) { + return null + } - return
- - {({ opacity }) => { - return
- { - lyrics.synced_lyrics.map((line, index) => { - return

- {line.text} -

- }) - } -
- }} -
-
+ return ( +
+ + {visible && ( + + {lyrics.synced_lyrics.map((line, index) => { + return ( +

+ {line.text} +

+ ) + })} +
+ )} +
+
+ ) }) -export default LyricsText \ No newline at end of file +export default LyricsText diff --git a/packages/app/src/settings/about/index.jsx b/packages/app/src/settings/about/index.jsx index 680b21c3..2d4ee8de 100755 --- a/packages/app/src/settings/about/index.jsx +++ b/packages/app/src/settings/about/index.jsx @@ -10,281 +10,299 @@ import config from "@config" 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.", + 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.", } export default { - id: "about", - icon: "FiInfo", - label: "About", - group: "bottom", - render: () => { - const isProduction = import.meta.env.PROD + id: "about", + icon: "FiInfo", + label: "About", + group: "bottom", + render: () => { + const isProduction = import.meta.env.PROD - const [serverManifest, setServerManifest] = React.useState(null) - const [serverOrigin, setServerOrigin] = React.useState(null) - const [secureConnection, setSecureConnection] = React.useState(false) - const [capInfo, setCapInfo] = React.useState(null) + const [serverManifest, setServerManifest] = React.useState(null) + const [serverOrigin, setServerOrigin] = React.useState(null) + const [secureConnection, setSecureConnection] = React.useState(false) + const [capInfo, setCapInfo] = React.useState(null) - const setCapacitorInfo = async () => { - if (Capacitor.Plugins.App) { - const info = await Capacitor.Plugins.App.getInfo() + const setCapacitorInfo = async () => { + if (Capacitor) { + if (Capacitor.Plugins.App) { + const info = await Capacitor.Plugins.App.getInfo() - setCapInfo(info) - } - } + setCapInfo(info) + } + } + } - const checkServerVersion = async () => { - const serverManifest = await app.cores.api.customRequest() + const checkServerVersion = async () => { + const serverManifest = await app.cores.api.customRequest() - setServerManifest(serverManifest.data) - } + setServerManifest(serverManifest.data) + } - const checkServerOrigin = async () => { - const instance = app.cores.api.client() + const checkServerOrigin = async () => { + const instance = app.cores.api.client() - if (instance) { - setServerOrigin(instance.mainOrigin) + if (instance) { + setServerOrigin(instance.mainOrigin) - if (instance.mainOrigin.startsWith("https")) { - setSecureConnection(true) - } - } - } + if (instance.mainOrigin.startsWith("https")) { + setSecureConnection(true) + } + } + } - React.useEffect(() => { - checkServerVersion() - checkServerOrigin() + React.useEffect(() => { + checkServerVersion() + checkServerOrigin() - setCapacitorInfo() - }, []) + setCapacitorInfo() + }, []) - return
-
-
-
- Logo -
-
-
-

{config.app.siteName}

- Beta -
- {config.author} - Licensed with {config.package?.license ?? "unlicensed"} -
-
-
- v{window.app.version ?? "experimental"} - - {isProduction ? : } - {String(import.meta.env.MODE)} - -
-
+ return ( +
+
+
+
+ Logo +
+
+
+

{config.app.siteName}

+ Beta +
+ {config.author} + + Licensed with{" "} + {config.package?.license ?? "unlicensed"}{" "} + +
+
+
+ + v + {window.app.version ?? "experimental"} + + + {isProduction ? ( + + ) : ( + + )} + {String(import.meta.env.MODE)} + +
+
-
-
-

Server information

-
+
+
+

+ + Server information +

+
-
-
-

Origin

+
+
+

+ Origin +

- - : } - > - { - secureConnection ? " Secure connection" : "Insecure connection" - } - - -
+ + + ) : ( + + ) + } + > + {secureConnection + ? " Secure connection" + : "Insecure connection"} + + +
-
- {serverOrigin ?? "Unknown"} -
-
+
+ {serverOrigin ?? "Unknown"} +
+
-
-
-

Instance Performance

-
+
+
+

+ Instance Performance +

+
-
-
- +
+
+ - -
-
-
+ +
+
+
-
-
-
- -
+
+
+
+ +
-

Version

-
+

Version

+
-
- {serverManifest?.version ?? "Unknown"} -
-
-
+
+ {serverManifest?.version ?? "Unknown"} +
+
+
-
-

Thanks to our sponsors

- -
+
+

Thanks to our sponsors

+ +
-
-
-
-
- -
+
+
+
+
+ +
-

Platform

-
+

Platform

+
-
- {Capacitor.platform} -
-
+
Web App
+
-
-
-
- -
+
+
+
+ +
-

React

-
+

React

+
-
- {React.version ?? "Unknown"} -
-
+
+ {React.version ?? "Unknown"} +
+
-
-
-
- -
+
+
+
+ +
-

Engine

-
+

Engine

+
-
- {app.__version ?? "Unknown"} -
-
+
+ {app.__version ?? "Unknown"} +
+
-
-
-
- -
+
+
+
+ +
-

Comty.js

-
+

Comty.js

+
-
- {__comty_shared_state.version ?? "Unknown"} -
-
+
+ {__comty_shared_state.version ?? "Unknown"} +
+
- { - capInfo &&
-
-
- -
+ {capInfo && ( +
+
+
+ +
-

App ID

-
+

App ID

+
-
- {capInfo.id} -
-
- } +
{capInfo.id}
+
+ )} - { - capInfo &&
-
-
- -
+ {capInfo && ( +
+
+
+ +
-

App Build

-
+

App Build

+
-
- {capInfo.build} -
-
- } +
{capInfo.build}
+
+ )} - { - capInfo &&
-
-
- -
+ {capInfo && ( +
+
+
+ +
-

App Version

-
+

App Version

+
-
- {capInfo.version} -
-
- } +
{capInfo.version}
+
+ )} -
-
-
- -
+
+
+
+ +
-

View Open Source Licenses

-
+

View Open Source Licenses

+
-
- } - onClick={() => app.location.push("/licenses")} - /> -
-
-
-
- } -} \ No newline at end of file +
+ } + onClick={() => app.location.push("/licenses")} + /> +
+
+
+
+ ) + }, +} diff --git a/packages/app/vite.config.js b/packages/app/vite.config.js index 117f9319..1310e204 100755 --- a/packages/app/vite.config.js +++ b/packages/app/vite.config.js @@ -11,70 +11,46 @@ const oneYearInSeconds = 60 * 60 * 24 * 365 const sslDirPath = path.join(__dirname, ".ssl") const config = { - plugins: [ - react(), - ], - resolve: { - alias: aliases, - }, - server: { - host: "0.0.0.0", - port: 8000, - fs: { - allow: ["..", "../../"], - }, - headers: { - "Strict-Transport-Security": `max-age=${oneYearInSeconds}` - }, - proxy: { - "/api": { - target: backendUri, - rewrite: (path) => path.replace(/^\/api/, ""), - hostRewrite: true, - changeOrigin: true, - xfwd: true, - 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 - }, - } + plugins: [react()], + resolve: { + alias: aliases, + }, + server: { + host: "0.0.0.0", + port: 8000, + fs: { + allow: ["..", "../../"], + }, + headers: { + "Strict-Transport-Security": `max-age=${oneYearInSeconds}`, + }, + proxy: { + "/api": { + target: backendUri, + rewrite: (path) => path.replace(/^\/api/, ""), + hostRewrite: true, + changeOrigin: true, + xfwd: true, + ws: true, + toProxy: true, + secure: false, + }, + }, + }, + css: { + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, } if (fs.existsSync(sslDirPath)) { - config.server.https = { - key: path.join(__dirname, ".ssl", "privkey.pem"), - cert: path.join(__dirname, ".ssl", "cert.pem"), - } + config.server.https = { + key: path.join(__dirname, ".ssl", "privkey.pem"), + cert: path.join(__dirname, ".ssl", "cert.pem"), + } } -export default defineConfig(config) \ No newline at end of file +export default defineConfig(config)