import "./patches"
import config from "@config"
import React from "react"
import { Runtime } from "vessel"
import { Helmet } from "react-helmet"
import { Translation } from "react-i18next"
import * as Sentry from "@sentry/browser"
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"
import SessionModel from "@models/session"
import UserModel from "@models/user"
import {
NotFound,
RenderError,
Crash,
Login,
UserRegister,
Searcher,
NotificationsCenter,
PostCreator,
} from "@components"
import { Icons } from "@components/Icons"
import DesktopTopBar from "@components/DesktopTopBar"
import { ThemeProvider } from "@cores/style/style.core.jsx"
import Layout from "./layout"
import * as Router from "./router"
import Splash from "./splash"
import "@styles/index.less"
if (IS_MOBILE_HOST) {
CapacitorUpdater.notifyAppReady()
}
class ComtyApp extends React.Component {
constructor(props) {
super(props)
Object.keys(this.eventsHandlers).forEach((event) => {
app.eventBus.on(event, this.eventsHandlers[event])
})
}
state = {
session: null,
initialized: false,
}
static splashAwaitEvent = "app.initialization.finish"
static async initialize() {
window.app.version = config.package.version
window.app.confirm = antd.Modal.confirm
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}!`)
window.localStorage.setItem("last_version", window.app.version)
}
if (import.meta.env.VITE_SENTRY_DSN && import.meta.env.PROD) {
console.log(`Initializing Sentry...`)
Sentry.init({
debug: true,
dsn: import.meta.env.VITE_SENTRY_DSN,
release: "comty-web-app",
})
}
if (window.__TAURI__) {
window.__TAURI__.invoke = invoke
}
}
static publicEvents = {}
static publicMethods = {
controls: {
toggleUIVisibility: (to) => {
if (app.layout.sidebar) {
app.layout.sidebar.toggleVisibility(to)
}
if (app.layout.tools_bar) {
app.layout.tools_bar.toggleVisibility(to)
}
if (app.layout.top_bar) {
app.layout.top_bar.toggleVisibility(to)
}
if (app.layout.floatingStack) {
app.layout.floatingStack.toggleGlobalVisibility(to)
}
},
openLoginForm: async (options = {}) => {
app.layout.draggable.open("login", Login, {
props: {
sessionController: this.sessionController,
onDone: () => {
app.layout.draggable.destroy("login")
}
},
})
},
openAppsMenu: () => {
app.layout.drawer.open("apps", AppsMenu)
},
openRegisterForm: async (options = {}) => {
app.layout.drawer.open("Register", UserRegister, {
defaultLocked: options.defaultLocked ?? false,
componentProps: {
sessionController: this.sessionController,
},
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",
},
allowMultiples: false,
escClosable: true,
})
},
openSearcher: (options) => {
if (app.isMobile) {
return app.layout.drawer.open("searcher", Searcher, {
...options,
componentProps: {
renderResults: true,
autoFocus: true,
}
})
}
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
/>)
},
openPostCreator: (params) => {
app.layout.modal.open("post_creator", (props) => , {
framed: false
})
}
},
navigation: {
reload: () => {
window.location.reload()
},
softReload: () => {
app.eventBus.emit("app.softReload")
},
goAuth: () => {
return app.location.push(config.app.authPath ?? "/auth")
},
goMain: () => {
return app.location.push(config.app.mainPath ?? "/home")
},
goToMusic: () => {
return app.location.push("/music")
},
goToSettings: (setting_id) => {
return app.location.push(`/settings`, {
query: {
setting: setting_id
}
})
},
goToAccount: (username) => {
if (!username) {
if (!app.userData) {
console.error("Cannot go to account, no username provided and no user logged in")
return false
}
username = app.userData.username
}
return app.location.push(`/account/${username}`)
},
goToPost: (post_id) => {
return app.location.push(`/post/${post_id}`)
},
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")
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")
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")
return false
}
return await StatusBar.hide()
},
showStatusBar: async () => {
if (!window.app.capacitor.isAppCapacitor()) {
console.warn("[App] showStatusBar is not supported on this platform")
return false
}
return await StatusBar.show()
},
},
maintenance: {
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.",
onOk: async () => {
Utils.deleteInternalStorage()
}
})
},
}
}
static staticRenders = {
PageLoad: () => {
return
},
NotFound: (props) => {
return
},
RenderError: (props) => {
return
},
Crash: Crash.CrashWrapper,
Initialization: Splash,
}
eventsHandlers = {
"app.softReload": () => {
this.forceUpdate()
app.eventBus.emit("layout.forceUpdate")
app.eventBus.emit("router.forceUpdate")
},
"app.logout_request": () => {
antd.Modal.confirm({
title: "Logout",
content: "Are you sure you want to logout?",
onOk: () => {
AuthModel.logout()
},
})
},
"session.invalid": async (error) => {
const token = await SessionModel.token
if (!this.state.session && !token) {
return false
}
await SessionModel.destroyCurrentSession()
await this.flushState()
app.navigation.goAuth()
antd.notification.open({
message:
{(t) => t("Invalid Session")}
,
description:
{(t) => t(error)}
,
icon: ,
})
},
"auth:login_success": async () => {
app.eventBus.emit("layout.animations.fadeOut")
await this.initialization()
app.cores.api.reconnectWebsockets()
app.navigation.goMain()
app.eventBus.emit("layout.animations.fadeIn")
},
"auth:logout_success": async () => {
app.cores.api.disconnectWebsockets()
app.navigation.goAuth()
await this.flushState()
},
"auth:disabled_account": async () => {
await SessionModel.removeToken()
app.navigation.goAuth()
}
}
flushState = async () => {
delete app.userData
await this.setState({ session: null, user: null })
}
componentDidMount = async () => {
if (app.isCapacitor) {
window.addEventListener("statusTap", () => {
app.eventBus.emit("statusTap")
})
StatusBar.setOverlaysWebView({ overlay: false })
CapacitorApp.addListener("backButton", ({ canGoBack }) => {
if (!canGoBack) {
CapacitorApp.exitApp()
} else {
app.location.back()
}
})
}
await this.initialization()
app.cores.sfx.play("splash_out")
this.setState({ initialized: true })
}
onRuntimeStateUpdate = (state) => {
console.debug(`[App] Runtime state updated`, state)
}
initialization = async () => {
// await new Promise((resolve) => {
// setTimeout(resolve, 8000)
// })
app.eventBus.emit("app.initialization.start")
console.debug(`[App] Initializing app`)
const initializationTasks = [
async () => {
try {
await this.__SessionInit()
} catch (error) {
console.error(`[App] Error while initializing session`, error)
throw {
cause: "Cannot initialize session",
details: error.message,
}
}
},
]
await Promise.tasked(initializationTasks).catch((reason) => {
console.error(`[App] Initialization failed: ${reason.cause}`)
app.eventBus.emit("runtime.crash", {
message: `App initialization failed (${reason.cause})`,
details: reason.details,
})
})
app.eventBus.emit("app.initialization.finish")
}
__SessionInit = async () => {
const token = await SessionModel.token
if (!token || token == null) {
app.eventBus.emit("app.no_session")
return false
}
const user = await UserModel.data().catch((err) => {
return false
})
if (!user) {
app.eventBus.emit("app.no_session")
return false
}
app.userData = user
await this.setState({
user: user,
})
}
render() {
return
{config.app.siteName}
{
window.__TAURI__ &&
}
{
this.state.initialized &&
}
}
}
export default new Runtime(ComtyApp)